mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 04:34:19 +01:00
This is going rather well.
This commit is contained in:
parent
73e2793da6
commit
bdaff7b781
48 changed files with 2944 additions and 2952 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit d7867dfa6579d4e69876753e9cde72e13d3372ce
|
Subproject commit 3d346700e8800c045aa19d70d516d8a4fda2f2ee
|
||||||
|
|
@ -1404,10 +1404,10 @@ public class IpcTester : IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var collection in Penumbra.TempMods.CustomCollections.Values )
|
foreach( var collection in Penumbra.TempCollections.Values )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var character = Penumbra.TempMods.Collections.Where( p => p.Collection == collection ).Select( p => p.DisplayName ).FirstOrDefault() ?? "Unknown";
|
var character = Penumbra.TempCollections.Collections.Where( p => p.Collection == collection ).Select( p => p.DisplayName ).FirstOrDefault() ?? "Unknown";
|
||||||
if( ImGui.Button( $"Save##{collection.Name}" ) )
|
if( ImGui.Button( $"Save##{collection.Name}" ) )
|
||||||
{
|
{
|
||||||
Mod.TemporaryMod.SaveTempCollection( collection, character );
|
Mod.TemporaryMod.SaveTempCollection( collection, character );
|
||||||
|
|
@ -1416,7 +1416,7 @@ public class IpcTester : IDisposable
|
||||||
ImGuiUtil.DrawTableColumn( collection.Name );
|
ImGuiUtil.DrawTableColumn( collection.Name );
|
||||||
ImGuiUtil.DrawTableColumn( collection.ResolvedFiles.Count.ToString() );
|
ImGuiUtil.DrawTableColumn( collection.ResolvedFiles.Count.ToString() );
|
||||||
ImGuiUtil.DrawTableColumn( collection.MetaCache?.Count.ToString() ?? "0" );
|
ImGuiUtil.DrawTableColumn( collection.MetaCache?.Count.ToString() ?? "0" );
|
||||||
ImGuiUtil.DrawTableColumn( string.Join( ", ", Penumbra.TempMods.Collections.Where( p => p.Collection == collection ).Select( c => c.DisplayName ) ) );
|
ImGuiUtil.DrawTableColumn( string.Join( ", ", Penumbra.TempCollections.Collections.Where( p => p.Collection == collection ).Select( c => c.DisplayName ) ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public (int, int) ApiVersion
|
public (int, int) ApiVersion
|
||||||
=> (4, 19);
|
=> (4, 19);
|
||||||
|
|
||||||
|
private CommunicatorService? _communicator;
|
||||||
private Penumbra? _penumbra;
|
private Penumbra? _penumbra;
|
||||||
private Lumina.GameData? _lumina;
|
private Lumina.GameData? _lumina;
|
||||||
|
|
||||||
|
|
@ -82,18 +83,17 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public bool Valid
|
public bool Valid
|
||||||
=> _penumbra != null;
|
=> _penumbra != null;
|
||||||
|
|
||||||
public unsafe PenumbraApi( Penumbra penumbra )
|
public unsafe PenumbraApi(CommunicatorService communicator, Penumbra penumbra)
|
||||||
{
|
{
|
||||||
|
_communicator = communicator;
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_lumina = (Lumina.GameData?)DalamudServices.GameData.GetType()
|
_lumina = (Lumina.GameData?)DalamudServices.GameData.GetType()
|
||||||
.GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic)
|
.GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
?.GetValue(DalamudServices.GameData);
|
?.GetValue(DalamudServices.GameData);
|
||||||
foreach (var collection in Penumbra.CollectionManager)
|
foreach (var collection in Penumbra.CollectionManager)
|
||||||
{
|
|
||||||
SubscribeToCollection(collection);
|
SubscribeToCollection(collection);
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections;
|
_communicator.CollectionChange.Event += SubscribeToNewCollections;
|
||||||
Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded;
|
Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||||
Penumbra.ModManager.ModPathChanged += ModPathChangeSubscriber;
|
Penumbra.ModManager.ModPathChanged += ModPathChangeSubscriber;
|
||||||
}
|
}
|
||||||
|
|
@ -101,18 +101,17 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public unsafe void Dispose()
|
public unsafe void Dispose()
|
||||||
{
|
{
|
||||||
Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded;
|
Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded;
|
||||||
Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections;
|
_communicator!.CollectionChange.Event -= SubscribeToNewCollections;
|
||||||
Penumbra.ModManager.ModPathChanged -= ModPathChangeSubscriber;
|
Penumbra.ModManager.ModPathChanged -= ModPathChangeSubscriber;
|
||||||
_penumbra = null;
|
_penumbra = null;
|
||||||
_lumina = null;
|
_lumina = null;
|
||||||
|
_communicator = null;
|
||||||
foreach (var collection in Penumbra.CollectionManager)
|
foreach (var collection in Penumbra.CollectionManager)
|
||||||
{
|
{
|
||||||
if (_delegates.TryGetValue(collection, out var del))
|
if (_delegates.TryGetValue(collection, out var del))
|
||||||
{
|
|
||||||
collection.ModSettingChanged -= del;
|
collection.ModSettingChanged -= del;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public event ChangedItemClick? ChangedItemClicked;
|
public event ChangedItemClick? ChangedItemClicked;
|
||||||
|
|
||||||
|
|
@ -126,11 +125,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
ResolveData resolveData)
|
ResolveData resolveData)
|
||||||
{
|
{
|
||||||
if (resolveData.AssociatedGameObject != IntPtr.Zero)
|
if (resolveData.AssociatedGameObject != IntPtr.Zero)
|
||||||
{
|
|
||||||
GameObjectResourceResolved?.Invoke(resolveData.AssociatedGameObject, originalPath.ToString(),
|
GameObjectResourceResolved?.Invoke(resolveData.AssociatedGameObject, originalPath.ToString(),
|
||||||
manipulatedPath?.ToString() ?? originalPath.ToString());
|
manipulatedPath?.ToString() ?? originalPath.ToString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string, bool>? ModDirectoryChanged
|
public event Action<string, bool>? ModDirectoryChanged
|
||||||
{
|
{
|
||||||
|
|
@ -176,33 +173,23 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (_penumbra!.ConfigWindow == null)
|
if (_penumbra!.ConfigWindow == null)
|
||||||
{
|
|
||||||
return PenumbraApiEc.SystemDisposed;
|
return PenumbraApiEc.SystemDisposed;
|
||||||
}
|
|
||||||
|
|
||||||
_penumbra!.ConfigWindow.IsOpen = true;
|
_penumbra!.ConfigWindow.IsOpen = true;
|
||||||
|
|
||||||
if (!Enum.IsDefined(tab))
|
if (!Enum.IsDefined(tab))
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
}
|
|
||||||
|
|
||||||
if (tab != TabType.None)
|
if (tab != TabType.None)
|
||||||
{
|
|
||||||
_penumbra!.ConfigWindow.SelectTab = tab;
|
_penumbra!.ConfigWindow.SelectTab = tab;
|
||||||
}
|
|
||||||
|
|
||||||
if (tab == TabType.Mods && (modDirectory.Length > 0 || modName.Length > 0))
|
if (tab == TabType.Mods && (modDirectory.Length > 0 || modName.Length > 0))
|
||||||
{
|
{
|
||||||
if (Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
_penumbra!.ConfigWindow.SelectMod(mod);
|
_penumbra!.ConfigWindow.SelectMod(mod);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
}
|
}
|
||||||
|
|
@ -211,9 +198,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (_penumbra!.ConfigWindow == null)
|
if (_penumbra!.ConfigWindow == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_penumbra!.ConfigWindow.IsOpen = false;
|
_penumbra!.ConfigWindow.IsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
@ -286,9 +271,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.Config.EnableMods)
|
if (!Penumbra.Config.EnableMods)
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
return new[] { path };
|
path,
|
||||||
}
|
};
|
||||||
|
|
||||||
var ret = Penumbra.CollectionManager.Individual(NameToIdentifier(characterName, worldId)).ReverseResolvePath(new FullPath(path));
|
var ret = Penumbra.CollectionManager.Individual(NameToIdentifier(characterName, worldId)).ReverseResolvePath(new FullPath(path));
|
||||||
return ret.Select(r => r.ToString()).ToArray();
|
return ret.Select(r => r.ToString()).ToArray();
|
||||||
|
|
@ -298,9 +284,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.Config.EnableMods)
|
if (!Penumbra.Config.EnableMods)
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
return new[] { path };
|
path,
|
||||||
}
|
};
|
||||||
|
|
||||||
AssociatedCollection(gameObjectIdx, out var collection);
|
AssociatedCollection(gameObjectIdx, out var collection);
|
||||||
var ret = collection.ReverseResolvePath(new FullPath(path));
|
var ret = collection.ReverseResolvePath(new FullPath(path));
|
||||||
|
|
@ -311,9 +298,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.Config.EnableMods)
|
if (!Penumbra.Config.EnableMods)
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
return new[] { path };
|
path,
|
||||||
}
|
};
|
||||||
|
|
||||||
var ret = PathResolver.PlayerCollection().ReverseResolvePath(new FullPath(path));
|
var ret = PathResolver.PlayerCollection().ReverseResolvePath(new FullPath(path));
|
||||||
return ret.Select(r => r.ToString()).ToArray();
|
return ret.Select(r => r.ToString()).ToArray();
|
||||||
|
|
@ -323,9 +311,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.Config.EnableMods)
|
if (!Penumbra.Config.EnableMods)
|
||||||
|
return (forward, reverse.Select(p => new[]
|
||||||
{
|
{
|
||||||
return ( forward, reverse.Select( p => new[] { p } ).ToArray() );
|
p,
|
||||||
}
|
}).ToArray());
|
||||||
|
|
||||||
var playerCollection = PathResolver.PlayerCollection();
|
var playerCollection = PathResolver.PlayerCollection();
|
||||||
var resolved = forward.Select(p => ResolvePath(p, Penumbra.ModManager, playerCollection)).ToArray();
|
var resolved = forward.Select(p => ResolvePath(p, Penumbra.ModManager, playerCollection)).ToArray();
|
||||||
|
|
@ -345,14 +334,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
collection = ModCollection.Empty;
|
collection = ModCollection.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
if (collection.HasCache)
|
if (collection.HasCache)
|
||||||
{
|
|
||||||
return collection.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Item2);
|
return collection.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Item2);
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Log.Warning($"Collection {collectionName} does not exist or is not loaded.");
|
Penumbra.Log.Warning($"Collection {collectionName} does not exist or is not loaded.");
|
||||||
return new Dictionary<string, object?>();
|
return new Dictionary<string, object?>();
|
||||||
|
|
@ -368,51 +353,40 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Enum.IsDefined(type))
|
if (!Enum.IsDefined(type))
|
||||||
{
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
var collection = Penumbra.CollectionManager.ByType((CollectionType)type);
|
var collection = Penumbra.CollectionManager.ByType((CollectionType)type);
|
||||||
return collection?.Name ?? string.Empty;
|
return collection?.Name ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (PenumbraApiEc, string OldCollection) SetCollectionForType( ApiCollectionType type, string collectionName, bool allowCreateNew, bool allowDelete )
|
public (PenumbraApiEc, string OldCollection) SetCollectionForType(ApiCollectionType type, string collectionName, bool allowCreateNew,
|
||||||
|
bool allowDelete)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Enum.IsDefined(type))
|
if (!Enum.IsDefined(type))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
||||||
}
|
|
||||||
|
|
||||||
var oldCollection = Penumbra.CollectionManager.ByType((CollectionType)type)?.Name ?? string.Empty;
|
var oldCollection = Penumbra.CollectionManager.ByType((CollectionType)type)?.Name ?? string.Empty;
|
||||||
|
|
||||||
if (collectionName.Length == 0)
|
if (collectionName.Length == 0)
|
||||||
{
|
{
|
||||||
if (oldCollection.Length == 0)
|
if (oldCollection.Length == 0)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.NothingChanged, oldCollection);
|
return (PenumbraApiEc.NothingChanged, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
if (!allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface)
|
if (!allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection);
|
return (PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.CollectionManager.RemoveSpecialCollection((CollectionType)type);
|
Penumbra.CollectionManager.RemoveSpecialCollection((CollectionType)type);
|
||||||
return (PenumbraApiEc.Success, oldCollection);
|
return (PenumbraApiEc.Success, oldCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.CollectionMissing, oldCollection);
|
return (PenumbraApiEc.CollectionMissing, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCollection.Length == 0)
|
if (oldCollection.Length == 0)
|
||||||
{
|
{
|
||||||
if (!allowCreateNew)
|
if (!allowCreateNew)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.AssignmentCreationDisallowed, oldCollection);
|
return (PenumbraApiEc.AssignmentCreationDisallowed, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.CollectionManager.CreateSpecialCollection((CollectionType)type);
|
Penumbra.CollectionManager.CreateSpecialCollection((CollectionType)type);
|
||||||
}
|
}
|
||||||
|
|
@ -430,41 +404,32 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
var id = AssociatedIdentifier(gameObjectIdx);
|
var id = AssociatedIdentifier(gameObjectIdx);
|
||||||
if (!id.IsValid)
|
if (!id.IsValid)
|
||||||
{
|
|
||||||
return (false, false, Penumbra.CollectionManager.Default.Name);
|
return (false, false, Penumbra.CollectionManager.Default.Name);
|
||||||
}
|
|
||||||
|
|
||||||
if (Penumbra.CollectionManager.Individuals.Individuals.TryGetValue(id, out var collection))
|
if (Penumbra.CollectionManager.Individuals.Individuals.TryGetValue(id, out var collection))
|
||||||
{
|
|
||||||
return (true, true, collection.Name);
|
return (true, true, collection.Name);
|
||||||
}
|
|
||||||
|
|
||||||
AssociatedCollection(gameObjectIdx, out collection);
|
AssociatedCollection(gameObjectIdx, out collection);
|
||||||
return (true, false, collection.Name);
|
return (true, false, collection.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (PenumbraApiEc, string OldCollection) SetCollectionForObject( int gameObjectIdx, string collectionName, bool allowCreateNew, bool allowDelete )
|
public (PenumbraApiEc, string OldCollection) SetCollectionForObject(int gameObjectIdx, string collectionName, bool allowCreateNew,
|
||||||
|
bool allowDelete)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
var id = AssociatedIdentifier(gameObjectIdx);
|
var id = AssociatedIdentifier(gameObjectIdx);
|
||||||
if (!id.IsValid)
|
if (!id.IsValid)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.InvalidIdentifier, Penumbra.CollectionManager.Default.Name);
|
return (PenumbraApiEc.InvalidIdentifier, Penumbra.CollectionManager.Default.Name);
|
||||||
}
|
|
||||||
|
|
||||||
var oldCollection = Penumbra.CollectionManager.Individuals.Individuals.TryGetValue(id, out var c) ? c.Name : string.Empty;
|
var oldCollection = Penumbra.CollectionManager.Individuals.Individuals.TryGetValue(id, out var c) ? c.Name : string.Empty;
|
||||||
|
|
||||||
if (collectionName.Length == 0)
|
if (collectionName.Length == 0)
|
||||||
{
|
{
|
||||||
if (oldCollection.Length == 0)
|
if (oldCollection.Length == 0)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.NothingChanged, oldCollection);
|
return (PenumbraApiEc.NothingChanged, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
if (!allowDelete)
|
if (!allowDelete)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection);
|
return (PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
var idx = Penumbra.CollectionManager.Individuals.Index(id);
|
var idx = Penumbra.CollectionManager.Individuals.Index(id);
|
||||||
Penumbra.CollectionManager.RemoveIndividualCollection(idx);
|
Penumbra.CollectionManager.RemoveIndividualCollection(idx);
|
||||||
|
|
@ -472,16 +437,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.CollectionMissing, oldCollection);
|
return (PenumbraApiEc.CollectionMissing, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCollection.Length == 0)
|
if (oldCollection.Length == 0)
|
||||||
{
|
{
|
||||||
if (!allowCreateNew)
|
if (!allowCreateNew)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.AssignmentCreationDisallowed, oldCollection);
|
return (PenumbraApiEc.AssignmentCreationDisallowed, oldCollection);
|
||||||
}
|
|
||||||
|
|
||||||
var ids = Penumbra.CollectionManager.Individuals.GetGroup(id);
|
var ids = Penumbra.CollectionManager.Individuals.GetGroup(id);
|
||||||
Penumbra.CollectionManager.CreateIndividualCollection(ids);
|
Penumbra.CollectionManager.CreateIndividualCollection(ids);
|
||||||
|
|
@ -563,20 +524,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.CollectionMissing, null);
|
return (PenumbraApiEc.CollectionMissing, null);
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.ModMissing, null);
|
return (PenumbraApiEc.ModMissing, null);
|
||||||
}
|
|
||||||
|
|
||||||
var settings = allowInheritance ? collection.Settings[mod.Index] : collection[mod.Index].Settings;
|
var settings = allowInheritance ? collection.Settings[mod.Index] : collection[mod.Index].Settings;
|
||||||
if (settings == null)
|
if (settings == null)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.Success, null);
|
return (PenumbraApiEc.Success, null);
|
||||||
}
|
|
||||||
|
|
||||||
var shareSettings = settings.ConvertToShareable(mod);
|
var shareSettings = settings.ConvertToShareable(mod);
|
||||||
return (PenumbraApiEc.Success,
|
return (PenumbraApiEc.Success,
|
||||||
|
|
@ -587,9 +542,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.ModManager.ReloadMod(mod.Index);
|
Penumbra.ModManager.ReloadMod(mod.Index);
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
|
|
@ -600,9 +553,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
var dir = new DirectoryInfo(Path.Join(Penumbra.ModManager.BasePath.FullName, Path.GetFileName(modDirectory)));
|
var dir = new DirectoryInfo(Path.Join(Penumbra.ModManager.BasePath.FullName, Path.GetFileName(modDirectory)));
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
{
|
|
||||||
return PenumbraApiEc.FileMissing;
|
return PenumbraApiEc.FileMissing;
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.ModManager.AddMod(dir);
|
Penumbra.ModManager.AddMod(dir);
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
|
|
@ -612,9 +563,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.NothingChanged;
|
return PenumbraApiEc.NothingChanged;
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.ModManager.DeleteMod(mod.Index);
|
Penumbra.ModManager.DeleteMod(mod.Index);
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
|
|
@ -646,9 +595,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod)
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod)
|
||||||
|| !_penumbra!.ModFileSystem.FindLeaf(mod, out var leaf))
|
|| !_penumbra!.ModFileSystem.FindLeaf(mod, out var leaf))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.ModMissing, string.Empty, false);
|
return (PenumbraApiEc.ModMissing, string.Empty, false);
|
||||||
}
|
|
||||||
|
|
||||||
var fullPath = leaf.FullName();
|
var fullPath = leaf.FullName();
|
||||||
|
|
||||||
|
|
@ -659,15 +606,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (newPath.Length == 0)
|
if (newPath.Length == 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod)
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod)
|
||||||
|| !_penumbra!.ModFileSystem.FindLeaf(mod, out var leaf))
|
|| !_penumbra!.ModFileSystem.FindLeaf(mod, out var leaf))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -684,14 +627,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return collection.SetModInheritance(mod.Index, inherit) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
return collection.SetModInheritance(mod.Index, inherit) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
||||||
|
|
@ -701,14 +640,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
return collection.SetModState(mod.Index, enabled) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
return collection.SetModState(mod.Index, enabled) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
||||||
}
|
}
|
||||||
|
|
@ -717,14 +652,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
return collection.SetModPriority(mod.Index, priority) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
return collection.SetModPriority(mod.Index, priority) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
||||||
}
|
}
|
||||||
|
|
@ -734,26 +665,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
||||||
if (groupIdx < 0)
|
if (groupIdx < 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.OptionGroupMissing;
|
return PenumbraApiEc.OptionGroupMissing;
|
||||||
}
|
|
||||||
|
|
||||||
var optionIdx = mod.Groups[groupIdx].IndexOf(o => o.Name == optionName);
|
var optionIdx = mod.Groups[groupIdx].IndexOf(o => o.Name == optionName);
|
||||||
if (optionIdx < 0)
|
if (optionIdx < 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.OptionMissing;
|
return PenumbraApiEc.OptionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
var setting = mod.Groups[groupIdx].Type == GroupType.Multi ? 1u << optionIdx : (uint)optionIdx;
|
var setting = mod.Groups[groupIdx].Type == GroupType.Multi ? 1u << optionIdx : (uint)optionIdx;
|
||||||
|
|
||||||
|
|
@ -765,20 +688,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
if (!Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
if (!Penumbra.ModManager.TryGetMod(modDirectory, modName, out var mod))
|
||||||
{
|
|
||||||
return PenumbraApiEc.ModMissing;
|
return PenumbraApiEc.ModMissing;
|
||||||
}
|
|
||||||
|
|
||||||
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
||||||
if (groupIdx < 0)
|
if (groupIdx < 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.OptionGroupMissing;
|
return PenumbraApiEc.OptionGroupMissing;
|
||||||
}
|
|
||||||
|
|
||||||
var group = mod.Groups[groupIdx];
|
var group = mod.Groups[groupIdx];
|
||||||
|
|
||||||
|
|
@ -787,9 +704,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
var optionIdx = optionNames.Count == 0 ? -1 : group.IndexOf(o => o.Name == optionNames[^1]);
|
var optionIdx = optionNames.Count == 0 ? -1 : group.IndexOf(o => o.Name == optionNames[^1]);
|
||||||
if (optionIdx < 0)
|
if (optionIdx < 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.OptionMissing;
|
return PenumbraApiEc.OptionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
setting = (uint)optionIdx;
|
setting = (uint)optionIdx;
|
||||||
}
|
}
|
||||||
|
|
@ -799,9 +714,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
var optionIdx = group.IndexOf(o => o.Name == name);
|
var optionIdx = group.IndexOf(o => o.Name == name);
|
||||||
if (optionIdx < 0)
|
if (optionIdx < 0)
|
||||||
{
|
|
||||||
return PenumbraApiEc.OptionMissing;
|
return PenumbraApiEc.OptionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
setting |= 1u << optionIdx;
|
setting |= 1u << optionIdx;
|
||||||
}
|
}
|
||||||
|
|
@ -815,23 +728,19 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
||||||
var sourceModIdx = Penumbra.ModManager.FirstOrDefault( m => string.Equals( m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase ) )?.Index ?? -1;
|
var sourceModIdx = Penumbra.ModManager
|
||||||
var targetModIdx = Penumbra.ModManager.FirstOrDefault( m => string.Equals( m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase ) )?.Index ?? -1;
|
.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase))?.Index
|
||||||
|
?? -1;
|
||||||
|
var targetModIdx = Penumbra.ModManager
|
||||||
|
.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase))?.Index
|
||||||
|
?? -1;
|
||||||
if (string.IsNullOrEmpty(collectionName))
|
if (string.IsNullOrEmpty(collectionName))
|
||||||
{
|
|
||||||
foreach (var collection in Penumbra.CollectionManager)
|
foreach (var collection in Penumbra.CollectionManager)
|
||||||
{
|
|
||||||
collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo);
|
collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo);
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
else if (Penumbra.CollectionManager.ByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo);
|
collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
}
|
}
|
||||||
|
|
@ -841,35 +750,25 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
||||||
if (!ActorManager.VerifyPlayerName(character.AsSpan()) || tag.Length == 0)
|
if (!ActorManager.VerifyPlayerName(character.AsSpan()) || tag.Length == 0)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
||||||
}
|
|
||||||
|
|
||||||
var identifier = NameToIdentifier(character, ushort.MaxValue);
|
var identifier = NameToIdentifier(character, ushort.MaxValue);
|
||||||
if (!identifier.IsValid)
|
if (!identifier.IsValid)
|
||||||
{
|
|
||||||
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
||||||
}
|
|
||||||
|
|
||||||
if (!forceOverwriteCharacter && Penumbra.CollectionManager.Individuals.Individuals.ContainsKey(identifier)
|
if (!forceOverwriteCharacter && Penumbra.CollectionManager.Individuals.Individuals.ContainsKey(identifier)
|
||||||
|| Penumbra.TempMods.Collections.Individuals.ContainsKey( identifier ) )
|
|| Penumbra.TempCollections.Collections.Individuals.ContainsKey(identifier))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.CharacterCollectionExists, string.Empty);
|
return (PenumbraApiEc.CharacterCollectionExists, string.Empty);
|
||||||
}
|
|
||||||
|
|
||||||
var name = $"{tag}_{character}";
|
var name = $"{tag}_{character}";
|
||||||
var ret = CreateNamedTemporaryCollection(name);
|
var ret = CreateNamedTemporaryCollection(name);
|
||||||
if (ret != PenumbraApiEc.Success)
|
if (ret != PenumbraApiEc.Success)
|
||||||
{
|
|
||||||
return (ret, name);
|
return (ret, name);
|
||||||
}
|
|
||||||
|
|
||||||
if( Penumbra.TempMods.AddIdentifier( name, identifier ) )
|
if (Penumbra.TempCollections.AddIdentifier(name, identifier))
|
||||||
{
|
|
||||||
return (PenumbraApiEc.Success, name);
|
return (PenumbraApiEc.Success, name);
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.TempMods.RemoveTemporaryCollection( name );
|
Penumbra.TempCollections.RemoveTemporaryCollection(name);
|
||||||
return (PenumbraApiEc.UnknownError, string.Empty);
|
return (PenumbraApiEc.UnknownError, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -877,11 +776,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (name.Length == 0 || Mod.Creator.ReplaceBadXivSymbols(name) != name)
|
if (name.Length == 0 || Mod.Creator.ReplaceBadXivSymbols(name) != name)
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
}
|
|
||||||
|
|
||||||
return Penumbra.TempMods.CreateTemporaryCollection( name ).Length > 0
|
return Penumbra.TempCollections.CreateTemporaryCollection(name).Length > 0
|
||||||
? PenumbraApiEc.Success
|
? PenumbraApiEc.Success
|
||||||
: PenumbraApiEc.CollectionExists;
|
: PenumbraApiEc.CollectionExists;
|
||||||
}
|
}
|
||||||
|
|
@ -891,29 +788,22 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
||||||
if (actorIndex < 0 || actorIndex >= DalamudServices.Objects.Length)
|
if (actorIndex < 0 || actorIndex >= DalamudServices.Objects.Length)
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
}
|
|
||||||
|
|
||||||
var identifier = Penumbra.Actors.FromObject(DalamudServices.Objects[actorIndex], false, false, true);
|
var identifier = Penumbra.Actors.FromObject(DalamudServices.Objects[actorIndex], false, false, true);
|
||||||
if (!identifier.IsValid)
|
if (!identifier.IsValid)
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
}
|
|
||||||
|
|
||||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection ) )
|
if (!Penumbra.TempCollections.CollectionByName(collectionName, out var collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!forceAssignment
|
if (!forceAssignment
|
||||||
&& ( Penumbra.TempMods.Collections.Individuals.ContainsKey( identifier ) || Penumbra.CollectionManager.Individuals.Individuals.ContainsKey( identifier ) ) )
|
&& (Penumbra.TempCollections.Collections.Individuals.ContainsKey(identifier)
|
||||||
{
|
|| Penumbra.CollectionManager.Individuals.Individuals.ContainsKey(identifier)))
|
||||||
return PenumbraApiEc.CharacterCollectionExists;
|
return PenumbraApiEc.CharacterCollectionExists;
|
||||||
}
|
|
||||||
|
|
||||||
var group = Penumbra.TempMods.Collections.GetGroup( identifier );
|
var group = Penumbra.TempCollections.Collections.GetGroup(identifier);
|
||||||
return Penumbra.TempMods.AddIdentifier( collection, group )
|
return Penumbra.TempCollections.AddIdentifier(collection, group)
|
||||||
? PenumbraApiEc.Success
|
? PenumbraApiEc.Success
|
||||||
: PenumbraApiEc.UnknownError;
|
: PenumbraApiEc.UnknownError;
|
||||||
}
|
}
|
||||||
|
|
@ -921,7 +811,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public PenumbraApiEc RemoveTemporaryCollection(string character)
|
public PenumbraApiEc RemoveTemporaryCollection(string character)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
return Penumbra.TempMods.RemoveByCharacterName( character )
|
return Penumbra.TempCollections.RemoveByCharacterName(character)
|
||||||
? PenumbraApiEc.Success
|
? PenumbraApiEc.Success
|
||||||
: PenumbraApiEc.NothingChanged;
|
: PenumbraApiEc.NothingChanged;
|
||||||
}
|
}
|
||||||
|
|
@ -929,7 +819,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public PenumbraApiEc RemoveTemporaryCollectionByName(string name)
|
public PenumbraApiEc RemoveTemporaryCollectionByName(string name)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
return Penumbra.TempMods.RemoveTemporaryCollection( name )
|
return Penumbra.TempCollections.RemoveTemporaryCollection(name)
|
||||||
? PenumbraApiEc.Success
|
? PenumbraApiEc.Success
|
||||||
: PenumbraApiEc.NothingChanged;
|
: PenumbraApiEc.NothingChanged;
|
||||||
}
|
}
|
||||||
|
|
@ -938,14 +828,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if (!ConvertPaths(paths, out var p))
|
if (!ConvertPaths(paths, out var p))
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidGamePath;
|
return PenumbraApiEc.InvalidGamePath;
|
||||||
}
|
|
||||||
|
|
||||||
if (!ConvertManips(manipString, out var m))
|
if (!ConvertManips(manipString, out var m))
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidManipulation;
|
return PenumbraApiEc.InvalidManipulation;
|
||||||
}
|
|
||||||
|
|
||||||
return Penumbra.TempMods.Register(tag, null, p, m, priority) switch
|
return Penumbra.TempMods.Register(tag, null, p, m, priority) switch
|
||||||
{
|
{
|
||||||
|
|
@ -958,21 +844,15 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
int priority)
|
int priority)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
if (!Penumbra.TempCollections.CollectionByName(collectionName, out var collection)
|
||||||
&& !Penumbra.CollectionManager.ByName(collectionName, out collection))
|
&& !Penumbra.CollectionManager.ByName(collectionName, out collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
if (!ConvertPaths(paths, out var p))
|
if (!ConvertPaths(paths, out var p))
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidGamePath;
|
return PenumbraApiEc.InvalidGamePath;
|
||||||
}
|
|
||||||
|
|
||||||
if (!ConvertManips(manipString, out var m))
|
if (!ConvertManips(manipString, out var m))
|
||||||
{
|
|
||||||
return PenumbraApiEc.InvalidManipulation;
|
return PenumbraApiEc.InvalidManipulation;
|
||||||
}
|
|
||||||
|
|
||||||
return Penumbra.TempMods.Register(tag, collection, p, m, priority) switch
|
return Penumbra.TempMods.Register(tag, collection, p, m, priority) switch
|
||||||
{
|
{
|
||||||
|
|
@ -995,11 +875,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public PenumbraApiEc RemoveTemporaryMod(string tag, string collectionName, int priority)
|
public PenumbraApiEc RemoveTemporaryMod(string tag, string collectionName, int priority)
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
if (!Penumbra.TempCollections.CollectionByName(collectionName, out var collection)
|
||||||
&& !Penumbra.CollectionManager.ByName(collectionName, out collection))
|
&& !Penumbra.CollectionManager.ByName(collectionName, out collection))
|
||||||
{
|
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
|
||||||
|
|
||||||
return Penumbra.TempMods.Unregister(tag, collection, priority) switch
|
return Penumbra.TempMods.Unregister(tag, collection, priority) switch
|
||||||
{
|
{
|
||||||
|
|
@ -1025,7 +903,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
var identifier = NameToIdentifier(characterName, worldId);
|
var identifier = NameToIdentifier(characterName, worldId);
|
||||||
var collection = Penumbra.TempMods.Collections.TryGetCollection( identifier, out var c )
|
var collection = Penumbra.TempCollections.Collections.TryGetCollection(identifier, out var c)
|
||||||
? c
|
? c
|
||||||
: Penumbra.CollectionManager.Individual(identifier);
|
: Penumbra.CollectionManager.Individual(identifier);
|
||||||
var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty<MetaManipulation>();
|
var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty<MetaManipulation>();
|
||||||
|
|
@ -1054,10 +932,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private void CheckInitialized()
|
private void CheckInitialized()
|
||||||
{
|
{
|
||||||
if (!Valid)
|
if (!Valid)
|
||||||
{
|
|
||||||
throw new Exception("PluginShare is not initialized.");
|
throw new Exception("PluginShare is not initialized.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Return the collection associated to a current game object. If it does not exist, return the default collection.
|
// Return the collection associated to a current game object. If it does not exist, return the default collection.
|
||||||
// If the index is invalid, returns false and the default collection.
|
// If the index is invalid, returns false and the default collection.
|
||||||
|
|
@ -1066,16 +942,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
collection = Penumbra.CollectionManager.Default;
|
collection = Penumbra.CollectionManager.Default;
|
||||||
if (gameObjectIdx < 0 || gameObjectIdx >= DalamudServices.Objects.Length)
|
if (gameObjectIdx < 0 || gameObjectIdx >= DalamudServices.Objects.Length)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)DalamudServices.Objects.GetObjectAddress(gameObjectIdx);
|
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)DalamudServices.Objects.GetObjectAddress(gameObjectIdx);
|
||||||
var data = PathResolver.IdentifyCollection(ptr, false);
|
var data = PathResolver.IdentifyCollection(ptr, false);
|
||||||
if (data.Valid)
|
if (data.Valid)
|
||||||
{
|
|
||||||
collection = data.ModCollection;
|
collection = data.ModCollection;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1084,9 +956,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private static unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
|
private static unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
|
||||||
{
|
{
|
||||||
if (gameObjectIdx < 0 || gameObjectIdx >= DalamudServices.Objects.Length)
|
if (gameObjectIdx < 0 || gameObjectIdx >= DalamudServices.Objects.Length)
|
||||||
{
|
|
||||||
return ActorIdentifier.Invalid;
|
return ActorIdentifier.Invalid;
|
||||||
}
|
|
||||||
|
|
||||||
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)DalamudServices.Objects.GetObjectAddress(gameObjectIdx);
|
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)DalamudServices.Objects.GetObjectAddress(gameObjectIdx);
|
||||||
return Penumbra.Actors.FromObject(ptr, out _, false, true, true);
|
return Penumbra.Actors.FromObject(ptr, out _, false, true, true);
|
||||||
|
|
@ -1097,9 +967,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private static string ResolvePath(string path, Mod.Manager _, ModCollection collection)
|
private static string ResolvePath(string path, Mod.Manager _, ModCollection collection)
|
||||||
{
|
{
|
||||||
if (!Penumbra.Config.EnableMods)
|
if (!Penumbra.Config.EnableMods)
|
||||||
{
|
|
||||||
return path;
|
return path;
|
||||||
}
|
|
||||||
|
|
||||||
var gamePath = Utf8GamePath.FromString(path, out var p, true) ? p : Utf8GamePath.Empty;
|
var gamePath = Utf8GamePath.FromString(path, out var p, true) ? p : Utf8GamePath.Empty;
|
||||||
var ret = collection.ResolvePath(gamePath);
|
var ret = collection.ResolvePath(gamePath);
|
||||||
|
|
@ -1113,9 +981,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Path.IsPathRooted(resolvedPath))
|
if (Path.IsPathRooted(resolvedPath))
|
||||||
{
|
|
||||||
return _lumina?.GetFileFromDisk<T>(resolvedPath);
|
return _lumina?.GetFileFromDisk<T>(resolvedPath);
|
||||||
}
|
|
||||||
|
|
||||||
return DalamudServices.GameData.GetFile<T>(resolvedPath);
|
return DalamudServices.GameData.GetFile<T>(resolvedPath);
|
||||||
}
|
}
|
||||||
|
|
@ -1197,20 +1063,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private void SubscribeToNewCollections(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _)
|
private void SubscribeToNewCollections(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _)
|
||||||
{
|
{
|
||||||
if (type != CollectionType.Inactive)
|
if (type != CollectionType.Inactive)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCollection != null && _delegates.TryGetValue(oldCollection, out var del))
|
if (oldCollection != null && _delegates.TryGetValue(oldCollection, out var del))
|
||||||
{
|
|
||||||
oldCollection.ModSettingChanged -= del;
|
oldCollection.ModSettingChanged -= del;
|
||||||
}
|
|
||||||
|
|
||||||
if (newCollection != null)
|
if (newCollection != null)
|
||||||
{
|
|
||||||
SubscribeToCollection(newCollection);
|
SubscribeToCollection(newCollection);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void InvokePreSettingsPanel(string modDirectory)
|
public void InvokePreSettingsPanel(string modDirectory)
|
||||||
=> PreSettingsPanelDraw?.Invoke(modDirectory);
|
=> PreSettingsPanelDraw?.Invoke(modDirectory);
|
||||||
|
|
|
||||||
119
Penumbra/Api/TempCollectionManager.cs
Normal file
119
Penumbra/Api/TempCollectionManager.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Services;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
|
public class TempCollectionManager : IDisposable
|
||||||
|
{
|
||||||
|
public int GlobalChangeCounter { get; private set; } = 0;
|
||||||
|
public readonly IndividualCollections Collections;
|
||||||
|
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
|
private readonly Dictionary<string, ModCollection> _customCollections = new();
|
||||||
|
|
||||||
|
public TempCollectionManager(CommunicatorService communicator, IndividualCollections collections)
|
||||||
|
{
|
||||||
|
_communicator = communicator;
|
||||||
|
Collections = collections;
|
||||||
|
|
||||||
|
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGlobalModChange(Mod.TemporaryMod mod, bool created, bool removed)
|
||||||
|
=> TempModManager.OnGlobalModChange(_customCollections.Values, mod, created, removed);
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
=> _customCollections.Count;
|
||||||
|
|
||||||
|
public IEnumerable<ModCollection> Values
|
||||||
|
=> _customCollections.Values;
|
||||||
|
|
||||||
|
public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection)
|
||||||
|
=> _customCollections.TryGetValue(name.ToLowerInvariant(), out collection);
|
||||||
|
|
||||||
|
public string CreateTemporaryCollection(string name)
|
||||||
|
{
|
||||||
|
if (Penumbra.CollectionManager.ByName(name, out _))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (GlobalChangeCounter == int.MaxValue)
|
||||||
|
GlobalChangeCounter = 0;
|
||||||
|
var collection = ModCollection.CreateNewTemporary(name, GlobalChangeCounter++);
|
||||||
|
if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection))
|
||||||
|
return collection.Name;
|
||||||
|
|
||||||
|
collection.ClearCache();
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveTemporaryCollection(string collectionName)
|
||||||
|
{
|
||||||
|
if (!_customCollections.Remove(collectionName.ToLowerInvariant(), out var collection))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0);
|
||||||
|
collection.ClearCache();
|
||||||
|
for (var i = 0; i < Collections.Count; ++i)
|
||||||
|
{
|
||||||
|
if (Collections[i].Collection == collection)
|
||||||
|
{
|
||||||
|
_communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName);
|
||||||
|
Collections.Delete(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddIdentifier(ModCollection collection, params ActorIdentifier[] identifiers)
|
||||||
|
{
|
||||||
|
if (Collections.Add(identifiers, collection))
|
||||||
|
{
|
||||||
|
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddIdentifier(string collectionName, params ActorIdentifier[] identifiers)
|
||||||
|
{
|
||||||
|
if (!_customCollections.TryGetValue(collectionName.ToLowerInvariant(), out var collection))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return AddIdentifier(collection, identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddIdentifier(string collectionName, string characterName, ushort worldId = ushort.MaxValue)
|
||||||
|
{
|
||||||
|
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var identifier = Penumbra.Actors.CreatePlayer(byteString, worldId);
|
||||||
|
if (!identifier.IsValid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return AddIdentifier(collectionName, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool RemoveByCharacterName(string characterName, ushort worldId = ushort.MaxValue)
|
||||||
|
{
|
||||||
|
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var identifier = Penumbra.Actors.CreatePlayer(byteString, worldId);
|
||||||
|
return Collections.Individuals.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,7 @@ using Penumbra.Collections;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using Penumbra.Services;
|
||||||
using System.Linq;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
using Penumbra.String;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
@ -19,15 +16,23 @@ public enum RedirectResult
|
||||||
FilteredGamePath = 3,
|
FilteredGamePath = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TempModManager
|
public class TempModManager : IDisposable
|
||||||
{
|
{
|
||||||
public int GlobalChangeCounter { get; private set; } = 0;
|
private readonly CommunicatorService _communicator;
|
||||||
|
|
||||||
private readonly Dictionary<ModCollection, List<Mod.TemporaryMod>> _mods = new();
|
private readonly Dictionary<ModCollection, List<Mod.TemporaryMod>> _mods = new();
|
||||||
private readonly List<Mod.TemporaryMod> _modsForAllCollections = new();
|
private readonly List<Mod.TemporaryMod> _modsForAllCollections = new();
|
||||||
private readonly Dictionary< string, ModCollection > _customCollections = new();
|
|
||||||
public readonly IndividualCollections Collections = new(Penumbra.Actors);
|
|
||||||
|
|
||||||
public event ModCollection.Manager.CollectionChangeDelegate? CollectionChanged;
|
public TempModManager(CommunicatorService communicator)
|
||||||
|
{
|
||||||
|
_communicator = communicator;
|
||||||
|
_communicator.CollectionChange.Event += OnCollectionChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_communicator.CollectionChange.Event -= OnCollectionChange;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyDictionary<ModCollection, List<Mod.TemporaryMod>> Mods
|
public IReadOnlyDictionary<ModCollection, List<Mod.TemporaryMod>> Mods
|
||||||
=> _mods;
|
=> _mods;
|
||||||
|
|
@ -35,83 +40,6 @@ public class TempModManager
|
||||||
public IReadOnlyList<Mod.TemporaryMod> ModsForAllCollections
|
public IReadOnlyList<Mod.TemporaryMod> ModsForAllCollections
|
||||||
=> _modsForAllCollections;
|
=> _modsForAllCollections;
|
||||||
|
|
||||||
public IReadOnlyDictionary< string, ModCollection > CustomCollections
|
|
||||||
=> _customCollections;
|
|
||||||
|
|
||||||
public bool CollectionByName( string name, [NotNullWhen( true )] out ModCollection? collection )
|
|
||||||
=> _customCollections.TryGetValue( name.ToLowerInvariant(), out collection );
|
|
||||||
|
|
||||||
// These functions to check specific redirections or meta manipulations for existence are currently unused.
|
|
||||||
//public bool IsRegistered( string tag, ModCollection? collection, Utf8GamePath gamePath, out FullPath? fullPath, out int priority )
|
|
||||||
//{
|
|
||||||
// var mod = GetExistingMod( tag, collection, null );
|
|
||||||
// if( mod == null )
|
|
||||||
// {
|
|
||||||
// priority = 0;
|
|
||||||
// fullPath = null;
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// priority = mod.Priority;
|
|
||||||
// if( mod.Default.Files.TryGetValue( gamePath, out var f ) )
|
|
||||||
// {
|
|
||||||
// fullPath = f;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fullPath = null;
|
|
||||||
// return false;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public bool IsRegistered( string tag, ModCollection? collection, MetaManipulation meta, out MetaManipulation? manipulation,
|
|
||||||
// out int priority )
|
|
||||||
//{
|
|
||||||
// var mod = GetExistingMod( tag, collection, null );
|
|
||||||
// if( mod == null )
|
|
||||||
// {
|
|
||||||
// priority = 0;
|
|
||||||
// manipulation = null;
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// priority = mod.Priority;
|
|
||||||
// // IReadOnlySet has no TryGetValue for some reason.
|
|
||||||
// if( ( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var manip ) )
|
|
||||||
// {
|
|
||||||
// manipulation = manip;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// manipulation = null;
|
|
||||||
// return false;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// These functions for setting single redirections or manips are currently unused.
|
|
||||||
//public RedirectResult Register( string tag, ModCollection? collection, Utf8GamePath path, FullPath file, int priority )
|
|
||||||
//{
|
|
||||||
// if( Mod.FilterFile( path ) )
|
|
||||||
// {
|
|
||||||
// return RedirectResult.FilteredGamePath;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var mod = GetOrCreateMod( tag, collection, priority, out var created );
|
|
||||||
//
|
|
||||||
// var changes = !mod.Default.Files.TryGetValue( path, out var oldFile ) || !oldFile.Equals( file );
|
|
||||||
// mod.SetFile( path, file );
|
|
||||||
// ApplyModChange( mod, collection, created, false );
|
|
||||||
// return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public RedirectResult Register( string tag, ModCollection? collection, MetaManipulation meta, int priority )
|
|
||||||
//{
|
|
||||||
// var mod = GetOrCreateMod( tag, collection, priority, out var created );
|
|
||||||
// var changes = !( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var oldMeta )
|
|
||||||
// || !oldMeta.Equals( meta );
|
|
||||||
// mod.SetManipulation( meta );
|
|
||||||
// ApplyModChange( mod, collection, created, false );
|
|
||||||
// return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success;
|
|
||||||
//}
|
|
||||||
|
|
||||||
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
|
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
|
||||||
HashSet<MetaManipulation> manips, int priority)
|
HashSet<MetaManipulation> manips, int priority)
|
||||||
{
|
{
|
||||||
|
|
@ -125,184 +53,54 @@ public class TempModManager
|
||||||
{
|
{
|
||||||
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null;
|
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null;
|
||||||
if (list == null)
|
if (list == null)
|
||||||
{
|
|
||||||
return RedirectResult.NotRegistered;
|
return RedirectResult.NotRegistered;
|
||||||
}
|
|
||||||
|
|
||||||
var removed = list.RemoveAll(m =>
|
var removed = list.RemoveAll(m =>
|
||||||
{
|
{
|
||||||
if (m.Name != tag || priority != null && m.Priority != priority.Value)
|
if (m.Name != tag || priority != null && m.Priority != priority.Value)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
ApplyModChange(m, collection, false, true);
|
ApplyModChange(m, collection, false, true);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (removed == 0)
|
if (removed == 0)
|
||||||
{
|
|
||||||
return RedirectResult.NotRegistered;
|
return RedirectResult.NotRegistered;
|
||||||
}
|
|
||||||
|
|
||||||
if (list.Count == 0 && collection != null)
|
if (list.Count == 0 && collection != null)
|
||||||
{
|
|
||||||
_mods.Remove(collection);
|
_mods.Remove(collection);
|
||||||
}
|
|
||||||
|
|
||||||
return RedirectResult.Success;
|
return RedirectResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreateTemporaryCollection( string name )
|
|
||||||
{
|
|
||||||
if( Penumbra.CollectionManager.ByName( name, out _ ) )
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( GlobalChangeCounter == int.MaxValue )
|
|
||||||
GlobalChangeCounter = 0;
|
|
||||||
var collection = ModCollection.CreateNewTemporary( name, GlobalChangeCounter++ );
|
|
||||||
if( _customCollections.TryAdd( collection.Name.ToLowerInvariant(), collection ) )
|
|
||||||
{
|
|
||||||
return collection.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.ClearCache();
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RemoveTemporaryCollection( string collectionName )
|
|
||||||
{
|
|
||||||
if( !_customCollections.Remove( collectionName.ToLowerInvariant(), out var collection ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0);
|
|
||||||
_mods.Remove( collection );
|
|
||||||
collection.ClearCache();
|
|
||||||
for( var i = 0; i < Collections.Count; ++i )
|
|
||||||
{
|
|
||||||
if( Collections[ i ].Collection == collection )
|
|
||||||
{
|
|
||||||
CollectionChanged?.Invoke( CollectionType.Temporary, collection, null, Collections[ i ].DisplayName );
|
|
||||||
Collections.Delete( i );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddIdentifier( ModCollection collection, params ActorIdentifier[] identifiers )
|
|
||||||
{
|
|
||||||
if( Collections.Add( identifiers, collection ) )
|
|
||||||
{
|
|
||||||
CollectionChanged?.Invoke( CollectionType.Temporary, null, collection, Collections.Last().DisplayName );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddIdentifier( string collectionName, params ActorIdentifier[] identifiers )
|
|
||||||
{
|
|
||||||
if( !_customCollections.TryGetValue( collectionName.ToLowerInvariant(), out var collection ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddIdentifier( collection, identifiers );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddIdentifier( string collectionName, string characterName, ushort worldId = ushort.MaxValue )
|
|
||||||
{
|
|
||||||
if( !ByteString.FromString( characterName, out var byteString, false ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var identifier = Penumbra.Actors.CreatePlayer( byteString, worldId );
|
|
||||||
if( !identifier.IsValid )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddIdentifier( collectionName, identifier );
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool RemoveByCharacterName( string characterName, ushort worldId = ushort.MaxValue )
|
|
||||||
{
|
|
||||||
if( !ByteString.FromString( characterName, out var byteString, false ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var identifier = Penumbra.Actors.CreatePlayer( byteString, worldId );
|
|
||||||
return Collections.Individuals.TryGetValue( identifier, out var collection ) && RemoveTemporaryCollection( collection.Name );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Apply any new changes to the temporary mod.
|
// Apply any new changes to the temporary mod.
|
||||||
private static void ApplyModChange( Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed )
|
private void ApplyModChange(Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed)
|
||||||
{
|
{
|
||||||
if( collection == null )
|
if (collection != null)
|
||||||
{
|
{
|
||||||
if (removed)
|
if (removed)
|
||||||
{
|
|
||||||
foreach( var c in Penumbra.CollectionManager )
|
|
||||||
{
|
|
||||||
c.Remove( mod );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach( var c in Penumbra.CollectionManager )
|
|
||||||
{
|
|
||||||
c.Apply( mod, created );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( removed )
|
|
||||||
{
|
|
||||||
collection.Remove(mod);
|
collection.Remove(mod);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
collection.Apply(mod, created);
|
collection.Apply(mod, created);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_communicator.TemporaryGlobalModChange.Invoke(mod, created, removed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only find already existing mods, currently unused.
|
/// <summary>
|
||||||
//private Mod.TemporaryMod? GetExistingMod( string tag, ModCollection? collection, int? priority )
|
/// Apply a mod change to a set of collections.
|
||||||
//{
|
/// </summary>
|
||||||
// var list = collection == null ? _modsForAllCollections : _mods.TryGetValue( collection, out var l ) ? l : null;
|
public static void OnGlobalModChange(IEnumerable<ModCollection> collections, Mod.TemporaryMod mod, bool created, bool removed)
|
||||||
// if( list == null )
|
{
|
||||||
// {
|
if (removed)
|
||||||
// return null;
|
foreach (var c in collections)
|
||||||
// }
|
c.Remove(mod);
|
||||||
//
|
else
|
||||||
// if( priority != null )
|
foreach (var c in collections)
|
||||||
// {
|
c.Apply(mod, created);
|
||||||
// return list.Find( m => m.Priority == priority.Value && m.Name == tag );
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Mod.TemporaryMod? highestMod = null;
|
|
||||||
// var highestPriority = int.MinValue;
|
|
||||||
// foreach( var m in list )
|
|
||||||
// {
|
|
||||||
// if( highestPriority < m.Priority && m.Name == tag )
|
|
||||||
// {
|
|
||||||
// highestPriority = m.Priority;
|
|
||||||
// highestMod = m;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return highestMod;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections).
|
// Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections).
|
||||||
// Returns the found or created mod and whether it was newly created.
|
// Returns the found or created mod and whether it was newly created.
|
||||||
|
|
@ -341,4 +139,11 @@ public class TempModManager
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection,
|
||||||
|
string _)
|
||||||
|
{
|
||||||
|
if (collectionType is CollectionType.Temporary or CollectionType.Inactive && newCollection == null && oldCollection != null)
|
||||||
|
_mods.Remove(oldCollection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
@ -21,9 +22,6 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
public const int Version = 1;
|
public const int Version = 1;
|
||||||
|
|
||||||
// Is invoked after the collections actually changed.
|
|
||||||
public event CollectionChangeDelegate CollectionChanged;
|
|
||||||
|
|
||||||
// The collection currently selected for changing settings.
|
// The collection currently selected for changing settings.
|
||||||
public ModCollection Current { get; private set; } = Empty;
|
public ModCollection Current { get; private set; } = Empty;
|
||||||
|
|
||||||
|
|
@ -40,6 +38,7 @@ public partial class ModCollection
|
||||||
private ModCollection DefaultName { get; set; } = Empty;
|
private ModCollection DefaultName { get; set; } = Empty;
|
||||||
|
|
||||||
// The list of character collections.
|
// The list of character collections.
|
||||||
|
// TODO
|
||||||
public readonly IndividualCollections Individuals = new(Penumbra.Actors);
|
public readonly IndividualCollections Individuals = new(Penumbra.Actors);
|
||||||
|
|
||||||
public ModCollection Individual(ActorIdentifier identifier)
|
public ModCollection Individual(ActorIdentifier identifier)
|
||||||
|
|
@ -56,9 +55,7 @@ public partial class ModCollection
|
||||||
public ModCollection? ByType(CollectionType type, ActorIdentifier identifier)
|
public ModCollection? ByType(CollectionType type, ActorIdentifier identifier)
|
||||||
{
|
{
|
||||||
if (type.IsSpecial())
|
if (type.IsSpecial())
|
||||||
{
|
|
||||||
return _specialCollections[(int)type];
|
return _specialCollections[(int)type];
|
||||||
}
|
|
||||||
|
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
|
|
@ -78,21 +75,19 @@ public partial class ModCollection
|
||||||
CollectionType.Default => Default.Index,
|
CollectionType.Default => Default.Index,
|
||||||
CollectionType.Interface => Interface.Index,
|
CollectionType.Interface => Interface.Index,
|
||||||
CollectionType.Current => Current.Index,
|
CollectionType.Current => Current.Index,
|
||||||
CollectionType.Individual => individualIndex < 0 || individualIndex >= Individuals.Count ? -1 : Individuals[ individualIndex ].Collection.Index,
|
CollectionType.Individual => individualIndex < 0 || individualIndex >= Individuals.Count
|
||||||
|
? -1
|
||||||
|
: Individuals[individualIndex].Collection.Index,
|
||||||
_ when collectionType.IsSpecial() => _specialCollections[(int)collectionType]?.Index ?? Default.Index,
|
_ when collectionType.IsSpecial() => _specialCollections[(int)collectionType]?.Index ?? Default.Index,
|
||||||
_ => -1,
|
_ => -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (oldCollectionIdx == -1 || newIdx == oldCollectionIdx)
|
if (oldCollectionIdx == -1 || newIdx == oldCollectionIdx)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var newCollection = this[newIdx];
|
var newCollection = this[newIdx];
|
||||||
if (newIdx > Empty.Index)
|
if (newIdx > Empty.Index)
|
||||||
{
|
|
||||||
newCollection.CreateCache();
|
newCollection.CreateCache();
|
||||||
}
|
|
||||||
|
|
||||||
switch (collectionType)
|
switch (collectionType)
|
||||||
{
|
{
|
||||||
|
|
@ -127,7 +122,8 @@ public partial class ModCollection
|
||||||
RemoveCache(oldCollectionIdx);
|
RemoveCache(oldCollectionIdx);
|
||||||
|
|
||||||
UpdateCurrentCollectionInUse();
|
UpdateCurrentCollectionInUse();
|
||||||
CollectionChanged.Invoke( collectionType, this[ oldCollectionIdx ], newCollection, collectionType == CollectionType.Individual ? Individuals[ individualIndex ].DisplayName : string.Empty );
|
_communicator.CollectionChange.Invoke(collectionType, this[oldCollectionIdx], newCollection,
|
||||||
|
collectionType == CollectionType.Individual ? Individuals[individualIndex].DisplayName : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCurrentCollectionInUse()
|
private void UpdateCurrentCollectionInUse()
|
||||||
|
|
@ -145,12 +141,10 @@ public partial class ModCollection
|
||||||
public bool CreateSpecialCollection(CollectionType collectionType)
|
public bool CreateSpecialCollection(CollectionType collectionType)
|
||||||
{
|
{
|
||||||
if (!collectionType.IsSpecial() || _specialCollections[(int)collectionType] != null)
|
if (!collectionType.IsSpecial() || _specialCollections[(int)collectionType] != null)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
_specialCollections[(int)collectionType] = Default;
|
_specialCollections[(int)collectionType] = Default;
|
||||||
CollectionChanged.Invoke( collectionType, null, Default );
|
_communicator.CollectionChange.Invoke(collectionType, null, Default, string.Empty);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,15 +152,13 @@ public partial class ModCollection
|
||||||
public void RemoveSpecialCollection(CollectionType collectionType)
|
public void RemoveSpecialCollection(CollectionType collectionType)
|
||||||
{
|
{
|
||||||
if (!collectionType.IsSpecial())
|
if (!collectionType.IsSpecial())
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var old = _specialCollections[(int)collectionType];
|
var old = _specialCollections[(int)collectionType];
|
||||||
if (old != null)
|
if (old != null)
|
||||||
{
|
{
|
||||||
_specialCollections[(int)collectionType] = null;
|
_specialCollections[(int)collectionType] = null;
|
||||||
CollectionChanged.Invoke( collectionType, old, null );
|
_communicator.CollectionChange.Invoke(collectionType, old, null, string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,39 +166,31 @@ public partial class ModCollection
|
||||||
public void CreateIndividualCollection(params ActorIdentifier[] identifiers)
|
public void CreateIndividualCollection(params ActorIdentifier[] identifiers)
|
||||||
{
|
{
|
||||||
if (Individuals.Add(identifiers, Default))
|
if (Individuals.Add(identifiers, Default))
|
||||||
{
|
_communicator.CollectionChange.Invoke(CollectionType.Individual, null, Default, Individuals.Last().DisplayName);
|
||||||
CollectionChanged.Invoke( CollectionType.Individual, null, Default, Individuals.Last().DisplayName );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveIndividualCollection(int individualIndex)
|
public void RemoveIndividualCollection(int individualIndex)
|
||||||
{
|
{
|
||||||
if (individualIndex < 0 || individualIndex >= Individuals.Count)
|
if (individualIndex < 0 || individualIndex >= Individuals.Count)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var (name, old) = Individuals[individualIndex];
|
var (name, old) = Individuals[individualIndex];
|
||||||
if (Individuals.Delete(individualIndex))
|
if (Individuals.Delete(individualIndex))
|
||||||
{
|
_communicator.CollectionChange.Invoke(CollectionType.Individual, old, null, name);
|
||||||
CollectionChanged.Invoke( CollectionType.Individual, old, null, name );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MoveIndividualCollection(int from, int to)
|
public void MoveIndividualCollection(int from, int to)
|
||||||
{
|
{
|
||||||
if (Individuals.Move(from, to))
|
if (Individuals.Move(from, to))
|
||||||
{
|
|
||||||
SaveActiveCollections();
|
SaveActiveCollections();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain the index of a collection by name.
|
// Obtain the index of a collection by name.
|
||||||
private int GetIndexForCollectionName(string name)
|
private int GetIndexForCollectionName(string name)
|
||||||
=> name.Length == 0 ? Empty.Index : _collections.IndexOf(c => c.Name == name);
|
=> name.Length == 0 ? Empty.Index : _collections.IndexOf(c => c.Name == name);
|
||||||
|
|
||||||
public static string ActiveCollectionFile
|
public static string ActiveCollectionFile(DalamudPluginInterface pi)
|
||||||
=> Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "active_collections.json" );
|
=> Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
|
||||||
|
|
||||||
// Load default, current, special, and character collections from config.
|
// Load default, current, special, and character collections from config.
|
||||||
// Then create caches. If a collection does not exist anymore, reset it to an appropriate default.
|
// Then create caches. If a collection does not exist anymore, reset it to an appropriate default.
|
||||||
|
|
@ -219,7 +203,8 @@ public partial class ModCollection
|
||||||
var defaultIdx = GetIndexForCollectionName(defaultName);
|
var defaultIdx = GetIndexForCollectionName(defaultName);
|
||||||
if (defaultIdx < 0)
|
if (defaultIdx < 0)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage( $"Last choice of {ConfigWindow.DefaultCollection} {defaultName} is not available, reset to {Empty.Name}.", "Load Failure",
|
ChatUtil.NotificationMessage(
|
||||||
|
$"Last choice of {ConfigWindow.DefaultCollection} {defaultName} is not available, reset to {Empty.Name}.", "Load Failure",
|
||||||
NotificationType.Warning);
|
NotificationType.Warning);
|
||||||
Default = Empty;
|
Default = Empty;
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
|
|
@ -235,7 +220,8 @@ public partial class ModCollection
|
||||||
if (interfaceIdx < 0)
|
if (interfaceIdx < 0)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage(
|
ChatUtil.NotificationMessage(
|
||||||
$"Last choice of {ConfigWindow.InterfaceCollection} {interfaceName} is not available, reset to {Empty.Name}.", "Load Failure", NotificationType.Warning );
|
$"Last choice of {ConfigWindow.InterfaceCollection} {interfaceName} is not available, reset to {Empty.Name}.",
|
||||||
|
"Load Failure", NotificationType.Warning);
|
||||||
Interface = Empty;
|
Interface = Empty;
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +236,8 @@ public partial class ModCollection
|
||||||
if (currentIdx < 0)
|
if (currentIdx < 0)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage(
|
ChatUtil.NotificationMessage(
|
||||||
$"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}.", "Load Failure", NotificationType.Warning );
|
$"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}.",
|
||||||
|
"Load Failure", NotificationType.Warning);
|
||||||
Current = DefaultName;
|
Current = DefaultName;
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +255,8 @@ public partial class ModCollection
|
||||||
var idx = GetIndexForCollectionName(typeName);
|
var idx = GetIndexForCollectionName(typeName);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage( $"Last choice of {name} Collection {typeName} is not available, removed.", "Load Failure", NotificationType.Warning );
|
ChatUtil.NotificationMessage($"Last choice of {name} Collection {typeName} is not available, removed.", "Load Failure",
|
||||||
|
NotificationType.Warning);
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -283,34 +271,28 @@ public partial class ModCollection
|
||||||
|
|
||||||
// Save any changes and create all required caches.
|
// Save any changes and create all required caches.
|
||||||
if (configChanged)
|
if (configChanged)
|
||||||
{
|
|
||||||
SaveActiveCollections();
|
SaveActiveCollections();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate ungendered collections to Male and Female for 0.5.9.0.
|
// Migrate ungendered collections to Male and Female for 0.5.9.0.
|
||||||
public static void MigrateUngenderedCollections()
|
public static void MigrateUngenderedCollections(FilenameService fileNames)
|
||||||
{
|
{
|
||||||
if (!ReadActiveCollections(out var jObject))
|
if (!ReadActiveCollections(out var jObject))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male ")))
|
foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male ")))
|
||||||
{
|
{
|
||||||
var oldName = type.ToString()[4..];
|
var oldName = type.ToString()[4..];
|
||||||
var value = jObject[oldName];
|
var value = jObject[oldName];
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
jObject.Remove(oldName);
|
jObject.Remove(oldName);
|
||||||
jObject.Add("Male" + oldName, value);
|
jObject.Add("Male" + oldName, value);
|
||||||
jObject.Add("Female" + oldName, value);
|
jObject.Add("Female" + oldName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = File.Open( ActiveCollectionFile, FileMode.Truncate );
|
using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate);
|
||||||
using var writer = new StreamWriter(stream);
|
using var writer = new StreamWriter(stream);
|
||||||
using var j = new JsonTextWriter(writer);
|
using var j = new JsonTextWriter(writer);
|
||||||
j.Formatting = Formatting.Indented;
|
j.Formatting = Formatting.Indented;
|
||||||
|
|
@ -322,9 +304,7 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
var version = jObject[nameof(Version)]?.Value<int>() ?? 0;
|
var version = jObject[nameof(Version)]?.Value<int>() ?? 0;
|
||||||
if (version > 0)
|
if (version > 0)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Load character collections. If a player name comes up multiple times, the last one is applied.
|
// Load character collections. If a player name comes up multiple times, the last one is applied.
|
||||||
var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
|
var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
|
||||||
|
|
@ -334,7 +314,8 @@ public partial class ModCollection
|
||||||
var idx = GetIndexForCollectionName(collectionName);
|
var idx = GetIndexForCollectionName(collectionName);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}.", "Load Failure",
|
ChatUtil.NotificationMessage(
|
||||||
|
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}.", "Load Failure",
|
||||||
NotificationType.Warning);
|
NotificationType.Warning);
|
||||||
dict.Add(player, Empty);
|
dict.Add(player, Empty);
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +337,8 @@ public partial class ModCollection
|
||||||
|
|
||||||
internal void SaveActiveCollectionsInternal()
|
internal void SaveActiveCollectionsInternal()
|
||||||
{
|
{
|
||||||
var file = ActiveCollectionFile;
|
// TODO
|
||||||
|
var file = ActiveCollectionFile(DalamudServices.PluginInterface);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jObj = new JObject
|
var jObj = new JObject
|
||||||
|
|
@ -366,16 +348,14 @@ public partial class ModCollection
|
||||||
{ nameof(Interface), Interface.Name },
|
{ nameof(Interface), Interface.Name },
|
||||||
{ nameof(Current), Current.Name },
|
{ nameof(Current), Current.Name },
|
||||||
};
|
};
|
||||||
foreach( var (type, collection) in _specialCollections.WithIndex().Where( p => p.Value != null ).Select( p => ( ( CollectionType )p.Index, p.Value! ) ) )
|
foreach (var (type, collection) in _specialCollections.WithIndex().Where(p => p.Value != null)
|
||||||
{
|
.Select(p => ((CollectionType)p.Index, p.Value!)))
|
||||||
jObj.Add(type.ToString(), collection.Name);
|
jObj.Add(type.ToString(), collection.Name);
|
||||||
}
|
|
||||||
|
|
||||||
jObj.Add(nameof(Individuals), Individuals.ToJObject());
|
jObj.Add(nameof(Individuals), Individuals.ToJObject());
|
||||||
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
||||||
using var writer = new StreamWriter(stream);
|
using var writer = new StreamWriter(stream);
|
||||||
using var j = new JsonTextWriter( writer )
|
using var j = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||||
{ Formatting = Formatting.Indented };
|
|
||||||
jObj.WriteTo(j);
|
jObj.WriteTo(j);
|
||||||
Penumbra.Log.Verbose("Active Collections saved.");
|
Penumbra.Log.Verbose("Active Collections saved.");
|
||||||
}
|
}
|
||||||
|
|
@ -389,9 +369,9 @@ public partial class ModCollection
|
||||||
// Returns true if this is successful, false if the file does not exist or it is unsuccessful.
|
// Returns true if this is successful, false if the file does not exist or it is unsuccessful.
|
||||||
private static bool ReadActiveCollections(out JObject ret)
|
private static bool ReadActiveCollections(out JObject ret)
|
||||||
{
|
{
|
||||||
var file = ActiveCollectionFile;
|
// TODO
|
||||||
|
var file = ActiveCollectionFile(DalamudServices.PluginInterface);
|
||||||
if (File.Exists(file))
|
if (File.Exists(file))
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ret = JObject.Parse(File.ReadAllText(file));
|
ret = JObject.Parse(File.ReadAllText(file));
|
||||||
|
|
@ -401,7 +381,6 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error($"Could not read active collections from file {file}:\n{e}");
|
Penumbra.Log.Error($"Could not read active collections from file {file}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ret = new JObject();
|
ret = new JObject();
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -410,11 +389,9 @@ public partial class ModCollection
|
||||||
// Save if any of the active collections is changed.
|
// Save if any of the active collections is changed.
|
||||||
private void SaveOnChange(CollectionType collectionType, ModCollection? _1, ModCollection? _2, string _3)
|
private void SaveOnChange(CollectionType collectionType, ModCollection? _1, ModCollection? _2, string _3)
|
||||||
{
|
{
|
||||||
if( collectionType != CollectionType.Inactive )
|
if (collectionType is not CollectionType.Inactive and not CollectionType.Temporary)
|
||||||
{
|
|
||||||
SaveActiveCollections();
|
SaveActiveCollections();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Cache handling. Usually recreate caches on the next framework tick,
|
// Cache handling. Usually recreate caches on the next framework tick,
|
||||||
// but at launch create all of them at once.
|
// but at launch create all of them at once.
|
||||||
|
|
@ -440,34 +417,26 @@ public partial class ModCollection
|
||||||
&& idx != Current.Index
|
&& idx != Current.Index
|
||||||
&& _specialCollections.All(c => c == null || c.Index != idx)
|
&& _specialCollections.All(c => c == null || c.Index != idx)
|
||||||
&& Individuals.Select(p => p.Collection).All(c => c.Index != idx))
|
&& Individuals.Select(p => p.Collection).All(c => c.Index != idx))
|
||||||
{
|
|
||||||
_collections[idx].ClearCache();
|
_collections[idx].ClearCache();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Recalculate effective files for active collections on events.
|
// Recalculate effective files for active collections on events.
|
||||||
private void OnModAddedActive(Mod mod)
|
private void OnModAddedActive(Mod mod)
|
||||||
{
|
{
|
||||||
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||||
{
|
|
||||||
collection._cache!.AddMod(mod, true);
|
collection._cache!.AddMod(mod, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnModRemovedActive(Mod mod)
|
private void OnModRemovedActive(Mod mod)
|
||||||
{
|
{
|
||||||
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||||
{
|
|
||||||
collection._cache!.RemoveMod(mod, true);
|
collection._cache!.RemoveMod(mod, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnModMovedActive(Mod mod)
|
private void OnModMovedActive(Mod mod)
|
||||||
{
|
{
|
||||||
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
foreach (var collection in this.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||||
{
|
|
||||||
collection._cache!.ReloadMod(mod, true);
|
collection._cache!.ReloadMod(mod, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -7,6 +7,8 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
||||||
|
|
@ -14,12 +16,8 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
public sealed partial class Manager : IDisposable, IEnumerable<ModCollection>
|
public sealed partial class Manager : IDisposable, IEnumerable<ModCollection>
|
||||||
{
|
{
|
||||||
// On addition, oldCollection is null. On deletion, newCollection is null.
|
|
||||||
// displayName is only set for type == Individual.
|
|
||||||
public delegate void CollectionChangeDelegate( CollectionType collectionType, ModCollection? oldCollection,
|
|
||||||
ModCollection? newCollection, string displayName = "" );
|
|
||||||
|
|
||||||
private readonly Mod.Manager _modManager;
|
private readonly Mod.Manager _modManager;
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
|
|
||||||
// The empty collection is always available and always has index 0.
|
// The empty collection is always available and always has index 0.
|
||||||
// It can not be deleted or moved.
|
// It can not be deleted or moved.
|
||||||
|
|
@ -51,8 +49,9 @@ public partial class ModCollection
|
||||||
public IEnumerable<ModCollection> GetEnumeratorWithEmpty()
|
public IEnumerable<ModCollection> GetEnumeratorWithEmpty()
|
||||||
=> _collections;
|
=> _collections;
|
||||||
|
|
||||||
public Manager( Mod.Manager manager )
|
public Manager(CommunicatorService communicator, Mod.Manager manager)
|
||||||
{
|
{
|
||||||
|
_communicator = communicator;
|
||||||
_modManager = manager;
|
_modManager = manager;
|
||||||
|
|
||||||
// The collection manager reacts to changes in mods by itself.
|
// The collection manager reacts to changes in mods by itself.
|
||||||
|
|
@ -60,7 +59,8 @@ public partial class ModCollection
|
||||||
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
||||||
_modManager.ModOptionChanged += OnModOptionsChanged;
|
_modManager.ModOptionChanged += OnModOptionsChanged;
|
||||||
_modManager.ModPathChanged += OnModPathChange;
|
_modManager.ModPathChanged += OnModPathChange;
|
||||||
CollectionChanged += SaveOnChange;
|
_communicator.CollectionChange.Event += SaveOnChange;
|
||||||
|
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
|
||||||
ReadCollections();
|
ReadCollections();
|
||||||
LoadCollections();
|
LoadCollections();
|
||||||
UpdateCurrentCollectionInUse();
|
UpdateCurrentCollectionInUse();
|
||||||
|
|
@ -68,12 +68,17 @@ public partial class ModCollection
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_communicator.CollectionChange.Event -= SaveOnChange;
|
||||||
|
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
|
||||||
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
||||||
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
||||||
_modManager.ModOptionChanged -= OnModOptionsChanged;
|
_modManager.ModOptionChanged -= OnModOptionsChanged;
|
||||||
_modManager.ModPathChanged -= OnModPathChange;
|
_modManager.ModPathChanged -= OnModPathChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnGlobalModChange(Mod.TemporaryMod mod, bool created, bool removed)
|
||||||
|
=> TempModManager.OnGlobalModChange(_collections, mod, created, removed);
|
||||||
|
|
||||||
// Returns true if the name is not empty, it is not the name of the empty collection
|
// Returns true if the name is not empty, it is not the name of the empty collection
|
||||||
// and no existing collection results in the same filename as name.
|
// and no existing collection results in the same filename as name.
|
||||||
public bool CanAddCollection(string name, out string fixedName)
|
public bool CanAddCollection(string name, out string fixedName)
|
||||||
|
|
@ -115,7 +120,7 @@ public partial class ModCollection
|
||||||
_collections.Add(newCollection);
|
_collections.Add(newCollection);
|
||||||
newCollection.Save();
|
newCollection.Save();
|
||||||
Penumbra.Log.Debug($"Added collection {newCollection.AnonymizedName}.");
|
Penumbra.Log.Debug($"Added collection {newCollection.AnonymizedName}.");
|
||||||
CollectionChanged.Invoke( CollectionType.Inactive, null, newCollection );
|
_communicator.CollectionChange.Invoke(CollectionType.Inactive, null, newCollection, string.Empty);
|
||||||
SetCollection(newCollection.Index, CollectionType.Current);
|
SetCollection(newCollection.Index, CollectionType.Current);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -138,38 +143,28 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx == Current.Index)
|
if (idx == Current.Index)
|
||||||
{
|
|
||||||
SetCollection(DefaultName.Index, CollectionType.Current);
|
SetCollection(DefaultName.Index, CollectionType.Current);
|
||||||
}
|
|
||||||
|
|
||||||
if (idx == Default.Index)
|
if (idx == Default.Index)
|
||||||
{
|
|
||||||
SetCollection(Empty.Index, CollectionType.Default);
|
SetCollection(Empty.Index, CollectionType.Default);
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < _specialCollections.Length; ++i)
|
for (var i = 0; i < _specialCollections.Length; ++i)
|
||||||
{
|
{
|
||||||
if (idx == _specialCollections[i]?.Index)
|
if (idx == _specialCollections[i]?.Index)
|
||||||
{
|
|
||||||
SetCollection(Empty, (CollectionType)i);
|
SetCollection(Empty, (CollectionType)i);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < Individuals.Count; ++i)
|
for (var i = 0; i < Individuals.Count; ++i)
|
||||||
{
|
{
|
||||||
if (Individuals[i].Collection.Index == idx)
|
if (Individuals[i].Collection.Index == idx)
|
||||||
{
|
|
||||||
SetCollection(Empty, CollectionType.Individual, i);
|
SetCollection(Empty, CollectionType.Individual, i);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var collection = _collections[idx];
|
var collection = _collections[idx];
|
||||||
|
|
||||||
// Clear own inheritances.
|
// Clear own inheritances.
|
||||||
foreach (var inheritance in collection.Inheritance)
|
foreach (var inheritance in collection.Inheritance)
|
||||||
{
|
|
||||||
collection.ClearSubscriptions(inheritance);
|
collection.ClearSubscriptions(inheritance);
|
||||||
}
|
|
||||||
|
|
||||||
collection.Delete();
|
collection.Delete();
|
||||||
_collections.RemoveAt(idx);
|
_collections.RemoveAt(idx);
|
||||||
|
|
@ -179,18 +174,14 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
var inheritedIdx = c._inheritance.IndexOf(collection);
|
var inheritedIdx = c._inheritance.IndexOf(collection);
|
||||||
if (inheritedIdx >= 0)
|
if (inheritedIdx >= 0)
|
||||||
{
|
|
||||||
c.RemoveInheritance(inheritedIdx);
|
c.RemoveInheritance(inheritedIdx);
|
||||||
}
|
|
||||||
|
|
||||||
if (c.Index > idx)
|
if (c.Index > idx)
|
||||||
{
|
|
||||||
--c.Index;
|
--c.Index;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Log.Debug($"Removed collection {collection.AnonymizedName}.");
|
Penumbra.Log.Debug($"Removed collection {collection.AnonymizedName}.");
|
||||||
CollectionChanged.Invoke( CollectionType.Inactive, collection, null );
|
_communicator.CollectionChange.Invoke(CollectionType.Inactive, collection, null, string.Empty);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,25 +191,19 @@ public partial class ModCollection
|
||||||
private void OnModDiscoveryStarted()
|
private void OnModDiscoveryStarted()
|
||||||
{
|
{
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
|
||||||
collection.PrepareModDiscovery();
|
collection.PrepareModDiscovery();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnModDiscoveryFinished()
|
private void OnModDiscoveryFinished()
|
||||||
{
|
{
|
||||||
// First, re-apply all mod settings.
|
// First, re-apply all mod settings.
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
|
||||||
collection.ApplyModSettings();
|
collection.ApplyModSettings();
|
||||||
}
|
|
||||||
|
|
||||||
// Afterwards, we update the caches. This can not happen in the same loop due to inheritance.
|
// Afterwards, we update the caches. This can not happen in the same loop due to inheritance.
|
||||||
foreach (var collection in this.Where(c => c.HasCache))
|
foreach (var collection in this.Where(c => c.HasCache))
|
||||||
{
|
|
||||||
collection.ForceCacheUpdate();
|
collection.ForceCacheUpdate();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// A changed mod path forces changes for all collections, active and inactive.
|
// A changed mod path forces changes for all collections, active and inactive.
|
||||||
|
|
@ -229,26 +214,20 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
case ModPathChangeType.Added:
|
case ModPathChangeType.Added:
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
|
||||||
collection.AddMod(mod);
|
collection.AddMod(mod);
|
||||||
}
|
|
||||||
|
|
||||||
OnModAddedActive(mod);
|
OnModAddedActive(mod);
|
||||||
break;
|
break;
|
||||||
case ModPathChangeType.Deleted:
|
case ModPathChangeType.Deleted:
|
||||||
OnModRemovedActive(mod);
|
OnModRemovedActive(mod);
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
|
||||||
collection.RemoveMod(mod, mod.Index);
|
collection.RemoveMod(mod, mod.Index);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ModPathChangeType.Moved:
|
case ModPathChangeType.Moved:
|
||||||
OnModMovedActive(mod);
|
OnModMovedActive(mod);
|
||||||
foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null))
|
foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null))
|
||||||
{
|
|
||||||
collection.Save();
|
collection.Save();
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ModPathChangeType.StartingReload:
|
case ModPathChangeType.StartingReload:
|
||||||
|
|
@ -272,10 +251,8 @@ public partial class ModCollection
|
||||||
foreach (var collection in this.Where(c => c.HasCache))
|
foreach (var collection in this.Where(c => c.HasCache))
|
||||||
{
|
{
|
||||||
if (collection[mod.Index].Settings is { Enabled: true })
|
if (collection[mod.Index].Settings is { Enabled: true })
|
||||||
{
|
|
||||||
collection._cache!.RemoveMod(mod, false);
|
collection._cache!.RemoveMod(mod, false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -284,36 +261,26 @@ public partial class ModCollection
|
||||||
|
|
||||||
// Handle changes that require overwriting the collection.
|
// Handle changes that require overwriting the collection.
|
||||||
if (requiresSaving)
|
if (requiresSaving)
|
||||||
{
|
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
{
|
||||||
if (collection._settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
if (collection._settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
||||||
{
|
|
||||||
collection.Save();
|
collection.Save();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle changes that reload the mod if the changes did not need to be prepared,
|
// Handle changes that reload the mod if the changes did not need to be prepared,
|
||||||
// or re-add the mod if they were prepared.
|
// or re-add the mod if they were prepared.
|
||||||
if (recomputeList)
|
if (recomputeList)
|
||||||
{
|
|
||||||
foreach (var collection in this.Where(c => c.HasCache))
|
foreach (var collection in this.Where(c => c.HasCache))
|
||||||
{
|
{
|
||||||
if (collection[mod.Index].Settings is { Enabled: true })
|
if (collection[mod.Index].Settings is { Enabled: true })
|
||||||
{
|
{
|
||||||
if (reload)
|
if (reload)
|
||||||
{
|
|
||||||
collection._cache!.ReloadMod(mod, true);
|
collection._cache!.ReloadMod(mod, true);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
collection._cache!.AddMod(mod, true);
|
collection._cache!.AddMod(mod, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the collection with the default name if it does not exist.
|
// Add the collection with the default name if it does not exist.
|
||||||
// It should always be ensured that it exists, otherwise it will be created.
|
// It should always be ensured that it exists, otherwise it will be created.
|
||||||
|
|
@ -355,33 +322,27 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes)
|
if (changes)
|
||||||
{
|
|
||||||
collection.Save();
|
collection.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Read all collection files in the Collection Directory.
|
// Read all collection files in the Collection Directory.
|
||||||
// Ensure that the default named collection exists, and apply inheritances afterwards.
|
// Ensure that the default named collection exists, and apply inheritances afterwards.
|
||||||
// Duplicate collection files are not deleted, just not added here.
|
// Duplicate collection files are not deleted, just not added here.
|
||||||
private void ReadCollections()
|
private void ReadCollections()
|
||||||
{
|
{
|
||||||
var collectionDir = new DirectoryInfo( CollectionDirectory );
|
// TODO
|
||||||
|
var collectionDir = new DirectoryInfo(CollectionDirectory(DalamudServices.PluginInterface));
|
||||||
var inheritances = new List<IReadOnlyList<string>>();
|
var inheritances = new List<IReadOnlyList<string>>();
|
||||||
if (collectionDir.Exists)
|
if (collectionDir.Exists)
|
||||||
{
|
|
||||||
foreach (var file in collectionDir.EnumerateFiles("*.json"))
|
foreach (var file in collectionDir.EnumerateFiles("*.json"))
|
||||||
{
|
{
|
||||||
var collection = LoadFromFile(file, out var inheritance);
|
var collection = LoadFromFile(file, out var inheritance);
|
||||||
if (collection == null || collection.Name.Length == 0)
|
if (collection == null || collection.Name.Length == 0)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json")
|
if (file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json")
|
||||||
{
|
|
||||||
Penumbra.Log.Warning($"Collection {file.Name} does not correspond to {collection.Name}.");
|
Penumbra.Log.Warning($"Collection {file.Name} does not correspond to {collection.Name}.");
|
||||||
}
|
|
||||||
|
|
||||||
if (this[collection.Name] != null)
|
if (this[collection.Name] != null)
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +355,6 @@ public partial class ModCollection
|
||||||
_collections.Add(collection);
|
_collections.Add(collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
AddDefaultCollection();
|
AddDefaultCollection();
|
||||||
ApplyInheritances(inheritances);
|
ApplyInheritances(inheritances);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
@ -20,6 +21,10 @@ public sealed partial class IndividualCollections
|
||||||
public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals
|
public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals
|
||||||
=> _individuals;
|
=> _individuals;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public IndividualCollections( ActorService actorManager )
|
||||||
|
=> _actorManager = actorManager.AwaitedService;
|
||||||
|
|
||||||
public IndividualCollections(ActorManager actorManager)
|
public IndividualCollections(ActorManager actorManager)
|
||||||
=> _actorManager = actorManager;
|
=> _actorManager = actorManager;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,20 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
||||||
// File operations like saving, loading and deleting for a collection.
|
// File operations like saving, loading and deleting for a collection.
|
||||||
public partial class ModCollection
|
public partial class ModCollection
|
||||||
{
|
{
|
||||||
public static string CollectionDirectory
|
public static string CollectionDirectory(DalamudPluginInterface pi)
|
||||||
=> Path.Combine( DalamudServices.PluginInterface.GetPluginConfigDirectory(), "collections" );
|
=> Path.Combine( pi.GetPluginConfigDirectory(), "collections" );
|
||||||
|
|
||||||
// We need to remove all invalid path symbols from the collection name to be able to save it to file.
|
// We need to remove all invalid path symbols from the collection name to be able to save it to file.
|
||||||
|
// TODO
|
||||||
public FileInfo FileName
|
public FileInfo FileName
|
||||||
=> new(Path.Combine( CollectionDirectory, $"{Name.RemoveInvalidPathSymbols()}.json" ));
|
=> new(Path.Combine( CollectionDirectory(DalamudServices.PluginInterface), $"{Name.RemoveInvalidPathSymbols()}.json" ));
|
||||||
|
|
||||||
// Custom serialization due to shared mod information across managers.
|
// Custom serialization due to shared mod information across managers.
|
||||||
private void SaveCollection()
|
private void SaveCollection()
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public partial class ModCollection
|
||||||
var collection = new ModCollection( name, Empty );
|
var collection = new ModCollection( name, Empty );
|
||||||
collection.ModSettingChanged -= collection.SaveOnChange;
|
collection.ModSettingChanged -= collection.SaveOnChange;
|
||||||
collection.InheritanceChanged -= collection.SaveOnChange;
|
collection.InheritanceChanged -= collection.SaveOnChange;
|
||||||
collection.Index = ~Penumbra.TempMods.Collections.Count;
|
collection.Index = ~Penumbra.TempCollections.Count;
|
||||||
collection.ChangeCounter = changeCounter;
|
collection.ChangeCounter = changeCounter;
|
||||||
collection.CreateCache();
|
collection.CreateCache();
|
||||||
return collection;
|
return collection;
|
||||||
|
|
|
||||||
|
|
@ -1,370 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using OtterGui.Filesystem;
|
|
||||||
using Penumbra.Collections;
|
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Services;
|
|
||||||
|
|
||||||
namespace Penumbra;
|
|
||||||
|
|
||||||
public partial class Configuration
|
|
||||||
{
|
|
||||||
// Contains everything to migrate from older versions of the config to the current,
|
|
||||||
// including deprecated fields.
|
|
||||||
private class Migration
|
|
||||||
{
|
|
||||||
private Configuration _config = null!;
|
|
||||||
private JObject _data = null!;
|
|
||||||
|
|
||||||
public string CurrentCollection = ModCollection.DefaultCollection;
|
|
||||||
public string DefaultCollection = ModCollection.DefaultCollection;
|
|
||||||
public string ForcedCollection = string.Empty;
|
|
||||||
public Dictionary< string, string > CharacterCollections = new();
|
|
||||||
public Dictionary< string, string > ModSortOrder = new();
|
|
||||||
public bool InvertModListOrder;
|
|
||||||
public bool SortFoldersFirst;
|
|
||||||
public SortModeV3 SortMode = SortModeV3.FoldersFirst;
|
|
||||||
|
|
||||||
public static void Migrate( Configuration config )
|
|
||||||
{
|
|
||||||
if( !File.Exists( DalamudServices.PluginInterface.ConfigFile.FullName ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var m = new Migration
|
|
||||||
{
|
|
||||||
_config = config,
|
|
||||||
_data = JObject.Parse( File.ReadAllText( DalamudServices.PluginInterface.ConfigFile.FullName ) ),
|
|
||||||
};
|
|
||||||
|
|
||||||
CreateBackup();
|
|
||||||
m.Version0To1();
|
|
||||||
m.Version1To2();
|
|
||||||
m.Version2To3();
|
|
||||||
m.Version3To4();
|
|
||||||
m.Version4To5();
|
|
||||||
m.Version5To6();
|
|
||||||
m.Version6To7();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gendered special collections were added.
|
|
||||||
private void Version6To7()
|
|
||||||
{
|
|
||||||
if( _config.Version != 6 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
ModCollection.Manager.MigrateUngenderedCollections();
|
|
||||||
_config.Version = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// A new tutorial step was inserted in the middle.
|
|
||||||
// The UI collection and a new tutorial for it was added.
|
|
||||||
// The migration for the UI collection itself happens in the ActiveCollections file.
|
|
||||||
private void Version5To6()
|
|
||||||
{
|
|
||||||
if( _config.Version != 5 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( _config.TutorialStep == 25 )
|
|
||||||
{
|
|
||||||
_config.TutorialStep = 27;
|
|
||||||
}
|
|
||||||
|
|
||||||
_config.Version = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mod backup extension was changed from .zip to .pmp.
|
|
||||||
// Actual migration takes place in ModManager.
|
|
||||||
private void Version4To5()
|
|
||||||
{
|
|
||||||
if( _config.Version != 4 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mod.Manager.MigrateModBackups = true;
|
|
||||||
_config.Version = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortMode was changed from an enum to a type.
|
|
||||||
private void Version3To4()
|
|
||||||
{
|
|
||||||
if( _config.Version != 3 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SortMode = _data[ nameof( SortMode ) ]?.ToObject< SortModeV3 >() ?? SortMode;
|
|
||||||
_config.SortMode = SortMode switch
|
|
||||||
{
|
|
||||||
SortModeV3.FoldersFirst => ISortMode< Mod >.FoldersFirst,
|
|
||||||
SortModeV3.Lexicographical => ISortMode< Mod >.Lexicographical,
|
|
||||||
SortModeV3.InverseFoldersFirst => ISortMode< Mod >.InverseFoldersFirst,
|
|
||||||
SortModeV3.InverseLexicographical => ISortMode< Mod >.InverseLexicographical,
|
|
||||||
SortModeV3.FoldersLast => ISortMode< Mod >.FoldersLast,
|
|
||||||
SortModeV3.InverseFoldersLast => ISortMode< Mod >.InverseFoldersLast,
|
|
||||||
SortModeV3.InternalOrder => ISortMode< Mod >.InternalOrder,
|
|
||||||
SortModeV3.InternalOrderInverse => ISortMode< Mod >.InverseInternalOrder,
|
|
||||||
_ => ISortMode< Mod >.FoldersFirst,
|
|
||||||
};
|
|
||||||
_config.Version = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortFoldersFirst was changed from a bool to the enum SortMode.
|
|
||||||
private void Version2To3()
|
|
||||||
{
|
|
||||||
if( _config.Version != 2 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SortFoldersFirst = _data[ nameof( SortFoldersFirst ) ]?.ToObject< bool >() ?? false;
|
|
||||||
SortMode = SortFoldersFirst ? SortModeV3.FoldersFirst : SortModeV3.Lexicographical;
|
|
||||||
_config.Version = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The forced collection was removed due to general inheritance.
|
|
||||||
// Sort Order was moved to a separate file and may contain empty folders.
|
|
||||||
// Active collections in general were moved to their own file.
|
|
||||||
// Delete the penumbrametatmp folder if it exists.
|
|
||||||
private void Version1To2()
|
|
||||||
{
|
|
||||||
if( _config.Version != 1 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the right meta files are loaded.
|
|
||||||
DeleteMetaTmp();
|
|
||||||
Penumbra.CharacterUtility.LoadCharacterResources();
|
|
||||||
ResettleSortOrder();
|
|
||||||
ResettleCollectionSettings();
|
|
||||||
ResettleForcedCollection();
|
|
||||||
_config.Version = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DeleteMetaTmp()
|
|
||||||
{
|
|
||||||
var path = Path.Combine( _config.ModDirectory, "penumbrametatmp" );
|
|
||||||
if( Directory.Exists( path ) )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.Delete( path, true );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not delete the outdated penumbrametatmp folder:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResettleForcedCollection()
|
|
||||||
{
|
|
||||||
ForcedCollection = _data[ nameof( ForcedCollection ) ]?.ToObject< string >() ?? ForcedCollection;
|
|
||||||
if( ForcedCollection.Length <= 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the previous forced collection to all current collections except itself as an inheritance.
|
|
||||||
foreach( var collection in Directory.EnumerateFiles( ModCollection.CollectionDirectory, "*.json" ) )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jObject = JObject.Parse( File.ReadAllText( collection ) );
|
|
||||||
if( jObject[ nameof( ModCollection.Name ) ]?.ToObject< string >() != ForcedCollection )
|
|
||||||
{
|
|
||||||
jObject[ nameof( ModCollection.Inheritance ) ] = JToken.FromObject( new List< string >() { ForcedCollection } );
|
|
||||||
File.WriteAllText( collection, jObject.ToString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error(
|
|
||||||
$"Could not transfer forced collection {ForcedCollection} to inheritance of collection {collection}:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the current sort order to its own file.
|
|
||||||
private void ResettleSortOrder()
|
|
||||||
{
|
|
||||||
ModSortOrder = _data[ nameof( ModSortOrder ) ]?.ToObject< Dictionary< string, string > >() ?? ModSortOrder;
|
|
||||||
var file = ModFileSystem.ModFileSystemFile;
|
|
||||||
using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew );
|
|
||||||
using var writer = new StreamWriter( stream );
|
|
||||||
using var j = new JsonTextWriter( writer );
|
|
||||||
j.Formatting = Formatting.Indented;
|
|
||||||
j.WriteStartObject();
|
|
||||||
j.WritePropertyName( "Data" );
|
|
||||||
j.WriteStartObject();
|
|
||||||
foreach( var (mod, path) in ModSortOrder.Where( kvp => Directory.Exists( Path.Combine( _config.ModDirectory, kvp.Key ) ) ) )
|
|
||||||
{
|
|
||||||
j.WritePropertyName( mod, true );
|
|
||||||
j.WriteValue( path );
|
|
||||||
}
|
|
||||||
|
|
||||||
j.WriteEndObject();
|
|
||||||
j.WritePropertyName( "EmptyFolders" );
|
|
||||||
j.WriteStartArray();
|
|
||||||
j.WriteEndArray();
|
|
||||||
j.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the active collections to their own file.
|
|
||||||
private void ResettleCollectionSettings()
|
|
||||||
{
|
|
||||||
CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection;
|
|
||||||
DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection;
|
|
||||||
CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections;
|
|
||||||
SaveActiveCollectionsV0( DefaultCollection, CurrentCollection, DefaultCollection,
|
|
||||||
CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ), Array.Empty< (CollectionType, string) >() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outdated saving using the Characters list.
|
|
||||||
private static void SaveActiveCollectionsV0( string def, string ui, string current, IEnumerable<(string, string)> characters,
|
|
||||||
IEnumerable<(CollectionType, string)> special )
|
|
||||||
{
|
|
||||||
var file = ModCollection.Manager.ActiveCollectionFile;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew );
|
|
||||||
using var writer = new StreamWriter( stream );
|
|
||||||
using var j = new JsonTextWriter( writer );
|
|
||||||
j.Formatting = Formatting.Indented;
|
|
||||||
j.WriteStartObject();
|
|
||||||
j.WritePropertyName( nameof( ModCollection.Manager.Default ) );
|
|
||||||
j.WriteValue( def );
|
|
||||||
j.WritePropertyName( nameof( ModCollection.Manager.Interface ) );
|
|
||||||
j.WriteValue( ui );
|
|
||||||
j.WritePropertyName( nameof( ModCollection.Manager.Current ) );
|
|
||||||
j.WriteValue( current );
|
|
||||||
foreach( var (type, collection) in special )
|
|
||||||
{
|
|
||||||
j.WritePropertyName( type.ToString() );
|
|
||||||
j.WriteValue( collection );
|
|
||||||
}
|
|
||||||
|
|
||||||
j.WritePropertyName( "Characters" );
|
|
||||||
j.WriteStartObject();
|
|
||||||
foreach( var (character, collection) in characters )
|
|
||||||
{
|
|
||||||
j.WritePropertyName( character, true );
|
|
||||||
j.WriteValue( collection );
|
|
||||||
}
|
|
||||||
|
|
||||||
j.WriteEndObject();
|
|
||||||
j.WriteEndObject();
|
|
||||||
Penumbra.Log.Verbose( "Active Collections saved." );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not save active collections to file {file}:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collections were introduced and the previous CurrentCollection got put into ModDirectory.
|
|
||||||
private void Version0To1()
|
|
||||||
{
|
|
||||||
if( _config.Version != 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_config.ModDirectory = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? string.Empty;
|
|
||||||
_config.Version = 1;
|
|
||||||
ResettleCollectionJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the previous mod configurations to a new default collection file.
|
|
||||||
private void ResettleCollectionJson()
|
|
||||||
{
|
|
||||||
var collectionJson = new FileInfo( Path.Combine( _config.ModDirectory, "collection.json" ) );
|
|
||||||
if( !collectionJson.Exists )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection );
|
|
||||||
var defaultCollectionFile = defaultCollection.FileName;
|
|
||||||
if( defaultCollectionFile.Exists )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var text = File.ReadAllText( collectionJson.FullName );
|
|
||||||
var data = JArray.Parse( text );
|
|
||||||
|
|
||||||
var maxPriority = 0;
|
|
||||||
var dict = new Dictionary< string, ModSettings.SavedSettings >();
|
|
||||||
foreach( var setting in data.Cast< JObject >() )
|
|
||||||
{
|
|
||||||
var modName = ( string )setting[ "FolderName" ]!;
|
|
||||||
var enabled = ( bool )setting[ "Enabled" ]!;
|
|
||||||
var priority = ( int )setting[ "Priority" ]!;
|
|
||||||
var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, long > >()
|
|
||||||
?? setting[ "Conf" ]!.ToObject< Dictionary< string, long > >();
|
|
||||||
|
|
||||||
dict[ modName ] = new ModSettings.SavedSettings()
|
|
||||||
{
|
|
||||||
Enabled = enabled,
|
|
||||||
Priority = priority,
|
|
||||||
Settings = settings!,
|
|
||||||
};
|
|
||||||
maxPriority = Math.Max( maxPriority, priority );
|
|
||||||
}
|
|
||||||
|
|
||||||
InvertModListOrder = _data[ nameof( InvertModListOrder ) ]?.ToObject< bool >() ?? InvertModListOrder;
|
|
||||||
if( !InvertModListOrder )
|
|
||||||
{
|
|
||||||
dict = dict.ToDictionary( kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority } );
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict );
|
|
||||||
defaultCollection.Save();
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not migrate the old collection file to new collection files:\n{e}" );
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a backup of the configuration file specifically.
|
|
||||||
private static void CreateBackup()
|
|
||||||
{
|
|
||||||
var name = DalamudServices.PluginInterface.ConfigFile.FullName;
|
|
||||||
var bakName = name + ".bak";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Copy( name, bakName, true );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not create backup copy of config at {bakName}:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SortModeV3 : byte
|
|
||||||
{
|
|
||||||
FoldersFirst = 0x00,
|
|
||||||
Lexicographical = 0x01,
|
|
||||||
InverseFoldersFirst = 0x02,
|
|
||||||
InverseLexicographical = 0x03,
|
|
||||||
FoldersLast = 0x04,
|
|
||||||
InverseFoldersLast = 0x05,
|
|
||||||
InternalOrder = 0x06,
|
|
||||||
InternalOrderInverse = 0x07,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,8 +19,14 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public partial class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
private readonly string _fileName;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private readonly FrameworkManager _framework;
|
||||||
|
|
||||||
public int Version { get; set; } = Constants.CurrentVersion;
|
public int Version { get; set; } = Constants.CurrentVersion;
|
||||||
|
|
||||||
public int LastSeenVersion { get; set; } = ConfigWindow.LastChangelogVersion;
|
public int LastSeenVersion { get; set; } = ConfigWindow.LastChangelogVersion;
|
||||||
|
|
@ -86,47 +92,44 @@ public partial class Configuration : IPluginConfiguration
|
||||||
public Dictionary< ColorId, uint > Colors { get; set; }
|
public Dictionary< ColorId, uint > Colors { get; set; }
|
||||||
= Enum.GetValues< ColorId >().ToDictionary( c => c, c => c.Data().DefaultColor );
|
= Enum.GetValues< ColorId >().ToDictionary( c => c, c => c.Data().DefaultColor );
|
||||||
|
|
||||||
// Load the current configuration.
|
/// <summary>
|
||||||
// Includes adding new colors and migrating from old versions.
|
/// Load the current configuration.
|
||||||
public static Configuration Load()
|
/// Includes adding new colors and migrating from old versions.
|
||||||
|
/// </summary>
|
||||||
|
public Configuration(FilenameService fileNames, ConfigMigrationService migrator, FrameworkManager framework)
|
||||||
{
|
{
|
||||||
void HandleDeserializationError( object? sender, ErrorEventArgs errorArgs )
|
_fileName = fileNames.ConfigFile;
|
||||||
|
_framework = framework;
|
||||||
|
Load(migrator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(ConfigMigrationService migrator)
|
||||||
|
{
|
||||||
|
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error(
|
Penumbra.Log.Error(
|
||||||
$"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}");
|
$"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}");
|
||||||
errorArgs.ErrorContext.Handled = true;
|
errorArgs.ErrorContext.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration? configuration = null;
|
if (File.Exists(_fileName))
|
||||||
if( File.Exists( DalamudServices.PluginInterface.ConfigFile.FullName ) )
|
|
||||||
{
|
{
|
||||||
var text = File.ReadAllText( DalamudServices.PluginInterface.ConfigFile.FullName );
|
var text = File.ReadAllText(_fileName);
|
||||||
configuration = JsonConvert.DeserializeObject< Configuration >( text, new JsonSerializerSettings
|
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
Error = HandleDeserializationError,
|
Error = HandleDeserializationError,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
migrator.Migrate(this);
|
||||||
configuration ??= new Configuration();
|
|
||||||
if( configuration.Version == Constants.CurrentVersion )
|
|
||||||
{
|
|
||||||
configuration.AddColors( false );
|
|
||||||
return configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Migration.Migrate( configuration );
|
/// <summary> Save the current configuration. </summary>
|
||||||
configuration.AddColors( true );
|
|
||||||
|
|
||||||
return configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the current configuration.
|
|
||||||
private void SaveConfiguration()
|
private void SaveConfiguration()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
||||||
File.WriteAllText( DalamudServices.PluginInterface.ConfigFile.FullName, text );
|
File.WriteAllText( _fileName, text );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|
@ -135,24 +138,9 @@ public partial class Configuration : IPluginConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveConfiguration ), SaveConfiguration );
|
=> _framework.RegisterDelayed( nameof( SaveConfiguration ), SaveConfiguration );
|
||||||
|
|
||||||
// Add missing colors to the dictionary if necessary.
|
/// <summary> Contains some default values or boundaries for config values. </summary>
|
||||||
private void AddColors( bool forceSave )
|
|
||||||
{
|
|
||||||
var save = false;
|
|
||||||
foreach( var color in Enum.GetValues< ColorId >() )
|
|
||||||
{
|
|
||||||
save |= Colors.TryAdd( color, color.Data().DefaultColor );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( save || forceSave )
|
|
||||||
{
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains some default values or boundaries for config values.
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public const int CurrentVersion = 7;
|
public const int CurrentVersion = 7;
|
||||||
|
|
@ -178,6 +166,7 @@ public partial class Configuration : IPluginConfiguration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Convert SortMode Types to their name. </summary>
|
||||||
private class SortModeConverter : JsonConverter< ISortMode< Mod > >
|
private class SortModeConverter : JsonConverter< ISortMode< Mod > >
|
||||||
{
|
{
|
||||||
public override void WriteJson( JsonWriter writer, ISortMode< Mod >? value, JsonSerializer serializer )
|
public override void WriteJson( JsonWriter writer, ISortMode< Mod >? value, JsonSerializer serializer )
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Services;
|
|
||||||
|
|
||||||
namespace Penumbra.Interop;
|
namespace Penumbra.Interop;
|
||||||
|
|
||||||
|
|
@ -52,14 +52,17 @@ public unsafe partial class CharacterUtility : IDisposable
|
||||||
public (IntPtr Address, int Size) DefaultResource( InternalIndex idx )
|
public (IntPtr Address, int Size) DefaultResource( InternalIndex idx )
|
||||||
=> _lists[ idx.Value ].DefaultResource;
|
=> _lists[ idx.Value ].DefaultResource;
|
||||||
|
|
||||||
public CharacterUtility()
|
private readonly Framework _framework;
|
||||||
|
|
||||||
|
public CharacterUtility(Framework framework)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise( this );
|
SignatureHelper.Initialise( this );
|
||||||
|
_framework = framework;
|
||||||
LoadingFinished += () => Penumbra.Log.Debug( "Loading of CharacterUtility finished." );
|
LoadingFinished += () => Penumbra.Log.Debug( "Loading of CharacterUtility finished." );
|
||||||
LoadDefaultResources( null! );
|
LoadDefaultResources( null! );
|
||||||
if( !Ready )
|
if( !Ready )
|
||||||
{
|
{
|
||||||
DalamudServices.Framework.Update += LoadDefaultResources;
|
_framework.Update += LoadDefaultResources;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +102,7 @@ public unsafe partial class CharacterUtility : IDisposable
|
||||||
if( !anyMissing )
|
if( !anyMissing )
|
||||||
{
|
{
|
||||||
Ready = true;
|
Ready = true;
|
||||||
DalamudServices.Framework.Update -= LoadDefaultResources;
|
_framework.Update -= LoadDefaultResources;
|
||||||
LoadingFinished.Invoke();
|
LoadingFinished.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ using FFXIVClientStructs.STD;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.String;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
@ -249,18 +248,4 @@ public unsafe partial class ResourceLoader
|
||||||
Penumbra.Log.Error( $"Caught decrease of Reference Counter for {handle->FileName} at 0x{( ulong )handle:X} below 0." );
|
Penumbra.Log.Error( $"Caught decrease of Reference Counter for {handle->FileName} at 0x{( ulong )handle:X} below 0." );
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logging functions for EnableFullLogging.
|
|
||||||
private static void LogPath( Utf8GamePath path, bool synchronous )
|
|
||||||
=> Penumbra.Log.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
|
|
||||||
|
|
||||||
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data )
|
|
||||||
{
|
|
||||||
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
|
|
||||||
Penumbra.Log.Information(
|
|
||||||
$"[ResourceLoader] [{handle->FileType}] Loaded {pathString} to 0x{( ulong )handle:X} using collection {data.ModCollection.AnonymizedName} for {data.AssociatedName()} (Refcount {handle->RefCount}) " );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void LogLoadedFile( Structs.ResourceHandle* resource, ByteString path, bool success, bool custom )
|
|
||||||
=> Penumbra.Log.Information( $"[ResourceLoader] Loading {path} from {( custom ? "local files" : "SqPack" )} into 0x{( ulong )resource:X} returned {success}." );
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,39 +18,6 @@ public unsafe partial class ResourceLoader : IDisposable
|
||||||
// Hooks are required for everything, even events firing.
|
// Hooks are required for everything, even events firing.
|
||||||
public bool HooksEnabled { get; private set; }
|
public bool HooksEnabled { get; private set; }
|
||||||
|
|
||||||
// This Logging just logs all file requests, returns and loads to the Dalamud log.
|
|
||||||
// Events can be used to make smarter logging.
|
|
||||||
public bool IsLoggingEnabled { get; private set; }
|
|
||||||
|
|
||||||
public void EnableFullLogging()
|
|
||||||
{
|
|
||||||
if( IsLoggingEnabled )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsLoggingEnabled = true;
|
|
||||||
ResourceRequested += LogPath;
|
|
||||||
ResourceLoaded += LogResource;
|
|
||||||
FileLoaded += LogLoadedFile;
|
|
||||||
ResourceHandleDestructorHook?.Enable();
|
|
||||||
EnableHooks();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableFullLogging()
|
|
||||||
{
|
|
||||||
if( !IsLoggingEnabled )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsLoggingEnabled = false;
|
|
||||||
ResourceRequested -= LogPath;
|
|
||||||
ResourceLoaded -= LogResource;
|
|
||||||
FileLoaded -= LogLoadedFile;
|
|
||||||
ResourceHandleDestructorHook?.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnableReplacements()
|
public void EnableReplacements()
|
||||||
{
|
{
|
||||||
if( DoReplacements )
|
if( DoReplacements )
|
||||||
|
|
@ -150,7 +117,6 @@ public unsafe partial class ResourceLoader : IDisposable
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DisableFullLogging();
|
|
||||||
DisposeHooks();
|
DisposeHooks();
|
||||||
DisposeTexMdlTreatment();
|
DisposeTexMdlTreatment();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Penumbra.String;
|
|
||||||
using Penumbra.String.Classes;
|
|
||||||
|
|
||||||
namespace Penumbra.Interop.Loader;
|
|
||||||
|
|
||||||
// A logger class that contains the relevant data to log requested files via regex.
|
|
||||||
// Filters are case-insensitive.
|
|
||||||
public class ResourceLogger : IDisposable
|
|
||||||
{
|
|
||||||
// Enable or disable the logging of resources subject to the current filter.
|
|
||||||
public void SetState( bool value )
|
|
||||||
{
|
|
||||||
if( value == Penumbra.Config.EnableResourceLogging )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Config.EnableResourceLogging = value;
|
|
||||||
Penumbra.Config.Save();
|
|
||||||
if( value )
|
|
||||||
{
|
|
||||||
_resourceLoader.ResourceRequested += OnResourceRequested;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_resourceLoader.ResourceRequested -= OnResourceRequested;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the current filter to a new string, doing all other necessary work.
|
|
||||||
public void SetFilter( string newFilter )
|
|
||||||
{
|
|
||||||
if( newFilter == Filter )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Config.ResourceLoggingFilter = newFilter;
|
|
||||||
Penumbra.Config.Save();
|
|
||||||
SetupRegex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether the current filter is a valid regular expression.
|
|
||||||
public bool ValidRegex
|
|
||||||
=> _filterRegex != null;
|
|
||||||
|
|
||||||
private readonly ResourceLoader _resourceLoader;
|
|
||||||
private Regex? _filterRegex;
|
|
||||||
|
|
||||||
private static string Filter
|
|
||||||
=> Penumbra.Config.ResourceLoggingFilter;
|
|
||||||
|
|
||||||
private void SetupRegex()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_filterRegex = new Regex( Filter, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant );
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_filterRegex = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceLogger( ResourceLoader loader )
|
|
||||||
{
|
|
||||||
_resourceLoader = loader;
|
|
||||||
SetupRegex();
|
|
||||||
if( Penumbra.Config.EnableResourceLogging )
|
|
||||||
{
|
|
||||||
_resourceLoader.ResourceRequested += OnResourceRequested;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnResourceRequested( Utf8GamePath data, bool synchronous )
|
|
||||||
{
|
|
||||||
var path = Match( data.Path );
|
|
||||||
if( path != null )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Information( $"{path} was requested {( synchronous ? "synchronously." : "asynchronously." )}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the converted string if the filter matches, and null otherwise.
|
|
||||||
// The filter matches if it is empty, if it is a valid and matching regex or if the given string contains it.
|
|
||||||
private string? Match( ByteString data )
|
|
||||||
{
|
|
||||||
var s = data.ToString();
|
|
||||||
return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.OrdinalIgnoreCase ) )
|
|
||||||
? s
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
=> _resourceLoader.ResourceRequested -= OnResourceRequested;
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using Penumbra.Services;
|
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
|
|
@ -14,15 +14,17 @@ public class CutsceneCharacters : IDisposable
|
||||||
public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots;
|
public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots;
|
||||||
|
|
||||||
private readonly GameEventManager _events;
|
private readonly GameEventManager _events;
|
||||||
|
private readonly ObjectTable _objects;
|
||||||
private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();
|
private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();
|
||||||
|
|
||||||
public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors
|
public IEnumerable<KeyValuePair<int, Dalamud.Game.ClientState.Objects.Types.GameObject>> Actors
|
||||||
=> Enumerable.Range(CutsceneStartIdx, CutsceneSlots)
|
=> Enumerable.Range(CutsceneStartIdx, CutsceneSlots)
|
||||||
.Where( i => DalamudServices.Objects[ i ] != null )
|
.Where(i => _objects[i] != null)
|
||||||
.Select( i => KeyValuePair.Create( i, this[ i ] ?? DalamudServices.Objects[ i ]! ) );
|
.Select(i => KeyValuePair.Create(i, this[i] ?? _objects[i]!));
|
||||||
|
|
||||||
public CutsceneCharacters(GameEventManager events)
|
public CutsceneCharacters(ObjectTable objects, GameEventManager events)
|
||||||
{
|
{
|
||||||
|
_objects = objects;
|
||||||
_events = events;
|
_events = events;
|
||||||
Enable();
|
Enable();
|
||||||
}
|
}
|
||||||
|
|
@ -30,13 +32,13 @@ public class CutsceneCharacters : IDisposable
|
||||||
// Get the related actor to a cutscene actor.
|
// Get the related actor to a cutscene actor.
|
||||||
// Does not check for valid input index.
|
// Does not check for valid input index.
|
||||||
// Returns null if no connected actor is set or the actor does not exist anymore.
|
// Returns null if no connected actor is set or the actor does not exist anymore.
|
||||||
public global::Dalamud.Game.ClientState.Objects.Types.GameObject? this[ int idx ]
|
public Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Debug.Assert(idx is >= CutsceneStartIdx and < CutsceneEndIdx);
|
Debug.Assert(idx is >= CutsceneStartIdx and < CutsceneEndIdx);
|
||||||
idx = _copiedCharacters[idx - CutsceneStartIdx];
|
idx = _copiedCharacters[idx - CutsceneStartIdx];
|
||||||
return idx < 0 ? null : DalamudServices.Objects[ idx ];
|
return idx < 0 ? null : _objects[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,9 +46,7 @@ public class CutsceneCharacters : IDisposable
|
||||||
public int GetParentIndex(int idx)
|
public int GetParentIndex(int idx)
|
||||||
{
|
{
|
||||||
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||||
{
|
|
||||||
return _copiedCharacters[idx - CutsceneStartIdx];
|
return _copiedCharacters[idx - CutsceneStartIdx];
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,24 @@ namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(IntPtr Address, ActorIdentifier Identifier, ModCollection Collection)>
|
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(IntPtr Address, ActorIdentifier Identifier, ModCollection Collection)>
|
||||||
{
|
{
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly GameEventManager _events;
|
private readonly GameEventManager _events;
|
||||||
private readonly Dictionary<IntPtr, (ActorIdentifier, ModCollection)> _cache = new(317);
|
private readonly Dictionary<IntPtr, (ActorIdentifier, ModCollection)> _cache = new(317);
|
||||||
private bool _dirty = false;
|
private bool _dirty = false;
|
||||||
private bool _enabled = false;
|
private bool _enabled = false;
|
||||||
|
|
||||||
public IdentifiedCollectionCache(GameEventManager events)
|
public IdentifiedCollectionCache(CommunicatorService communicator, GameEventManager events)
|
||||||
{
|
{
|
||||||
|
_communicator = communicator;
|
||||||
_events = events;
|
_events = events;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
if (_enabled)
|
if (_enabled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.CollectionManager.CollectionChanged += CollectionChangeClear;
|
_communicator.CollectionChange.Event += CollectionChangeClear;
|
||||||
Penumbra.TempMods.CollectionChanged += CollectionChangeClear;
|
|
||||||
DalamudServices.ClientState.TerritoryChanged += TerritoryClear;
|
DalamudServices.ClientState.TerritoryChanged += TerritoryClear;
|
||||||
_events.CharacterDestructor += OnCharacterDestruct;
|
_events.CharacterDestructor += OnCharacterDestruct;
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
|
|
@ -38,12 +37,9 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
|
||||||
public void Disable()
|
public void Disable()
|
||||||
{
|
{
|
||||||
if (!_enabled)
|
if (!_enabled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.CollectionManager.CollectionChanged -= CollectionChangeClear;
|
_communicator.CollectionChange.Event -= CollectionChangeClear;
|
||||||
Penumbra.TempMods.CollectionChanged -= CollectionChangeClear;
|
|
||||||
DalamudServices.ClientState.TerritoryChanged -= TerritoryClear;
|
DalamudServices.ClientState.TerritoryChanged -= TerritoryClear;
|
||||||
_events.CharacterDestructor -= OnCharacterDestruct;
|
_events.CharacterDestructor -= OnCharacterDestruct;
|
||||||
_enabled = false;
|
_enabled = false;
|
||||||
|
|
@ -89,9 +85,7 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
|
||||||
foreach (var (address, (identifier, collection)) in _cache)
|
foreach (var (address, (identifier, collection)) in _cache)
|
||||||
{
|
{
|
||||||
if (_dirty)
|
if (_dirty)
|
||||||
{
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
|
||||||
|
|
||||||
yield return (address, identifier, collection);
|
yield return (address, identifier, collection);
|
||||||
}
|
}
|
||||||
|
|
@ -106,10 +100,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
|
||||||
private void CollectionChangeClear(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
private void CollectionChangeClear(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
||||||
{
|
{
|
||||||
if (type is not (CollectionType.Current or CollectionType.Interface or CollectionType.Inactive))
|
if (type is not (CollectionType.Current or CollectionType.Interface or CollectionType.Inactive))
|
||||||
{
|
|
||||||
_dirty = _cache.Count > 0;
|
_dirty = _cache.Count > 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void TerritoryClear(object? _1, ushort _2)
|
private void TerritoryClear(object? _1, ushort _2)
|
||||||
=> _dirty = _cache.Count > 0;
|
=> _dirty = _cache.Count > 0;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
public class DrawObjectState
|
public class DrawObjectState
|
||||||
{
|
{
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
public static event CreatingCharacterBaseDelegate? CreatingCharacterBase;
|
public static event CreatingCharacterBaseDelegate? CreatingCharacterBase;
|
||||||
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||||
|
|
||||||
|
|
@ -33,9 +34,7 @@ public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
gameObject = null;
|
gameObject = null;
|
||||||
if (!_drawObjectToObject.TryGetValue(drawObject, out value))
|
if (!_drawObjectToObject.TryGetValue(drawObject, out value))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
var gameObjectIdx = value.Item2;
|
var gameObjectIdx = value.Item2;
|
||||||
return VerifyEntry(drawObject, gameObjectIdx, out gameObject);
|
return VerifyEntry(drawObject, gameObjectIdx, out gameObject);
|
||||||
|
|
@ -76,9 +75,10 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
public GameObject* LastGameObject { get; private set; }
|
public GameObject* LastGameObject { get; private set; }
|
||||||
|
|
||||||
public DrawObjectState()
|
public DrawObjectState(CommunicatorService communicator)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
|
_communicator = communicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
|
|
@ -88,8 +88,7 @@ public unsafe partial class PathResolver
|
||||||
_enableDrawHook.Enable();
|
_enableDrawHook.Enable();
|
||||||
_weaponReloadHook.Enable();
|
_weaponReloadHook.Enable();
|
||||||
InitializeDrawObjects();
|
InitializeDrawObjects();
|
||||||
Penumbra.CollectionManager.CollectionChanged += CheckCollections;
|
_communicator.CollectionChange.Event += CheckCollections;
|
||||||
Penumbra.TempMods.CollectionChanged += CheckCollections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Disable()
|
public void Disable()
|
||||||
|
|
@ -98,8 +97,7 @@ public unsafe partial class PathResolver
|
||||||
_characterBaseDestructorHook.Disable();
|
_characterBaseDestructorHook.Disable();
|
||||||
_enableDrawHook.Disable();
|
_enableDrawHook.Disable();
|
||||||
_weaponReloadHook.Disable();
|
_weaponReloadHook.Disable();
|
||||||
Penumbra.CollectionManager.CollectionChanged -= CheckCollections;
|
_communicator.CollectionChange.Event -= CheckCollections;
|
||||||
Penumbra.TempMods.CollectionChanged -= CheckCollections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -118,9 +116,7 @@ public unsafe partial class PathResolver
|
||||||
var draw = (DrawObject*)drawObject;
|
var draw = (DrawObject*)drawObject;
|
||||||
if (gameObject != null
|
if (gameObject != null
|
||||||
&& (gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject))
|
&& (gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject))
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
gameObject = null;
|
gameObject = null;
|
||||||
_drawObjectToObject.Remove(drawObject);
|
_drawObjectToObject.Remove(drawObject);
|
||||||
|
|
@ -232,16 +228,12 @@ public unsafe partial class PathResolver
|
||||||
private void CheckCollections(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
private void CheckCollections(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
||||||
{
|
{
|
||||||
if (type is CollectionType.Inactive or CollectionType.Current or CollectionType.Interface)
|
if (type is CollectionType.Inactive or CollectionType.Current or CollectionType.Interface)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (key, (_, idx)) in _drawObjectToObject.ToArray())
|
foreach (var (key, (_, idx)) in _drawObjectToObject.ToArray())
|
||||||
{
|
{
|
||||||
if (!VerifyEntry(key, idx, out var obj))
|
if (!VerifyEntry(key, idx, out var obj))
|
||||||
{
|
|
||||||
_drawObjectToObject.Remove(key);
|
_drawObjectToObject.Remove(key);
|
||||||
}
|
|
||||||
|
|
||||||
var newCollection = IdentifyCollection(obj, false);
|
var newCollection = IdentifyCollection(obj, false);
|
||||||
_drawObjectToObject[key] = (newCollection, idx);
|
_drawObjectToObject[key] = (newCollection, idx);
|
||||||
|
|
@ -256,10 +248,8 @@ public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
var ptr = (GameObject*)DalamudServices.Objects.GetObjectAddress(i);
|
var ptr = (GameObject*)DalamudServices.Objects.GetObjectAddress(i);
|
||||||
if (ptr != null && ptr->IsCharacter() && ptr->DrawObject != null)
|
if (ptr != null && ptr->IsCharacter() && ptr->DrawObject != null)
|
||||||
{
|
|
||||||
_drawObjectToObject[(IntPtr)ptr->DrawObject] = (IdentifyCollection(ptr, false), ptr->ObjectIndex);
|
_drawObjectToObject[(IntPtr)ptr->DrawObject] = (IdentifyCollection(ptr, false), ptr->ObjectIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -103,7 +103,7 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
// Check both temporary and permanent character collections. Temporary first.
|
// Check both temporary and permanent character collections. Temporary first.
|
||||||
private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier )
|
private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier )
|
||||||
=> Penumbra.TempMods.Collections.TryGetCollection( identifier, out var collection )
|
=> Penumbra.TempCollections.Collections.TryGetCollection( identifier, out var collection )
|
||||||
|| Penumbra.CollectionManager.Individuals.TryGetCollection( identifier, out collection )
|
|| Penumbra.CollectionManager.Individuals.TryGetCollection( identifier, out collection )
|
||||||
? collection
|
? collection
|
||||||
: null;
|
: null;
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ public partial class PathResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject;
|
var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject;
|
||||||
var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject );
|
var parentCollection = _drawObjects.CheckParentDrawObject( drawObject, parentObject );
|
||||||
if( parentCollection.Valid )
|
if( parentCollection.Valid )
|
||||||
{
|
{
|
||||||
return _parent._paths.ResolvePath( ( IntPtr )FindParent( parentObject, out _ ), parentCollection.ModCollection, path );
|
return _parent._paths.ResolvePath( ( IntPtr )FindParent( parentObject, out _ ), parentCollection.ModCollection, path );
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
using OtterGui.Classes;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
|
|
@ -24,11 +25,12 @@ public partial class PathResolver : IDisposable
|
||||||
{
|
{
|
||||||
public bool Enabled { get; private set; }
|
public bool Enabled { get; private set; }
|
||||||
|
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly ResourceLoader _loader;
|
private readonly ResourceLoader _loader;
|
||||||
private static readonly CutsceneCharacters Cutscenes = new(Penumbra.GameEvents);
|
private static readonly CutsceneCharacters Cutscenes = new(DalamudServices.Objects, Penumbra.GameEvents); // TODO
|
||||||
private static readonly DrawObjectState DrawObjects = new();
|
private static DrawObjectState _drawObjects = null!; // TODO
|
||||||
private static readonly BitArray ValidHumanModels;
|
private static readonly BitArray ValidHumanModels;
|
||||||
internal static readonly IdentifiedCollectionCache IdentifiedCache = new(Penumbra.GameEvents);
|
internal static IdentifiedCollectionCache IdentifiedCache = null!; // TODO
|
||||||
private readonly AnimationState _animations;
|
private readonly AnimationState _animations;
|
||||||
private readonly PathState _paths;
|
private readonly PathState _paths;
|
||||||
private readonly MetaState _meta;
|
private readonly MetaState _meta;
|
||||||
|
|
@ -37,12 +39,15 @@ public partial class PathResolver : IDisposable
|
||||||
static PathResolver()
|
static PathResolver()
|
||||||
=> ValidHumanModels = GetValidHumanModels(DalamudServices.GameData);
|
=> ValidHumanModels = GetValidHumanModels(DalamudServices.GameData);
|
||||||
|
|
||||||
public unsafe PathResolver( ResourceLoader loader )
|
public unsafe PathResolver(StartTracker timer, CommunicatorService communicator, GameEventManager events, ResourceLoader loader)
|
||||||
{
|
{
|
||||||
using var tApi = Penumbra.StartTimer.Measure( StartTimeType.PathResolver );
|
using var tApi = timer.Measure(StartTimeType.PathResolver);
|
||||||
|
_communicator = communicator;
|
||||||
|
IdentifiedCache = new IdentifiedCollectionCache(communicator, events);
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
|
_drawObjects = new DrawObjectState(_communicator);
|
||||||
_loader = loader;
|
_loader = loader;
|
||||||
_animations = new AnimationState( DrawObjects );
|
_animations = new AnimationState(_drawObjects);
|
||||||
_paths = new PathState(this);
|
_paths = new PathState(this);
|
||||||
_meta = new MetaState(_paths.HumanVTable);
|
_meta = new MetaState(_paths.HumanVTable);
|
||||||
_subFiles = new SubfileHelper(_loader, Penumbra.GameEvents);
|
_subFiles = new SubfileHelper(_loader, Penumbra.GameEvents);
|
||||||
|
|
@ -61,11 +66,9 @@ public partial class PathResolver : IDisposable
|
||||||
var nonDefault = _subFiles.HandleSubFiles(type, out var resolveData)
|
var nonDefault = _subFiles.HandleSubFiles(type, out var resolveData)
|
||||||
|| _paths.Consume(gamePath.Path, out resolveData)
|
|| _paths.Consume(gamePath.Path, out resolveData)
|
||||||
|| _animations.HandleFiles(type, gamePath, out resolveData)
|
|| _animations.HandleFiles(type, gamePath, out resolveData)
|
||||||
|| DrawObjects.HandleDecalFile( type, gamePath, out resolveData );
|
|| _drawObjects.HandleDecalFile(type, gamePath, out resolveData);
|
||||||
if (!nonDefault || !resolveData.Valid)
|
if (!nonDefault || !resolveData.Valid)
|
||||||
{
|
|
||||||
resolveData = Penumbra.CollectionManager.Default.ToResolveData();
|
resolveData = Penumbra.CollectionManager.Default.ToResolveData();
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve using character/default collection first, otherwise forced, as usual.
|
// Resolve using character/default collection first, otherwise forced, as usual.
|
||||||
var resolved = resolveData.ModCollection.ResolvePath(gamePath);
|
var resolved = resolveData.ModCollection.ResolvePath(gamePath);
|
||||||
|
|
@ -81,13 +84,11 @@ public partial class PathResolver : IDisposable
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
if (Enabled)
|
if (Enabled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
Cutscenes.Enable();
|
Cutscenes.Enable();
|
||||||
DrawObjects.Enable();
|
_drawObjects.Enable();
|
||||||
IdentifiedCache.Enable();
|
IdentifiedCache.Enable();
|
||||||
_animations.Enable();
|
_animations.Enable();
|
||||||
_paths.Enable();
|
_paths.Enable();
|
||||||
|
|
@ -101,13 +102,11 @@ public partial class PathResolver : IDisposable
|
||||||
public void Disable()
|
public void Disable()
|
||||||
{
|
{
|
||||||
if (!Enabled)
|
if (!Enabled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Enabled = false;
|
Enabled = false;
|
||||||
_animations.Disable();
|
_animations.Disable();
|
||||||
DrawObjects.Disable();
|
_drawObjects.Disable();
|
||||||
Cutscenes.Disable();
|
Cutscenes.Disable();
|
||||||
IdentifiedCache.Disable();
|
IdentifiedCache.Disable();
|
||||||
_paths.Disable();
|
_paths.Disable();
|
||||||
|
|
@ -123,7 +122,7 @@ public partial class PathResolver : IDisposable
|
||||||
Disable();
|
Disable();
|
||||||
_paths.Dispose();
|
_paths.Dispose();
|
||||||
_animations.Dispose();
|
_animations.Dispose();
|
||||||
DrawObjects.Dispose();
|
_drawObjects.Dispose();
|
||||||
Cutscenes.Dispose();
|
Cutscenes.Dispose();
|
||||||
IdentifiedCache.Dispose();
|
IdentifiedCache.Dispose();
|
||||||
_meta.Dispose();
|
_meta.Dispose();
|
||||||
|
|
@ -142,17 +141,17 @@ public partial class PathResolver : IDisposable
|
||||||
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
||||||
public static unsafe GameObject* FindParent(IntPtr drawObject, out ResolveData resolveData)
|
public static unsafe GameObject* FindParent(IntPtr drawObject, out ResolveData resolveData)
|
||||||
{
|
{
|
||||||
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
|
if (_drawObjects.TryGetValue(drawObject, out var data, out var gameObject))
|
||||||
{
|
{
|
||||||
resolveData = data.Item1;
|
resolveData = data.Item1;
|
||||||
return gameObject;
|
return gameObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( DrawObjects.LastGameObject != null
|
if (_drawObjects.LastGameObject != null
|
||||||
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
|
&& (_drawObjects.LastGameObject->DrawObject == null || _drawObjects.LastGameObject->DrawObject == (DrawObject*)drawObject))
|
||||||
{
|
{
|
||||||
resolveData = IdentifyCollection( DrawObjects.LastGameObject, true );
|
resolveData = IdentifyCollection(_drawObjects.LastGameObject, true);
|
||||||
return DrawObjects.LastGameObject;
|
return _drawObjects.LastGameObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveData = IdentifyCollection(null, true);
|
resolveData = IdentifyCollection(null, true);
|
||||||
|
|
@ -169,7 +168,7 @@ public partial class PathResolver : IDisposable
|
||||||
=> _paths.Paths;
|
=> _paths.Paths;
|
||||||
|
|
||||||
internal IEnumerable<KeyValuePair<IntPtr, (ResolveData, int)>> DrawObjectMap
|
internal IEnumerable<KeyValuePair<IntPtr, (ResolveData, int)>> DrawObjectMap
|
||||||
=> DrawObjects.DrawObjects;
|
=> _drawObjects.DrawObjects;
|
||||||
|
|
||||||
internal IEnumerable<KeyValuePair<int, global::Dalamud.Game.ClientState.Objects.Types.GameObject>> CutsceneActors
|
internal IEnumerable<KeyValuePair<int, global::Dalamud.Game.ClientState.Objects.Types.GameObject>> CutsceneActors
|
||||||
=> Cutscenes.Actors;
|
=> Cutscenes.Actors;
|
||||||
|
|
@ -187,8 +186,8 @@ public partial class PathResolver : IDisposable
|
||||||
=> _subFiles.AvfxData;
|
=> _subFiles.AvfxData;
|
||||||
|
|
||||||
internal ResolveData LastGameObjectData
|
internal ResolveData LastGameObjectData
|
||||||
=> DrawObjects.LastCreatedCollection;
|
=> _drawObjects.LastCreatedCollection;
|
||||||
|
|
||||||
internal unsafe nint LastGameObject
|
internal unsafe nint LastGameObject
|
||||||
=> (nint) DrawObjects.LastGameObject;
|
=> (nint)_drawObjects.LastGameObject;
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ public partial class MetaManager
|
||||||
|
|
||||||
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
|
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
|
||||||
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
|
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
|
||||||
if( ( Penumbra.TempMods.CollectionByName( name, out var collection )
|
if( ( Penumbra.TempCollections.CollectionByName( name, out var collection )
|
||||||
|| Penumbra.CollectionManager.ByName( name, out collection ) )
|
|| Penumbra.CollectionManager.ByName( name, out collection ) )
|
||||||
&& collection.HasCache
|
&& collection.HasCache
|
||||||
&& collection.MetaCache!._imcFiles.TryGetValue( Utf8GamePath.FromSpan( path.Span, out var p ) ? p : Utf8GamePath.Empty, out var file ) )
|
&& collection.MetaCache!._imcFiles.TryGetValue( Utf8GamePath.FromSpan( path.Span, out var p ) ? p : Utf8GamePath.Empty, out var file ) )
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
|
@ -10,8 +11,8 @@ namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed partial class Mod
|
public sealed partial class Mod
|
||||||
{
|
{
|
||||||
public static DirectoryInfo LocalDataDirectory
|
public static DirectoryInfo LocalDataDirectory(DalamudPluginInterface pi)
|
||||||
=> new(Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data" ));
|
=> new(Path.Combine( pi.ConfigDirectory.FullName, "mod_data" ));
|
||||||
|
|
||||||
public long ImportDate { get; private set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
public long ImportDate { get; private set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
|
@ -11,15 +12,16 @@ namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
||||||
{
|
{
|
||||||
public static string ModFileSystemFile
|
public static string ModFileSystemFile(DalamudPluginInterface pi)
|
||||||
=> Path.Combine( DalamudServices.PluginInterface.GetPluginConfigDirectory(), "sort_order.json" );
|
=> Path.Combine( pi.GetPluginConfigDirectory(), "sort_order.json" );
|
||||||
|
|
||||||
// Save the current sort order.
|
// Save the current sort order.
|
||||||
// Does not save or copy the backup in the current mod directory,
|
// Does not save or copy the backup in the current mod directory,
|
||||||
// as this is done on mod directory changes only.
|
// as this is done on mod directory changes only.
|
||||||
|
// TODO
|
||||||
private void SaveFilesystem()
|
private void SaveFilesystem()
|
||||||
{
|
{
|
||||||
SaveToFile( new FileInfo( ModFileSystemFile ), SaveMod, true );
|
SaveToFile( new FileInfo( ModFileSystemFile(DalamudServices.PluginInterface) ), SaveMod, true );
|
||||||
Penumbra.Log.Verbose( "Saved mod filesystem." );
|
Penumbra.Log.Verbose( "Saved mod filesystem." );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +77,8 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
||||||
// Used on construction and on mod rediscoveries.
|
// Used on construction and on mod rediscoveries.
|
||||||
private void Reload()
|
private void Reload()
|
||||||
{
|
{
|
||||||
if( Load( new FileInfo( ModFileSystemFile ), Penumbra.ModManager, ModToIdentifier, ModToName ) )
|
// TODO
|
||||||
|
if( Load( new FileInfo( ModFileSystemFile(DalamudServices.PluginInterface) ), Penumbra.ModManager, ModToIdentifier, ModToName ) )
|
||||||
{
|
{
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -29,45 +28,10 @@ using Penumbra.Mods;
|
||||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||||
using DalamudUtil = Dalamud.Utility.Util;
|
using DalamudUtil = Dalamud.Utility.Util;
|
||||||
using ResidentResourceManager = Penumbra.Interop.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.ResidentResourceManager;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
|
|
||||||
public class PenumbraNew
|
|
||||||
{
|
|
||||||
public string Name
|
|
||||||
=> "Penumbra";
|
|
||||||
|
|
||||||
public static readonly Logger Log = new();
|
|
||||||
public readonly StartTimeTracker< StartTimeType > StartTimer = new();
|
|
||||||
|
|
||||||
public readonly IServiceCollection Services = new ServiceCollection();
|
|
||||||
|
|
||||||
|
|
||||||
public PenumbraNew( DalamudPluginInterface pi )
|
|
||||||
{
|
|
||||||
using var time = StartTimer.Measure( StartTimeType.Total );
|
|
||||||
|
|
||||||
// Add meta services.
|
|
||||||
Services.AddSingleton( Log );
|
|
||||||
Services.AddSingleton( StartTimer );
|
|
||||||
Services.AddSingleton< ValidityChecker >();
|
|
||||||
Services.AddSingleton< PerformanceTracker< PerformanceType > >();
|
|
||||||
|
|
||||||
// Add Dalamud services
|
|
||||||
var dalamud = new DalamudServices( pi );
|
|
||||||
dalamud.AddServices( Services );
|
|
||||||
|
|
||||||
// Add Game Data
|
|
||||||
|
|
||||||
|
|
||||||
// Add Configuration
|
|
||||||
Services.AddSingleton< Configuration >();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Penumbra : IDalamudPlugin
|
public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
|
|
@ -87,21 +51,22 @@ public class Penumbra : IDalamudPlugin
|
||||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||||
public static Mod.Manager ModManager { get; private set; } = null!;
|
public static Mod.Manager ModManager { get; private set; } = null!;
|
||||||
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
|
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
|
||||||
|
public static TempCollectionManager TempCollections { get; private set; } = null!;
|
||||||
public static TempModManager TempMods { get; private set; } = null!;
|
public static TempModManager TempMods { get; private set; } = null!;
|
||||||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||||
public static FrameworkManager Framework { get; private set; } = null!;
|
public static FrameworkManager Framework { get; private set; } = null!;
|
||||||
public static ActorManager Actors { get; private set; } = null!;
|
public static ActorManager Actors { get; private set; } = null!;
|
||||||
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
||||||
public static IGamePathParser GamePathParser { get; private set; } = null!;
|
public static IGamePathParser GamePathParser { get; private set; } = null!;
|
||||||
public static StainManager StainManager { get; private set; } = null!;
|
public static StainService StainService { get; private set; } = null!;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public static DalamudServices Dalamud { get; private set; } = null!;
|
||||||
|
|
||||||
public static ValidityChecker ValidityChecker { get; private set; } = null!;
|
public static ValidityChecker ValidityChecker { get; private set; } = null!;
|
||||||
|
|
||||||
public static PerformanceTracker< PerformanceType > Performance { get; private set; } = null!;
|
public static PerformanceTracker Performance { get; private set; } = null!;
|
||||||
|
|
||||||
public static readonly StartTimeTracker< StartTimeType > StartTimer = new();
|
|
||||||
|
|
||||||
public readonly ResourceLogger ResourceLogger;
|
|
||||||
public readonly PathResolver PathResolver;
|
public readonly PathResolver PathResolver;
|
||||||
public readonly ObjectReloader ObjectReloader;
|
public readonly ObjectReloader ObjectReloader;
|
||||||
public readonly ModFileSystem ModFileSystem;
|
public readonly ModFileSystem ModFileSystem;
|
||||||
|
|
@ -116,59 +81,52 @@ public class Penumbra : IDalamudPlugin
|
||||||
private readonly ResourceWatcher _resourceWatcher;
|
private readonly ResourceWatcher _resourceWatcher;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
private readonly PenumbraNew _tmp;
|
||||||
public static ItemData ItemData { get; private set; } = null!;
|
public static ItemData ItemData { get; private set; } = null!;
|
||||||
|
|
||||||
public Penumbra(DalamudPluginInterface pluginInterface)
|
public Penumbra(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
using var time = StartTimer.Measure( StartTimeType.Total );
|
Log = PenumbraNew.Log;
|
||||||
|
_tmp = new PenumbraNew(pluginInterface);
|
||||||
|
Performance = _tmp.Services.GetRequiredService<PerformanceTracker>();
|
||||||
|
ValidityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
||||||
|
_tmp.Services.GetRequiredService<BackupService>();
|
||||||
|
Config = _tmp.Services.GetRequiredService<Configuration>();
|
||||||
|
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
||||||
|
GameEvents = _tmp.Services.GetRequiredService<GameEventManager>();
|
||||||
|
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
||||||
|
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
|
||||||
|
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
||||||
|
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
||||||
|
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
||||||
|
StainService = _tmp.Services.GetRequiredService<StainService>();
|
||||||
|
ItemData = _tmp.Services.GetRequiredService<ItemService>().AwaitedService;
|
||||||
|
Dalamud = _tmp.Services.GetRequiredService<DalamudServices>();
|
||||||
|
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DalamudServices.Initialize( pluginInterface );
|
|
||||||
|
|
||||||
Performance = new PerformanceTracker< PerformanceType >( DalamudServices.Framework );
|
|
||||||
Log = new Logger();
|
|
||||||
ValidityChecker = new ValidityChecker( DalamudServices.PluginInterface );
|
|
||||||
|
|
||||||
GameEvents = new GameEventManager();
|
|
||||||
StartTimer.Measure( StartTimeType.Identifier, () => Identifier = GameData.GameData.GetIdentifier( DalamudServices.PluginInterface, DalamudServices.GameData ) );
|
|
||||||
StartTimer.Measure( StartTimeType.GamePathParser, () => GamePathParser = GameData.GameData.GetGamePathParser() );
|
|
||||||
StartTimer.Measure( StartTimeType.Stains, () => StainManager = new StainManager( DalamudServices.PluginInterface, DalamudServices.GameData ) );
|
|
||||||
ItemData = StartTimer.Measure( StartTimeType.Items,
|
|
||||||
() => new ItemData( DalamudServices.PluginInterface, DalamudServices.GameData, DalamudServices.GameData.Language ) );
|
|
||||||
StartTimer.Measure( StartTimeType.Actors,
|
|
||||||
() => Actors = new ActorManager( DalamudServices.PluginInterface, DalamudServices.Objects, DalamudServices.ClientState, DalamudServices.Framework,
|
|
||||||
DalamudServices.GameData, DalamudServices.GameGui,
|
|
||||||
ResolveCutscene ) );
|
|
||||||
|
|
||||||
Framework = new FrameworkManager( DalamudServices.Framework, Log );
|
|
||||||
CharacterUtility = new CharacterUtility();
|
|
||||||
|
|
||||||
StartTimer.Measure( StartTimeType.Backup, () => Backup.CreateBackup( pluginInterface.ConfigDirectory, PenumbraBackupFiles() ) );
|
|
||||||
Config = Configuration.Load();
|
|
||||||
|
|
||||||
TempMods = new TempModManager();
|
|
||||||
MetaFileManager = new MetaFileManager();
|
|
||||||
ResourceLoader = new ResourceLoader(this);
|
ResourceLoader = new ResourceLoader(this);
|
||||||
ResourceLoader.EnableHooks();
|
ResourceLoader.EnableHooks();
|
||||||
_resourceWatcher = new ResourceWatcher(ResourceLoader);
|
_resourceWatcher = new ResourceWatcher(ResourceLoader);
|
||||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
|
||||||
ResidentResources = new ResidentResourceManager();
|
ResidentResources = new ResidentResourceManager();
|
||||||
StartTimer.Measure( StartTimeType.Mods, () =>
|
_tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Mods, () =>
|
||||||
{
|
{
|
||||||
ModManager = new Mod.Manager(Config.ModDirectory);
|
ModManager = new Mod.Manager(Config.ModDirectory);
|
||||||
ModManager.DiscoverMods();
|
ModManager.DiscoverMods();
|
||||||
});
|
});
|
||||||
|
|
||||||
StartTimer.Measure( StartTimeType.Collections, () =>
|
_tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Collections, () =>
|
||||||
{
|
{
|
||||||
CollectionManager = new ModCollection.Manager( ModManager );
|
CollectionManager = new ModCollection.Manager(_tmp.Services.GetRequiredService<CommunicatorService>(), ModManager);
|
||||||
CollectionManager.CreateNecessaryCaches();
|
CollectionManager.CreateNecessaryCaches();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||||
|
|
||||||
ModFileSystem = ModFileSystem.Load();
|
ModFileSystem = ModFileSystem.Load();
|
||||||
ObjectReloader = new ObjectReloader();
|
ObjectReloader = new ObjectReloader();
|
||||||
PathResolver = new PathResolver( ResourceLoader );
|
PathResolver = new PathResolver(_tmp.Services.GetRequiredService<StartTracker>(), _tmp.Services.GetRequiredService<CommunicatorService>(), _tmp.Services.GetRequiredService<GameEventManager>(), ResourceLoader);
|
||||||
|
|
||||||
SetupInterface();
|
SetupInterface();
|
||||||
|
|
||||||
|
|
@ -179,19 +137,15 @@ public class Penumbra : IDalamudPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.DebugMode)
|
if (Config.DebugMode)
|
||||||
{
|
|
||||||
ResourceLoader.EnableDebug();
|
ResourceLoader.EnableDebug();
|
||||||
}
|
|
||||||
|
|
||||||
using( var tApi = StartTimer.Measure( StartTimeType.Api ) )
|
using (var tApi = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Api))
|
||||||
{
|
{
|
||||||
Api = new PenumbraApi( this );
|
Api = new PenumbraApi(_tmp.Services.GetRequiredService<CommunicatorService>(), this);
|
||||||
IpcProviders = new PenumbraIpcProviders(DalamudServices.PluginInterface, Api);
|
IpcProviders = new PenumbraIpcProviders(DalamudServices.PluginInterface, Api);
|
||||||
HttpApi = new HttpApi(Api);
|
HttpApi = new HttpApi(Api);
|
||||||
if (Config.EnableHttpApi)
|
if (Config.EnableHttpApi)
|
||||||
{
|
|
||||||
HttpApi.CreateWebServer();
|
HttpApi.CreateWebServer();
|
||||||
}
|
|
||||||
|
|
||||||
SubscribeItemLinks();
|
SubscribeItemLinks();
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +156,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
||||||
|
|
||||||
if (CharacterUtility.Ready)
|
if (CharacterUtility.Ready)
|
||||||
{
|
|
||||||
ResidentResources.Reload();
|
ResidentResources.Reload();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
|
|
@ -217,15 +169,16 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
using var tInterface = StartTimer.Measure( StartTimeType.Interface );
|
using var tInterface = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Interface);
|
||||||
var changelog = ConfigWindow.CreateChangelog();
|
var changelog = ConfigWindow.CreateChangelog();
|
||||||
var cfg = new ConfigWindow( this, _resourceWatcher )
|
var cfg = new ConfigWindow(_tmp.Services.GetRequiredService<CommunicatorService>(), _tmp.Services.GetRequiredService<StartTracker>(), this, _resourceWatcher)
|
||||||
{
|
{
|
||||||
IsOpen = Config.DebugMode,
|
IsOpen = Config.DebugMode,
|
||||||
};
|
};
|
||||||
var btn = new LaunchButton(cfg);
|
var btn = new LaunchButton(cfg);
|
||||||
var system = new WindowSystem(Name);
|
var system = new WindowSystem(Name);
|
||||||
var cmd = new CommandHandler( DalamudServices.Commands, ObjectReloader, Config, this, cfg, ModManager, CollectionManager, Actors );
|
var cmd = new CommandHandler(DalamudServices.Commands, ObjectReloader, Config, this, cfg, ModManager, CollectionManager,
|
||||||
|
Actors);
|
||||||
system.AddWindow(cfg);
|
system.AddWindow(cfg);
|
||||||
system.AddWindow(cfg.ModEditPopup);
|
system.AddWindow(cfg.ModEditPopup);
|
||||||
system.AddWindow(changelog);
|
system.AddWindow(changelog);
|
||||||
|
|
@ -252,9 +205,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
private void DisposeInterface()
|
private void DisposeInterface()
|
||||||
{
|
{
|
||||||
if (_windowSystem != null)
|
if (_windowSystem != null)
|
||||||
{
|
|
||||||
DalamudServices.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
DalamudServices.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||||
}
|
|
||||||
|
|
||||||
_launchButton?.Dispose();
|
_launchButton?.Dispose();
|
||||||
if (ConfigWindow != null)
|
if (ConfigWindow != null)
|
||||||
|
|
@ -269,9 +220,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
public bool SetEnabled(bool enabled)
|
public bool SetEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
if (enabled == Config.EnableMods)
|
if (enabled == Config.EnableMods)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Config.EnableMods = enabled;
|
Config.EnableMods = enabled;
|
||||||
if (enabled)
|
if (enabled)
|
||||||
|
|
@ -306,45 +255,36 @@ public class Penumbra : IDalamudPlugin
|
||||||
public void ForceChangelogOpen()
|
public void ForceChangelogOpen()
|
||||||
{
|
{
|
||||||
if (_changelog != null)
|
if (_changelog != null)
|
||||||
{
|
|
||||||
_changelog.ForceOpen = true;
|
_changelog.ForceOpen = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void SubscribeItemLinks()
|
private void SubscribeItemLinks()
|
||||||
{
|
{
|
||||||
Api.ChangedItemTooltip += it =>
|
Api.ChangedItemTooltip += it =>
|
||||||
{
|
{
|
||||||
if (it is Item)
|
if (it is Item)
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("Left Click to create an item link in chat.");
|
ImGui.TextUnformatted("Left Click to create an item link in chat.");
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Api.ChangedItemClicked += (button, it) =>
|
Api.ChangedItemClicked += (button, it) =>
|
||||||
{
|
{
|
||||||
if (button == MouseButton.Left && it is Item item)
|
if (button == MouseButton.Left && it is Item item)
|
||||||
{
|
|
||||||
ChatUtil.LinkItem(item);
|
ChatUtil.LinkItem(item);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private short ResolveCutscene( ushort index )
|
|
||||||
=> ( short )PathResolver.CutsceneActor( index );
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
_tmp?.Dispose();
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
HttpApi?.Dispose();
|
HttpApi?.Dispose();
|
||||||
IpcProviders?.Dispose();
|
IpcProviders?.Dispose();
|
||||||
Api?.Dispose();
|
Api?.Dispose();
|
||||||
_commandHandler?.Dispose();
|
_commandHandler?.Dispose();
|
||||||
StainManager?.Dispose();
|
StainService?.Dispose();
|
||||||
ItemData?.Dispose();
|
ItemData?.Dispose();
|
||||||
Actors?.Dispose();
|
Actors?.Dispose();
|
||||||
Identifier?.Dispose();
|
Identifier?.Dispose();
|
||||||
|
|
@ -354,7 +294,6 @@ public class Penumbra : IDalamudPlugin
|
||||||
ModFileSystem?.Dispose();
|
ModFileSystem?.Dispose();
|
||||||
CollectionManager?.Dispose();
|
CollectionManager?.Dispose();
|
||||||
PathResolver?.Dispose();
|
PathResolver?.Dispose();
|
||||||
ResourceLogger?.Dispose();
|
|
||||||
_resourceWatcher?.Dispose();
|
_resourceWatcher?.Dispose();
|
||||||
ResourceLoader?.Dispose();
|
ResourceLoader?.Dispose();
|
||||||
GameEvents?.Dispose();
|
GameEvents?.Dispose();
|
||||||
|
|
@ -362,21 +301,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
Performance?.Dispose();
|
Performance?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all relevant files for penumbra configuration.
|
public string GatherSupportInformation()
|
||||||
private static IReadOnlyList< FileInfo > PenumbraBackupFiles()
|
|
||||||
{
|
|
||||||
var collectionDir = ModCollection.CollectionDirectory;
|
|
||||||
var list = Directory.Exists( collectionDir )
|
|
||||||
? new DirectoryInfo( collectionDir ).EnumerateFiles( "*.json" ).ToList()
|
|
||||||
: new List< FileInfo >();
|
|
||||||
list.AddRange( Mod.LocalDataDirectory.Exists ? Mod.LocalDataDirectory.EnumerateFiles( "*.json" ) : Enumerable.Empty< FileInfo >() );
|
|
||||||
list.Add( DalamudServices.PluginInterface.ConfigFile );
|
|
||||||
list.Add( new FileInfo( ModFileSystem.ModFileSystemFile ) );
|
|
||||||
list.Add( new FileInfo( ModCollection.Manager.ActiveCollectionFile ) );
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GatherSupportInformation()
|
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder(10240);
|
var sb = new StringBuilder(10240);
|
||||||
var exists = Config.ModDirectory.Length > 0 && Directory.Exists(Config.ModDirectory);
|
var exists = Config.ModDirectory.Length > 0 && Directory.Exists(Config.ModDirectory);
|
||||||
|
|
@ -388,28 +313,35 @@ public class Penumbra : IDalamudPlugin
|
||||||
sb.Append($"> **`Enable HTTP API: `** {Config.EnableHttpApi}\n");
|
sb.Append($"> **`Enable HTTP API: `** {Config.EnableHttpApi}\n");
|
||||||
sb.Append($"> **`Operating System: `** {(DalamudUtil.IsLinux() ? "Mac/Linux (Wine)" : "Windows")}\n");
|
sb.Append($"> **`Operating System: `** {(DalamudUtil.IsLinux() ? "Mac/Linux (Wine)" : "Windows")}\n");
|
||||||
sb.Append($"> **`Root Directory: `** `{Config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}\n");
|
sb.Append($"> **`Root Directory: `** `{Config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}\n");
|
||||||
sb.Append( $"> **`Free Drive Space: `** {( drive != null ? Functions.HumanReadableSize( drive.AvailableFreeSpace ) : "Unknown" )}\n" );
|
sb.Append(
|
||||||
|
$"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n");
|
||||||
sb.Append($"> **`Auto-Deduplication: `** {Config.AutoDeduplicateOnImport}\n");
|
sb.Append($"> **`Auto-Deduplication: `** {Config.AutoDeduplicateOnImport}\n");
|
||||||
sb.Append($"> **`Debug Mode: `** {Config.DebugMode}\n");
|
sb.Append($"> **`Debug Mode: `** {Config.DebugMode}\n");
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"> **`Synchronous Load (Dalamud): `** {( DalamudServices.GetDalamudConfig( DalamudServices.WaitingForPluginsOption, out bool v ) ? v.ToString() : "Unknown" )}\n" );
|
$"> **`Synchronous Load (Dalamud): `** {(_tmp.Services.GetRequiredService<DalamudServices>().GetDalamudConfig(DalamudServices.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n");
|
||||||
sb.Append( $"> **`Logging: `** Log: {Config.EnableResourceLogging}, Watcher: {Config.EnableResourceWatcher} ({Config.MaxResourceWatcherRecords})\n" );
|
sb.Append(
|
||||||
|
$"> **`Logging: `** Log: {Config.EnableResourceLogging}, Watcher: {Config.EnableResourceWatcher} ({Config.MaxResourceWatcherRecords})\n");
|
||||||
sb.Append($"> **`Use Ownership: `** {Config.UseOwnerNameForCharacterCollection}\n");
|
sb.Append($"> **`Use Ownership: `** {Config.UseOwnerNameForCharacterCollection}\n");
|
||||||
sb.AppendLine("**Mods**");
|
sb.AppendLine("**Mods**");
|
||||||
sb.Append($"> **`Installed Mods: `** {ModManager.Count}\n");
|
sb.Append($"> **`Installed Mods: `** {ModManager.Count}\n");
|
||||||
sb.Append($"> **`Mods with Config: `** {ModManager.Count(m => m.HasOptions)}\n");
|
sb.Append($"> **`Mods with Config: `** {ModManager.Count(m => m.HasOptions)}\n");
|
||||||
sb.Append( $"> **`Mods with File Redirections: `** {ModManager.Count( m => m.TotalFileCount > 0 )}, Total: {ModManager.Sum( m => m.TotalFileCount )}\n" );
|
sb.Append(
|
||||||
sb.Append( $"> **`Mods with FileSwaps: `** {ModManager.Count( m => m.TotalSwapCount > 0 )}, Total: {ModManager.Sum( m => m.TotalSwapCount )}\n" );
|
$"> **`Mods with File Redirections: `** {ModManager.Count(m => m.TotalFileCount > 0)}, Total: {ModManager.Sum(m => m.TotalFileCount)}\n");
|
||||||
sb.Append( $"> **`Mods with Meta Manipulations:`** {ModManager.Count( m => m.TotalManipulations > 0 )}, Total {ModManager.Sum( m => m.TotalManipulations )}\n" );
|
sb.Append(
|
||||||
|
$"> **`Mods with FileSwaps: `** {ModManager.Count(m => m.TotalSwapCount > 0)}, Total: {ModManager.Sum(m => m.TotalSwapCount)}\n");
|
||||||
|
sb.Append(
|
||||||
|
$"> **`Mods with Meta Manipulations:`** {ModManager.Count(m => m.TotalManipulations > 0)}, Total {ModManager.Sum(m => m.TotalManipulations)}\n");
|
||||||
sb.Append($"> **`IMC Exceptions Thrown: `** {ValidityChecker.ImcExceptions.Count}\n");
|
sb.Append($"> **`IMC Exceptions Thrown: `** {ValidityChecker.ImcExceptions.Count}\n");
|
||||||
sb.Append( $"> **`#Temp Mods: `** {TempMods.Mods.Sum( kvp => kvp.Value.Count ) + TempMods.ModsForAllCollections.Count}\n" );
|
sb.Append(
|
||||||
|
$"> **`#Temp Mods: `** {TempMods.Mods.Sum(kvp => kvp.Value.Count) + TempMods.ModsForAllCollections.Count}\n");
|
||||||
|
|
||||||
string CharacterName(ActorIdentifier id, string name)
|
string CharacterName(ActorIdentifier id, string name)
|
||||||
{
|
{
|
||||||
if (id.Type is IdentifierType.Player or IdentifierType.Owned)
|
if (id.Type is IdentifierType.Player or IdentifierType.Owned)
|
||||||
{
|
{
|
||||||
var parts = name.Split(' ', 3);
|
var parts = name.Split(' ', 3);
|
||||||
return string.Join( " ", parts.Length != 3 ? parts.Select( n => $"{n[ 0 ]}." ) : parts[ ..2 ].Select( n => $"{n[ 0 ]}." ).Append( parts[ 2 ] ) );
|
return string.Join(" ",
|
||||||
|
parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return name + ':';
|
return name + ':';
|
||||||
|
|
@ -423,7 +355,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
|
|
||||||
sb.AppendLine("**Collections**");
|
sb.AppendLine("**Collections**");
|
||||||
sb.Append($"> **`#Collections: `** {CollectionManager.Count - 1}\n");
|
sb.Append($"> **`#Collections: `** {CollectionManager.Count - 1}\n");
|
||||||
sb.Append( $"> **`#Temp Collections: `** {TempMods.CustomCollections.Count}\n" );
|
sb.Append($"> **`#Temp Collections: `** {TempCollections.Count}\n");
|
||||||
sb.Append($"> **`Active Collections: `** {CollectionManager.Count(c => c.HasCache)}\n");
|
sb.Append($"> **`Active Collections: `** {CollectionManager.Count(c => c.HasCache)}\n");
|
||||||
sb.Append($"> **`Base Collection: `** {CollectionManager.Default.AnonymizedName}\n");
|
sb.Append($"> **`Base Collection: `** {CollectionManager.Default.AnonymizedName}\n");
|
||||||
sb.Append($"> **`Interface Collection: `** {CollectionManager.Interface.AnonymizedName}\n");
|
sb.Append($"> **`Interface Collection: `** {CollectionManager.Interface.AnonymizedName}\n");
|
||||||
|
|
@ -432,20 +364,14 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
var collection = CollectionManager.ByType(type);
|
var collection = CollectionManager.ByType(type);
|
||||||
if (collection != null)
|
if (collection != null)
|
||||||
{
|
|
||||||
sb.Append($"> **`{name,-30}`** {collection.AnonymizedName}\n");
|
sb.Append($"> **`{name,-30}`** {collection.AnonymizedName}\n");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (name, id, collection) in CollectionManager.Individuals.Assignments)
|
foreach (var (name, id, collection) in CollectionManager.Individuals.Assignments)
|
||||||
{
|
|
||||||
sb.Append($"> **`{CharacterName(id[0], name),-30}`** {collection.AnonymizedName}\n");
|
sb.Append($"> **`{CharacterName(id[0], name),-30}`** {collection.AnonymizedName}\n");
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var collection in CollectionManager.Where(c => c.HasCache))
|
foreach (var collection in CollectionManager.Where(c => c.HasCache))
|
||||||
{
|
|
||||||
PrintCollection(collection);
|
PrintCollection(collection);
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
using System.IO;
|
using System;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Interop;
|
using Penumbra.Interop;
|
||||||
|
using Penumbra.Interop.Resolver;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
@ -17,34 +20,60 @@ public class PenumbraNew
|
||||||
=> "Penumbra";
|
=> "Penumbra";
|
||||||
|
|
||||||
public static readonly Logger Log = new();
|
public static readonly Logger Log = new();
|
||||||
public readonly StartTimeTracker<StartTimeType> StartTimer = new();
|
public readonly ServiceProvider Services;
|
||||||
|
|
||||||
public readonly IServiceCollection Services = new ServiceCollection();
|
|
||||||
|
|
||||||
|
|
||||||
public PenumbraNew(DalamudPluginInterface pi)
|
public PenumbraNew(DalamudPluginInterface pi)
|
||||||
{
|
{
|
||||||
using var time = StartTimer.Measure(StartTimeType.Total);
|
var startTimer = new StartTracker();
|
||||||
|
using var time = startTimer.Measure(StartTimeType.Total);
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
// Add meta services.
|
// Add meta services.
|
||||||
Services.AddSingleton(Log);
|
services.AddSingleton(Log)
|
||||||
Services.AddSingleton(StartTimer);
|
.AddSingleton(startTimer)
|
||||||
Services.AddSingleton<ValidityChecker>();
|
.AddSingleton<ValidityChecker>()
|
||||||
Services.AddSingleton<PerformanceTracker<PerformanceType>>();
|
.AddSingleton<PerformanceTracker>()
|
||||||
|
.AddSingleton<FilenameService>()
|
||||||
|
.AddSingleton<BackupService>()
|
||||||
|
.AddSingleton<CommunicatorService>();
|
||||||
|
|
||||||
// Add Dalamud services
|
// Add Dalamud services
|
||||||
var dalamud = new DalamudServices(pi);
|
var dalamud = new DalamudServices(pi);
|
||||||
dalamud.AddServices(Services);
|
dalamud.AddServices(services);
|
||||||
|
|
||||||
// Add Game Data
|
// Add Game Data
|
||||||
Services.AddSingleton<GameEventManager>();
|
services.AddSingleton<IGamePathParser, GamePathParser>()
|
||||||
Services.AddSingleton<IGamePathParser, GamePathParser>();
|
.AddSingleton<IdentifierService>()
|
||||||
Services.AddSingleton<IObjectIdentifier, ObjectIdentifier>();
|
.AddSingleton<StainService>()
|
||||||
|
.AddSingleton<ItemService>()
|
||||||
|
.AddSingleton<ActorService>();
|
||||||
|
|
||||||
|
// Add Game Services
|
||||||
|
services.AddSingleton<GameEventManager>()
|
||||||
|
.AddSingleton<FrameworkManager>()
|
||||||
|
.AddSingleton<MetaFileManager>()
|
||||||
|
.AddSingleton<CutsceneCharacters>()
|
||||||
|
.AddSingleton<CharacterUtility>();
|
||||||
|
|
||||||
|
|
||||||
// Add Configuration
|
// Add Configuration
|
||||||
Services.AddSingleton<Configuration>();
|
services.AddTransient<ConfigMigrationService>()
|
||||||
|
.AddSingleton<Configuration>();
|
||||||
|
|
||||||
|
// Add Collection Services
|
||||||
|
services.AddTransient<IndividualCollections>()
|
||||||
|
.AddSingleton<TempCollectionManager>();
|
||||||
|
|
||||||
|
// Add Mod Services
|
||||||
|
// TODO
|
||||||
|
services.AddSingleton<TempModManager>();
|
||||||
|
|
||||||
|
// Add Interface
|
||||||
|
Services = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{ }
|
{
|
||||||
|
Services.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
29
Penumbra/Services/BackupService.cs
Normal file
29
Penumbra/Services/BackupService.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Log;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public class BackupService
|
||||||
|
{
|
||||||
|
public BackupService(Logger logger, StartTracker timer, FilenameService fileNames)
|
||||||
|
{
|
||||||
|
using var t = timer.Measure(StartTimeType.Backup);
|
||||||
|
var files = PenumbraFiles(fileNames);
|
||||||
|
Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all relevant files for penumbra configuration.
|
||||||
|
private static IReadOnlyList<FileInfo> PenumbraFiles(FilenameService fileNames)
|
||||||
|
{
|
||||||
|
var list = fileNames.CollectionFiles.ToList();
|
||||||
|
list.AddRange(fileNames.LocalDataFiles);
|
||||||
|
list.Add(new FileInfo(fileNames.ConfigFile));
|
||||||
|
list.Add(new FileInfo(fileNames.FilesystemFile));
|
||||||
|
list.Add(new FileInfo(fileNames.ActiveCollectionsFile));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Penumbra/Services/CommunicatorService.cs
Normal file
30
Penumbra/Services/CommunicatorService.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public class CommunicatorService : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary> <list type="number">
|
||||||
|
/// <item>Parameter is the type of the changed collection. (Inactive or Temporary for additions or deletions)</item>
|
||||||
|
/// <item>Parameter is the old collection, or null on additions.</item>
|
||||||
|
/// <item>Parameter is the new collection, or null on deletions.</item>
|
||||||
|
/// <item>Parameter is the display name for Individual collections or an empty string otherwise.</item>
|
||||||
|
/// </list> </summary>
|
||||||
|
public readonly EventWrapper<CollectionType, ModCollection?, ModCollection?, string> CollectionChange = new(nameof(CollectionChange));
|
||||||
|
|
||||||
|
/// <summary> <list type="number">
|
||||||
|
/// <item>Parameter added, deleted or edited temporary mod.</item>
|
||||||
|
/// <item>Parameter is whether the mod was newly created.</item>
|
||||||
|
/// <item>Parameter is whether the mod was deleted.</item>
|
||||||
|
/// </list> </summary>
|
||||||
|
public readonly EventWrapper<Mod.TemporaryMod, bool, bool> TemporaryGlobalModChange = new(nameof(TemporaryGlobalModChange));
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CollectionChange.Dispose();
|
||||||
|
TemporaryGlobalModChange.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
377
Penumbra/Services/ConfigMigrationService.cs
Normal file
377
Penumbra/Services/ConfigMigrationService.cs
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui.Filesystem;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.UI.Classes;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains everything to migrate from older versions of the config to the current,
|
||||||
|
/// including deprecated fields.
|
||||||
|
/// </summary>
|
||||||
|
public class ConfigMigrationService
|
||||||
|
{
|
||||||
|
private readonly FilenameService _fileNames;
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
|
private Configuration _config = null!;
|
||||||
|
private JObject _data = null!;
|
||||||
|
|
||||||
|
public string CurrentCollection = ModCollection.DefaultCollection;
|
||||||
|
public string DefaultCollection = ModCollection.DefaultCollection;
|
||||||
|
public string ForcedCollection = string.Empty;
|
||||||
|
public Dictionary<string, string> CharacterCollections = new();
|
||||||
|
public Dictionary<string, string> ModSortOrder = new();
|
||||||
|
public bool InvertModListOrder;
|
||||||
|
public bool SortFoldersFirst;
|
||||||
|
public SortModeV3 SortMode = SortModeV3.FoldersFirst;
|
||||||
|
|
||||||
|
public ConfigMigrationService(FilenameService fileNames, DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
_fileNames = fileNames;
|
||||||
|
_pluginInterface = pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Add missing colors to the dictionary if necessary. </summary>
|
||||||
|
private static void AddColors(Configuration config, bool forceSave)
|
||||||
|
{
|
||||||
|
var save = false;
|
||||||
|
foreach (var color in Enum.GetValues<ColorId>())
|
||||||
|
{
|
||||||
|
save |= config.Colors.TryAdd(color, color.Data().DefaultColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save || forceSave)
|
||||||
|
{
|
||||||
|
config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Migrate(Configuration config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
// Do this on every migration from now on for a while
|
||||||
|
// because it stayed alive for a bunch of people for some reason.
|
||||||
|
DeleteMetaTmp();
|
||||||
|
|
||||||
|
if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(_fileNames.ConfigFile))
|
||||||
|
{
|
||||||
|
AddColors(config, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = JObject.Parse(File.ReadAllText(_fileNames.ConfigFile));
|
||||||
|
CreateBackup();
|
||||||
|
|
||||||
|
Version0To1();
|
||||||
|
Version1To2();
|
||||||
|
Version2To3();
|
||||||
|
Version3To4();
|
||||||
|
Version4To5();
|
||||||
|
Version5To6();
|
||||||
|
Version6To7();
|
||||||
|
AddColors(config, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gendered special collections were added.
|
||||||
|
private void Version6To7()
|
||||||
|
{
|
||||||
|
if (_config.Version != 6)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ModCollection.Manager.MigrateUngenderedCollections(_fileNames);
|
||||||
|
_config.Version = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A new tutorial step was inserted in the middle.
|
||||||
|
// The UI collection and a new tutorial for it was added.
|
||||||
|
// The migration for the UI collection itself happens in the ActiveCollections file.
|
||||||
|
private void Version5To6()
|
||||||
|
{
|
||||||
|
if (_config.Version != 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_config.TutorialStep == 25)
|
||||||
|
_config.TutorialStep = 27;
|
||||||
|
|
||||||
|
_config.Version = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mod backup extension was changed from .zip to .pmp.
|
||||||
|
// Actual migration takes place in ModManager.
|
||||||
|
private void Version4To5()
|
||||||
|
{
|
||||||
|
if (_config.Version != 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Mod.Manager.MigrateModBackups = true;
|
||||||
|
_config.Version = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortMode was changed from an enum to a type.
|
||||||
|
private void Version3To4()
|
||||||
|
{
|
||||||
|
if (_config.Version != 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SortMode = _data[nameof(SortMode)]?.ToObject<SortModeV3>() ?? SortMode;
|
||||||
|
_config.SortMode = SortMode switch
|
||||||
|
{
|
||||||
|
SortModeV3.FoldersFirst => ISortMode<Mod>.FoldersFirst,
|
||||||
|
SortModeV3.Lexicographical => ISortMode<Mod>.Lexicographical,
|
||||||
|
SortModeV3.InverseFoldersFirst => ISortMode<Mod>.InverseFoldersFirst,
|
||||||
|
SortModeV3.InverseLexicographical => ISortMode<Mod>.InverseLexicographical,
|
||||||
|
SortModeV3.FoldersLast => ISortMode<Mod>.FoldersLast,
|
||||||
|
SortModeV3.InverseFoldersLast => ISortMode<Mod>.InverseFoldersLast,
|
||||||
|
SortModeV3.InternalOrder => ISortMode<Mod>.InternalOrder,
|
||||||
|
SortModeV3.InternalOrderInverse => ISortMode<Mod>.InverseInternalOrder,
|
||||||
|
_ => ISortMode<Mod>.FoldersFirst,
|
||||||
|
};
|
||||||
|
_config.Version = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFoldersFirst was changed from a bool to the enum SortMode.
|
||||||
|
private void Version2To3()
|
||||||
|
{
|
||||||
|
if (_config.Version != 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SortFoldersFirst = _data[nameof(SortFoldersFirst)]?.ToObject<bool>() ?? false;
|
||||||
|
SortMode = SortFoldersFirst ? SortModeV3.FoldersFirst : SortModeV3.Lexicographical;
|
||||||
|
_config.Version = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The forced collection was removed due to general inheritance.
|
||||||
|
// Sort Order was moved to a separate file and may contain empty folders.
|
||||||
|
// Active collections in general were moved to their own file.
|
||||||
|
// Delete the penumbrametatmp folder if it exists.
|
||||||
|
private void Version1To2()
|
||||||
|
{
|
||||||
|
if (_config.Version != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ensure the right meta files are loaded.
|
||||||
|
DeleteMetaTmp();
|
||||||
|
Penumbra.CharacterUtility.LoadCharacterResources();
|
||||||
|
ResettleSortOrder();
|
||||||
|
ResettleCollectionSettings();
|
||||||
|
ResettleForcedCollection();
|
||||||
|
_config.Version = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteMetaTmp()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_config.ModDirectory, "penumbrametatmp");
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(path, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not delete the outdated penumbrametatmp folder:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResettleForcedCollection()
|
||||||
|
{
|
||||||
|
ForcedCollection = _data[nameof(ForcedCollection)]?.ToObject<string>() ?? ForcedCollection;
|
||||||
|
if (ForcedCollection.Length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add the previous forced collection to all current collections except itself as an inheritance.
|
||||||
|
foreach (var collection in _fileNames.CollectionFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jObject = JObject.Parse(File.ReadAllText(collection.FullName));
|
||||||
|
if (jObject[nameof(ModCollection.Name)]?.ToObject<string>() == ForcedCollection)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
jObject[nameof(ModCollection.Inheritance)] = JToken.FromObject(new List<string> { ForcedCollection });
|
||||||
|
File.WriteAllText(collection.FullName, jObject.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error(
|
||||||
|
$"Could not transfer forced collection {ForcedCollection} to inheritance of collection {collection}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the current sort order to its own file.
|
||||||
|
private void ResettleSortOrder()
|
||||||
|
{
|
||||||
|
ModSortOrder = _data[nameof(ModSortOrder)]?.ToObject<Dictionary<string, string>>() ?? ModSortOrder;
|
||||||
|
var file = _fileNames.FilesystemFile;
|
||||||
|
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
||||||
|
using var writer = new StreamWriter(stream);
|
||||||
|
using var j = new JsonTextWriter(writer);
|
||||||
|
j.Formatting = Formatting.Indented;
|
||||||
|
j.WriteStartObject();
|
||||||
|
j.WritePropertyName("Data");
|
||||||
|
j.WriteStartObject();
|
||||||
|
foreach (var (mod, path) in ModSortOrder.Where(kvp => Directory.Exists(Path.Combine(_config.ModDirectory, kvp.Key))))
|
||||||
|
{
|
||||||
|
j.WritePropertyName(mod, true);
|
||||||
|
j.WriteValue(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WriteEndObject();
|
||||||
|
j.WritePropertyName("EmptyFolders");
|
||||||
|
j.WriteStartArray();
|
||||||
|
j.WriteEndArray();
|
||||||
|
j.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the active collections to their own file.
|
||||||
|
private void ResettleCollectionSettings()
|
||||||
|
{
|
||||||
|
CurrentCollection = _data[nameof(CurrentCollection)]?.ToObject<string>() ?? CurrentCollection;
|
||||||
|
DefaultCollection = _data[nameof(DefaultCollection)]?.ToObject<string>() ?? DefaultCollection;
|
||||||
|
CharacterCollections = _data[nameof(CharacterCollections)]?.ToObject<Dictionary<string, string>>() ?? CharacterCollections;
|
||||||
|
SaveActiveCollectionsV0(DefaultCollection, CurrentCollection, DefaultCollection,
|
||||||
|
CharacterCollections.Select(kvp => (kvp.Key, kvp.Value)), Array.Empty<(CollectionType, string)>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outdated saving using the Characters list.
|
||||||
|
private void SaveActiveCollectionsV0(string def, string ui, string current, IEnumerable<(string, string)> characters,
|
||||||
|
IEnumerable<(CollectionType, string)> special)
|
||||||
|
{
|
||||||
|
var file = _fileNames.ActiveCollectionsFile;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
||||||
|
using var writer = new StreamWriter(stream);
|
||||||
|
using var j = new JsonTextWriter(writer);
|
||||||
|
j.Formatting = Formatting.Indented;
|
||||||
|
j.WriteStartObject();
|
||||||
|
j.WritePropertyName(nameof(ModCollection.Manager.Default));
|
||||||
|
j.WriteValue(def);
|
||||||
|
j.WritePropertyName(nameof(ModCollection.Manager.Interface));
|
||||||
|
j.WriteValue(ui);
|
||||||
|
j.WritePropertyName(nameof(ModCollection.Manager.Current));
|
||||||
|
j.WriteValue(current);
|
||||||
|
foreach (var (type, collection) in special)
|
||||||
|
{
|
||||||
|
j.WritePropertyName(type.ToString());
|
||||||
|
j.WriteValue(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WritePropertyName("Characters");
|
||||||
|
j.WriteStartObject();
|
||||||
|
foreach (var (character, collection) in characters)
|
||||||
|
{
|
||||||
|
j.WritePropertyName(character, true);
|
||||||
|
j.WriteValue(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WriteEndObject();
|
||||||
|
j.WriteEndObject();
|
||||||
|
Penumbra.Log.Verbose("Active Collections saved.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not save active collections to file {file}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections were introduced and the previous CurrentCollection got put into ModDirectory.
|
||||||
|
private void Version0To1()
|
||||||
|
{
|
||||||
|
if (_config.Version != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_config.ModDirectory = _data[nameof(CurrentCollection)]?.ToObject<string>() ?? string.Empty;
|
||||||
|
_config.Version = 1;
|
||||||
|
ResettleCollectionJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the previous mod configurations to a new default collection file.
|
||||||
|
private void ResettleCollectionJson()
|
||||||
|
{
|
||||||
|
var collectionJson = new FileInfo(Path.Combine(_config.ModDirectory, "collection.json"));
|
||||||
|
if (!collectionJson.Exists)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var defaultCollection = ModCollection.CreateNewEmpty(ModCollection.DefaultCollection);
|
||||||
|
var defaultCollectionFile = defaultCollection.FileName;
|
||||||
|
if (defaultCollectionFile.Exists)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(collectionJson.FullName);
|
||||||
|
var data = JArray.Parse(text);
|
||||||
|
|
||||||
|
var maxPriority = 0;
|
||||||
|
var dict = new Dictionary<string, ModSettings.SavedSettings>();
|
||||||
|
foreach (var setting in data.Cast<JObject>())
|
||||||
|
{
|
||||||
|
var modName = (string)setting["FolderName"]!;
|
||||||
|
var enabled = (bool)setting["Enabled"]!;
|
||||||
|
var priority = (int)setting["Priority"]!;
|
||||||
|
var settings = setting["Settings"]!.ToObject<Dictionary<string, long>>()
|
||||||
|
?? setting["Conf"]!.ToObject<Dictionary<string, long>>();
|
||||||
|
|
||||||
|
dict[modName] = new ModSettings.SavedSettings()
|
||||||
|
{
|
||||||
|
Enabled = enabled,
|
||||||
|
Priority = priority,
|
||||||
|
Settings = settings!,
|
||||||
|
};
|
||||||
|
maxPriority = Math.Max(maxPriority, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
InvertModListOrder = _data[nameof(InvertModListOrder)]?.ToObject<bool>() ?? InvertModListOrder;
|
||||||
|
if (!InvertModListOrder)
|
||||||
|
dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority });
|
||||||
|
|
||||||
|
defaultCollection = ModCollection.MigrateFromV0(ModCollection.DefaultCollection, dict);
|
||||||
|
defaultCollection.Save();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not migrate the old collection file to new collection files:\n{e}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a backup of the configuration file specifically.
|
||||||
|
private void CreateBackup()
|
||||||
|
{
|
||||||
|
var name = _fileNames.ConfigFile;
|
||||||
|
var bakName = name + ".bak";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Copy(name, bakName, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not create backup copy of config at {bakName}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SortModeV3 : byte
|
||||||
|
{
|
||||||
|
FoldersFirst = 0x00,
|
||||||
|
Lexicographical = 0x01,
|
||||||
|
InverseFoldersFirst = 0x02,
|
||||||
|
InverseLexicographical = 0x03,
|
||||||
|
FoldersLast = 0x04,
|
||||||
|
InverseFoldersLast = 0x05,
|
||||||
|
InternalOrder = 0x06,
|
||||||
|
InternalOrderInverse = 0x07,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -79,20 +79,21 @@ public class DalamudServices
|
||||||
services.AddSingleton(this);
|
services.AddSingleton(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove static
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
[PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public CommandManager Commands { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public DataManager GameData { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public ClientState ClientState { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public ChatGui Chat { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public Framework Framework { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public Condition Conditions { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static Condition Conditions { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public TargetManager Targets { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public ObjectTable Objects { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public TitleScreenMenu TitleScreenMenu { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public GameGui GameGui { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public KeyState KeyState { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static KeyState KeyState { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public SigScanner SigScanner { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static SigScanner SigScanner { get; private set; } = null!;
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
public const string WaitingForPluginsOption = "IsResumeGameAfterPluginLoad";
|
public const string WaitingForPluginsOption = "IsResumeGameAfterPluginLoad";
|
||||||
|
|
|
||||||
51
Penumbra/Services/FilenameService.cs
Normal file
51
Penumbra/Services/FilenameService.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using OtterGui.Filesystem;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public class FilenameService
|
||||||
|
{
|
||||||
|
public readonly string ConfigDirectory;
|
||||||
|
public readonly string CollectionDirectory;
|
||||||
|
public readonly string LocalDataDirectory;
|
||||||
|
public readonly string ConfigFile;
|
||||||
|
public readonly string FilesystemFile;
|
||||||
|
public readonly string ActiveCollectionsFile;
|
||||||
|
|
||||||
|
public FilenameService(DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
ConfigDirectory = pi.ConfigDirectory.FullName;
|
||||||
|
CollectionDirectory = Path.Combine(pi.GetPluginConfigDirectory(), "collections");
|
||||||
|
LocalDataDirectory = Path.Combine(pi.ConfigDirectory.FullName, "mod_data");
|
||||||
|
ConfigFile = pi.ConfigFile.FullName;
|
||||||
|
FilesystemFile = Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json");
|
||||||
|
ActiveCollectionsFile = Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CollectionFile(string collectionName)
|
||||||
|
=> Path.Combine(CollectionDirectory, $"{collectionName.RemoveInvalidPathSymbols()}.json");
|
||||||
|
|
||||||
|
public string LocalDataFile(string modPath)
|
||||||
|
=> Path.Combine(LocalDataDirectory, $"{modPath}.json");
|
||||||
|
|
||||||
|
public IEnumerable<FileInfo> CollectionFiles
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var directory = new DirectoryInfo(CollectionDirectory);
|
||||||
|
return directory.Exists ? directory.EnumerateFiles("*.json") : Array.Empty<FileInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileInfo> LocalDataFiles
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var directory = new DirectoryInfo(LocalDataDirectory);
|
||||||
|
return directory.Exists ? directory.EnumerateFiles("*.json") : Array.Empty<FileInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using OtterGui.Classes;
|
|
||||||
using Penumbra.GameData;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
using Penumbra.Util;
|
|
||||||
using Action = System.Action;
|
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
|
||||||
|
|
||||||
public sealed class ObjectIdentifier : IObjectIdentifier
|
|
||||||
{
|
|
||||||
private const string Prefix = $"[{nameof(ObjectIdentifier)}]";
|
|
||||||
|
|
||||||
public IObjectIdentifier? Identifier { get; private set; }
|
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
|
||||||
|
|
||||||
public bool Ready
|
|
||||||
=> Identifier != null && !IsDisposed;
|
|
||||||
|
|
||||||
public event Action? FinishedCreation;
|
|
||||||
|
|
||||||
public ObjectIdentifier(StartTimeTracker<StartTimeType> tracker, DalamudPluginInterface pi, DataManager data)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
using var timer = tracker.Measure(StartTimeType.Identifier);
|
|
||||||
var identifier = GameData.GameData.GetIdentifier(pi, data);
|
|
||||||
if (IsDisposed)
|
|
||||||
{
|
|
||||||
identifier.Dispose();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Identifier = identifier;
|
|
||||||
Penumbra.Log.Verbose($"{Prefix} Created.");
|
|
||||||
FinishedCreation?.Invoke();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Identifier?.Dispose();
|
|
||||||
IsDisposed = true;
|
|
||||||
Penumbra.Log.Verbose($"{Prefix} Disposed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public IGamePathParser GamePathParser
|
|
||||||
=> Identifier?.GamePathParser ?? throw new Exception($"{Prefix} Not yet ready.");
|
|
||||||
|
|
||||||
public void Identify(IDictionary<string, object?> set, string path)
|
|
||||||
=> Identifier?.Identify(set, path);
|
|
||||||
|
|
||||||
public Dictionary<string, object?> Identify(string path)
|
|
||||||
=> Identifier?.Identify(path) ?? new Dictionary<string, object?>();
|
|
||||||
|
|
||||||
public IEnumerable<Item> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot)
|
|
||||||
=> Identifier?.Identify(setId, weaponType, variant, slot) ?? Array.Empty<Item>();
|
|
||||||
}
|
|
||||||
117
Penumbra/Services/ServiceWrapper.cs
Normal file
117
Penumbra/Services/ServiceWrapper.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public interface IServiceWrapper<out T> : IDisposable
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public T? Service { get; }
|
||||||
|
public bool Valid { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class SyncServiceWrapper<T> : IServiceWrapper<T>
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public T Service { get; }
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> !_isDisposed;
|
||||||
|
|
||||||
|
protected SyncServiceWrapper(string name, StartTracker tracker, StartTimeType type, Func<T> factory)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
using var timer = tracker.Measure(type);
|
||||||
|
Service = factory();
|
||||||
|
Penumbra.Log.Verbose($"[{Name}] Created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
if (Service is IDisposable d)
|
||||||
|
d.Dispose();
|
||||||
|
Penumbra.Log.Verbose($"[{Name}] Disposed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class AsyncServiceWrapper<T> : IServiceWrapper<T>
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public T? Service { get; private set; }
|
||||||
|
|
||||||
|
public T AwaitedService
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_task.Wait();
|
||||||
|
return Service!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> Service != null && !_isDisposed;
|
||||||
|
|
||||||
|
public event Action? FinishedCreation;
|
||||||
|
private readonly Task _task;
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
protected AsyncServiceWrapper(string name, StartTracker tracker, StartTimeType type, Func<T> factory)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var timer = tracker.Measure(type);
|
||||||
|
var service = factory();
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
if (service is IDisposable d)
|
||||||
|
d.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Service = service;
|
||||||
|
Penumbra.Log.Verbose($"[{Name}] Created.");
|
||||||
|
FinishedCreation?.Invoke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AsyncServiceWrapper(string name, Func<T> factory)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
var service = factory();
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
if (service is IDisposable d)
|
||||||
|
d.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Service = service;
|
||||||
|
Penumbra.Log.Verbose($"[{Name}] Created.");
|
||||||
|
FinishedCreation?.Invoke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
if (Service is IDisposable d)
|
||||||
|
d.Dispose();
|
||||||
|
Penumbra.Log.Verbose($"[{Name}] Disposed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Penumbra/Services/StainService.cs
Normal file
43
Penumbra/Services/StainService.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public class StainService : IDisposable
|
||||||
|
{
|
||||||
|
public sealed class StainTemplateCombo : FilterComboCache<ushort>
|
||||||
|
{
|
||||||
|
public StainTemplateCombo(IEnumerable<ushort> items)
|
||||||
|
: base(items)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly StainData StainData;
|
||||||
|
public readonly FilterComboColors StainCombo;
|
||||||
|
public readonly StmFile StmFile;
|
||||||
|
public readonly StainTemplateCombo TemplateCombo;
|
||||||
|
|
||||||
|
public StainService(StartTracker timer, DalamudPluginInterface pluginInterface, DataManager dataManager)
|
||||||
|
{
|
||||||
|
using var t = timer.Measure(StartTimeType.Stains);
|
||||||
|
StainData = new StainData(pluginInterface, dataManager, dataManager.Language);
|
||||||
|
StainCombo = new FilterComboColors(140, StainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
||||||
|
StmFile = new StmFile(dataManager);
|
||||||
|
TemplateCombo = new StainTemplateCombo(StmFile.Entries.Keys.Prepend((ushort)0));
|
||||||
|
Penumbra.Log.Verbose($"[{nameof(StainService)}] Created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StainData.Dispose();
|
||||||
|
Penumbra.Log.Verbose($"[{nameof(StainService)}] Disposed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Penumbra/Services/Wrappers.cs
Normal file
37
Penumbra/Services/Wrappers.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.Interop.Resolver;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
|
||||||
|
{
|
||||||
|
public IdentifierService(StartTracker tracker, DalamudPluginInterface pi, DataManager data)
|
||||||
|
: base(nameof(IdentifierService), tracker, StartTimeType.Identifier, () => GameData.GameData.GetIdentifier(pi, data))
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ItemService : AsyncServiceWrapper<ItemData>
|
||||||
|
{
|
||||||
|
public ItemService(StartTracker tracker, DalamudPluginInterface pi, DataManager gameData)
|
||||||
|
: base(nameof(ItemService), tracker, StartTimeType.Items, () => new ItemData(pi, gameData, gameData.Language))
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
|
||||||
|
{
|
||||||
|
public ActorService(StartTracker tracker, DalamudPluginInterface pi, ObjectTable objects, ClientState clientState,
|
||||||
|
Framework framework, DataManager gameData, GameGui gui, CutsceneCharacters cutscene)
|
||||||
|
: base(nameof(ActorService), tracker, StartTimeType.Actors,
|
||||||
|
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)cutscene.GetParentIndex(idx)))
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.ItemSwap;
|
using Penumbra.Mods.ItemSwap;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
@ -62,29 +63,41 @@ public class ItemSwapWindow : IDisposable
|
||||||
=> type.ToName();
|
=> type.ToName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSwapWindow()
|
private readonly CommunicatorService _communicator;
|
||||||
|
|
||||||
|
public ItemSwapWindow(CommunicatorService communicator)
|
||||||
{
|
{
|
||||||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
_communicator = communicator;
|
||||||
|
_communicator.CollectionChange.Event += OnCollectionChange;
|
||||||
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
_communicator.CollectionChange.Event -= OnCollectionChange;
|
||||||
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)> _selectors = new()
|
private readonly Dictionary<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)> _selectors = new()
|
||||||
{
|
{
|
||||||
[ SwapType.Hat ] = ( new ItemSelector( FullEquipType.Head ), new ItemSelector( FullEquipType.Head ), "Take this Hat", "and put it on this one" ),
|
[SwapType.Hat] =
|
||||||
[ SwapType.Top ] = ( new ItemSelector( FullEquipType.Body ), new ItemSelector( FullEquipType.Body ), "Take this Top", "and put it on this one" ),
|
(new ItemSelector(FullEquipType.Head), new ItemSelector(FullEquipType.Head), "Take this Hat", "and put it on this one"),
|
||||||
[ SwapType.Gloves ] = ( new ItemSelector( FullEquipType.Hands ), new ItemSelector( FullEquipType.Hands ), "Take these Gloves", "and put them on these" ),
|
[SwapType.Top] =
|
||||||
[ SwapType.Pants ] = ( new ItemSelector( FullEquipType.Legs ), new ItemSelector( FullEquipType.Legs ), "Take these Pants", "and put them on these" ),
|
(new ItemSelector(FullEquipType.Body), new ItemSelector(FullEquipType.Body), "Take this Top", "and put it on this one"),
|
||||||
[ SwapType.Shoes ] = ( new ItemSelector( FullEquipType.Feet ), new ItemSelector( FullEquipType.Feet ), "Take these Shoes", "and put them on these" ),
|
[SwapType.Gloves] =
|
||||||
[ SwapType.Earrings ] = ( new ItemSelector( FullEquipType.Ears ), new ItemSelector( FullEquipType.Ears ), "Take these Earrings", "and put them on these" ),
|
(new ItemSelector(FullEquipType.Hands), new ItemSelector(FullEquipType.Hands), "Take these Gloves", "and put them on these"),
|
||||||
[ SwapType.Necklace ] = ( new ItemSelector( FullEquipType.Neck ), new ItemSelector( FullEquipType.Neck ), "Take this Necklace", "and put it on this one" ),
|
[SwapType.Pants] =
|
||||||
[ SwapType.Bracelet ] = ( new ItemSelector( FullEquipType.Wrists ), new ItemSelector( FullEquipType.Wrists ), "Take these Bracelets", "and put them on these" ),
|
(new ItemSelector(FullEquipType.Legs), new ItemSelector(FullEquipType.Legs), "Take these Pants", "and put them on these"),
|
||||||
[ SwapType.Ring ] = ( new ItemSelector( FullEquipType.Finger ), new ItemSelector( FullEquipType.Finger ), "Take this Ring", "and put it on this one" ),
|
[SwapType.Shoes] =
|
||||||
|
(new ItemSelector(FullEquipType.Feet), new ItemSelector(FullEquipType.Feet), "Take these Shoes", "and put them on these"),
|
||||||
|
[SwapType.Earrings] =
|
||||||
|
(new ItemSelector(FullEquipType.Ears), new ItemSelector(FullEquipType.Ears), "Take these Earrings", "and put them on these"),
|
||||||
|
[SwapType.Necklace] =
|
||||||
|
(new ItemSelector(FullEquipType.Neck), new ItemSelector(FullEquipType.Neck), "Take this Necklace", "and put it on this one"),
|
||||||
|
[SwapType.Bracelet] =
|
||||||
|
(new ItemSelector(FullEquipType.Wrists), new ItemSelector(FullEquipType.Wrists), "Take these Bracelets", "and put them on these"),
|
||||||
|
[SwapType.Ring] = (new ItemSelector(FullEquipType.Finger), new ItemSelector(FullEquipType.Finger), "Take this Ring",
|
||||||
|
"and put it on this one"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private ItemSelector? _weaponSource = null;
|
private ItemSelector? _weaponSource = null;
|
||||||
|
|
@ -120,15 +133,11 @@ public class ItemSwapWindow : IDisposable
|
||||||
public void UpdateMod(Mod mod, ModSettings? settings)
|
public void UpdateMod(Mod mod, ModSettings? settings)
|
||||||
{
|
{
|
||||||
if (mod == _mod && settings == _modSettings)
|
if (mod == _mod && settings == _modSettings)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var oldDefaultName = $"{_mod?.Name.Text ?? "Unknown"} (Swapped)";
|
var oldDefaultName = $"{_mod?.Name.Text ?? "Unknown"} (Swapped)";
|
||||||
if (_newModName.Length == 0 || oldDefaultName == _newModName)
|
if (_newModName.Length == 0 || oldDefaultName == _newModName)
|
||||||
{
|
|
||||||
_newModName = $"{mod.Name.Text} (Swapped)";
|
_newModName = $"{mod.Name.Text} (Swapped)";
|
||||||
}
|
|
||||||
|
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
_modSettings = settings;
|
_modSettings = settings;
|
||||||
|
|
@ -140,9 +149,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
private void UpdateState()
|
private void UpdateState()
|
||||||
{
|
{
|
||||||
if (!_dirty)
|
if (!_dirty)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_swapData.Clear();
|
_swapData.Clear();
|
||||||
_loadException = null;
|
_loadException = null;
|
||||||
|
|
@ -178,19 +185,23 @@ public class ItemSwapWindow : IDisposable
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
|
_swapData.LoadCustomization(BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||||
|
(SetId)_targetId,
|
||||||
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization( BodySlot.Face, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
|
_swapData.LoadCustomization(BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||||
|
(SetId)_targetId,
|
||||||
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization( BodySlot.Zear, Names.CombinedRace( _currentGender, ModelRace.Viera ), ( SetId )_sourceId, ( SetId )_targetId,
|
_swapData.LoadCustomization(BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera), (SetId)_sourceId,
|
||||||
|
(SetId)_targetId,
|
||||||
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
|
_swapData.LoadCustomization(BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||||
|
(SetId)_targetId,
|
||||||
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Weapon: break;
|
case SwapType.Weapon: break;
|
||||||
|
|
@ -212,7 +223,8 @@ public class ItemSwapWindow : IDisposable
|
||||||
return swap switch
|
return swap switch
|
||||||
{
|
{
|
||||||
MetaSwap meta => $"{meta.SwapFrom}: {meta.SwapFrom.EntryToString()} -> {meta.SwapApplied.EntryToString()}",
|
MetaSwap meta => $"{meta.SwapFrom}: {meta.SwapFrom.EntryToString()} -> {meta.SwapApplied.EntryToString()}",
|
||||||
FileSwap file => $"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{( file.DataWasChanged ? " (EDITED)" : string.Empty )}",
|
FileSwap file =>
|
||||||
|
$"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{(file.DataWasChanged ? " (EDITED)" : string.Empty)}",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +235,10 @@ public class ItemSwapWindow : IDisposable
|
||||||
private void UpdateOption()
|
private void UpdateOption()
|
||||||
{
|
{
|
||||||
_selectedGroup = _mod?.Groups.FirstOrDefault(g => g.Name == _newGroupName);
|
_selectedGroup = _mod?.Groups.FirstOrDefault(g => g.Name == _newGroupName);
|
||||||
_subModValid = _mod != null && _newGroupName.Length > 0 && _newOptionName.Length > 0 && ( _selectedGroup?.All( o => o.Name != _newOptionName ) ?? true );
|
_subModValid = _mod != null
|
||||||
|
&& _newGroupName.Length > 0
|
||||||
|
&& _newOptionName.Length > 0
|
||||||
|
&& (_selectedGroup?.All(o => o.Name != _newOptionName) ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateMod()
|
private void CreateMod()
|
||||||
|
|
@ -232,18 +247,15 @@ public class ItemSwapWindow : IDisposable
|
||||||
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||||
Mod.Creator.CreateDefaultFiles(newDir);
|
Mod.Creator.CreateDefaultFiles(newDir);
|
||||||
Penumbra.ModManager.AddMod(newDir);
|
Penumbra.ModManager.AddMod(newDir);
|
||||||
if( !_swapData.WriteMod( Penumbra.ModManager.Last(), _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps ) )
|
if (!_swapData.WriteMod(Penumbra.ModManager.Last(),
|
||||||
{
|
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
||||||
Penumbra.ModManager.DeleteMod(Penumbra.ModManager.Count - 1);
|
Penumbra.ModManager.DeleteMod(Penumbra.ModManager.Count - 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateOption()
|
private void CreateOption()
|
||||||
{
|
{
|
||||||
if (_mod == null || !_subModValid)
|
if (_mod == null || !_subModValid)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var groupCreated = false;
|
var groupCreated = false;
|
||||||
var dirCreated = false;
|
var dirCreated = false;
|
||||||
|
|
@ -251,11 +263,11 @@ public class ItemSwapWindow : IDisposable
|
||||||
DirectoryInfo? optionFolderName = null;
|
DirectoryInfo? optionFolderName = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
optionFolderName = Mod.Creator.NewSubFolderName( new DirectoryInfo( Path.Combine( _mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName ) ), _newOptionName );
|
optionFolderName =
|
||||||
|
Mod.Creator.NewSubFolderName(new DirectoryInfo(Path.Combine(_mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName)),
|
||||||
|
_newOptionName);
|
||||||
if (optionFolderName?.Exists == true)
|
if (optionFolderName?.Exists == true)
|
||||||
{
|
|
||||||
throw new Exception($"The folder {optionFolderName.FullName} for the option already exists.");
|
throw new Exception($"The folder {optionFolderName.FullName} for the option already exists.");
|
||||||
}
|
|
||||||
|
|
||||||
if (optionFolderName != null)
|
if (optionFolderName != null)
|
||||||
{
|
{
|
||||||
|
|
@ -270,22 +282,19 @@ public class ItemSwapWindow : IDisposable
|
||||||
optionCreated = true;
|
optionCreated = true;
|
||||||
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
||||||
dirCreated = true;
|
dirCreated = true;
|
||||||
if( !_swapData.WriteMod( _mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps, optionFolderName,
|
if (!_swapData.WriteMod(_mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps,
|
||||||
|
optionFolderName,
|
||||||
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
||||||
{
|
|
||||||
throw new Exception("Failure writing files for mod swap.");
|
throw new Exception("Failure writing files for mod swap.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
ChatUtil.NotificationMessage($"Could not create new Swap Option:\n{e}", "Error", NotificationType.Error);
|
ChatUtil.NotificationMessage($"Could not create new Swap Option:\n{e}", "Error", NotificationType.Error);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (optionCreated && _selectedGroup != null)
|
if (optionCreated && _selectedGroup != null)
|
||||||
{
|
|
||||||
Penumbra.ModManager.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
|
Penumbra.ModManager.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
|
||||||
}
|
|
||||||
|
|
||||||
if (groupCreated)
|
if (groupCreated)
|
||||||
{
|
{
|
||||||
|
|
@ -294,10 +303,8 @@ public class ItemSwapWindow : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirCreated && optionFolderName != null)
|
if (dirCreated && optionFolderName != null)
|
||||||
{
|
|
||||||
Directory.Delete(optionFolderName.FullName, true);
|
Directory.Delete(optionFolderName.FullName, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
|
|
@ -322,9 +329,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
? "Please enter a name for your mod."
|
? "Please enter a name for your mod."
|
||||||
: "Create a new mod of the given name containing only the swap.";
|
: "Create a new mod of the given name containing only the swap.";
|
||||||
if (ImGuiUtil.DrawDisabledButton("Create New Mod", new Vector2(width / 2, 0), tt, !newModAvailable || _newModName.Length == 0))
|
if (ImGuiUtil.DrawDisabledButton("Create New Mod", new Vector2(width / 2, 0), tt, !newModAvailable || _newModName.Length == 0))
|
||||||
{
|
|
||||||
CreateMod();
|
CreateMod();
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
|
||||||
|
|
@ -334,16 +339,12 @@ public class ItemSwapWindow : IDisposable
|
||||||
|
|
||||||
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
|
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
|
||||||
if (ImGui.InputTextWithHint("##groupName", "Group Name...", ref _newGroupName, 32))
|
if (ImGui.InputTextWithHint("##groupName", "Group Name...", ref _newGroupName, 32))
|
||||||
{
|
|
||||||
UpdateOption();
|
UpdateOption();
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
|
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
|
||||||
if (ImGui.InputTextWithHint("##optionName", "New Option Name...", ref _newOptionName, 32))
|
if (ImGui.InputTextWithHint("##optionName", "New Option Name...", ref _newOptionName, 32))
|
||||||
{
|
|
||||||
UpdateOption();
|
UpdateOption();
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
tt = !_subModValid
|
tt = !_subModValid
|
||||||
|
|
@ -352,14 +353,13 @@ public class ItemSwapWindow : IDisposable
|
||||||
? "Create a new option inside this mod containing only the swap."
|
? "Create a new option inside this mod containing only the swap."
|
||||||
: "Create a new option (and possibly Multi-Group) inside the currently selected mod containing the swap.";
|
: "Create a new option (and possibly Multi-Group) inside the currently selected mod containing the swap.";
|
||||||
if (ImGuiUtil.DrawDisabledButton("Create New Option", new Vector2(width / 2, 0), tt, !newModAvailable || !_subModValid))
|
if (ImGuiUtil.DrawDisabledButton("Create New Option", new Vector2(width / 2, 0), tt, !newModAvailable || !_subModValid))
|
||||||
{
|
|
||||||
CreateOption();
|
CreateOption();
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
|
||||||
_dirty |= ImGui.Checkbox("Use Entire Collection", ref _useCurrentCollection);
|
_dirty |= ImGui.Checkbox("Use Entire Collection", ref _useCurrentCollection);
|
||||||
ImGuiUtil.HoverTooltip( "Use all applied mods from the Selected Collection with their current settings and respecting the enabled state of mods and inheritance,\n"
|
ImGuiUtil.HoverTooltip(
|
||||||
|
"Use all applied mods from the Selected Collection with their current settings and respecting the enabled state of mods and inheritance,\n"
|
||||||
+ "instead of using only the selected mod with its current settings in the Selected collection or the default settings, ignoring the enabled state and inheritance.");
|
+ "instead of using only the selected mod with its current settings in the Selected collection or the default settings, ignoring the enabled state and inheritance.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,9 +495,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
using var tab = DrawTab(type);
|
using var tab = DrawTab(type);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var (sourceSelector, targetSelector, text1, text2) = _selectors[type];
|
var (sourceSelector, targetSelector, text1, text2) = _selectors[type];
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
|
|
@ -505,7 +503,8 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(text1);
|
ImGui.TextUnformatted(text1);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_dirty |= sourceSelector.Draw( "##itemSource", sourceSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
_dirty |= sourceSelector.Draw("##itemSource", sourceSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
|
||||||
|
ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
|
||||||
if (type == SwapType.Ring)
|
if (type == SwapType.Ring)
|
||||||
{
|
{
|
||||||
|
|
@ -517,7 +516,8 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(text2);
|
ImGui.TextUnformatted(text2);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_dirty |= targetSelector.Draw( "##itemTarget", targetSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
_dirty |= targetSelector.Draw("##itemTarget", targetSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
|
||||||
|
ImGui.GetTextLineHeightWithSpacing());
|
||||||
if (type == SwapType.Ring)
|
if (type == SwapType.Ring)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -527,22 +527,19 @@ public class ItemSwapWindow : IDisposable
|
||||||
if (_affectedItems is { Length: > 1 })
|
if (_affectedItems is { Length: > 1 })
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGuiUtil.DrawTextButton( $"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, Colors.PressEnterWarningBg );
|
ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero,
|
||||||
|
Colors.PressEnterWarningBg);
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
|
||||||
ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i, targetSelector.CurrentSelection.Item2))
|
ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i, targetSelector.CurrentSelection.Item2))
|
||||||
.Select(i => i.Name.ToDalamudString().TextValue)));
|
.Select(i => i.Name.ToDalamudString().TextValue)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawHairSwap()
|
private void DrawHairSwap()
|
||||||
{
|
{
|
||||||
using var tab = DrawTab(SwapType.Hair);
|
using var tab = DrawTab(SwapType.Hair);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
DrawTargetIdInput("Take this Hairstyle");
|
DrawTargetIdInput("Take this Hairstyle");
|
||||||
|
|
@ -555,9 +552,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
using var tab = DrawTab(SwapType.Face);
|
using var tab = DrawTab(SwapType.Face);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
DrawTargetIdInput("Take this Face Type");
|
DrawTargetIdInput("Take this Face Type");
|
||||||
|
|
@ -569,9 +564,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
using var tab = DrawTab(SwapType.Tail);
|
using var tab = DrawTab(SwapType.Tail);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
DrawTargetIdInput("Take this Tail Type");
|
DrawTargetIdInput("Take this Tail Type");
|
||||||
|
|
@ -584,9 +577,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
using var tab = DrawTab(SwapType.Ears);
|
using var tab = DrawTab(SwapType.Ears);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
DrawTargetIdInput("Take this Ear Type");
|
DrawTargetIdInput("Take this Ear Type");
|
||||||
|
|
@ -600,16 +591,15 @@ public class ItemSwapWindow : IDisposable
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
using var tab = DrawTab(SwapType.Weapon);
|
using var tab = DrawTab(SwapType.Weapon);
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("Select the weapon or tool you want");
|
ImGui.TextUnformatted("Select the weapon or tool you want");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if( _slotSelector.Draw( "##weaponSlot", _slotSelector.CurrentSelection.ToName(), string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() ) )
|
if (_slotSelector.Draw("##weaponSlot", _slotSelector.CurrentSelection.ToName(), string.Empty, InputWidth * 2,
|
||||||
|
ImGui.GetTextLineHeightWithSpacing()))
|
||||||
{
|
{
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
_weaponSource = new ItemSelector(_slotSelector.CurrentSelection);
|
_weaponSource = new ItemSelector(_slotSelector.CurrentSelection);
|
||||||
|
|
@ -626,13 +616,15 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("and put this variant of it");
|
ImGui.TextUnformatted("and put this variant of it");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_dirty |= _weaponSource.Draw( "##weaponSource", _weaponSource.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
_dirty |= _weaponSource.Draw("##weaponSource", _weaponSource.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
|
||||||
|
ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("onto this one");
|
ImGui.TextUnformatted("onto this one");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_dirty |= _weaponTarget.Draw( "##weaponTarget", _weaponTarget.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
_dirty |= _weaponTarget.Draw("##weaponTarget", _weaponTarget.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
|
||||||
|
ImGui.GetTextLineHeightWithSpacing());
|
||||||
}
|
}
|
||||||
|
|
||||||
private const float InputWidth = 120;
|
private const float InputWidth = 120;
|
||||||
|
|
@ -646,9 +638,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.InputInt("##targetId", ref _targetId, 0, 0))
|
if (ImGui.InputInt("##targetId", ref _targetId, 0, 0))
|
||||||
{
|
|
||||||
_targetId = Math.Clamp(_targetId, 0, byte.MaxValue);
|
_targetId = Math.Clamp(_targetId, 0, byte.MaxValue);
|
||||||
}
|
|
||||||
|
|
||||||
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
|
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
|
||||||
}
|
}
|
||||||
|
|
@ -662,9 +652,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.InputInt("##sourceId", ref _sourceId, 0, 0))
|
if (ImGui.InputInt("##sourceId", ref _sourceId, 0, 0))
|
||||||
{
|
|
||||||
_sourceId = Math.Clamp(_sourceId, 0, byte.MaxValue);
|
_sourceId = Math.Clamp(_sourceId, 0, byte.MaxValue);
|
||||||
}
|
|
||||||
|
|
||||||
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
|
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
|
||||||
}
|
}
|
||||||
|
|
@ -686,11 +674,14 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (_currentRace is not ModelRace.Miqote and not ModelRace.AuRa and not ModelRace.Hrothgar)
|
if (_currentRace is not ModelRace.Miqote and not ModelRace.AuRa and not ModelRace.Hrothgar)
|
||||||
{
|
|
||||||
_currentRace = ModelRace.Miqote;
|
_currentRace = ModelRace.Miqote;
|
||||||
}
|
|
||||||
|
|
||||||
_dirty |= ImGuiUtil.GenericEnumCombo( "##Race", InputWidth, _currentRace, out _currentRace, new[] { ModelRace.Miqote, ModelRace.AuRa, ModelRace.Hrothgar },
|
_dirty |= ImGuiUtil.GenericEnumCombo("##Race", InputWidth, _currentRace, out _currentRace, new[]
|
||||||
|
{
|
||||||
|
ModelRace.Miqote,
|
||||||
|
ModelRace.AuRa,
|
||||||
|
ModelRace.Hrothgar,
|
||||||
|
},
|
||||||
RaceEnumExtensions.ToName);
|
RaceEnumExtensions.ToName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -720,9 +711,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
using var tab = ImRaii.TabItem("Item Swap (WIP)");
|
using var tab = ImRaii.TabItem("Item Swap (WIP)");
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawHeaderLine(300 * ImGuiHelpers.GlobalScale);
|
DrawHeaderLine(300 * ImGuiHelpers.GlobalScale);
|
||||||
|
|
@ -732,52 +721,36 @@ public class ItemSwapWindow : IDisposable
|
||||||
|
|
||||||
using var table = ImRaii.ListBox("##swaps", -Vector2.One);
|
using var table = ImRaii.ListBox("##swaps", -Vector2.One);
|
||||||
if (_loadException != null)
|
if (_loadException != null)
|
||||||
{
|
|
||||||
ImGuiUtil.TextWrapped($"Could not load Customization Swap:\n{_loadException}");
|
ImGuiUtil.TextWrapped($"Could not load Customization Swap:\n{_loadException}");
|
||||||
}
|
|
||||||
else if (_swapData.Loaded)
|
else if (_swapData.Loaded)
|
||||||
{
|
|
||||||
foreach (var swap in _swapData.Swaps)
|
foreach (var swap in _swapData.Swaps)
|
||||||
{
|
|
||||||
DrawSwap(swap);
|
DrawSwap(swap);
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(NonExistentText());
|
ImGui.TextUnformatted(NonExistentText());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void DrawSwap(Swap swap)
|
private static void DrawSwap(Swap swap)
|
||||||
{
|
{
|
||||||
var flags = swap.ChildSwaps.Count == 0 ? ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf : ImGuiTreeNodeFlags.DefaultOpen;
|
var flags = swap.ChildSwaps.Count == 0 ? ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf : ImGuiTreeNodeFlags.DefaultOpen;
|
||||||
using var tree = ImRaii.TreeNode(SwapToString(swap), flags);
|
using var tree = ImRaii.TreeNode(SwapToString(swap), flags);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in swap.ChildSwaps)
|
foreach (var child in swap.ChildSwaps)
|
||||||
{
|
|
||||||
DrawSwap(child);
|
DrawSwap(child);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection,
|
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection,
|
||||||
ModCollection? newCollection, string _)
|
ModCollection? newCollection, string _)
|
||||||
{
|
{
|
||||||
if (collectionType != CollectionType.Current || _mod == null || newCollection == null)
|
if (collectionType != CollectionType.Current || _mod == null || newCollection == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.Settings[_mod.Index] : null);
|
UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.Settings[_mod.Index] : null);
|
||||||
newCollection.ModSettingChanged += OnSettingChange;
|
newCollection.ModSettingChanged += OnSettingChange;
|
||||||
if (oldCollection != null)
|
if (oldCollection != null)
|
||||||
{
|
|
||||||
oldCollection.ModSettingChanged -= OnSettingChange;
|
oldCollection.ModSettingChanged -= OnSettingChange;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
|
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
|
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||||
{
|
{
|
||||||
var (dyeId, (name, dyeColor, _)) = Penumbra.StainManager.StainCombo.CurrentSelection;
|
var (dyeId, (name, dyeColor, _)) = Penumbra.StainService.StainCombo.CurrentSelection;
|
||||||
var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled.";
|
var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled.";
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) )
|
if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) )
|
||||||
{
|
{
|
||||||
|
|
@ -106,7 +106,7 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||||
{
|
{
|
||||||
ret |= file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, j, i, dyeId );
|
ret |= file.ApplyDyeTemplate( Penumbra.StainService.StmFile, j, i, dyeId );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
||||||
Penumbra.StainManager.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
Penumbra.StainService.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,10 +355,10 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if( hasDye )
|
if( hasDye )
|
||||||
{
|
{
|
||||||
if( Penumbra.StainManager.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
if( Penumbra.StainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||||
{
|
{
|
||||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainManager.TemplateCombo.CurrentSelection;
|
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainService.TemplateCombo.CurrentSelection;
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,8 +378,8 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||||
{
|
{
|
||||||
var stain = Penumbra.StainManager.StainCombo.CurrentSelection.Key;
|
var stain = Penumbra.StainService.StainCombo.CurrentSelection.Key;
|
||||||
if( stain == 0 || !Penumbra.StainManager.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
if( stain == 0 || !Penumbra.StainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +390,7 @@ public partial class ModEditWindow
|
||||||
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
||||||
"Apply the selected dye to this row.", disabled, true );
|
"Apply the selected dye to this row.", disabled, true );
|
||||||
|
|
||||||
ret = ret && file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, colorSetIdx, rowIdx, stain );
|
ret = ret && file.ApplyDyeTemplate( Penumbra.StainService.StmFile, colorSetIdx, rowIdx, stain );
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Import.Textures;
|
using Penumbra.Import.Textures;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using static Penumbra.Mods.Mod;
|
using static Penumbra.Mods.Mod;
|
||||||
|
|
@ -22,7 +23,7 @@ namespace Penumbra.UI.Classes;
|
||||||
public partial class ModEditWindow : Window, IDisposable
|
public partial class ModEditWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
private const string WindowBaseLabel = "###SubModEdit";
|
private const string WindowBaseLabel = "###SubModEdit";
|
||||||
internal readonly ItemSwapWindow _swapWindow = new();
|
internal readonly ItemSwapWindow _swapWindow;
|
||||||
|
|
||||||
private Editor? _editor;
|
private Editor? _editor;
|
||||||
private Mod? _mod;
|
private Mod? _mod;
|
||||||
|
|
@ -567,9 +568,10 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
return new FullPath( path );
|
return new FullPath( path );
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModEditWindow()
|
public ModEditWindow(CommunicatorService communicator)
|
||||||
: base( WindowBaseLabel )
|
: base( WindowBaseLabel )
|
||||||
{
|
{
|
||||||
|
_swapWindow = new ItemSwapWindow( communicator );
|
||||||
_materialTab = new FileEditor< MtrlTab >( "Materials", ".mtrl",
|
_materialTab = new FileEditor< MtrlTab >( "Materials", ".mtrl",
|
||||||
() => _editor?.MtrlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
() => _editor?.MtrlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
||||||
DrawMaterialPanel,
|
DrawMaterialPanel,
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,16 @@ namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState>
|
public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState>
|
||||||
{
|
{
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly FileDialogManager _fileManager = ConfigWindow.SetupFileManager();
|
private readonly FileDialogManager _fileManager = ConfigWindow.SetupFileManager();
|
||||||
private TexToolsImporter? _import;
|
private TexToolsImporter? _import;
|
||||||
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
|
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
|
||||||
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
|
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
|
||||||
|
|
||||||
public ModFileSystemSelector( ModFileSystem fileSystem )
|
public ModFileSystemSelector(CommunicatorService communicator, ModFileSystem fileSystem)
|
||||||
: base(fileSystem, DalamudServices.KeyState)
|
: base(fileSystem, DalamudServices.KeyState)
|
||||||
{
|
{
|
||||||
|
_communicator = communicator;
|
||||||
SubscribeRightClickFolder(EnableDescendants, 10);
|
SubscribeRightClickFolder(EnableDescendants, 10);
|
||||||
SubscribeRightClickFolder(DisableDescendants, 10);
|
SubscribeRightClickFolder(DisableDescendants, 10);
|
||||||
SubscribeRightClickFolder(InheritDescendants, 15);
|
SubscribeRightClickFolder(InheritDescendants, 15);
|
||||||
|
|
@ -42,7 +44,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
SetFilterTooltip();
|
SetFilterTooltip();
|
||||||
|
|
||||||
SelectionChanged += OnSelectionChange;
|
SelectionChanged += OnSelectionChange;
|
||||||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
_communicator.CollectionChange.Event += OnCollectionChange;
|
||||||
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||||
Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
||||||
Penumbra.ModManager.ModDataChanged += OnModDataChange;
|
Penumbra.ModManager.ModDataChanged += OnModDataChange;
|
||||||
|
|
@ -59,7 +61,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
Penumbra.ModManager.ModDataChanged -= OnModDataChange;
|
Penumbra.ModManager.ModDataChanged -= OnModDataChange;
|
||||||
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||||
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
||||||
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
|
_communicator.CollectionChange.Event -= OnCollectionChange;
|
||||||
_import?.Dispose();
|
_import?.Dispose();
|
||||||
_import = null;
|
_import = null;
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +92,6 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
DrawInfoPopup();
|
DrawInfoPopup();
|
||||||
|
|
||||||
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
|
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
||||||
|
|
@ -103,7 +104,6 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error($"Could not create directory for new Mod {_newModName}:\n{e}");
|
Penumbra.Log.Error($"Could not create directory for new Mod {_newModName}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
while (_modsToAdd.TryDequeue(out var dir))
|
while (_modsToAdd.TryDequeue(out var dir))
|
||||||
{
|
{
|
||||||
|
|
@ -131,42 +131,32 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
private static void EnableDescendants(ModFileSystem.Folder folder)
|
private static void EnableDescendants(ModFileSystem.Folder folder)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Enable Descendants"))
|
if (ImGui.MenuItem("Enable Descendants"))
|
||||||
{
|
|
||||||
SetDescendants(folder, true);
|
SetDescendants(folder, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void DisableDescendants(ModFileSystem.Folder folder)
|
private static void DisableDescendants(ModFileSystem.Folder folder)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Disable Descendants"))
|
if (ImGui.MenuItem("Disable Descendants"))
|
||||||
{
|
|
||||||
SetDescendants(folder, false);
|
SetDescendants(folder, false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void InheritDescendants(ModFileSystem.Folder folder)
|
private static void InheritDescendants(ModFileSystem.Folder folder)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Inherit Descendants"))
|
if (ImGui.MenuItem("Inherit Descendants"))
|
||||||
{
|
|
||||||
SetDescendants(folder, true, true);
|
SetDescendants(folder, true, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void OwnDescendants(ModFileSystem.Folder folder)
|
private static void OwnDescendants(ModFileSystem.Folder folder)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Stop Inheriting Descendants"))
|
if (ImGui.MenuItem("Stop Inheriting Descendants"))
|
||||||
{
|
|
||||||
SetDescendants(folder, false, true);
|
SetDescendants(folder, false, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void ToggleLeafFavorite(FileSystem<Mod>.Leaf mod)
|
private static void ToggleLeafFavorite(FileSystem<Mod>.Leaf mod)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite"))
|
if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite"))
|
||||||
{
|
|
||||||
Penumbra.ModManager.ChangeModFavorite(mod.Value.Index, !mod.Value.Favorite);
|
Penumbra.ModManager.ChangeModFavorite(mod.Value.Index, !mod.Value.Favorite);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetDefaultImportFolder(ModFileSystem.Folder folder)
|
private static void SetDefaultImportFolder(ModFileSystem.Folder folder)
|
||||||
{
|
{
|
||||||
|
|
@ -198,10 +188,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
{
|
{
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.",
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.",
|
||||||
!Penumbra.ModManager.Valid, true))
|
!Penumbra.ModManager.Valid, true))
|
||||||
{
|
|
||||||
ImGui.OpenPopup("Create New Mod");
|
ImGui.OpenPopup("Create New Mod");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add an import mods button that opens a file selector.
|
// Add an import mods button that opens a file selector.
|
||||||
// Only set the initial directory once.
|
// Only set the initial directory once.
|
||||||
|
|
@ -213,9 +201,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
"Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !Penumbra.ModManager.Valid, true);
|
"Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !Penumbra.ModManager.Valid, true);
|
||||||
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.ModImport);
|
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.ModImport);
|
||||||
if (!button)
|
if (!button)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var modPath = _hasSetFolder && !Penumbra.Config.AlwaysOpenDefaultImport ? null
|
var modPath = _hasSetFolder && !Penumbra.Config.AlwaysOpenDefaultImport ? null
|
||||||
: Penumbra.Config.DefaultModImportPath.Length > 0 ? Penumbra.Config.DefaultModImportPath
|
: Penumbra.Config.DefaultModImportPath.Length > 0 ? Penumbra.Config.DefaultModImportPath
|
||||||
|
|
@ -245,17 +231,13 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
ImGui.SetNextWindowSize(size);
|
ImGui.SetNextWindowSize(size);
|
||||||
using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal);
|
using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal);
|
||||||
if (_import == null || !popup.Success)
|
if (_import == null || !popup.Success)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2)))
|
using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2)))
|
||||||
{
|
{
|
||||||
if (child)
|
if (child)
|
||||||
{
|
|
||||||
_import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight()));
|
_import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (_import.State == ImporterState.Done && ImGui.Button("Close", -Vector2.UnitX)
|
if (_import.State == ImporterState.Done && ImGui.Button("Close", -Vector2.UnitX)
|
||||||
|| _import.State != ImporterState.Done && _import.DrawCancelButton(-Vector2.UnitX))
|
|| _import.State != ImporterState.Done && _import.DrawCancelButton(-Vector2.UnitX))
|
||||||
|
|
@ -276,7 +258,6 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
if (error != null)
|
if (error != null)
|
||||||
{
|
{
|
||||||
if (dir != null && Directory.Exists(dir.FullName))
|
if (dir != null && Directory.Exists(dir.FullName))
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.Delete(dir.FullName, true);
|
Directory.Delete(dir.FullName, true);
|
||||||
|
|
@ -285,13 +266,10 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error($"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}");
|
Penumbra.Log.Error($"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (error is not OperationCanceledException)
|
if (error is not OperationCanceledException)
|
||||||
{
|
|
||||||
Penumbra.Log.Error($"Error extracting {file.FullName}, mod skipped:\n{error}");
|
Penumbra.Log.Error($"Error extracting {file.FullName}, mod skipped:\n{error}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (dir != null)
|
else if (dir != null)
|
||||||
{
|
{
|
||||||
_modsToAdd.Enqueue(dir);
|
_modsToAdd.Enqueue(dir);
|
||||||
|
|
@ -306,23 +284,17 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
: "Delete the currently selected mod entirely from your drive.\n"
|
: "Delete the currently selected mod entirely from your drive.\n"
|
||||||
+ "This can not be undone.";
|
+ "This can not be undone.";
|
||||||
if (!keys)
|
if (!keys)
|
||||||
{
|
|
||||||
tt += $"\nHold {Penumbra.Config.DeleteModModifier} while clicking to delete the mod.";
|
tt += $"\nHold {Penumbra.Config.DeleteModModifier} while clicking to delete the mod.";
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
||||||
&& Selected != null)
|
&& Selected != null)
|
||||||
{
|
|
||||||
Penumbra.ModManager.DeleteMod(Selected.Index);
|
Penumbra.ModManager.DeleteMod(Selected.Index);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddHelpButton(Vector2 size)
|
private static void AddHelpButton(Vector2 size)
|
||||||
{
|
{
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.QuestionCircle.ToIconString(), size, "Open extended help.", false, true))
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.QuestionCircle.ToIconString(), size, "Open extended help.", false, true))
|
||||||
{
|
|
||||||
ImGui.OpenPopup("ExtendedHelp");
|
ImGui.OpenPopup("ExtendedHelp");
|
||||||
}
|
|
||||||
|
|
||||||
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.AdvancedHelp);
|
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.AdvancedHelp);
|
||||||
}
|
}
|
||||||
|
|
@ -338,14 +310,10 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
});
|
});
|
||||||
|
|
||||||
if (inherit)
|
if (inherit)
|
||||||
{
|
|
||||||
Penumbra.CollectionManager.Current.SetMultipleModInheritances(mods, enabled);
|
Penumbra.CollectionManager.Current.SetMultipleModInheritances(mods, enabled);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
Penumbra.CollectionManager.Current.SetMultipleModStates(mods, enabled);
|
Penumbra.CollectionManager.Current.SetMultipleModStates(mods, enabled);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Automatic cache update functions.
|
// Automatic cache update functions.
|
||||||
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
|
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
|
||||||
|
|
@ -353,10 +321,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
// TODO: maybe make more efficient
|
// TODO: maybe make more efficient
|
||||||
SetFilterDirty();
|
SetFilterDirty();
|
||||||
if (modIdx == Selected?.Index)
|
if (modIdx == Selected?.Index)
|
||||||
{
|
|
||||||
OnSelectionChange(Selected, Selected, default);
|
OnSelectionChange(Selected, Selected, default);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName)
|
private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName)
|
||||||
{
|
{
|
||||||
|
|
@ -381,9 +347,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _)
|
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _)
|
||||||
{
|
{
|
||||||
if (collectionType != CollectionType.Current || oldCollection == newCollection)
|
if (collectionType != CollectionType.Current || oldCollection == newCollection)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCollection != null)
|
if (oldCollection != null)
|
||||||
{
|
{
|
||||||
|
|
@ -440,18 +404,14 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
private void MoveModToDefaultDirectory(Mod mod)
|
private void MoveModToDefaultDirectory(Mod mod)
|
||||||
{
|
{
|
||||||
if (Penumbra.Config.DefaultImportFolder.Length == 0)
|
if (Penumbra.Config.DefaultImportFolder.Length == 0)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var leaf = FileSystem.Root.GetChildren(ISortMode<Mod>.Lexicographical)
|
var leaf = FileSystem.Root.GetChildren(ISortMode<Mod>.Lexicographical)
|
||||||
.FirstOrDefault(f => f is FileSystem<Mod>.Leaf l && l.Value == mod);
|
.FirstOrDefault(f => f is FileSystem<Mod>.Leaf l && l.Value == mod);
|
||||||
if (leaf == null)
|
if (leaf == null)
|
||||||
{
|
|
||||||
throw new Exception("Mod was not found at root.");
|
throw new Exception("Mod was not found at root.");
|
||||||
}
|
|
||||||
|
|
||||||
var folder = FileSystem.FindOrCreateAllFolders(Penumbra.Config.DefaultImportFolder);
|
var folder = FileSystem.FindOrCreateAllFolders(Penumbra.Config.DefaultImportFolder);
|
||||||
FileSystem.Move(leaf, folder);
|
FileSystem.Move(leaf, folder);
|
||||||
|
|
@ -472,7 +432,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
ImGui.BulletText("You can create empty mods or import mods with the buttons in this row.");
|
ImGui.BulletText("You can create empty mods or import mods with the buttons in this row.");
|
||||||
using var indent = ImRaii.PushIndent();
|
using var indent = ImRaii.PushIndent();
|
||||||
ImGui.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp.");
|
ImGui.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp.");
|
||||||
ImGui.BulletText( "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata." );
|
ImGui.BulletText(
|
||||||
|
"You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata.");
|
||||||
indent.Pop(1);
|
indent.Pop(1);
|
||||||
ImGui.BulletText("You can also create empty mod folders and delete mods.");
|
ImGui.BulletText("You can also create empty mod folders and delete mods.");
|
||||||
ImGui.BulletText("For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup.");
|
ImGui.BulletText("For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup.");
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
@ -16,20 +17,22 @@ public partial class ConfigWindow
|
||||||
// Encapsulate for less pollution.
|
// Encapsulate for less pollution.
|
||||||
private partial class CollectionsTab : IDisposable, ITab
|
private partial class CollectionsTab : IDisposable, ITab
|
||||||
{
|
{
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly ConfigWindow _window;
|
private readonly ConfigWindow _window;
|
||||||
|
|
||||||
public CollectionsTab( ConfigWindow window )
|
public CollectionsTab( CommunicatorService communicator, ConfigWindow window )
|
||||||
{
|
{
|
||||||
_window = window;
|
_window = window;
|
||||||
|
_communicator = communicator;
|
||||||
|
|
||||||
Penumbra.CollectionManager.CollectionChanged += UpdateIdentifiers;
|
_communicator.CollectionChange.Event += UpdateIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Collections"u8;
|
=> "Collections"u8;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> Penumbra.CollectionManager.CollectionChanged -= UpdateIdentifiers;
|
=> _communicator.CollectionChange.Event -= UpdateIdentifiers;
|
||||||
|
|
||||||
public void DrawHeader()
|
public void DrawHeader()
|
||||||
=> OpenTutorial( BasicTutorialSteps.Collections );
|
=> OpenTutorial( BasicTutorialSteps.Collections );
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
|
@ -28,10 +29,14 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
private class DebugTab : ITab
|
private class DebugTab : ITab
|
||||||
{
|
{
|
||||||
|
private readonly StartTracker _timer;
|
||||||
private readonly ConfigWindow _window;
|
private readonly ConfigWindow _window;
|
||||||
|
|
||||||
public DebugTab( ConfigWindow window )
|
public DebugTab( ConfigWindow window, StartTracker timer)
|
||||||
=> _window = window;
|
{
|
||||||
|
_window = window;
|
||||||
|
_timer = timer;
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Debug"u8;
|
=> "Debug"u8;
|
||||||
|
|
@ -109,7 +114,7 @@ public partial class ConfigWindow
|
||||||
PrintValue( "Web Server Enabled", _window._penumbra.HttpApi.Enabled.ToString() );
|
PrintValue( "Web Server Enabled", _window._penumbra.HttpApi.Enabled.ToString() );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawPerformanceTab()
|
private void DrawPerformanceTab()
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if( ImGui.CollapsingHeader( "Performance" ) )
|
if( ImGui.CollapsingHeader( "Performance" ) )
|
||||||
|
|
@ -121,7 +126,7 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
if( start )
|
if( start )
|
||||||
{
|
{
|
||||||
Penumbra.StartTimer.Draw( "##startTimer", TimingExtensions.ToName );
|
_timer.Draw( "##startTimer", TimingExtensions.ToName );
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -397,7 +402,7 @@ public partial class ConfigWindow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var (key, data) in Penumbra.StainManager.StmFile.Entries )
|
foreach( var (key, data) in Penumbra.StainService.StmFile.Entries )
|
||||||
{
|
{
|
||||||
using var tree = TreeNode( $"Template {key}" );
|
using var tree = TreeNode( $"Template {key}" );
|
||||||
if( !tree )
|
if( !tree )
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ public partial class ConfigWindow
|
||||||
|
|
||||||
private static void DrawWaitForPluginsReflection()
|
private static void DrawWaitForPluginsReflection()
|
||||||
{
|
{
|
||||||
if( !DalamudServices.GetDalamudConfig( DalamudServices.WaitingForPluginsOption, out bool value ) )
|
if( !Penumbra.Dalamud.GetDalamudConfig( DalamudServices.WaitingForPluginsOption, out bool value ) )
|
||||||
{
|
{
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
Checkbox( "Wait for Plugins on Startup (Disabled, can not access Dalamud Configuration)", string.Empty, false, v => { } );
|
Checkbox( "Wait for Plugins on Startup (Disabled, can not access Dalamud Configuration)", string.Empty, false, v => { } );
|
||||||
|
|
@ -113,7 +113,7 @@ public partial class ConfigWindow
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Checkbox( "Wait for Plugins on Startup", "This changes a setting in the Dalamud Configuration found at /xlsettings -> General.", value,
|
Checkbox( "Wait for Plugins on Startup", "This changes a setting in the Dalamud Configuration found at /xlsettings -> General.", value,
|
||||||
v => DalamudServices.SetDalamudConfig( DalamudServices.WaitingForPluginsOption, v, "doWaitForPluginsOnStartup" ) );
|
v => Penumbra.Dalamud.SetDalamudConfig( DalamudServices.WaitingForPluginsOption, v, "doWaitForPluginsOnStartup" ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,11 +299,11 @@ public partial class ConfigWindow
|
||||||
|
|
||||||
private const string SupportInfoButtonText = "Copy Support Info to Clipboard";
|
private const string SupportInfoButtonText = "Copy Support Info to Clipboard";
|
||||||
|
|
||||||
public static void DrawSupportButton()
|
public static void DrawSupportButton(Penumbra penumbra)
|
||||||
{
|
{
|
||||||
if( ImGui.Button( SupportInfoButtonText ) )
|
if( ImGui.Button( SupportInfoButtonText ) )
|
||||||
{
|
{
|
||||||
var text = Penumbra.GatherSupportInformation();
|
var text = penumbra.GatherSupportInformation();
|
||||||
ImGui.SetClipboardText( text );
|
ImGui.SetClipboardText( text );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -345,7 +345,7 @@ public partial class ConfigWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetCursorPos( new Vector2( xPos, ImGui.GetFrameHeightWithSpacing() ) );
|
ImGui.SetCursorPos( new Vector2( xPos, ImGui.GetFrameHeightWithSpacing() ) );
|
||||||
DrawSupportButton();
|
DrawSupportButton(_window._penumbra);
|
||||||
|
|
||||||
ImGui.SetCursorPos( new Vector2( xPos, 0 ) );
|
ImGui.SetCursorPos( new Vector2( xPos, 0 ) );
|
||||||
DrawDiscordButton( width );
|
DrawDiscordButton( width );
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
private readonly Penumbra _penumbra;
|
private readonly Penumbra _penumbra;
|
||||||
private readonly ModFileSystemSelector _selector;
|
private readonly ModFileSystemSelector _selector;
|
||||||
private readonly ModPanel _modPanel;
|
private readonly ModPanel _modPanel;
|
||||||
public readonly ModEditWindow ModEditPopup = new();
|
public readonly ModEditWindow ModEditPopup;
|
||||||
|
|
||||||
private readonly SettingsTab _settingsTab;
|
private readonly SettingsTab _settingsTab;
|
||||||
private readonly CollectionsTab _collectionsTab;
|
private readonly CollectionsTab _collectionsTab;
|
||||||
|
|
@ -31,29 +31,29 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
private readonly ResourceWatcher _resourceWatcher;
|
private readonly ResourceWatcher _resourceWatcher;
|
||||||
|
|
||||||
public TabType SelectTab = TabType.None;
|
public TabType SelectTab = TabType.None;
|
||||||
|
|
||||||
public void SelectMod(Mod mod)
|
public void SelectMod(Mod mod)
|
||||||
=> _selector.SelectByValue(mod);
|
=> _selector.SelectByValue(mod);
|
||||||
|
|
||||||
public ConfigWindow( Penumbra penumbra, ResourceWatcher watcher )
|
public ConfigWindow(CommunicatorService communicator, StartTracker timer, Penumbra penumbra, ResourceWatcher watcher)
|
||||||
: base(GetLabel())
|
: base(GetLabel())
|
||||||
{
|
{
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_resourceWatcher = watcher;
|
_resourceWatcher = watcher;
|
||||||
|
|
||||||
|
ModEditPopup = new ModEditWindow(communicator);
|
||||||
_settingsTab = new SettingsTab(this);
|
_settingsTab = new SettingsTab(this);
|
||||||
_selector = new ModFileSystemSelector( _penumbra.ModFileSystem );
|
_selector = new ModFileSystemSelector(communicator, _penumbra.ModFileSystem);
|
||||||
_modPanel = new ModPanel(this);
|
_modPanel = new ModPanel(this);
|
||||||
_modsTab = new ModsTab(_selector, _modPanel, _penumbra);
|
_modsTab = new ModsTab(_selector, _modPanel, _penumbra);
|
||||||
_selector.SelectionChanged += _modPanel.OnSelectionChange;
|
_selector.SelectionChanged += _modPanel.OnSelectionChange;
|
||||||
_collectionsTab = new CollectionsTab( this );
|
_collectionsTab = new CollectionsTab(communicator, this);
|
||||||
_changedItemsTab = new ChangedItemsTab(this);
|
_changedItemsTab = new ChangedItemsTab(this);
|
||||||
_effectiveTab = new EffectiveTab();
|
_effectiveTab = new EffectiveTab();
|
||||||
_debugTab = new DebugTab( this );
|
_debugTab = new DebugTab(this, timer);
|
||||||
_resourceTab = new ResourceTab();
|
_resourceTab = new ResourceTab();
|
||||||
if (Penumbra.Config.FixMainWindow)
|
if (Penumbra.Config.FixMainWindow)
|
||||||
{
|
|
||||||
Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
|
Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
|
||||||
}
|
|
||||||
|
|
||||||
DalamudServices.PluginInterface.UiBuilder.DisableGposeUiHide = !Penumbra.Config.HideUiInGPose;
|
DalamudServices.PluginInterface.UiBuilder.DisableGposeUiHide = !Penumbra.Config.HideUiInGPose;
|
||||||
DalamudServices.PluginInterface.UiBuilder.DisableCutsceneUiHide = !Penumbra.Config.HideUiInCutscenes;
|
DalamudServices.PluginInterface.UiBuilder.DisableCutsceneUiHide = !Penumbra.Config.HideUiInCutscenes;
|
||||||
|
|
@ -89,21 +89,22 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
if (Penumbra.ValidityChecker.ImcExceptions.Count > 0)
|
if (Penumbra.ValidityChecker.ImcExceptions.Count > 0)
|
||||||
{
|
{
|
||||||
DrawProblemWindow( $"There were {Penumbra.ValidityChecker.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n"
|
DrawProblemWindow(_penumbra,
|
||||||
|
$"There were {Penumbra.ValidityChecker.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n"
|
||||||
+ "This usually means that your game installation was corrupted by updating the game while having TexTools mods still active.\n"
|
+ "This usually means that your game installation was corrupted by updating the game while having TexTools mods still active.\n"
|
||||||
+ "It is recommended to not use TexTools and Penumbra (or other Lumina-based tools) at the same time.\n\n"
|
+ "It is recommended to not use TexTools and Penumbra (or other Lumina-based tools) at the same time.\n\n"
|
||||||
+ "Please use the Launcher's Repair Game Files function to repair your client installation.", true);
|
+ "Please use the Launcher's Repair Game Files function to repair your client installation.", true);
|
||||||
}
|
}
|
||||||
else if (!Penumbra.ValidityChecker.IsValidSourceRepo)
|
else if (!Penumbra.ValidityChecker.IsValidSourceRepo)
|
||||||
{
|
{
|
||||||
DrawProblemWindow(
|
DrawProblemWindow(_penumbra,
|
||||||
$"You are loading a release version of Penumbra from the repository \"{DalamudServices.PluginInterface.SourceRepository}\" instead of the official repository.\n"
|
$"You are loading a release version of Penumbra from the repository \"{DalamudServices.PluginInterface.SourceRepository}\" instead of the official repository.\n"
|
||||||
+ $"Please use the official repository at {ValidityChecker.Repository}.\n\n"
|
+ $"Please use the official repository at {ValidityChecker.Repository}.\n\n"
|
||||||
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it.", false);
|
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it.", false);
|
||||||
}
|
}
|
||||||
else if (Penumbra.ValidityChecker.IsNotInstalledPenumbra)
|
else if (Penumbra.ValidityChecker.IsNotInstalledPenumbra)
|
||||||
{
|
{
|
||||||
DrawProblemWindow(
|
DrawProblemWindow(_penumbra,
|
||||||
$"You are loading a release version of Penumbra from \"{DalamudServices.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n"
|
$"You are loading a release version of Penumbra from \"{DalamudServices.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n"
|
||||||
+ "You should not install Penumbra manually, but rather add the plugin repository under settings and then install it via the plugin installer.\n\n"
|
+ "You should not install Penumbra manually, but rather add the plugin repository under settings and then install it via the plugin installer.\n\n"
|
||||||
+ "If you do not know how to do this, please take a look at the readme in Penumbras github repository or join us in discord.\n"
|
+ "If you do not know how to do this, please take a look at the readme in Penumbras github repository or join us in discord.\n"
|
||||||
|
|
@ -111,29 +112,28 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
else if (Penumbra.ValidityChecker.DevPenumbraExists)
|
else if (Penumbra.ValidityChecker.DevPenumbraExists)
|
||||||
{
|
{
|
||||||
DrawProblemWindow(
|
DrawProblemWindow(_penumbra,
|
||||||
$"You are loading a installed version of Penumbra from \"{DalamudServices.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", "
|
$"You are loading a installed version of Penumbra from \"{DalamudServices.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", "
|
||||||
+ "but also still have some remnants of a custom install of Penumbra in your devPlugins folder.\n\n"
|
+ "but also still have some remnants of a custom install of Penumbra in your devPlugins folder.\n\n"
|
||||||
+ "This can cause some issues, so please go to your \"%%appdata%%\\XIVLauncher\\devPlugins\" folder and delete the Penumbra folder from there.\n\n"
|
+ "This can cause some issues, so please go to your \"%%appdata%%\\XIVLauncher\\devPlugins\" folder and delete the Penumbra folder from there.\n\n"
|
||||||
+ "If you are developing for Penumbra, try to avoid mixing versions. This warning will not appear if compiled in Debug mode.", false );
|
+ "If you are developing for Penumbra, try to avoid mixing versions. This warning will not appear if compiled in Debug mode.",
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetupSizes();
|
SetupSizes();
|
||||||
if (TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ToLabel(SelectTab), _settingsTab, _modsTab, _collectionsTab,
|
if (TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ToLabel(SelectTab), _settingsTab, _modsTab, _collectionsTab,
|
||||||
_changedItemsTab, _effectiveTab, _resourceWatcher, _debugTab, _resourceTab))
|
_changedItemsTab, _effectiveTab, _resourceWatcher, _debugTab, _resourceTab))
|
||||||
{
|
|
||||||
SelectTab = TabType.None;
|
SelectTab = TabType.None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error($"Exception thrown during UI Render:\n{e}");
|
Penumbra.Log.Error($"Exception thrown during UI Render:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawProblemWindow( string text, bool withExceptions )
|
private static void DrawProblemWindow(Penumbra penumbra, string text, bool withExceptions)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
|
@ -145,7 +145,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
SettingsTab.DrawDiscordButton(0);
|
SettingsTab.DrawDiscordButton(0);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
SettingsTab.DrawSupportButton();
|
SettingsTab.DrawSupportButton(penumbra);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
|
||||||
|
|
|
||||||
329
Penumbra/Util/EventWrapper.cs
Normal file
329
Penumbra/Util/EventWrapper.cs
Normal file
|
|
@ -0,0 +1,329 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Penumbra.Util;
|
||||||
|
|
||||||
|
public readonly struct EventWrapper : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventWrapper<T1, T2> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action<T1, T2>> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke(T1 arg1, T2 arg2)
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke(arg1, arg2);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<T1, T2> Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventWrapper<T1, T2, T3> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action<T1, T2, T3>> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke(T1 arg1, T2 arg2, T3 arg3)
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke(arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<T1, T2, T3> Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventWrapper<T1, T2, T3, T4> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action<T1, T2, T3, T4>> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke(arg1, arg2, arg3, arg4);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<T1, T2, T3, T4> Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventWrapper<T1, T2, T3, T4, T5> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action<T1, T2, T3, T4, T5>> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke(arg1, arg2, arg3, arg4, arg5);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<T1, T2, T3, T4, T5> Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EventWrapper<T1, T2, T3, T4, T5, T6> : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly List<Action<T1, T2, T3, T4, T5, T6>> _event = new();
|
||||||
|
|
||||||
|
public EventWrapper(string name)
|
||||||
|
=> _name = name;
|
||||||
|
|
||||||
|
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
foreach (var action in _event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke(arg1, arg2, arg3, arg4, arg5, arg6);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[{_name}] Exception thrown during invocation:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<T1, T2, T3, T4, T5, T6> Event
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
if (_event.All(a => a != value))
|
||||||
|
_event.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_event)
|
||||||
|
{
|
||||||
|
_event.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
global using StartTracker = OtterGui.Classes.StartTimeTracker<Penumbra.Util.StartTimeType>;
|
||||||
|
global using PerformanceTracker = OtterGui.Classes.PerformanceTracker<Penumbra.Util.PerformanceType>;
|
||||||
|
|
||||||
namespace Penumbra.Util;
|
namespace Penumbra.Util;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using OtterGui.Widgets;
|
|
||||||
using Penumbra.GameData.Data;
|
|
||||||
using Penumbra.GameData.Files;
|
|
||||||
|
|
||||||
namespace Penumbra.Util;
|
|
||||||
|
|
||||||
public class StainManager : IDisposable
|
|
||||||
{
|
|
||||||
public sealed class StainTemplateCombo : FilterComboCache< ushort >
|
|
||||||
{
|
|
||||||
public StainTemplateCombo( IEnumerable< ushort > items )
|
|
||||||
: base( items )
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly StainData StainData;
|
|
||||||
public readonly FilterComboColors StainCombo;
|
|
||||||
public readonly StmFile StmFile;
|
|
||||||
public readonly StainTemplateCombo TemplateCombo;
|
|
||||||
|
|
||||||
public StainManager( DalamudPluginInterface pluginInterface, DataManager dataManager )
|
|
||||||
{
|
|
||||||
StainData = new StainData( pluginInterface, dataManager, dataManager.Language );
|
|
||||||
StainCombo = new FilterComboColors( 140, StainData.Data.Prepend( new KeyValuePair< byte, (string Name, uint Dye, bool Gloss) >( 0, ( "None", 0, false ) ) ) );
|
|
||||||
StmFile = new StmFile( dataManager );
|
|
||||||
TemplateCombo = new StainTemplateCombo( StmFile.Entries.Keys.Prepend( ( ushort )0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
=> StainData.Dispose();
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue