mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Untangling the mods.
This commit is contained in:
parent
1d82e882ed
commit
4972dd1c9f
39 changed files with 883 additions and 935 deletions
|
|
@ -26,10 +26,10 @@ public class HttpApi : IDisposable
|
|||
private readonly IPenumbraApi _api;
|
||||
private WebServer? _server;
|
||||
|
||||
public HttpApi(IPenumbraApi api)
|
||||
public HttpApi(Configuration config, IPenumbraApi api)
|
||||
{
|
||||
_api = api;
|
||||
if (Penumbra.Config.EnableHttpApi)
|
||||
if (config.EnableHttpApi)
|
||||
CreateWebServer();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using Penumbra.Mods.Manager;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -39,7 +40,8 @@ public class IpcTester : IDisposable
|
|||
private readonly ModSettings _modSettings;
|
||||
private readonly Temporary _temporary;
|
||||
|
||||
public IpcTester(DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders, ModManager modManager)
|
||||
public IpcTester(DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders, ModManager modManager, CollectionManager collections,
|
||||
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService)
|
||||
{
|
||||
_ipcProviders = ipcProviders;
|
||||
_pluginState = new PluginState(pi);
|
||||
|
|
@ -52,7 +54,7 @@ public class IpcTester : IDisposable
|
|||
_meta = new Meta(pi);
|
||||
_mods = new Mods(pi);
|
||||
_modSettings = new ModSettings(pi);
|
||||
_temporary = new Temporary(pi, modManager);
|
||||
_temporary = new Temporary(pi, modManager, collections, tempMods, tempCollections, saveService);
|
||||
UnsubscribeEvents();
|
||||
}
|
||||
|
||||
|
|
@ -1151,11 +1153,20 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly CollectionManager _collections;
|
||||
private readonly TempModManager _tempMods;
|
||||
private readonly TempCollectionManager _tempCollections;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public Temporary(DalamudPluginInterface pi, ModManager modManager)
|
||||
public Temporary(DalamudPluginInterface pi, ModManager modManager, CollectionManager collections, TempModManager tempMods,
|
||||
TempCollectionManager tempCollections, SaveService saveService)
|
||||
{
|
||||
_pi = pi;
|
||||
_modManager = modManager;
|
||||
_pi = pi;
|
||||
_modManager = modManager;
|
||||
_collections = collections;
|
||||
_tempMods = tempMods;
|
||||
_tempCollections = tempCollections;
|
||||
_saveService = saveService;
|
||||
}
|
||||
|
||||
public string LastCreatedCollectionName = string.Empty;
|
||||
|
|
@ -1223,7 +1234,7 @@ public class IpcTester : IDisposable
|
|||
DrawIntro(Ipc.CreateTemporaryCollection.Label, "Copy Existing Collection");
|
||||
if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
|
||||
"Copies the effective list from the collection named in Temporary Mod Name...",
|
||||
!Penumbra.CollectionManager.Storage.ByName(_tempModName, out var copyCollection))
|
||||
!_collections.Storage.ByName(_tempModName, out var copyCollection))
|
||||
&& copyCollection is { HasCache: true })
|
||||
{
|
||||
var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString());
|
||||
|
|
@ -1249,7 +1260,7 @@ public class IpcTester : IDisposable
|
|||
|
||||
public void DrawCollections()
|
||||
{
|
||||
using var collTree = ImRaii.TreeNode("Collections##TempCollections");
|
||||
using var collTree = ImRaii.TreeNode("Temporary Collections##TempCollections");
|
||||
if (!collTree)
|
||||
return;
|
||||
|
||||
|
|
@ -1257,26 +1268,26 @@ public class IpcTester : IDisposable
|
|||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var collection in Penumbra.TempCollections.Values)
|
||||
foreach (var collection in _tempCollections.Values)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var character = Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
|
||||
var character = _tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
|
||||
.FirstOrDefault()
|
||||
?? "Unknown";
|
||||
if (ImGui.Button($"Save##{collection.Name}"))
|
||||
TemporaryMod.SaveTempCollection(_modManager, collection, character);
|
||||
TemporaryMod.SaveTempCollection(_saveService, _modManager, collection, character);
|
||||
|
||||
ImGuiUtil.DrawTableColumn(collection.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
|
||||
ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ",
|
||||
Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
|
||||
_tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawMods()
|
||||
{
|
||||
using var modTree = ImRaii.TreeNode("Mods##TempMods");
|
||||
using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods");
|
||||
if (!modTree)
|
||||
return;
|
||||
|
||||
|
|
@ -1314,8 +1325,8 @@ public class IpcTester : IDisposable
|
|||
|
||||
if (table)
|
||||
{
|
||||
PrintList("All", Penumbra.TempMods.ModsForAllCollections);
|
||||
foreach (var (collection, list) in Penumbra.TempMods.Mods)
|
||||
PrintList("All", _tempMods.ModsForAllCollections);
|
||||
foreach (var (collection, list) in _tempMods.Mods)
|
||||
PrintList(collection.Name, list);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -114,7 +115,8 @@ public class PenumbraIpcProviders : IDisposable
|
|||
internal readonly FuncProvider<string, int, PenumbraApiEc> RemoveTemporaryModAll;
|
||||
internal readonly FuncProvider<string, string, int, PenumbraApiEc> RemoveTemporaryMod;
|
||||
|
||||
public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager)
|
||||
public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager, CollectionManager collections,
|
||||
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService)
|
||||
{
|
||||
Api = api;
|
||||
|
||||
|
|
@ -226,7 +228,7 @@ public class PenumbraIpcProviders : IDisposable
|
|||
RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider(pi, Api.RemoveTemporaryModAll);
|
||||
RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider(pi, Api.RemoveTemporaryMod);
|
||||
|
||||
Tester = new IpcTester(pi, this, modManager);
|
||||
Tester = new IpcTester(pi, this, modManager, collections, tempMods, tempCollections, saveService);
|
||||
|
||||
Initialized.Invoke();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class CollectionCache : IDisposable
|
|||
private readonly ModCollection _collection;
|
||||
public readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new();
|
||||
public readonly Dictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
|
||||
public readonly MetaCache MetaManipulations;
|
||||
public readonly MetaCache Meta;
|
||||
public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
|
||||
|
||||
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
|
||||
|
|
@ -50,18 +50,18 @@ public class CollectionCache : IDisposable
|
|||
// The cache reacts through events on its collection changing.
|
||||
public CollectionCache(CollectionCacheManager manager, ModCollection collection)
|
||||
{
|
||||
_manager = manager;
|
||||
_collection = collection;
|
||||
MetaManipulations = new MetaCache(manager.MetaFileManager, _collection);
|
||||
_manager = manager;
|
||||
_collection = collection;
|
||||
Meta = new MetaCache(manager.MetaFileManager, _collection);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MetaManipulations.Dispose();
|
||||
Meta.Dispose();
|
||||
}
|
||||
|
||||
~CollectionCache()
|
||||
=> MetaManipulations.Dispose();
|
||||
=> Meta.Dispose();
|
||||
|
||||
// Resolve a given game path according to this collection.
|
||||
public FullPath? ResolvePath(Utf8GamePath gameResourcePath)
|
||||
|
|
@ -119,16 +119,16 @@ public class CollectionCache : IDisposable
|
|||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
|
||||
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
|
||||
internal void ForceFile(Utf8GamePath path, FullPath fullPath)
|
||||
{
|
||||
if (CheckFullPath(path, fullPath))
|
||||
ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath);
|
||||
}
|
||||
|
||||
/// <summary> Force a file resolve to be removed. </summary>
|
||||
/// <summary> Force a file resolve to be removed. </summary>
|
||||
internal void RemoveFile(Utf8GamePath path)
|
||||
=> ResolvedFiles.Remove(path);
|
||||
=> ResolvedFiles.Remove(path);
|
||||
|
||||
public void ReloadMod(IMod mod, bool addMetaChanges)
|
||||
{
|
||||
|
|
@ -151,8 +151,8 @@ public class CollectionCache : IDisposable
|
|||
|
||||
foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations))
|
||||
{
|
||||
if (MetaManipulations.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod)
|
||||
MetaManipulations.RevertMod(manipulation);
|
||||
if (Meta.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod)
|
||||
Meta.RevertMod(manipulation);
|
||||
}
|
||||
|
||||
_conflicts.Remove(mod);
|
||||
|
|
@ -175,11 +175,7 @@ public class CollectionCache : IDisposable
|
|||
if (addMetaChanges)
|
||||
{
|
||||
++_collection.ChangeCounter;
|
||||
if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods)
|
||||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
MetaManipulations.SetFiles();
|
||||
}
|
||||
_manager.MetaFileManager.ApplyDefaultFiles(_collection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,11 +221,7 @@ public class CollectionCache : IDisposable
|
|||
if ((mod is TemporaryMod temp ? temp.TotalManipulations : Penumbra.ModCaches[mod.Index].TotalManipulations) > 0)
|
||||
AddMetaFiles();
|
||||
|
||||
if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods)
|
||||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
MetaManipulations.SetFiles();
|
||||
}
|
||||
_manager.MetaFileManager.ApplyDefaultFiles(_collection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,9 +327,9 @@ public class CollectionCache : IDisposable
|
|||
// Inside the same mod, conflicts are not recorded.
|
||||
private void AddManipulation(MetaManipulation manip, IMod mod)
|
||||
{
|
||||
if (!MetaManipulations.TryGetValue(manip, out var existingMod))
|
||||
if (!Meta.TryGetValue(manip, out var existingMod))
|
||||
{
|
||||
MetaManipulations.ApplyMod(manip, mod);
|
||||
Meta.ApplyMod(manip, mod);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -346,13 +338,13 @@ public class CollectionCache : IDisposable
|
|||
return;
|
||||
|
||||
if (AddConflict(manip, mod, existingMod))
|
||||
MetaManipulations.ApplyMod(manip, mod);
|
||||
Meta.ApplyMod(manip, mod);
|
||||
}
|
||||
|
||||
|
||||
// Add all necessary meta file redirects.
|
||||
public void AddMetaFiles()
|
||||
=> MetaManipulations.SetImcFiles();
|
||||
=> Meta.SetImcFiles();
|
||||
|
||||
|
||||
// Identify and record all manipulated objects for this entire collection.
|
||||
|
|
@ -367,7 +359,7 @@ public class CollectionCache : IDisposable
|
|||
_changedItems.Clear();
|
||||
// Skip IMCs because they would result in far too many false-positive items,
|
||||
// since they are per set instead of per item-slot/item/variant.
|
||||
var identifier = Penumbra.Identifier;
|
||||
var identifier = _manager.MetaFileManager.Identifier.AwaitedService;
|
||||
var items = new SortedList<string, object?>(512);
|
||||
|
||||
void AddItems(IMod mod)
|
||||
|
|
@ -391,7 +383,7 @@ public class CollectionCache : IDisposable
|
|||
AddItems(modPath.Mod);
|
||||
}
|
||||
|
||||
foreach (var (manip, mod) in MetaManipulations)
|
||||
foreach (var (manip, mod) in Meta)
|
||||
{
|
||||
ModCacheManager.ComputeChangedItems(identifier, items, manip);
|
||||
AddItems(mod);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class CollectionCacheManager : IDisposable
|
|||
private void FullRecalculation(ModCollection collection, CollectionCache cache)
|
||||
{
|
||||
cache.ResolvedFiles.Clear();
|
||||
cache.MetaManipulations.Reset();
|
||||
cache.Meta.Reset();
|
||||
cache._conflicts.Clear();
|
||||
|
||||
// Add all forced redirects.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Penumbra.GameData.Actors;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Util;
|
||||
using static OtterGui.Raii.ImRaii;
|
||||
|
||||
namespace Penumbra.Collections.Manager;
|
||||
|
||||
|
|
@ -22,7 +21,7 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
private readonly CommunicatorService _communicator;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public ActiveCollections(CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService)
|
||||
public ActiveCollections(Configuration config, CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService)
|
||||
{
|
||||
_storage = storage;
|
||||
_communicator = communicator;
|
||||
|
|
@ -30,7 +29,7 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
Current = storage.DefaultNamed;
|
||||
Default = storage.DefaultNamed;
|
||||
Interface = storage.DefaultNamed;
|
||||
Individuals = new IndividualCollections(actors.AwaitedService);
|
||||
Individuals = new IndividualCollections(actors.AwaitedService, config);
|
||||
_communicator.CollectionChange.Subscribe(OnCollectionChange);
|
||||
LoadCollections();
|
||||
UpdateCurrentCollectionInUse();
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ using Penumbra.String;
|
|||
|
||||
namespace Penumbra.Collections.Manager;
|
||||
|
||||
public sealed partial class IndividualCollections : IReadOnlyList< (string DisplayName, ModCollection Collection) >
|
||||
public sealed partial class IndividualCollections : IReadOnlyList<(string DisplayName, ModCollection Collection)>
|
||||
{
|
||||
public IEnumerator< (string DisplayName, ModCollection Collection) > GetEnumerator()
|
||||
=> _assignments.Select( t => ( t.DisplayName, t.Collection ) ).GetEnumerator();
|
||||
public IEnumerator<(string DisplayName, ModCollection Collection)> GetEnumerator()
|
||||
=> _assignments.Select(t => (t.DisplayName, t.Collection)).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
|
@ -20,59 +20,51 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
|
|||
public int Count
|
||||
=> _assignments.Count;
|
||||
|
||||
public (string DisplayName, ModCollection Collection) this[ int index ]
|
||||
=> ( _assignments[ index ].DisplayName, _assignments[ index ].Collection );
|
||||
public (string DisplayName, ModCollection Collection) this[int index]
|
||||
=> (_assignments[index].DisplayName, _assignments[index].Collection);
|
||||
|
||||
public bool TryGetCollection( ActorIdentifier identifier, [NotNullWhen( true )] out ModCollection? collection )
|
||||
public bool TryGetCollection(ActorIdentifier identifier, [NotNullWhen(true)] out ModCollection? collection)
|
||||
{
|
||||
if( Count == 0 )
|
||||
if (Count == 0)
|
||||
{
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch( identifier.Type )
|
||||
switch (identifier.Type)
|
||||
{
|
||||
case IdentifierType.Player: return CheckWorlds( identifier, out collection );
|
||||
case IdentifierType.Player: return CheckWorlds(identifier, out collection);
|
||||
case IdentifierType.Retainer:
|
||||
{
|
||||
if( _individuals.TryGetValue( identifier, out collection ) )
|
||||
{
|
||||
if (_individuals.TryGetValue(identifier, out collection))
|
||||
return true;
|
||||
}
|
||||
|
||||
if( identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && Penumbra.Config.UseOwnerNameForCharacterCollection )
|
||||
{
|
||||
return CheckWorlds( _actorManager.GetCurrentPlayer(), out collection );
|
||||
}
|
||||
if (identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && _config.UseOwnerNameForCharacterCollection)
|
||||
return CheckWorlds(_actorManager.GetCurrentPlayer(), out collection);
|
||||
|
||||
break;
|
||||
}
|
||||
case IdentifierType.Owned:
|
||||
{
|
||||
if( CheckWorlds( identifier, out collection! ) )
|
||||
{
|
||||
if (CheckWorlds(identifier, out collection!))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle generic NPC
|
||||
var npcIdentifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, identifier.Kind, identifier.DataId );
|
||||
if( npcIdentifier.IsValid && _individuals.TryGetValue( npcIdentifier, out collection ) )
|
||||
{
|
||||
var npcIdentifier = _actorManager.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue,
|
||||
identifier.Kind, identifier.DataId);
|
||||
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Ownership.
|
||||
if( Penumbra.Config.UseOwnerNameForCharacterCollection )
|
||||
{
|
||||
identifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, ObjectKind.None, uint.MaxValue );
|
||||
return CheckWorlds( identifier, out collection );
|
||||
}
|
||||
if (!_config.UseOwnerNameForCharacterCollection)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
identifier = _actorManager.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld,
|
||||
ObjectKind.None, uint.MaxValue);
|
||||
return CheckWorlds(identifier, out collection);
|
||||
}
|
||||
case IdentifierType.Npc: return _individuals.TryGetValue( identifier, out collection );
|
||||
case IdentifierType.Special: return CheckWorlds( ConvertSpecialIdentifier( identifier ).Item1, out collection );
|
||||
case IdentifierType.Npc: return _individuals.TryGetValue(identifier, out collection);
|
||||
case IdentifierType.Special: return CheckWorlds(ConvertSpecialIdentifier(identifier).Item1, out collection);
|
||||
}
|
||||
|
||||
collection = null;
|
||||
|
|
@ -94,80 +86,71 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
|
|||
Invalid,
|
||||
}
|
||||
|
||||
public (ActorIdentifier, SpecialResult) ConvertSpecialIdentifier( ActorIdentifier identifier )
|
||||
public (ActorIdentifier, SpecialResult) ConvertSpecialIdentifier(ActorIdentifier identifier)
|
||||
{
|
||||
if( identifier.Type != IdentifierType.Special )
|
||||
{
|
||||
return ( identifier, SpecialResult.Invalid );
|
||||
}
|
||||
if (identifier.Type != IdentifierType.Special)
|
||||
return (identifier, SpecialResult.Invalid);
|
||||
|
||||
if( _actorManager.ResolvePartyBannerPlayer( identifier.Special, out var id ) )
|
||||
{
|
||||
return ( id, SpecialResult.PartyBanner );
|
||||
}
|
||||
if (_actorManager.ResolvePartyBannerPlayer(identifier.Special, out var id))
|
||||
return (id, SpecialResult.PartyBanner);
|
||||
|
||||
if( _actorManager.ResolvePvPBannerPlayer( identifier.Special, out id ) )
|
||||
{
|
||||
return ( id, SpecialResult.PvPBanner );
|
||||
}
|
||||
if (_actorManager.ResolvePvPBannerPlayer(identifier.Special, out id))
|
||||
return (id, SpecialResult.PvPBanner);
|
||||
|
||||
if( _actorManager.ResolveMahjongPlayer( identifier.Special, out id ) )
|
||||
{
|
||||
return ( id, SpecialResult.Mahjong );
|
||||
}
|
||||
if (_actorManager.ResolveMahjongPlayer(identifier.Special, out id))
|
||||
return (id, SpecialResult.Mahjong);
|
||||
|
||||
switch( identifier.Special )
|
||||
switch (identifier.Special)
|
||||
{
|
||||
case ScreenActor.CharacterScreen when Penumbra.Config.UseCharacterCollectionInMainWindow: return ( _actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen );
|
||||
case ScreenActor.FittingRoom when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom );
|
||||
case ScreenActor.DyePreview when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.DyePreview );
|
||||
case ScreenActor.Portrait when Penumbra.Config.UseCharacterCollectionsInCards: return ( _actorManager.GetCurrentPlayer(), SpecialResult.Portrait );
|
||||
case ScreenActor.CharacterScreen when _config.UseCharacterCollectionInMainWindow:
|
||||
return (_actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen);
|
||||
case ScreenActor.FittingRoom when _config.UseCharacterCollectionInTryOn:
|
||||
return (_actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom);
|
||||
case ScreenActor.DyePreview when _config.UseCharacterCollectionInTryOn:
|
||||
return (_actorManager.GetCurrentPlayer(), SpecialResult.DyePreview);
|
||||
case ScreenActor.Portrait when _config.UseCharacterCollectionsInCards:
|
||||
return (_actorManager.GetCurrentPlayer(), SpecialResult.Portrait);
|
||||
case ScreenActor.ExamineScreen:
|
||||
{
|
||||
identifier = _actorManager.GetInspectPlayer();
|
||||
if( identifier.IsValid )
|
||||
{
|
||||
return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect );
|
||||
}
|
||||
if (identifier.IsValid)
|
||||
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect);
|
||||
|
||||
identifier = _actorManager.GetCardPlayer();
|
||||
if( identifier.IsValid )
|
||||
{
|
||||
return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card );
|
||||
}
|
||||
if (identifier.IsValid)
|
||||
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card);
|
||||
|
||||
return Penumbra.Config.UseCharacterCollectionInTryOn ? ( _actorManager.GetGlamourPlayer(), SpecialResult.Glamour ) : ( identifier, SpecialResult.Invalid );
|
||||
return _config.UseCharacterCollectionInTryOn
|
||||
? (_actorManager.GetGlamourPlayer(), SpecialResult.Glamour)
|
||||
: (identifier, SpecialResult.Invalid);
|
||||
}
|
||||
default: return ( identifier, SpecialResult.Invalid );
|
||||
default: return (identifier, SpecialResult.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCollection( GameObject? gameObject, out ModCollection? collection )
|
||||
=> TryGetCollection( _actorManager.FromObject( gameObject, true, false, false ), out collection );
|
||||
public bool TryGetCollection(GameObject? gameObject, out ModCollection? collection)
|
||||
=> TryGetCollection(_actorManager.FromObject(gameObject, true, false, false), out collection);
|
||||
|
||||
public unsafe bool TryGetCollection( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection )
|
||||
=> TryGetCollection( _actorManager.FromObject( gameObject, out _, true, false, false ), out collection );
|
||||
public unsafe bool TryGetCollection(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection)
|
||||
=> TryGetCollection(_actorManager.FromObject(gameObject, out _, true, false, false), out collection);
|
||||
|
||||
private bool CheckWorlds( ActorIdentifier identifier, out ModCollection? collection )
|
||||
private bool CheckWorlds(ActorIdentifier identifier, out ModCollection? collection)
|
||||
{
|
||||
if( !identifier.IsValid )
|
||||
if (!identifier.IsValid)
|
||||
{
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if( _individuals.TryGetValue( identifier, out collection ) )
|
||||
{
|
||||
if (_individuals.TryGetValue(identifier, out collection))
|
||||
return true;
|
||||
}
|
||||
|
||||
identifier = _actorManager.CreateIndividualUnchecked( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId );
|
||||
if( identifier.IsValid && _individuals.TryGetValue( identifier, out collection ) )
|
||||
{
|
||||
identifier = _actorManager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind,
|
||||
identifier.DataId);
|
||||
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
|
||||
return true;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace Penumbra.Collections.Manager;
|
|||
|
||||
public sealed partial class IndividualCollections
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly ActorManager _actorManager;
|
||||
private readonly List<(string DisplayName, IReadOnlyList<ActorIdentifier> Identifiers, ModCollection Collection)> _assignments = new();
|
||||
private readonly Dictionary<ActorIdentifier, ModCollection> _individuals = new();
|
||||
|
|
@ -20,11 +21,17 @@ public sealed partial class IndividualCollections
|
|||
=> _assignments;
|
||||
|
||||
// TODO
|
||||
public IndividualCollections(ActorService actorManager)
|
||||
=> _actorManager = actorManager.AwaitedService;
|
||||
public IndividualCollections(ActorService actorManager, Configuration config)
|
||||
{
|
||||
_config = config;
|
||||
_actorManager = actorManager.AwaitedService;
|
||||
}
|
||||
|
||||
public IndividualCollections(ActorManager actorManager)
|
||||
=> _actorManager = actorManager;
|
||||
public IndividualCollections(ActorManager actorManager, Configuration config)
|
||||
{
|
||||
_actorManager = actorManager;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public enum AddResult
|
||||
{
|
||||
|
|
@ -234,7 +241,7 @@ public sealed partial class IndividualCollections
|
|||
=> identifier.IsValid ? Index(DisplayString(identifier)) : -1;
|
||||
|
||||
private string DisplayString(ActorIdentifier identifier)
|
||||
{
|
||||
{
|
||||
return identifier.Type switch
|
||||
{
|
||||
IdentifierType.Player => $"{identifier.PlayerName} ({_actorManager.Data.ToWorldName(identifier.HomeWorld)})",
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ public class TempCollectionManager : IDisposable
|
|||
private readonly CollectionStorage _storage;
|
||||
private readonly Dictionary<string, ModCollection> _customCollections = new();
|
||||
|
||||
public TempCollectionManager(CommunicatorService communicator, ActorService actors, CollectionStorage storage)
|
||||
public TempCollectionManager(Configuration config, CommunicatorService communicator, ActorService actors, CollectionStorage storage)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_storage = storage;
|
||||
Collections = new IndividualCollections(actors);
|
||||
Collections = new IndividualCollections(actors, config);
|
||||
|
||||
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ public partial class ModCollection
|
|||
|
||||
// Obtain data from the cache.
|
||||
internal MetaCache? MetaCache
|
||||
=> _cache?.MetaManipulations;
|
||||
=> _cache?.Meta;
|
||||
|
||||
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file)
|
||||
{
|
||||
if (_cache != null)
|
||||
return _cache.MetaManipulations.GetImcFile(path, out file);
|
||||
return _cache.Meta.GetImcFile(path, out file);
|
||||
|
||||
file = null;
|
||||
return false;
|
||||
|
|
@ -80,7 +80,7 @@ public partial class ModCollection
|
|||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetFiles();
|
||||
_cache.Meta.SetFiles();
|
||||
Penumbra.Log.Debug($"Set CharacterUtility resources for collection {Name}.");
|
||||
}
|
||||
}
|
||||
|
|
@ -90,28 +90,28 @@ public partial class ModCollection
|
|||
if (_cache == null)
|
||||
Penumbra.CharacterUtility.ResetResource(idx);
|
||||
else
|
||||
_cache.MetaManipulations.SetFile(idx);
|
||||
_cache.Meta.SetFile(idx);
|
||||
}
|
||||
|
||||
// Used for short periods of changed files.
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
|
||||
=> _cache?.MetaManipulations.TemporarilySetEqdpFile(genderRace, accessory)
|
||||
=> _cache?.Meta.TemporarilySetEqdpFile(genderRace, accessory)
|
||||
?? Penumbra.CharacterUtility.TemporarilyResetResource(Interop.Structs.CharacterUtilityData.EqdpIdx(genderRace, accessory));
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEqpFile()
|
||||
=> _cache?.MetaManipulations.TemporarilySetEqpFile()
|
||||
=> _cache?.Meta.TemporarilySetEqpFile()
|
||||
?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.Eqp);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetGmpFile()
|
||||
=> _cache?.MetaManipulations.TemporarilySetGmpFile()
|
||||
=> _cache?.Meta.TemporarilySetGmpFile()
|
||||
?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.Gmp);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetCmpFile()
|
||||
=> _cache?.MetaManipulations.TemporarilySetCmpFile()
|
||||
=> _cache?.Meta.TemporarilySetCmpFile()
|
||||
?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.HumanCmp);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
|
||||
=> _cache?.MetaManipulations.TemporarilySetEstFile(type)
|
||||
=> _cache?.Meta.TemporarilySetEstFile(type)
|
||||
?? Penumbra.CharacterUtility.TemporarilyResetResource((MetaIndex)type);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -550,19 +550,19 @@ public class CommandHandler : IDisposable
|
|||
|
||||
private void Print(string text)
|
||||
{
|
||||
if (Penumbra.Config.PrintSuccessfulCommandsToChat)
|
||||
if (_config.PrintSuccessfulCommandsToChat)
|
||||
_chat.Print(text);
|
||||
}
|
||||
|
||||
private void Print(DefaultInterpolatedStringHandler text)
|
||||
{
|
||||
if (Penumbra.Config.PrintSuccessfulCommandsToChat)
|
||||
if (_config.PrintSuccessfulCommandsToChat)
|
||||
_chat.Print(text.ToStringAndClear());
|
||||
}
|
||||
|
||||
private void Print(Func<SeString> text)
|
||||
{
|
||||
if (Penumbra.Config.PrintSuccessfulCommandsToChat)
|
||||
if (_config.PrintSuccessfulCommandsToChat)
|
||||
_chat.Print(text());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal static class DefaultTexToolsData
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SimpleMod
|
||||
public class SimpleMod
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public string Category = string.Empty;
|
||||
|
|
@ -24,14 +24,14 @@ internal class SimpleMod
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ModPackPage
|
||||
public class ModPackPage
|
||||
{
|
||||
public int PageIndex = 0;
|
||||
public ModGroup[] ModGroups = Array.Empty<ModGroup>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ModGroup
|
||||
public class ModGroup
|
||||
{
|
||||
public string GroupName = string.Empty;
|
||||
public GroupType SelectionType = GroupType.Single;
|
||||
|
|
@ -40,7 +40,7 @@ internal class ModGroup
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
internal class OptionList
|
||||
public class OptionList
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
|
|
@ -52,7 +52,7 @@ internal class OptionList
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ExtendedModPack
|
||||
public class ExtendedModPack
|
||||
{
|
||||
public string PackVersion = string.Empty;
|
||||
public string Name = DefaultTexToolsData.Name;
|
||||
|
|
@ -65,7 +65,7 @@ internal class ExtendedModPack
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SimpleModPack
|
||||
public class SimpleModPack
|
||||
{
|
||||
public string TtmpVersion = string.Empty;
|
||||
public string Name = DefaultTexToolsData.Name;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ namespace Penumbra.Import;
|
|||
|
||||
public partial class TexToolsImporter
|
||||
{
|
||||
// Extract regular compressed archives that are folders containing penumbra-formatted mods.
|
||||
// The mod has to either contain a meta.json at top level, or one folder deep.
|
||||
// If the meta.json is one folder deep, all other files have to be in the same folder.
|
||||
// The extracted folder gets its name either from that one top-level folder or from the mod name.
|
||||
// All data is extracted without manipulation of the files or metadata.
|
||||
/// <summary>
|
||||
/// Extract regular compressed archives that are folders containing penumbra-formatted mods.
|
||||
/// The mod has to either contain a meta.json at top level, or one folder deep.
|
||||
/// If the meta.json is one folder deep, all other files have to be in the same folder.
|
||||
/// The extracted folder gets its name either from that one top-level folder or from the mod name.
|
||||
/// All data is extracted without manipulation of the files or metadata.
|
||||
/// </summary>
|
||||
private DirectoryInfo HandleRegularArchive( FileInfo modPackFile )
|
||||
{
|
||||
using var zfs = modPackFile.OpenRead();
|
||||
|
|
@ -111,7 +113,7 @@ public partial class TexToolsImporter
|
|||
}
|
||||
|
||||
_currentModDirectory.Refresh();
|
||||
ModCreator.SplitMultiGroups( _currentModDirectory );
|
||||
_modManager.Creator.SplitMultiGroups( _currentModDirectory );
|
||||
|
||||
return _currentModDirectory;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public partial class TexToolsImporter
|
|||
// Open the mod data file from the mod pack as a SqPackStream
|
||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||
ExtractSimpleModList( _currentModDirectory, modList );
|
||||
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
|
||||
ResetStreamDisposer();
|
||||
return _currentModDirectory;
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ public partial class TexToolsImporter
|
|||
// Open the mod data file from the mod pack as a SqPackStream
|
||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
|
||||
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
|
||||
ResetStreamDisposer();
|
||||
return _currentModDirectory;
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ public partial class TexToolsImporter
|
|||
var optionFolder = ModCreator.NewSubFolderName( groupFolder, option.Name )
|
||||
?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) );
|
||||
ExtractSimpleModList( optionFolder, option.ModsJsons );
|
||||
options.Add( ModCreator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
|
||||
options.Add( _modManager.Creator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
|
||||
if( option.IsChecked )
|
||||
{
|
||||
defaultSettings = group.SelectionType == GroupType.Multi
|
||||
|
|
@ -211,7 +211,7 @@ public partial class TexToolsImporter
|
|||
}
|
||||
}
|
||||
|
||||
ModCreator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
|
||||
_modManager.Creator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
|
||||
defaultSettings ?? 0, group.Description, options );
|
||||
++groupPriority;
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ public partial class TexToolsImporter
|
|||
}
|
||||
|
||||
ResetStreamDisposer();
|
||||
ModCreator.CreateDefaultFiles( _currentModDirectory );
|
||||
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
|
||||
return _currentModDirectory;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,26 @@ using Penumbra.Meta.Manipulations;
|
|||
namespace Penumbra.Import;
|
||||
|
||||
public partial class TexToolsMeta
|
||||
{
|
||||
{
|
||||
public static void WriteTexToolsMeta(MetaFileManager manager, IEnumerable<MetaManipulation> manipulations, DirectoryInfo basePath)
|
||||
{
|
||||
var files = ConvertToTexTools(manager, manipulations);
|
||||
|
||||
foreach (var (file, data) in files)
|
||||
{
|
||||
var path = Path.Combine(basePath.FullName, file);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
File.WriteAllBytes(path, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not write meta file {path}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary< string, byte[] > ConvertToTexTools( MetaFileManager manager, IEnumerable< MetaManipulation > manips )
|
||||
{
|
||||
var ret = new Dictionary< string, byte[] >();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Penumbra.GameData;
|
|||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Services;
|
||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
|
@ -21,9 +22,10 @@ public unsafe class MetaFileManager
|
|||
internal readonly DataManager GameData;
|
||||
internal readonly ActiveCollections ActiveCollections;
|
||||
internal readonly ValidityChecker ValidityChecker;
|
||||
internal readonly IdentifierService Identifier;
|
||||
|
||||
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, DataManager gameData,
|
||||
ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker)
|
||||
ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker, IdentifierService identifier)
|
||||
{
|
||||
CharacterUtility = characterUtility;
|
||||
ResidentResources = residentResources;
|
||||
|
|
@ -31,6 +33,7 @@ public unsafe class MetaFileManager
|
|||
ActiveCollections = activeCollections;
|
||||
Config = config;
|
||||
ValidityChecker = validityChecker;
|
||||
Identifier = identifier;
|
||||
SignatureHelper.Initialise(this);
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +58,7 @@ public unsafe class MetaFileManager
|
|||
return;
|
||||
|
||||
ResidentResources.Reload();
|
||||
collection._cache?.MetaManipulations.SetFiles();
|
||||
collection._cache?.Meta.SetFiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ public class DuplicateManager
|
|||
try
|
||||
{
|
||||
var mod = new Mod(modDirectory);
|
||||
mod.Reload(_modManager, true, out _);
|
||||
_modManager.Creator.ReloadMod(mod, true, out _);
|
||||
|
||||
Finished = false;
|
||||
_files.UpdateAll(mod, mod.Default);
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ public interface IMod
|
|||
public ISubMod Default { get; }
|
||||
public IReadOnlyList< IModGroup > Groups { get; }
|
||||
|
||||
public IEnumerable< ISubMod > AllSubMods { get; }
|
||||
public IEnumerable< SubMod > AllSubMods { get; }
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ public class ModFileEditor
|
|||
if (deletions <= 0)
|
||||
return;
|
||||
|
||||
mod.Reload(_modManager, false, out _);
|
||||
_modManager.Creator.ReloadMod(mod, false, out _);
|
||||
_files.UpdateAll(mod, option);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -221,10 +221,10 @@ public class ModCacheManager : IDisposable, IReadOnlyList<ModCache>
|
|||
if (!_identifier.Valid)
|
||||
return;
|
||||
|
||||
foreach (var gamePath in mod.AllRedirects)
|
||||
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
||||
_identifier.AwaitedService.Identify(cache.ChangedItems, gamePath.ToString());
|
||||
|
||||
foreach (var manip in mod.AllManipulations)
|
||||
foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations))
|
||||
ComputeChangedItems(_identifier.AwaitedService, cache.ChangedItems, manip);
|
||||
|
||||
cache.LowerChangedItemsString = string.Join("\0", cache.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public class ModDataEditor
|
|||
return changes;
|
||||
}
|
||||
|
||||
public ModDataChangeType LoadMeta(Mod mod)
|
||||
public ModDataChangeType LoadMeta(ModCreator creator, Mod mod)
|
||||
{
|
||||
var metaFile = _saveService.FileNames.ModMetaPath(mod);
|
||||
if (!File.Exists(metaFile))
|
||||
|
|
@ -171,7 +171,7 @@ public class ModDataEditor
|
|||
}
|
||||
|
||||
if (newFileVersion != ModMeta.FileVersion)
|
||||
if (ModMigration.Migrate(_saveService, mod, json, ref newFileVersion))
|
||||
if (ModMigration.Migrate(creator, _saveService, mod, json, ref newFileVersion))
|
||||
{
|
||||
changes |= ModDataChangeType.Migration;
|
||||
_saveService.ImmediateSave(new ModMeta(mod));
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
|||
{
|
||||
private readonly ModManager _modManager;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly FilenameService _files;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||
public ModFileSystem(ModManager modManager, CommunicatorService communicator, FilenameService files)
|
||||
public ModFileSystem(ModManager modManager, CommunicatorService communicator, SaveService saveService)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_communicator = communicator;
|
||||
_files = files;
|
||||
_saveService = saveService;
|
||||
Reload();
|
||||
Changed += OnChange;
|
||||
_communicator.ModDiscoveryFinished.Subscribe(Reload);
|
||||
|
|
@ -66,8 +66,8 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
|||
private void Reload()
|
||||
{
|
||||
// TODO
|
||||
if (Load(new FileInfo(_files.FilesystemFile), _modManager, ModToIdentifier, ModToName))
|
||||
Penumbra.SaveService.ImmediateSave(this);
|
||||
if (Load(new FileInfo(_saveService.FileNames.FilesystemFile), _modManager, ModToIdentifier, ModToName))
|
||||
_saveService.ImmediateSave(this);
|
||||
|
||||
Penumbra.Log.Debug("Reloaded mod filesystem.");
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
|||
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
|
||||
{
|
||||
if (type != FileSystemChangeType.Reload)
|
||||
Penumbra.SaveService.QueueSave(this);
|
||||
_saveService.QueueSave(this);
|
||||
}
|
||||
|
||||
// Update sort order when defaulted mod names change.
|
||||
|
|
@ -111,7 +111,7 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
|||
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
Penumbra.SaveService.QueueSave(this);
|
||||
_saveService.QueueSave(this);
|
||||
break;
|
||||
case ModPathChangeType.Reloaded:
|
||||
// Nothing
|
||||
|
|
|
|||
|
|
@ -24,18 +24,21 @@ public sealed class ModManager : ModStorage
|
|||
private readonly Configuration _config;
|
||||
private readonly CommunicatorService _communicator;
|
||||
|
||||
public readonly ModCreator Creator;
|
||||
public readonly ModDataEditor DataEditor;
|
||||
public readonly ModOptionEditor OptionEditor;
|
||||
|
||||
public DirectoryInfo BasePath { get; private set; } = null!;
|
||||
public bool Valid { get; private set; }
|
||||
|
||||
public ModManager(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor)
|
||||
public ModManager(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor,
|
||||
ModCreator creator)
|
||||
{
|
||||
_config = config;
|
||||
_communicator = communicator;
|
||||
DataEditor = dataEditor;
|
||||
OptionEditor = optionEditor;
|
||||
Creator = creator;
|
||||
SetBaseDirectory(config.ModDirectory, true);
|
||||
DiscoverMods();
|
||||
}
|
||||
|
|
@ -73,8 +76,8 @@ public sealed class ModManager : ModStorage
|
|||
if (this.Any(m => m.ModPath.Name == modFolder.Name))
|
||||
return;
|
||||
|
||||
ModCreator.SplitMultiGroups(modFolder);
|
||||
var mod = Mod.LoadMod(this, modFolder, true);
|
||||
Creator.SplitMultiGroups(modFolder);
|
||||
var mod = Creator.LoadMod(modFolder, true);
|
||||
if (mod == null)
|
||||
return;
|
||||
|
||||
|
|
@ -119,7 +122,7 @@ public sealed class ModManager : ModStorage
|
|||
var oldName = mod.Name;
|
||||
|
||||
_communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
||||
if (!mod.Reload(Penumbra.ModManager, true, out var metaChange))
|
||||
if (!Creator.ReloadMod(mod, true, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead."
|
||||
|
|
@ -185,7 +188,7 @@ public sealed class ModManager : ModStorage
|
|||
|
||||
dir.Refresh();
|
||||
mod.ModPath = dir;
|
||||
if (!mod.Reload(Penumbra.ModManager, false, out var metaChange))
|
||||
if (!Creator.ReloadMod(mod, false, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Error($"Error reloading moved mod {mod.Name}.");
|
||||
return;
|
||||
|
|
@ -307,7 +310,7 @@ public sealed class ModManager : ModStorage
|
|||
var queue = new ConcurrentQueue<Mod>();
|
||||
Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir =>
|
||||
{
|
||||
var mod = Mod.LoadMod(this, dir, false);
|
||||
var mod = Creator.LoadMod(dir, false);
|
||||
if (mod != null)
|
||||
queue.Enqueue(mod);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ public static partial class ModMigration
|
|||
[GeneratedRegex("^group_", RegexOptions.Compiled)]
|
||||
private static partial Regex GroupStartRegex();
|
||||
|
||||
public static bool Migrate(SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||
=> MigrateV0ToV1(saveService, mod, json, ref fileVersion) || MigrateV1ToV2(mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
|
||||
public static bool Migrate(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||
=> MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion) || MigrateV1ToV2(saveService, mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
|
||||
|
||||
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
|
||||
{
|
||||
|
|
@ -33,13 +33,13 @@ public static partial class ModMigration
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool MigrateV1ToV2(Mod mod, ref uint fileVersion)
|
||||
private static bool MigrateV1ToV2(SaveService saveService, Mod mod, ref uint fileVersion)
|
||||
{
|
||||
if (fileVersion > 1)
|
||||
return false;
|
||||
|
||||
if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name)))
|
||||
foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray())
|
||||
if (!saveService.FileNames.GetOptionGroupFiles(mod).All(g => GroupRegex().IsMatch(g.Name)))
|
||||
foreach (var (group, index) in saveService.FileNames.GetOptionGroupFiles(mod).WithIndex().ToArray())
|
||||
{
|
||||
var newName = GroupStartRegex().Replace(group.Name, $"group_{index + 1:D3}_");
|
||||
try
|
||||
|
|
@ -58,7 +58,7 @@ public static partial class ModMigration
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool MigrateV0ToV1(SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||
private static bool MigrateV0ToV1(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||
{
|
||||
if (fileVersion > 0)
|
||||
return false;
|
||||
|
|
@ -69,21 +69,21 @@ public static partial class ModMigration
|
|||
var priority = 1;
|
||||
var seenMetaFiles = new HashSet<FullPath>();
|
||||
foreach (var group in groups.Values)
|
||||
ConvertGroup(mod, group, ref priority, seenMetaFiles);
|
||||
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
|
||||
|
||||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||
{
|
||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||
&& !mod._default.FileData.TryAdd(gamePath, unusedFile))
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}.");
|
||||
&& !mod.Default.FileData.TryAdd(gamePath, unusedFile))
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.FileData[gamePath]}.");
|
||||
}
|
||||
|
||||
mod._default.FileSwapData.Clear();
|
||||
mod._default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||
mod.Default.FileSwapData.Clear();
|
||||
mod.Default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||
foreach (var (gamePath, swapPath) in swaps)
|
||||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||
mod.Default.FileSwapData.Add(gamePath, swapPath);
|
||||
|
||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||
creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true);
|
||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||
saveService.ImmediateSave(new ModSaveGroup(mod, index));
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ public static partial class ModMigration
|
|||
return true;
|
||||
}
|
||||
|
||||
private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
||||
private static void ConvertGroup(ModCreator creator, Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
if (group.Options.Count == 0)
|
||||
return;
|
||||
|
|
@ -134,15 +134,15 @@ public static partial class ModMigration
|
|||
Priority = priority++,
|
||||
Description = string.Empty,
|
||||
};
|
||||
mod._groups.Add(newMultiGroup);
|
||||
mod.Groups.Add(newMultiGroup);
|
||||
foreach (var option in group.Options)
|
||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++));
|
||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(creator, mod, option, seenMetaFiles), optionPriority++));
|
||||
|
||||
break;
|
||||
case GroupType.Single:
|
||||
if (group.Options.Count == 1)
|
||||
{
|
||||
AddFilesToSubMod(mod._default, mod.ModPath, group.Options[0], seenMetaFiles);
|
||||
AddFilesToSubMod(mod.Default, mod.ModPath, group.Options[0], seenMetaFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -152,9 +152,9 @@ public static partial class ModMigration
|
|||
Priority = priority++,
|
||||
Description = string.Empty,
|
||||
};
|
||||
mod._groups.Add(newSingleGroup);
|
||||
mod.Groups.Add(newSingleGroup);
|
||||
foreach (var option in group.Options)
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles));
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(creator, mod, option, seenMetaFiles));
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -173,11 +173,11 @@ public static partial class ModMigration
|
|||
}
|
||||
}
|
||||
|
||||
private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
private static SubMod SubModFromOption(ModCreator creator, Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
var subMod = new SubMod(mod) { Name = option.OptionName };
|
||||
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||
subMod.IncorporateMetaChanges(mod.ModPath, false);
|
||||
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||
return subMod;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ public class ModOptionEditor
|
|||
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
|
||||
public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.Type == type)
|
||||
return;
|
||||
|
||||
mod._groups[groupIdx] = group.Convert(type);
|
||||
mod.Groups[groupIdx] = group.Convert(type);
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ public class ModOptionEditor
|
|||
/// <summary> Change the settings stored as default options in a mod.</summary>
|
||||
public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.DefaultSettings == defaultOption)
|
||||
return;
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ public class ModOptionEditor
|
|||
/// <summary> Rename an option group if possible. </summary>
|
||||
public void RenameModGroup(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
var oldName = group.Name;
|
||||
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
||||
return;
|
||||
|
|
@ -93,9 +93,9 @@ public class ModOptionEditor
|
|||
if (!VerifyFileName(mod, null, newName, true))
|
||||
return;
|
||||
|
||||
var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1;
|
||||
var maxPriority = mod.Groups.Count == 0 ? 0 : mod.Groups.Max(o => o.Priority) + 1;
|
||||
|
||||
mod._groups.Add(type == GroupType.Multi
|
||||
mod.Groups.Add(type == GroupType.Multi
|
||||
? new MultiModGroup
|
||||
{
|
||||
Name = newName,
|
||||
|
|
@ -106,16 +106,16 @@ public class ModOptionEditor
|
|||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
});
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, mod._groups.Count - 1));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, -1, -1);
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Delete a given option group. Fires an event to prepare before actually deleting. </summary>
|
||||
public void DeleteModGroup(Mod mod, int groupIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||
mod._groups.RemoveAt(groupIdx);
|
||||
mod.Groups.RemoveAt(groupIdx);
|
||||
UpdateSubModPositions(mod, groupIdx);
|
||||
_saveService.SaveAllOptionGroups(mod);
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||
|
|
@ -124,7 +124,7 @@ public class ModOptionEditor
|
|||
/// <summary> Move the index of a given option group. </summary>
|
||||
public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo)
|
||||
{
|
||||
if (!mod._groups.Move(groupIdxFrom, groupIdxTo))
|
||||
if (!mod.Groups.Move(groupIdxFrom, groupIdxTo))
|
||||
return;
|
||||
|
||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||
|
|
@ -135,7 +135,7 @@ public class ModOptionEditor
|
|||
/// <summary> Change the description of the given option group. </summary>
|
||||
public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.Description == newDescription)
|
||||
return;
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ public class ModOptionEditor
|
|||
/// <summary> Change the description of the given option. </summary>
|
||||
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
var option = group[optionIdx];
|
||||
if (option.Description == newDescription || option is not SubMod s)
|
||||
return;
|
||||
|
|
@ -165,7 +165,7 @@ public class ModOptionEditor
|
|||
/// <summary> Change the internal priority of the given option group. </summary>
|
||||
public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.Priority == newPriority)
|
||||
return;
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ public class ModOptionEditor
|
|||
/// <summary> Change the internal priority of the given option. </summary>
|
||||
public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup:
|
||||
ChangeGroupPriority(mod, groupIdx, newPriority);
|
||||
|
|
@ -201,7 +201,7 @@ public class ModOptionEditor
|
|||
/// <summary> Rename the given option. </summary>
|
||||
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup s:
|
||||
if (s.OptionData[optionIdx].Name == newName)
|
||||
|
|
@ -225,7 +225,7 @@ public class ModOptionEditor
|
|||
/// <summary> Add a new empty option of the given name for the given group. </summary>
|
||||
public void AddOption(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
var subMod = new SubMod(mod) { Name = newName };
|
||||
subMod.SetPosition(groupIdx, group.Count);
|
||||
switch (group)
|
||||
|
|
@ -248,7 +248,7 @@ public class ModOptionEditor
|
|||
if (option is not SubMod o)
|
||||
return;
|
||||
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.Type is GroupType.Multi && group.Count >= IModGroup.MaxMultiOptions)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
|
|
@ -276,7 +276,7 @@ public class ModOptionEditor
|
|||
/// <summary> Delete the given option from the given group. </summary>
|
||||
public void DeleteOption(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
switch (group)
|
||||
{
|
||||
|
|
@ -297,7 +297,7 @@ public class ModOptionEditor
|
|||
/// <summary> Move an option inside the given option group. </summary>
|
||||
public void MoveOption(Mod mod, int groupIdx, int optionIdxFrom, int optionIdxTo)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (!group.MoveOption(optionIdxFrom, optionIdxTo))
|
||||
return;
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ public class ModOptionEditor
|
|||
/// <summary> Update the indices stored in options from a given group on. </summary>
|
||||
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
||||
{
|
||||
foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup))
|
||||
foreach (var (group, groupIdx) in mod.Groups.WithIndex().Skip(fromGroup))
|
||||
{
|
||||
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
||||
o.SetPosition(groupIdx, optionIdx);
|
||||
|
|
@ -390,9 +390,9 @@ public class ModOptionEditor
|
|||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
if (groupIdx == -1 && optionIdx == 0)
|
||||
return mod._default;
|
||||
return mod.Default;
|
||||
|
||||
return mod._groups[groupIdx] switch
|
||||
return mod.Groups[groupIdx] switch
|
||||
{
|
||||
SingleModGroup s => s.OptionData[optionIdx],
|
||||
MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod,
|
||||
|
|
|
|||
|
|
@ -31,76 +31,6 @@ public partial class Mod
|
|||
internal Mod( DirectoryInfo modPath )
|
||||
{
|
||||
ModPath = modPath;
|
||||
_default = new SubMod( this );
|
||||
}
|
||||
|
||||
public static Mod? LoadMod( ModManager modManager, DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
{
|
||||
modPath.Refresh();
|
||||
if( !modPath.Exists )
|
||||
{
|
||||
Penumbra.Log.Error( $"Supplied mod directory {modPath} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
var mod = new Mod(modPath);
|
||||
if (mod.Reload(modManager, incorporateMetaChanges, out _))
|
||||
return mod;
|
||||
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Warning( $"Mod at {modPath} without name is not supported." );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
internal bool Reload(ModManager modManager, bool incorporateMetaChanges, out ModDataChangeType modDataChange )
|
||||
{
|
||||
modDataChange = ModDataChangeType.Deletion;
|
||||
ModPath.Refresh();
|
||||
if( !ModPath.Exists )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
modDataChange = modManager.DataEditor.LoadMeta(this);
|
||||
if( modDataChange.HasFlag( ModDataChangeType.Deletion ) || Name.Length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
modManager.DataEditor.LoadLocalData(this);
|
||||
|
||||
LoadDefaultOption();
|
||||
LoadAllGroups();
|
||||
if( incorporateMetaChanges )
|
||||
{
|
||||
IncorporateAllMetaChanges(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert all .meta and .rgsp files to their respective meta changes and add them to their options.
|
||||
// Deletes the source files if delete is true.
|
||||
private void IncorporateAllMetaChanges( bool delete )
|
||||
{
|
||||
var changes = false;
|
||||
List< string > deleteList = new();
|
||||
foreach( var subMod in AllSubMods.OfType< SubMod >() )
|
||||
{
|
||||
var (localChanges, localDeleteList) = subMod.IncorporateMetaChanges( ModPath, false );
|
||||
changes |= localChanges;
|
||||
if( delete )
|
||||
{
|
||||
deleteList.AddRange( localDeleteList );
|
||||
}
|
||||
}
|
||||
|
||||
SubMod.DeleteDeleteList( deleteList, delete );
|
||||
|
||||
if( changes )
|
||||
{
|
||||
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||
}
|
||||
Default = new SubMod( this );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
internal static partial class ModCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// Create and return a new directory based on the given directory and name, that is <br/>
|
||||
/// - Not Empty.<br/>
|
||||
/// - Unique, by appending (digit) for duplicates.<br/>
|
||||
/// - Containing no symbols invalid for FFXIV or windows paths.<br/>
|
||||
/// </summary>
|
||||
/// <param name="outDirectory"></param>
|
||||
/// <param name="modListName"></param>
|
||||
/// <param name="create"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="IOException"></exception>
|
||||
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName, bool create = true )
|
||||
{
|
||||
var name = modListName;
|
||||
if( name.Length == 0 )
|
||||
{
|
||||
name = "_";
|
||||
}
|
||||
|
||||
var newModFolderBase = NewOptionDirectory( outDirectory, name );
|
||||
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
|
||||
if( newModFolder.Length == 0 )
|
||||
{
|
||||
throw new IOException( "Could not create mod folder: too many folders of the same name exist." );
|
||||
}
|
||||
|
||||
if( create )
|
||||
{
|
||||
Directory.CreateDirectory( newModFolder );
|
||||
}
|
||||
|
||||
return new DirectoryInfo( newModFolder );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the name for a group or option subfolder based on its parent folder and given name.
|
||||
/// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
|
||||
/// </summary>
|
||||
public static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName )
|
||||
{
|
||||
var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName );
|
||||
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
|
||||
return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder );
|
||||
}
|
||||
|
||||
/// <summary> Create a file for an option group from given data. </summary>
|
||||
public static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name,
|
||||
int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case GroupType.Multi:
|
||||
{
|
||||
var group = new MultiModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
case GroupType.Single:
|
||||
{
|
||||
var group = new SingleModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Create the data for a given sub mod from its data and the folder it is based on. </summary>
|
||||
public static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option )
|
||||
{
|
||||
var list = optionFolder.EnumerateNonHiddenFiles()
|
||||
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
|
||||
.Where( t => t.Item1 );
|
||||
|
||||
var mod = new SubMod( null! ) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = option.Name,
|
||||
Description = option.Description,
|
||||
};
|
||||
foreach( var (_, gamePath, file) in list )
|
||||
{
|
||||
mod.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
|
||||
mod.IncorporateMetaChanges( baseFolder, true );
|
||||
return mod;
|
||||
}
|
||||
|
||||
/// <summary> Create an empty sub mod for single groups with None options. </summary>
|
||||
internal static ISubMod CreateEmptySubMod( string name )
|
||||
=> new SubMod( null! ) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create the default data file from all unused files that were not handled before
|
||||
/// and are used in sub mods.
|
||||
/// </summary>
|
||||
internal static void CreateDefaultFiles( DirectoryInfo directory )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Reload( Penumbra.ModManager, false, out _ );
|
||||
foreach( var file in mod.FindUnusedFiles() )
|
||||
{
|
||||
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
|
||||
mod._default.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges( directory, true );
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, -1));
|
||||
}
|
||||
|
||||
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
|
||||
public static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName )
|
||||
=> new(Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) ));
|
||||
|
||||
/// <summary> Normalize for nicer names, and remove invalid symbols or invalid paths. </summary>
|
||||
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
|
||||
{
|
||||
switch( s )
|
||||
{
|
||||
case ".": return replacement;
|
||||
case "..": return replacement + replacement;
|
||||
}
|
||||
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach( var c in s.Normalize( NormalizationForm.FormKC ) )
|
||||
{
|
||||
if( c.IsInvalidInPath() )
|
||||
{
|
||||
sb.Append( replacement );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append( c );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static void SplitMultiGroups( DirectoryInfo baseDir )
|
||||
{
|
||||
var mod = new Mod( baseDir );
|
||||
|
||||
var files = mod.GroupFiles.ToList();
|
||||
var idx = 0;
|
||||
var reorder = false;
|
||||
foreach( var groupFile in files )
|
||||
{
|
||||
++idx;
|
||||
try
|
||||
{
|
||||
if( reorder )
|
||||
{
|
||||
var newName = $"{baseDir.FullName}\\group_{idx:D3}{groupFile.Name[ 9.. ]}";
|
||||
Penumbra.Log.Debug( $"Moving {groupFile.Name} to {Path.GetFileName( newName )} due to reordering after multi group split." );
|
||||
groupFile.MoveTo( newName, false );
|
||||
}
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
throw new Exception( "Could not reorder group file after splitting multi group on .pmp import.", ex );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = JObject.Parse( File.ReadAllText( groupFile.FullName ) );
|
||||
if( json[ nameof( IModGroup.Type ) ]?.ToObject< GroupType >() is not GroupType.Multi )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = json[ nameof( IModGroup.Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
if( name.Length == 0 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var options = json[ "Options" ]?.Children().ToList();
|
||||
if( options == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( options.Count <= IModGroup.MaxMultiOptions )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Penumbra.Log.Information( $"Splitting multi group {name} in {mod.Name} due to {options.Count} being too many options." );
|
||||
var clone = json.DeepClone();
|
||||
reorder = true;
|
||||
foreach( var o in options.Skip( IModGroup.MaxMultiOptions ) )
|
||||
{
|
||||
o.Remove();
|
||||
}
|
||||
|
||||
var newOptions = clone[ "Options" ]!.Children().ToList();
|
||||
foreach( var o in newOptions.Take( IModGroup.MaxMultiOptions ) )
|
||||
{
|
||||
o.Remove();
|
||||
}
|
||||
|
||||
var match = DuplicateNumber().Match( name );
|
||||
var startNumber = match.Success ? int.Parse( match.Groups[ 0 ].Value ) : 1;
|
||||
name = match.Success ? name[ ..4 ] : name;
|
||||
var oldName = $"{name}, Part {startNumber}";
|
||||
var oldPath = $"{baseDir.FullName}\\group_{idx:D3}_{oldName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json";
|
||||
var newName = $"{name}, Part {startNumber + 1}";
|
||||
var newPath = $"{baseDir.FullName}\\group_{++idx:D3}_{newName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json";
|
||||
json[ nameof( IModGroup.Name ) ] = oldName;
|
||||
clone[ nameof( IModGroup.Name ) ] = newName;
|
||||
|
||||
clone[ nameof( IModGroup.DefaultSettings ) ] = 0u;
|
||||
|
||||
Penumbra.Log.Debug( $"Writing the first {IModGroup.MaxMultiOptions} options to {Path.GetFileName( oldPath )} after split." );
|
||||
using( var oldFile = File.CreateText( oldPath ) )
|
||||
{
|
||||
using var j = new JsonTextWriter( oldFile )
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
json.WriteTo( j );
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug( $"Writing the remaining {options.Count - IModGroup.MaxMultiOptions} options to {Path.GetFileName( newPath )} after split." );
|
||||
using( var newFile = File.CreateText( newPath ) )
|
||||
{
|
||||
using var j = new JsonTextWriter( newFile )
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
clone.WriteTo( j );
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug(
|
||||
$"Deleting the old group file at {groupFile.Name} after splitting it into {Path.GetFileName( oldPath )} and {Path.GetFileName( newPath )}." );
|
||||
groupFile.Delete();
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
throw new Exception( $"Could not split multi group file {groupFile.Name} on .pmp import.", ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking )]
|
||||
private static partial Regex DuplicateNumber();
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public ISubMod Default
|
||||
=> _default;
|
||||
|
||||
public IReadOnlyList<IModGroup> Groups
|
||||
=> _groups;
|
||||
|
||||
internal readonly SubMod _default;
|
||||
internal readonly List<IModGroup> _groups = new();
|
||||
|
||||
public IEnumerable<ISubMod> AllSubMods
|
||||
=> _groups.SelectMany(o => o).Prepend(_default);
|
||||
|
||||
public IEnumerable<MetaManipulation> AllManipulations
|
||||
=> AllSubMods.SelectMany(s => s.Manipulations);
|
||||
|
||||
public IEnumerable<Utf8GamePath> AllRedirects
|
||||
=> AllSubMods.SelectMany(s => s.Files.Keys.Concat(s.FileSwaps.Keys));
|
||||
|
||||
public IEnumerable<FullPath> AllFiles
|
||||
=> AllSubMods.SelectMany(o => o.Files)
|
||||
.Select(p => p.Value);
|
||||
|
||||
public IEnumerable<FileInfo> GroupFiles
|
||||
=> ModPath.EnumerateFiles("group_*.json");
|
||||
|
||||
public List<FullPath> FindUnusedFiles()
|
||||
{
|
||||
var modFiles = AllFiles.ToHashSet();
|
||||
return ModPath.EnumerateDirectories()
|
||||
.Where(d => !d.IsHidden())
|
||||
.SelectMany(FileExtensions.EnumerateNonHiddenFiles)
|
||||
.Select(f => new FullPath(f))
|
||||
.Where(f => !modFiles.Contains(f))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IModGroup? LoadModGroup(Mod mod, FileInfo file, int groupIdx)
|
||||
{
|
||||
if (!File.Exists(file.FullName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single)
|
||||
{
|
||||
case GroupType.Multi: return MultiModGroup.Load(mod, json, groupIdx);
|
||||
case GroupType.Single: return SingleModGroup.Load(mod, json, groupIdx);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not read mod group from {file.FullName}:\n{e}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void LoadAllGroups()
|
||||
{
|
||||
_groups.Clear();
|
||||
var changes = false;
|
||||
foreach (var file in GroupFiles)
|
||||
{
|
||||
var group = LoadModGroup(this, file, _groups.Count);
|
||||
if (group != null && _groups.All(g => g.Name != group.Name))
|
||||
{
|
||||
changes = changes || Penumbra.Filenames.OptionGroupFile(ModPath.FullName, Groups.Count, group.Name) != file.FullName;
|
||||
_groups.Add(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes)
|
||||
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||
}
|
||||
|
||||
private void LoadDefaultOption()
|
||||
{
|
||||
var defaultFile = Penumbra.Filenames.OptionGroupFile(this, -1);
|
||||
_default.SetPosition(-1, 0);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(defaultFile))
|
||||
_default.Load(ModPath, new JObject(), out _);
|
||||
else
|
||||
_default.Load(ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not parse default file for {Name}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteAllTexToolsMeta(MetaFileManager manager)
|
||||
{
|
||||
try
|
||||
{
|
||||
_default.WriteTexToolsMeta(manager, ModPath);
|
||||
foreach (var group in Groups)
|
||||
{
|
||||
var dir = ModCreator.NewOptionDirectory(ModPath, group.Name);
|
||||
if (!dir.Exists)
|
||||
dir.Create();
|
||||
|
||||
foreach (var option in group.OfType<SubMod>())
|
||||
{
|
||||
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name);
|
||||
if (!optionDir.Exists)
|
||||
optionDir.Create();
|
||||
|
||||
option.WriteTexToolsMeta(manager, optionDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Error writing TexToolsMeta:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -29,7 +34,61 @@ public sealed partial class Mod : IMod
|
|||
public bool Favorite { get; internal set; } = false;
|
||||
|
||||
|
||||
// Options
|
||||
public readonly SubMod Default;
|
||||
public readonly List<IModGroup> Groups = new();
|
||||
|
||||
ISubMod IMod.Default
|
||||
=> Default;
|
||||
|
||||
IReadOnlyList<IModGroup> IMod.Groups
|
||||
=> Groups;
|
||||
|
||||
public IEnumerable<SubMod> AllSubMods
|
||||
=> Groups.SelectMany(o => o).OfType<SubMod>().Prepend(Default);
|
||||
|
||||
public List<FullPath> FindUnusedFiles()
|
||||
{
|
||||
var modFiles = AllSubMods.SelectMany(o => o.Files)
|
||||
.Select(p => p.Value)
|
||||
.ToHashSet();
|
||||
return ModPath.EnumerateDirectories()
|
||||
.Where(d => !d.IsHidden())
|
||||
.SelectMany(FileExtensions.EnumerateNonHiddenFiles)
|
||||
.Select(f => new FullPath(f))
|
||||
.Where(f => !modFiles.Contains(f))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Access
|
||||
public override string ToString()
|
||||
=> Name.Text;
|
||||
|
||||
public void WriteAllTexToolsMeta(MetaFileManager manager)
|
||||
{
|
||||
try
|
||||
{
|
||||
TexToolsMeta.WriteTexToolsMeta(manager, Default.Manipulations, ModPath);
|
||||
foreach (var group in Groups)
|
||||
{
|
||||
var dir = ModCreator.NewOptionDirectory(ModPath, group.Name);
|
||||
if (!dir.Exists)
|
||||
dir.Create();
|
||||
|
||||
foreach (var option in group.OfType<SubMod>())
|
||||
{
|
||||
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name);
|
||||
if (!optionDir.Exists)
|
||||
optionDir.Create();
|
||||
|
||||
TexToolsMeta.WriteTexToolsMeta(manager, option.Manipulations, optionDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Error writing TexToolsMeta:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
476
Penumbra/Mods/ModCreator.cs
Normal file
476
Penumbra/Mods/ModCreator.cs
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class ModCreator
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ModDataEditor _dataEditor;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
private readonly IGamePathParser _gamePathParser;
|
||||
|
||||
public ModCreator(SaveService saveService, Configuration config, ModDataEditor dataEditor, MetaFileManager metaFileManager,
|
||||
IGamePathParser gamePathParser)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_config = config;
|
||||
_dataEditor = dataEditor;
|
||||
_metaFileManager = metaFileManager;
|
||||
_gamePathParser = gamePathParser;
|
||||
}
|
||||
|
||||
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
||||
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var newDir = CreateModFolder(basePath, newName);
|
||||
_dataEditor.CreateMeta(newDir, newName, _config.DefaultModAuthor, description, "1.0", string.Empty);
|
||||
CreateDefaultFiles(newDir);
|
||||
return newDir;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.ChatService.NotificationMessage($"Could not create directory for new Mod {newName}:\n{e}", "Failure",
|
||||
NotificationType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Load a mod by its directory. </summary>
|
||||
public Mod? LoadMod(DirectoryInfo modPath, bool incorporateMetaChanges)
|
||||
{
|
||||
modPath.Refresh();
|
||||
if (!modPath.Exists)
|
||||
{
|
||||
Penumbra.Log.Error($"Supplied mod directory {modPath} does not exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var mod = new Mod(modPath);
|
||||
if (ReloadMod(mod, incorporateMetaChanges, out _))
|
||||
return mod;
|
||||
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Warning($"Mod at {modPath} without name is not supported.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary> Reload a mod from its mod path. </summary>
|
||||
public bool ReloadMod(Mod mod, bool incorporateMetaChanges, out ModDataChangeType modDataChange)
|
||||
{
|
||||
modDataChange = ModDataChangeType.Deletion;
|
||||
if (!Directory.Exists(mod.ModPath.FullName))
|
||||
return false;
|
||||
|
||||
modDataChange = _dataEditor.LoadMeta(this, mod);
|
||||
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0)
|
||||
return false;
|
||||
|
||||
_dataEditor.LoadLocalData(mod);
|
||||
LoadDefaultOption(mod);
|
||||
LoadAllGroups(mod);
|
||||
if (incorporateMetaChanges)
|
||||
IncorporateAllMetaChanges(mod, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Load all option groups for a given mod. </summary>
|
||||
public void LoadAllGroups(Mod mod)
|
||||
{
|
||||
mod.Groups.Clear();
|
||||
var changes = false;
|
||||
foreach (var file in _saveService.FileNames.GetOptionGroupFiles(mod))
|
||||
{
|
||||
var group = LoadModGroup(mod, file, mod.Groups.Count);
|
||||
if (group != null && mod.Groups.All(g => g.Name != group.Name))
|
||||
{
|
||||
changes = changes
|
||||
|| _saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name) != file.FullName;
|
||||
mod.Groups.Add(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes)
|
||||
_saveService.SaveAllOptionGroups(mod);
|
||||
}
|
||||
|
||||
/// <summary> Load the default option for a given mod.</summary>
|
||||
public void LoadDefaultOption(Mod mod)
|
||||
{
|
||||
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1);
|
||||
mod.Default.SetPosition(-1, 0);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(defaultFile))
|
||||
mod.Default.Load(mod.ModPath, new JObject(), out _);
|
||||
else
|
||||
mod.Default.Load(mod.ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not parse default file for {mod.Name}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and return a new directory based on the given directory and name, that is <br/>
|
||||
/// - Not Empty.<br/>
|
||||
/// - Unique, by appending (digit) for duplicates.<br/>
|
||||
/// - Containing no symbols invalid for FFXIV or windows paths.<br/>
|
||||
/// </summary>
|
||||
public static DirectoryInfo CreateModFolder(DirectoryInfo outDirectory, string modListName, bool create = true)
|
||||
{
|
||||
var name = modListName;
|
||||
if (name.Length == 0)
|
||||
name = "_";
|
||||
|
||||
var newModFolderBase = NewOptionDirectory(outDirectory, name);
|
||||
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
|
||||
if (newModFolder.Length == 0)
|
||||
throw new IOException("Could not create mod folder: too many folders of the same name exist.");
|
||||
|
||||
if (create)
|
||||
Directory.CreateDirectory(newModFolder);
|
||||
|
||||
return new DirectoryInfo(newModFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert all .meta and .rgsp files to their respective meta changes and add them to their options.
|
||||
/// Deletes the source files if delete is true.
|
||||
/// </summary>
|
||||
public void IncorporateAllMetaChanges(Mod mod, bool delete)
|
||||
{
|
||||
var changes = false;
|
||||
List<string> deleteList = new();
|
||||
foreach (var subMod in mod.AllSubMods)
|
||||
{
|
||||
var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||
changes |= localChanges;
|
||||
if (delete)
|
||||
deleteList.AddRange(localDeleteList);
|
||||
}
|
||||
|
||||
SubMod.DeleteDeleteList(deleteList, delete);
|
||||
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
_saveService.SaveAllOptionGroups(mod);
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod.ModPath, mod.Default));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
|
||||
/// If delete is true, the files are deleted afterwards.
|
||||
/// </summary>
|
||||
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(SubMod option, DirectoryInfo basePath, bool delete)
|
||||
{
|
||||
var deleteList = new List<string>();
|
||||
var oldSize = option.ManipulationData.Count;
|
||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||
foreach (var (key, file) in option.Files.ToList())
|
||||
{
|
||||
var ext1 = key.Extension().AsciiToLower().ToString();
|
||||
var ext2 = file.Extension.ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
if (ext1 == ".meta" || ext2 == ".meta")
|
||||
{
|
||||
option.FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
var meta = new TexToolsMeta(_metaFileManager, _gamePathParser, File.ReadAllBytes(file.FullName),
|
||||
_config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
option.ManipulationData.UnionWith(meta.MetaManipulations);
|
||||
}
|
||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||
{
|
||||
option.FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName),
|
||||
_config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
|
||||
option.ManipulationData.UnionWith(rgsp.MetaManipulations);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
SubMod.DeleteDeleteList(deleteList, delete);
|
||||
return (oldSize < option.ManipulationData.Count, deleteList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the name for a group or option subfolder based on its parent folder and given name.
|
||||
/// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
|
||||
/// </summary>
|
||||
public static DirectoryInfo? NewSubFolderName(DirectoryInfo parentFolder, string subFolderName)
|
||||
{
|
||||
var newModFolderBase = NewOptionDirectory(parentFolder, subFolderName);
|
||||
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
|
||||
return newModFolder.Length == 0 ? null : new DirectoryInfo(newModFolder);
|
||||
}
|
||||
|
||||
/// <summary> Create a file for an option group from given data. </summary>
|
||||
public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name,
|
||||
int priority, int index, uint defaultSettings, string desc, IEnumerable<ISubMod> subMods)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GroupType.Multi:
|
||||
{
|
||||
var group = new MultiModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.PrioritizedOptions.AddRange(subMods.OfType<SubMod>().Select((s, idx) => (s, idx)));
|
||||
_saveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
case GroupType.Single:
|
||||
{
|
||||
var group = new SingleModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.OptionData.AddRange(subMods.OfType<SubMod>());
|
||||
_saveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Create the data for a given sub mod from its data and the folder it is based on. </summary>
|
||||
public ISubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option)
|
||||
{
|
||||
var list = optionFolder.EnumerateNonHiddenFiles()
|
||||
.Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f)))
|
||||
.Where(t => t.Item1);
|
||||
|
||||
var mod = new SubMod(null!) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = option.Name,
|
||||
Description = option.Description,
|
||||
};
|
||||
foreach (var (_, gamePath, file) in list)
|
||||
mod.FileData.TryAdd(gamePath, file);
|
||||
|
||||
IncorporateMetaChanges(mod, baseFolder, true);
|
||||
return mod;
|
||||
}
|
||||
|
||||
/// <summary> Create an empty sub mod for single groups with None options. </summary>
|
||||
internal static ISubMod CreateEmptySubMod(string name)
|
||||
=> new SubMod(null!) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create the default data file from all unused files that were not handled before
|
||||
/// and are used in sub mods.
|
||||
/// </summary>
|
||||
internal void CreateDefaultFiles(DirectoryInfo directory)
|
||||
{
|
||||
var mod = new Mod(directory);
|
||||
ReloadMod(mod, false, out _);
|
||||
foreach (var file in mod.FindUnusedFiles())
|
||||
{
|
||||
if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true))
|
||||
mod.Default.FileData.TryAdd(gamePath, file);
|
||||
}
|
||||
|
||||
IncorporateMetaChanges(mod.Default, directory, true);
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, -1));
|
||||
}
|
||||
|
||||
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
|
||||
public static DirectoryInfo NewOptionDirectory(DirectoryInfo baseDir, string optionName)
|
||||
=> new(Path.Combine(baseDir.FullName, ReplaceBadXivSymbols(optionName)));
|
||||
|
||||
/// <summary> Normalize for nicer names, and remove invalid symbols or invalid paths. </summary>
|
||||
public static string ReplaceBadXivSymbols(string s, string replacement = "_")
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case ".": return replacement;
|
||||
case "..": return replacement + replacement;
|
||||
}
|
||||
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach (var c in s.Normalize(NormalizationForm.FormKC))
|
||||
{
|
||||
if (c.IsInvalidInPath())
|
||||
sb.Append(replacement);
|
||||
else
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void SplitMultiGroups(DirectoryInfo baseDir)
|
||||
{
|
||||
var mod = new Mod(baseDir);
|
||||
|
||||
var files = _saveService.FileNames.GetOptionGroupFiles(mod).ToList();
|
||||
var idx = 0;
|
||||
var reorder = false;
|
||||
foreach (var groupFile in files)
|
||||
{
|
||||
++idx;
|
||||
try
|
||||
{
|
||||
if (reorder)
|
||||
{
|
||||
var newName = $"{baseDir.FullName}\\group_{idx:D3}{groupFile.Name[9..]}";
|
||||
Penumbra.Log.Debug($"Moving {groupFile.Name} to {Path.GetFileName(newName)} due to reordering after multi group split.");
|
||||
groupFile.MoveTo(newName, false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Could not reorder group file after splitting multi group on .pmp import.", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = JObject.Parse(File.ReadAllText(groupFile.FullName));
|
||||
if (json[nameof(IModGroup.Type)]?.ToObject<GroupType>() is not GroupType.Multi)
|
||||
continue;
|
||||
|
||||
var name = json[nameof(IModGroup.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
if (name.Length == 0)
|
||||
continue;
|
||||
|
||||
|
||||
var options = json["Options"]?.Children().ToList();
|
||||
if (options is not { Count: > IModGroup.MaxMultiOptions })
|
||||
continue;
|
||||
|
||||
Penumbra.Log.Information($"Splitting multi group {name} in {mod.Name} due to {options.Count} being too many options.");
|
||||
var clone = json.DeepClone();
|
||||
reorder = true;
|
||||
foreach (var o in options.Skip(IModGroup.MaxMultiOptions))
|
||||
o.Remove();
|
||||
|
||||
var newOptions = clone["Options"]!.Children().ToList();
|
||||
foreach (var o in newOptions.Take(IModGroup.MaxMultiOptions))
|
||||
o.Remove();
|
||||
|
||||
var match = DuplicateNumber().Match(name);
|
||||
var startNumber = match.Success ? int.Parse(match.Groups[0].Value) : 1;
|
||||
name = match.Success ? name[..4] : name;
|
||||
var oldName = $"{name}, Part {startNumber}";
|
||||
var oldPath = $"{baseDir.FullName}\\group_{idx:D3}_{oldName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json";
|
||||
var newName = $"{name}, Part {startNumber + 1}";
|
||||
var newPath = $"{baseDir.FullName}\\group_{++idx:D3}_{newName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json";
|
||||
json[nameof(IModGroup.Name)] = oldName;
|
||||
clone[nameof(IModGroup.Name)] = newName;
|
||||
|
||||
clone[nameof(IModGroup.DefaultSettings)] = 0u;
|
||||
|
||||
Penumbra.Log.Debug($"Writing the first {IModGroup.MaxMultiOptions} options to {Path.GetFileName(oldPath)} after split.");
|
||||
using (var oldFile = File.CreateText(oldPath))
|
||||
{
|
||||
using var j = new JsonTextWriter(oldFile)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
json.WriteTo(j);
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug(
|
||||
$"Writing the remaining {options.Count - IModGroup.MaxMultiOptions} options to {Path.GetFileName(newPath)} after split.");
|
||||
using (var newFile = File.CreateText(newPath))
|
||||
{
|
||||
using var j = new JsonTextWriter(newFile)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
clone.WriteTo(j);
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug(
|
||||
$"Deleting the old group file at {groupFile.Name} after splitting it into {Path.GetFileName(oldPath)} and {Path.GetFileName(newPath)}.");
|
||||
groupFile.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Could not split multi group file {groupFile.Name} on .pmp import.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex DuplicateNumber();
|
||||
|
||||
|
||||
/// <summary> Load an option group for a specific mod by its file and index. </summary>
|
||||
private static IModGroup? LoadModGroup(Mod mod, FileInfo file, int groupIdx)
|
||||
{
|
||||
if (!File.Exists(file.FullName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single)
|
||||
{
|
||||
case GroupType.Multi: return MultiModGroup.Load(mod, json, groupIdx);
|
||||
case GroupType.Single: return SingleModGroup.Load(mod, json, groupIdx);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not read mod group from {file.FullName}:\n{e}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -93,57 +93,6 @@ public sealed class SubMod : ISubMod
|
|||
ManipulationData.Add(s);
|
||||
}
|
||||
|
||||
// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
|
||||
// If delete is true, the files are deleted afterwards.
|
||||
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(DirectoryInfo basePath, bool delete)
|
||||
{
|
||||
var deleteList = new List<string>();
|
||||
var oldSize = ManipulationData.Count;
|
||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||
foreach (var (key, file) in Files.ToList())
|
||||
{
|
||||
var ext1 = key.Extension().AsciiToLower().ToString();
|
||||
var ext2 = file.Extension.ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
if (ext1 == ".meta" || ext2 == ".meta")
|
||||
{
|
||||
FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
var meta = new TexToolsMeta(Penumbra.MetaFileManager, Penumbra.GamePathParser, File.ReadAllBytes(file.FullName),
|
||||
Penumbra.Config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
ManipulationData.UnionWith(meta.MetaManipulations);
|
||||
}
|
||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||
{
|
||||
FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName),
|
||||
Penumbra.Config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
|
||||
ManipulationData.UnionWith(rgsp.MetaManipulations);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
DeleteDeleteList(deleteList, delete);
|
||||
return (oldSize < ManipulationData.Count, deleteList);
|
||||
}
|
||||
|
||||
internal static void DeleteDeleteList(IEnumerable<string> deleteList, bool delete)
|
||||
{
|
||||
if (!delete)
|
||||
|
|
@ -161,63 +110,4 @@ public sealed class SubMod : ISubMod
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTexToolsMeta(MetaFileManager manager, DirectoryInfo basePath, bool test = false)
|
||||
{
|
||||
var files = TexToolsMeta.ConvertToTexTools(manager, Manipulations);
|
||||
|
||||
foreach (var (file, data) in files)
|
||||
{
|
||||
var path = Path.Combine(basePath.FullName, file);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
File.WriteAllBytes(path, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not write meta file {path}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
if (test)
|
||||
TestMetaWriting(manager, files);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void TestMetaWriting(MetaFileManager manager, Dictionary<string, byte[]> files)
|
||||
{
|
||||
var meta = new HashSet<MetaManipulation>(Manipulations.Count);
|
||||
foreach (var (file, data) in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var x = file.EndsWith("rgsp")
|
||||
? TexToolsMeta.FromRgspFile(manager, file, data, Penumbra.Config.KeepDefaultMetaChanges)
|
||||
: new TexToolsMeta(manager, Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges);
|
||||
meta.UnionWith(x.MetaManipulations);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (!Manipulations.SetEquals(meta))
|
||||
{
|
||||
Penumbra.Log.Information("Meta Sets do not equal.");
|
||||
foreach (var (m1, m2) in Manipulations.Zip(meta))
|
||||
Penumbra.Log.Information($"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}");
|
||||
|
||||
foreach (var m in Manipulations.Skip(meta.Count))
|
||||
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
|
||||
|
||||
foreach (var m in meta.Skip(Manipulations.Count))
|
||||
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Log.Information("Meta Sets are equal.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Penumbra.Collections;
|
|||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -19,33 +20,33 @@ public class TemporaryMod : IMod
|
|||
public int TotalManipulations
|
||||
=> Default.Manipulations.Count;
|
||||
|
||||
public ISubMod Default
|
||||
=> _default;
|
||||
public readonly SubMod Default;
|
||||
|
||||
ISubMod IMod.Default
|
||||
=> Default;
|
||||
|
||||
public IReadOnlyList< IModGroup > Groups
|
||||
=> Array.Empty< IModGroup >();
|
||||
|
||||
public IEnumerable< ISubMod > AllSubMods
|
||||
public IEnumerable< SubMod > AllSubMods
|
||||
=> new[] { Default };
|
||||
|
||||
private readonly SubMod _default;
|
||||
|
||||
public TemporaryMod()
|
||||
=> _default = new SubMod( this );
|
||||
=> Default = new SubMod( this );
|
||||
|
||||
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
|
||||
=> _default.FileData[ gamePath ] = fullPath;
|
||||
=> Default.FileData[ gamePath ] = fullPath;
|
||||
|
||||
public bool SetManipulation( MetaManipulation manip )
|
||||
=> _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( manip );
|
||||
=> Default.ManipulationData.Remove( manip ) | Default.ManipulationData.Add( manip );
|
||||
|
||||
public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips )
|
||||
{
|
||||
_default.FileData = dict;
|
||||
_default.ManipulationData = manips;
|
||||
Default.FileData = dict;
|
||||
Default.ManipulationData = manips;
|
||||
}
|
||||
|
||||
public static void SaveTempCollection( ModManager modManager, ModCollection collection, string? character = null )
|
||||
public static void SaveTempCollection( SaveService saveService, ModManager modManager, ModCollection collection, string? character = null )
|
||||
{
|
||||
DirectoryInfo? dir = null;
|
||||
try
|
||||
|
|
@ -55,7 +56,7 @@ public class TemporaryMod : IMod
|
|||
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
||||
var mod = new Mod( dir );
|
||||
var defaultMod = (SubMod) mod.Default;
|
||||
var defaultMod = mod.Default;
|
||||
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
|
||||
{
|
||||
if( gamePath.Path.EndsWith( ".imc"u8 ) )
|
||||
|
|
@ -84,7 +85,7 @@ public class TemporaryMod : IMod
|
|||
foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() )
|
||||
defaultMod.ManipulationData.Add( manip );
|
||||
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
|
||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
|
||||
modManager.AddMod( dir );
|
||||
Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using ImGuiNET;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
|
|
@ -28,8 +27,8 @@ using Penumbra.Interop.Services;
|
|||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Meta;
|
||||
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
public class Penumbra : IDalamudPlugin
|
||||
|
|
@ -39,32 +38,26 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public static Logger Log { get; private set; } = null!;
|
||||
public static ChatService ChatService { get; private set; } = null!;
|
||||
public static FilenameService Filenames { get; private set; } = null!;
|
||||
public static SaveService SaveService { get; private set; } = null!;
|
||||
public static Configuration Config { get; private set; } = null!;
|
||||
|
||||
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
|
||||
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||
public static ModManager ModManager { get; private set; } = null!;
|
||||
public static ModCacheManager ModCaches { get; private set; } = null!;
|
||||
public static CollectionManager CollectionManager { get; private set; } = null!;
|
||||
public static TempCollectionManager TempCollections { get; private set; } = null!;
|
||||
public static TempModManager TempMods { get; private set; } = null!;
|
||||
public static ActorManager Actors { get; private set; } = null!;
|
||||
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
||||
public static IGamePathParser GamePathParser { get; private set; } = null!;
|
||||
public static StainService StainService { get; private set; } = null!;
|
||||
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||
public static ModManager ModManager { get; private set; } = null!;
|
||||
public static ModCacheManager ModCaches { get; private set; } = null!;
|
||||
public static CollectionManager CollectionManager { get; private set; } = null!;
|
||||
public static ActorManager Actors { get; private set; } = null!;
|
||||
|
||||
// TODO
|
||||
public static ValidityChecker ValidityChecker { get; private set; } = null!;
|
||||
public readonly RedrawService RedrawService;
|
||||
public readonly ModFileSystem ModFileSystem;
|
||||
public HttpApi HttpApi = null!;
|
||||
internal ConfigWindow? ConfigWindow { get; private set; }
|
||||
|
||||
public readonly RedrawService RedrawService;
|
||||
public readonly ModFileSystem ModFileSystem;
|
||||
public HttpApi HttpApi = null!;
|
||||
internal ConfigWindow? ConfigWindow { get; private set; }
|
||||
private PenumbraWindowSystem? _windowSystem;
|
||||
private bool _disposed;
|
||||
private readonly ValidityChecker _validityChecker;
|
||||
private readonly ResidentResourceManager _residentResources;
|
||||
private readonly TempModManager _tempMods;
|
||||
private readonly TempCollectionManager _tempCollections;
|
||||
private PenumbraWindowSystem? _windowSystem;
|
||||
private bool _disposed;
|
||||
|
||||
private readonly PenumbraNew _tmp;
|
||||
|
||||
|
|
@ -73,29 +66,24 @@ public class Penumbra : IDalamudPlugin
|
|||
Log = PenumbraNew.Log;
|
||||
try
|
||||
{
|
||||
_tmp = new PenumbraNew(this, pluginInterface);
|
||||
ChatService = _tmp.Services.GetRequiredService<ChatService>();
|
||||
Filenames = _tmp.Services.GetRequiredService<FilenameService>();
|
||||
SaveService = _tmp.Services.GetRequiredService<SaveService>();
|
||||
ValidityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
||||
_tmp = new PenumbraNew(this, pluginInterface);
|
||||
ChatService = _tmp.Services.GetRequiredService<ChatService>();
|
||||
_validityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
||||
_tmp.Services.GetRequiredService<BackupService>();
|
||||
Config = _tmp.Services.GetRequiredService<Configuration>();
|
||||
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
||||
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
||||
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
||||
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
||||
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
||||
StainService = _tmp.Services.GetRequiredService<StainService>();
|
||||
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||
ResidentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
||||
_tempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||
_residentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
||||
_tmp.Services.GetRequiredService<ResourceManagerService>();
|
||||
ModManager = _tmp.Services.GetRequiredService<ModManager>();
|
||||
CollectionManager = _tmp.Services.GetRequiredService<CollectionManager>();
|
||||
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||
_tempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||
ModFileSystem = _tmp.Services.GetRequiredService<ModFileSystem>();
|
||||
RedrawService = _tmp.Services.GetRequiredService<RedrawService>();
|
||||
_tmp.Services.GetRequiredService<ResourceService>();
|
||||
ModCaches = _tmp.Services.GetRequiredService<ModCacheManager>();
|
||||
ModCaches = _tmp.Services.GetRequiredService<ModCacheManager>();
|
||||
using (var t = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.PathResolver))
|
||||
{
|
||||
_tmp.Services.GetRequiredService<PathResolver>();
|
||||
|
|
@ -104,14 +92,14 @@ public class Penumbra : IDalamudPlugin
|
|||
SetupInterface();
|
||||
SetupApi();
|
||||
|
||||
ValidityChecker.LogExceptions();
|
||||
_validityChecker.LogExceptions();
|
||||
Log.Information(
|
||||
$"Penumbra Version {ValidityChecker.Version}, Commit #{ValidityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}.");
|
||||
$"Penumbra Version {_validityChecker.Version}, Commit #{_validityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}.");
|
||||
OtterTex.NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName);
|
||||
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
||||
|
||||
if (CharacterUtility.Ready)
|
||||
ResidentResources.Reload();
|
||||
_residentResources.Reload();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -173,7 +161,7 @@ public class Penumbra : IDalamudPlugin
|
|||
if (CharacterUtility.Ready)
|
||||
{
|
||||
CollectionManager.Active.Default.SetFiles();
|
||||
ResidentResources.Reload();
|
||||
_residentResources.Reload();
|
||||
RedrawService.RedrawAll(RedrawType.Redraw);
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +170,7 @@ public class Penumbra : IDalamudPlugin
|
|||
if (CharacterUtility.Ready)
|
||||
{
|
||||
CharacterUtility.ResetAll();
|
||||
ResidentResources.Reload();
|
||||
_residentResources.Reload();
|
||||
RedrawService.RedrawAll(RedrawType.Redraw);
|
||||
}
|
||||
}
|
||||
|
|
@ -211,8 +199,8 @@ public class Penumbra : IDalamudPlugin
|
|||
var exists = Config.ModDirectory.Length > 0 && Directory.Exists(Config.ModDirectory);
|
||||
var drive = exists ? new DriveInfo(new DirectoryInfo(Config.ModDirectory).Root.FullName) : null;
|
||||
sb.AppendLine("**Settings**");
|
||||
sb.Append($"> **`Plugin Version: `** {ValidityChecker.Version}\n");
|
||||
sb.Append($"> **`Commit Hash: `** {ValidityChecker.CommitHash}\n");
|
||||
sb.Append($"> **`Plugin Version: `** {_validityChecker.Version}\n");
|
||||
sb.Append($"> **`Commit Hash: `** {_validityChecker.CommitHash}\n");
|
||||
sb.Append($"> **`Enable Mods: `** {Config.EnableMods}\n");
|
||||
sb.Append($"> **`Enable HTTP API: `** {Config.EnableHttpApi}\n");
|
||||
sb.Append($"> **`Operating System: `** {(DalamudUtil.IsLinux() ? "Mac/Linux (Wine)" : "Windows")}\n");
|
||||
|
|
@ -235,9 +223,9 @@ public class Penumbra : IDalamudPlugin
|
|||
$"> **`Mods with FileSwaps: `** {ModCaches.Count(m => m.TotalSwapCount > 0)}, Total: {ModCaches.Sum(m => m.TotalSwapCount)}\n");
|
||||
sb.Append(
|
||||
$"> **`Mods with Meta Manipulations:`** {ModCaches.Count(m => m.TotalManipulations > 0)}, Total {ModCaches.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");
|
||||
$"> **`#Temp Mods: `** {_tempMods.Mods.Sum(kvp => kvp.Value.Count) + _tempMods.ModsForAllCollections.Count}\n");
|
||||
|
||||
string CharacterName(ActorIdentifier id, string name)
|
||||
{
|
||||
|
|
@ -259,8 +247,8 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
sb.AppendLine("**Collections**");
|
||||
sb.Append($"> **`#Collections: `** {CollectionManager.Storage.Count - 1}\n");
|
||||
sb.Append($"> **`#Temp Collections: `** {TempCollections.Count}\n");
|
||||
sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count - TempCollections.Count}\n");
|
||||
sb.Append($"> **`#Temp Collections: `** {_tempCollections.Count}\n");
|
||||
sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count - _tempCollections.Count}\n");
|
||||
sb.Append($"> **`Base Collection: `** {CollectionManager.Active.Default.AnonymizedName}\n");
|
||||
sb.Append($"> **`Interface Collection: `** {CollectionManager.Active.Interface.AnonymizedName}\n");
|
||||
sb.Append($"> **`Selected Collection: `** {CollectionManager.Active.Current.AnonymizedName}\n");
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ public class PenumbraNew
|
|||
services.AddSingleton<TempModManager>()
|
||||
.AddSingleton<ModDataEditor>()
|
||||
.AddSingleton<ModOptionEditor>()
|
||||
.AddSingleton<ModCreator>()
|
||||
.AddSingleton<ModManager>()
|
||||
.AddSingleton<ModExportManager>()
|
||||
.AddSingleton<ModImportManager>()
|
||||
|
|
|
|||
|
|
@ -218,22 +218,26 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera), (SetId)_sourceId,
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
|
|
@ -276,9 +280,10 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
|
||||
private void CreateMod()
|
||||
{
|
||||
var newDir = ModCreator.CreateModFolder(_modManager.BasePath, _newModName);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||
ModCreator.CreateDefaultFiles(newDir);
|
||||
var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName, CreateDescription());
|
||||
if (newDir == null)
|
||||
return;
|
||||
|
||||
_modManager.AddMod(newDir);
|
||||
if (!_swapData.WriteMod(_modManager, _modManager[^1],
|
||||
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private static bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
private bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||
{
|
||||
if( !file.ColorSets.Any( c => c.HasRows ) )
|
||||
{
|
||||
|
|
@ -95,9 +95,9 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
|
||||
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||
private bool DrawPreviewDye( MtrlFile file, bool disabled )
|
||||
{
|
||||
var (dyeId, (name, dyeColor, _)) = Penumbra.StainService.StainCombo.CurrentSelection;
|
||||
var (dyeId, (name, dyeColor, _)) = _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.";
|
||||
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 )
|
||||
{
|
||||
ret |= file.ApplyDyeTemplate( Penumbra.StainService.StmFile, j, i, dyeId );
|
||||
ret |= file.ApplyDyeTemplate( _stainService.StmFile, j, i, dyeId );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ public partial class ModEditWindow
|
|||
|
||||
ImGui.SameLine();
|
||||
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
|
||||
Penumbra.StainService.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
||||
_stainService.StainCombo.Draw( label, dyeColor, string.Empty, true );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ public partial class ModEditWindow
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
private bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||
{
|
||||
static bool FixFloat( ref float val, float current )
|
||||
{
|
||||
|
|
@ -355,10 +355,10 @@ public partial class ModEditWindow
|
|||
ImGui.TableNextColumn();
|
||||
if( hasDye )
|
||||
{
|
||||
if( Penumbra.StainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
if(_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
|
||||
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
|
||||
{
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainService.TemplateCombo.CurrentSelection;
|
||||
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = _stainService.TemplateCombo.CurrentSelection;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
|
@ -376,10 +376,10 @@ public partial class ModEditWindow
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
private bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
|
||||
{
|
||||
var stain = Penumbra.StainService.StainCombo.CurrentSelection.Key;
|
||||
if( stain == 0 || !Penumbra.StainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||
var stain = _stainService.StainCombo.CurrentSelection.Key;
|
||||
if( stain == 0 || !_stainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -390,7 +390,7 @@ public partial class ModEditWindow
|
|||
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
|
||||
"Apply the selected dye to this row.", disabled, true );
|
||||
|
||||
ret = ret && file.ApplyDyeTemplate( Penumbra.StainService.StmFile, colorSetIdx, rowIdx, stain );
|
||||
ret = ret && file.ApplyDyeTemplate(_stainService.StmFile, colorSetIdx, rowIdx, stain );
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ using Penumbra.Import.Textures;
|
|||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -33,6 +34,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
private readonly ItemSwapTab _itemSwapTab;
|
||||
private readonly DataManager _gameData;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
private readonly StainService _stainService;
|
||||
|
||||
private Mod? _mod;
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
|
|
@ -495,17 +497,18 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, DataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager)
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager, StainService stainService)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_modCaches = modCaches;
|
||||
_metaFileManager = metaFileManager;
|
||||
_gameData = gameData;
|
||||
_fileDialog = fileDialog;
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_modCaches = modCaches;
|
||||
_metaFileManager = metaFileManager;
|
||||
_stainService = stainService;
|
||||
_gameData = gameData;
|
||||
_fileDialog = fileDialog;
|
||||
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _fileDialog, "Materials", ".mtrl",
|
||||
() => _editor.Files.Mtrl, DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||
bytes => new MtrlTab(this, new MtrlFile(bytes)));
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public sealed class ConfigWindow : Window
|
|||
UiHelpers.SetupCommonSizes();
|
||||
try
|
||||
{
|
||||
if (Penumbra.ValidityChecker.ImcExceptions.Count > 0)
|
||||
if (_validityChecker.ImcExceptions.Count > 0)
|
||||
{
|
||||
DrawProblemWindow(
|
||||
$"There were {_validityChecker.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n"
|
||||
|
|
@ -75,14 +75,14 @@ public sealed class ConfigWindow : Window
|
|||
+ "Please use the Launcher's Repair Game Files function to repair your client installation.");
|
||||
DrawImcExceptions();
|
||||
}
|
||||
else if (!Penumbra.ValidityChecker.IsValidSourceRepo)
|
||||
else if (!_validityChecker.IsValidSourceRepo)
|
||||
{
|
||||
DrawProblemWindow(
|
||||
$"You are loading a release version of Penumbra from the repository \"{_pluginInterface.SourceRepository}\" instead of the official repository.\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.");
|
||||
}
|
||||
else if (Penumbra.ValidityChecker.IsNotInstalledPenumbra)
|
||||
else if (_validityChecker.IsNotInstalledPenumbra)
|
||||
{
|
||||
DrawProblemWindow(
|
||||
$"You are loading a release version of Penumbra from \"{_pluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n"
|
||||
|
|
@ -90,7 +90,7 @@ public sealed class ConfigWindow : Window
|
|||
+ "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 are developing for Penumbra and see this, you should compile your version in debug mode to avoid it.");
|
||||
}
|
||||
else if (Penumbra.ValidityChecker.DevPenumbraExists)
|
||||
else if (_validityChecker.DevPenumbraExists)
|
||||
{
|
||||
DrawProblemWindow(
|
||||
$"You are loading a installed version of Penumbra from \"{_pluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", "
|
||||
|
|
|
|||
|
|
@ -119,18 +119,14 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
DrawHelpPopup();
|
||||
|
||||
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
|
||||
try
|
||||
{
|
||||
var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName);
|
||||
if (newDir != null)
|
||||
{
|
||||
var newDir = ModCreator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||
ModCreator.CreateDefaultFiles(newDir);
|
||||
_modManager.AddMod(newDir);
|
||||
_newModName = string.Empty;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not create directory for new Mod {_newModName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
while (_modImportManager.AddUnpackedMod(out var mod))
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue