mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Tmp for Mod2
This commit is contained in:
parent
069ae772a5
commit
48e442a9fd
30 changed files with 697 additions and 252 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0c32ec432d38093a402e0dae6dc5c62a883b163b
|
Subproject commit 5968fc8dde7867ec9b7216deeed93d7b59a41ab8
|
||||||
|
|
@ -172,7 +172,7 @@ public partial class ModCollection
|
||||||
|
|
||||||
|
|
||||||
// A changed mod forces changes for all collections, active and inactive.
|
// A changed mod forces changes for all collections, active and inactive.
|
||||||
private void OnModChanged( Mod.ChangeType type, int idx, Mod mod )
|
private void OnModChanged( Mod.ChangeType type, Mod mod )
|
||||||
{
|
{
|
||||||
switch( type )
|
switch( type )
|
||||||
{
|
{
|
||||||
|
|
@ -188,15 +188,15 @@ public partial class ModCollection
|
||||||
var settings = new List< ModSettings? >( _collections.Count );
|
var settings = new List< ModSettings? >( _collections.Count );
|
||||||
foreach( var collection in this )
|
foreach( var collection in this )
|
||||||
{
|
{
|
||||||
settings.Add( collection[ idx ].Settings );
|
settings.Add( collection[ mod.Index ].Settings );
|
||||||
collection.RemoveMod( mod, idx );
|
collection.RemoveMod( mod, mod.Index );
|
||||||
}
|
}
|
||||||
|
|
||||||
OnModRemovedActive( mod.Resources.MetaManipulations.Count > 0, settings );
|
OnModRemovedActive( mod.Resources.MetaManipulations.Count > 0, settings );
|
||||||
break;
|
break;
|
||||||
case Mod.ChangeType.Changed:
|
case Mod.ChangeType.Changed:
|
||||||
foreach( var collection in this.Where(
|
foreach( var collection in this.Where(
|
||||||
collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
|
collection => collection.Settings[ mod.Index ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
|
||||||
{
|
{
|
||||||
collection.Save();
|
collection.Save();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Dalamud.Logging;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public interface IModGroup : IEnumerable< ISubMod >
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string Description { get; }
|
|
||||||
public SelectType Type { get; }
|
|
||||||
public int Priority { get; }
|
|
||||||
|
|
||||||
public int OptionPriority( Index optionIdx );
|
|
||||||
|
|
||||||
public ISubMod this[ Index idx ] { get; }
|
|
||||||
|
|
||||||
public int Count { get; }
|
|
||||||
|
|
||||||
public bool IsOption
|
|
||||||
=> Type switch
|
|
||||||
{
|
|
||||||
SelectType.Single => Count > 1,
|
|
||||||
SelectType.Multi => Count > 0,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
public void Save( DirectoryInfo basePath );
|
|
||||||
|
|
||||||
public string FileName( DirectoryInfo basePath )
|
|
||||||
=> Path.Combine( basePath.FullName, Name.RemoveInvalidPathSymbols() + ".json" );
|
|
||||||
|
|
||||||
public void DeleteFile( DirectoryInfo basePath )
|
|
||||||
{
|
|
||||||
var file = FileName( basePath );
|
|
||||||
if( !File.Exists( file ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete( file );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( $"Could not delete file {file}:\n{e}" );
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public interface ISubMod
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
|
|
||||||
public IReadOnlyList< MetaManipulation > Manipulations { get; }
|
|
||||||
}
|
|
||||||
|
|
@ -29,7 +29,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
public void RenameModGroup( Mod2 mod, int groupIdx, string newName )
|
public void RenameModGroup( Mod2 mod, int groupIdx, string newName )
|
||||||
{
|
{
|
||||||
var group = mod._options[ groupIdx ];
|
var group = mod._groups[ groupIdx ];
|
||||||
var oldName = group.Name;
|
var oldName = group.Name;
|
||||||
if( oldName == newName || !VerifyFileName( mod, group, newName ) )
|
if( oldName == newName || !VerifyFileName( mod, group, newName ) )
|
||||||
{
|
{
|
||||||
|
|
@ -53,25 +53,25 @@ public sealed partial class Mod2
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxPriority = mod._options.Max( o => o.Priority ) + 1;
|
var maxPriority = mod._groups.Max( o => o.Priority ) + 1;
|
||||||
|
|
||||||
mod._options.Add( type == SelectType.Multi
|
mod._groups.Add( type == SelectType.Multi
|
||||||
? new MultiModGroup { Name = newName, Priority = maxPriority }
|
? new MultiModGroup { Name = newName, Priority = maxPriority }
|
||||||
: new SingleModGroup { Name = newName, Priority = maxPriority } );
|
: new SingleModGroup { Name = newName, Priority = maxPriority } );
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.GroupAdded, mod, mod._options.Count - 1, 0 );
|
ModOptionChanged.Invoke( ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteModGroup( Mod2 mod, int groupIdx )
|
public void DeleteModGroup( Mod2 mod, int groupIdx )
|
||||||
{
|
{
|
||||||
var group = mod._options[ groupIdx ];
|
var group = mod._groups[ groupIdx ];
|
||||||
mod._options.RemoveAt( groupIdx );
|
mod._groups.RemoveAt( groupIdx );
|
||||||
group.DeleteFile( BasePath );
|
group.DeleteFile( BasePath );
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.GroupDeleted, mod, groupIdx, 0 );
|
ModOptionChanged.Invoke( ModOptionChangeType.GroupDeleted, mod, groupIdx, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeGroupDescription( Mod2 mod, int groupIdx, string newDescription )
|
public void ChangeGroupDescription( Mod2 mod, int groupIdx, string newDescription )
|
||||||
{
|
{
|
||||||
var group = mod._options[ groupIdx ];
|
var group = mod._groups[ groupIdx ];
|
||||||
if( group.Description == newDescription )
|
if( group.Description == newDescription )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -88,7 +88,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
public void ChangeGroupPriority( Mod2 mod, int groupIdx, int newPriority )
|
public void ChangeGroupPriority( Mod2 mod, int groupIdx, int newPriority )
|
||||||
{
|
{
|
||||||
var group = mod._options[ groupIdx ];
|
var group = mod._groups[ groupIdx ];
|
||||||
if( group.Priority == newPriority )
|
if( group.Priority == newPriority )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -105,7 +105,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
public void ChangeOptionPriority( Mod2 mod, int groupIdx, int optionIdx, int newPriority )
|
public void ChangeOptionPriority( Mod2 mod, int groupIdx, int optionIdx, int newPriority )
|
||||||
{
|
{
|
||||||
switch( mod._options[ groupIdx ] )
|
switch( mod._groups[ groupIdx ] )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
ChangeGroupPriority( mod, groupIdx, newPriority );
|
ChangeGroupPriority( mod, groupIdx, newPriority );
|
||||||
|
|
@ -124,7 +124,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
public void RenameOption( Mod2 mod, int groupIdx, int optionIdx, string newName )
|
public void RenameOption( Mod2 mod, int groupIdx, int optionIdx, string newName )
|
||||||
{
|
{
|
||||||
switch( mod._options[ groupIdx ] )
|
switch( mod._groups[ groupIdx ] )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
if( s.OptionData[ optionIdx ].Name == newName )
|
if( s.OptionData[ optionIdx ].Name == newName )
|
||||||
|
|
@ -150,7 +150,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
public void AddOption( Mod2 mod, int groupIdx, string newName )
|
public void AddOption( Mod2 mod, int groupIdx, string newName )
|
||||||
{
|
{
|
||||||
switch( mod._options[ groupIdx ] )
|
switch( mod._groups[ groupIdx ] )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
s.OptionData.Add( new SubMod { Name = newName } );
|
s.OptionData.Add( new SubMod { Name = newName } );
|
||||||
|
|
@ -160,12 +160,12 @@ public sealed partial class Mod2
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, mod._options[ groupIdx ].Count - 1 );
|
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, mod._groups[ groupIdx ].Count - 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteOption( Mod2 mod, int groupIdx, int optionIdx )
|
public void DeleteOption( Mod2 mod, int groupIdx, int optionIdx )
|
||||||
{
|
{
|
||||||
switch( mod._options[ groupIdx ] )
|
switch( mod._groups[ groupIdx ] )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
s.OptionData.RemoveAt( optionIdx );
|
s.OptionData.RemoveAt( optionIdx );
|
||||||
|
|
@ -181,26 +181,24 @@ public sealed partial class Mod2
|
||||||
public void OptionSetManipulation( Mod2 mod, int groupIdx, int optionIdx, MetaManipulation manip, bool delete = false )
|
public void OptionSetManipulation( Mod2 mod, int groupIdx, int optionIdx, MetaManipulation manip, bool delete = false )
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod( mod, groupIdx, optionIdx );
|
var subMod = GetSubMod( mod, groupIdx, optionIdx );
|
||||||
var idx = subMod.ManipulationData.FindIndex( m => m.Equals( manip ) );
|
|
||||||
if( delete )
|
if( delete )
|
||||||
{
|
{
|
||||||
if( idx < 0 )
|
if( !subMod.ManipulationData.Remove( manip ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
subMod.ManipulationData.RemoveAt( idx );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( idx >= 0 )
|
if( subMod.ManipulationData.TryGetValue( manip, out var oldManip ) )
|
||||||
{
|
{
|
||||||
if( manip.EntryEquals( subMod.ManipulationData[ idx ] ) )
|
if( manip.EntryEquals( oldManip ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
subMod.ManipulationData[ idx ] = manip;
|
subMod.ManipulationData.Remove( oldManip );
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -232,7 +230,7 @@ public sealed partial class Mod2
|
||||||
private bool VerifyFileName( Mod2 mod, IModGroup? group, string newName )
|
private bool VerifyFileName( Mod2 mod, IModGroup? group, string newName )
|
||||||
{
|
{
|
||||||
var path = newName.RemoveInvalidPathSymbols();
|
var path = newName.RemoveInvalidPathSymbols();
|
||||||
if( mod.Options.Any( o => !ReferenceEquals( o, group )
|
if( mod.Groups.Any( o => !ReferenceEquals( o, group )
|
||||||
&& string.Equals( o.Name.RemoveInvalidPathSymbols(), path, StringComparison.InvariantCultureIgnoreCase ) ) )
|
&& string.Equals( o.Name.RemoveInvalidPathSymbols(), path, StringComparison.InvariantCultureIgnoreCase ) ) )
|
||||||
{
|
{
|
||||||
PluginLog.Warning( $"Could not name option {newName} because option with same filename {path} already exists." );
|
PluginLog.Warning( $"Could not name option {newName} because option with same filename {path} already exists." );
|
||||||
|
|
@ -244,7 +242,7 @@ public sealed partial class Mod2
|
||||||
|
|
||||||
private static SubMod GetSubMod( Mod2 mod, int groupIdx, int optionIdx )
|
private static SubMod GetSubMod( Mod2 mod, int groupIdx, int optionIdx )
|
||||||
{
|
{
|
||||||
return mod._options[ groupIdx ] switch
|
return mod._groups[ groupIdx ] switch
|
||||||
{
|
{
|
||||||
SingleModGroup s => s.OptionData[ optionIdx ],
|
SingleModGroup s => s.OptionData[ optionIdx ],
|
||||||
MultiModGroup m => m.PrioritizedOptions[ optionIdx ].Mod,
|
MultiModGroup m => m.PrioritizedOptions[ optionIdx ].Mod,
|
||||||
|
|
@ -285,15 +283,15 @@ public sealed partial class Mod2
|
||||||
// File deletion is handled in the actual function.
|
// File deletion is handled in the actual function.
|
||||||
if( type != ModOptionChangeType.GroupDeleted )
|
if( type != ModOptionChangeType.GroupDeleted )
|
||||||
{
|
{
|
||||||
mod._options[groupIdx].Save( mod.BasePath );
|
IModGroup.SaveModGroup( mod._groups[ groupIdx ], mod.BasePath );
|
||||||
}
|
}
|
||||||
|
|
||||||
// State can not change on adding groups, as they have no immediate options.
|
// State can not change on adding groups, as they have no immediate options.
|
||||||
mod.HasOptions = type switch
|
mod.HasOptions = type switch
|
||||||
{
|
{
|
||||||
ModOptionChangeType.GroupDeleted => mod.HasOptions = mod.Options.Any( o => o.IsOption ),
|
ModOptionChangeType.GroupDeleted => mod.HasOptions = mod.Groups.Any( o => o.IsOption ),
|
||||||
ModOptionChangeType.OptionAdded => mod.HasOptions |= mod._options[groupIdx].IsOption,
|
ModOptionChangeType.OptionAdded => mod.HasOptions |= mod._groups[ groupIdx ].IsOption,
|
||||||
ModOptionChangeType.OptionDeleted => mod.HasOptions = mod.Options.Any( o => o.IsOption ),
|
ModOptionChangeType.OptionDeleted => mod.HasOptions = mod.Groups.Any( o => o.IsOption ),
|
||||||
_ => mod.HasOptions,
|
_ => mod.HasOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -15,18 +15,10 @@ public partial class Mod2
|
||||||
public DirectoryInfo BasePath { get; private set; }
|
public DirectoryInfo BasePath { get; private set; }
|
||||||
public int Index { get; private set; } = -1;
|
public int Index { get; private set; } = -1;
|
||||||
|
|
||||||
private FileInfo MetaFile
|
private Mod2( DirectoryInfo basePath )
|
||||||
=> new(Path.Combine( BasePath.FullName, "meta.json" ));
|
=> BasePath = basePath;
|
||||||
|
|
||||||
private Mod2( ModFolder parentFolder, DirectoryInfo basePath )
|
public static Mod2? LoadMod( DirectoryInfo basePath )
|
||||||
{
|
|
||||||
BasePath = basePath;
|
|
||||||
Order = new Mod.SortOrder( parentFolder, Name );
|
|
||||||
//Order.ParentFolder.AddMod( this ); // TODO
|
|
||||||
ComputeChangedItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Mod2? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
|
|
||||||
{
|
{
|
||||||
basePath.Refresh();
|
basePath.Refresh();
|
||||||
if( !basePath.Exists )
|
if( !basePath.Exists )
|
||||||
|
|
@ -35,22 +27,18 @@ public partial class Mod2
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mod = new Mod2( parentFolder, basePath );
|
var mod = new Mod2( basePath );
|
||||||
|
mod.LoadMeta();
|
||||||
var metaFile = mod.MetaFile;
|
|
||||||
if( !metaFile.Exists )
|
|
||||||
{
|
|
||||||
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mod.LoadMetaFromFile( metaFile );
|
|
||||||
if( mod.Name.Length == 0 )
|
if( mod.Name.Length == 0 )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"Mod at {basePath} without name is not supported." );
|
PluginLog.Error( $"Mod at {basePath} without name is not supported." );
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.ReloadFiles();
|
mod.LoadDefaultOption();
|
||||||
|
mod.LoadAllGroups();
|
||||||
|
mod.ComputeChangedItems();
|
||||||
|
mod.SetHasOptions();
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,16 +5,16 @@ namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed partial class Mod2
|
public sealed partial class Mod2
|
||||||
{
|
{
|
||||||
public SortedList<string, object?> ChangedItems { get; } = new();
|
public SortedList< string, object? > ChangedItems { get; } = new();
|
||||||
public string LowerChangedItemsString { get; private set; } = string.Empty;
|
public string LowerChangedItemsString { get; private set; } = string.Empty;
|
||||||
|
|
||||||
public void ComputeChangedItems()
|
public void ComputeChangedItems()
|
||||||
{
|
{
|
||||||
var identifier = GameData.GameData.GetIdentifier();
|
var identifier = GameData.GameData.GetIdentifier();
|
||||||
ChangedItems.Clear();
|
ChangedItems.Clear();
|
||||||
foreach( var (file, _) in AllFiles )
|
foreach( var gamePath in AllRedirects )
|
||||||
{
|
{
|
||||||
identifier.Identify( ChangedItems, file.ToGamePath() );
|
identifier.Identify( ChangedItems, gamePath.ToGamePath() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: manipulations
|
// TODO: manipulations
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public partial class Mod2
|
|
||||||
{
|
|
||||||
private sealed class SubMod : ISubMod
|
|
||||||
{
|
|
||||||
public string Name { get; set; } = "Default";
|
|
||||||
|
|
||||||
[JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
|
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > FileData = new();
|
|
||||||
|
|
||||||
[JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
|
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
|
||||||
|
|
||||||
public readonly List< MetaManipulation > ManipulationData = new();
|
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
|
||||||
=> FileData;
|
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
|
|
||||||
=> FileSwapData;
|
|
||||||
|
|
||||||
public IReadOnlyList< MetaManipulation > Manipulations
|
|
||||||
=> ManipulationData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
|
@ -8,36 +11,88 @@ namespace Penumbra.Mods;
|
||||||
|
|
||||||
public partial class Mod2
|
public partial class Mod2
|
||||||
{
|
{
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > RemainingFiles
|
public ISubMod Default
|
||||||
=> _remainingFiles;
|
=> _default;
|
||||||
|
|
||||||
public IReadOnlyList< IModGroup > Options
|
public IReadOnlyList< IModGroup > Groups
|
||||||
=> _options;
|
=> _groups;
|
||||||
|
|
||||||
public bool HasOptions { get; private set; } = false;
|
public bool HasOptions { get; private set; }
|
||||||
|
|
||||||
private void SetHasOptions()
|
private void SetHasOptions()
|
||||||
{
|
{
|
||||||
HasOptions = _options.Any( o
|
HasOptions = _groups.Any( o
|
||||||
=> o is MultiModGroup m && m.PrioritizedOptions.Count > 0 || o is SingleModGroup s && s.OptionData.Count > 1 );
|
=> o is MultiModGroup m && m.PrioritizedOptions.Count > 0
|
||||||
|
|| o is SingleModGroup s && s.OptionData.Count > 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary< Utf8GamePath, FullPath > _remainingFiles = new();
|
|
||||||
private readonly List< IModGroup > _options = new();
|
|
||||||
|
|
||||||
public IEnumerable< (Utf8GamePath, FullPath) > AllFiles
|
private readonly SubMod _default = new();
|
||||||
=> _remainingFiles.Concat( _options.SelectMany( o => o ).SelectMany( o => o.Files.Concat( o.FileSwaps ) ) )
|
private readonly List< IModGroup > _groups = new();
|
||||||
.Select( kvp => ( kvp.Key, kvp.Value ) );
|
|
||||||
|
public IEnumerable< ISubMod > AllSubMods
|
||||||
|
=> _groups.SelectMany( o => o ).Prepend( _default );
|
||||||
|
|
||||||
public IEnumerable< MetaManipulation > AllManipulations
|
public IEnumerable< MetaManipulation > AllManipulations
|
||||||
=> _options.SelectMany( o => o ).SelectMany( o => o.Manipulations );
|
=> AllSubMods.SelectMany( s => s.Manipulations );
|
||||||
|
|
||||||
private void ReloadFiles()
|
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
|
||||||
|
=> BasePath.EnumerateFiles( "group_*.json" );
|
||||||
|
|
||||||
|
public List< FullPath > FindUnusedFiles()
|
||||||
{
|
{
|
||||||
// _remainingFiles.Clear();
|
var modFiles = AllFiles.ToHashSet();
|
||||||
// _options.Clear();
|
return BasePath.EnumerateDirectories()
|
||||||
// HasOptions = false;
|
.SelectMany( f => f.EnumerateFiles( "*", SearchOption.AllDirectories ) )
|
||||||
// if( !Directory.Exists( BasePath.FullName ) )
|
.Select( f => new FullPath( f ) )
|
||||||
// return;
|
.Where( f => !modFiles.Contains( f ) )
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List< FullPath > FindMissingFiles()
|
||||||
|
=> AllFiles.Where( f => !f.Exists ).ToList();
|
||||||
|
|
||||||
|
public static IModGroup? LoadModGroup( FileInfo file, DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
if( !File.Exists( file.FullName ) )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JObject.Parse( File.ReadAllText( file.FullName ) );
|
||||||
|
switch( json[ nameof( Type ) ]?.ToObject< SelectType >() ?? SelectType.Single )
|
||||||
|
{
|
||||||
|
case SelectType.Multi: return MultiModGroup.Load( json, basePath );
|
||||||
|
case SelectType.Single: return SingleModGroup.Load( json, basePath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not read mod group from {file.FullName}:\n{e}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadAllGroups()
|
||||||
|
{
|
||||||
|
_groups.Clear();
|
||||||
|
foreach( var file in GroupFiles )
|
||||||
|
{
|
||||||
|
var group = LoadModGroup( file, BasePath );
|
||||||
|
if( group != null )
|
||||||
|
{
|
||||||
|
_groups.Add( group );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.Importer;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
@ -10,29 +15,184 @@ public sealed partial class Mod2
|
||||||
{
|
{
|
||||||
private static class Migration
|
private static class Migration
|
||||||
{
|
{
|
||||||
public static void Migrate( Mod2 mod, string text )
|
public static bool Migrate( Mod2 mod, JObject json )
|
||||||
{
|
=> MigrateV0ToV1( mod, json );
|
||||||
MigrateV0ToV1( mod, text );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void MigrateV0ToV1( Mod2 mod, string text )
|
private static bool MigrateV0ToV1( Mod2 mod, JObject json )
|
||||||
{
|
{
|
||||||
if( mod.FileVersion > 0 )
|
if( mod.FileVersion > 0 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var swaps = json[ "FileSwaps" ]?.ToObject< Dictionary< Utf8GamePath, FullPath > >()
|
||||||
|
?? new Dictionary< Utf8GamePath, FullPath >();
|
||||||
|
var groups = json[ "Groups" ]?.ToObject< Dictionary< string, OptionGroupV0 > >() ?? new Dictionary< string, OptionGroupV0 >();
|
||||||
|
var priority = 1;
|
||||||
|
foreach( var group in groups.Values )
|
||||||
|
{
|
||||||
|
ConvertGroup( mod, group, ref priority );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var unusedFile in mod.FindUnusedFiles() )
|
||||||
|
{
|
||||||
|
if( unusedFile.ToGamePath( mod.BasePath, out var gamePath ) )
|
||||||
|
{
|
||||||
|
mod._default.FileData.Add( gamePath, unusedFile );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod._default.FileSwapData.Clear();
|
||||||
|
mod._default.FileSwapData.EnsureCapacity( swaps.Count );
|
||||||
|
foreach( var (gamePath, swapPath) in swaps )
|
||||||
|
{
|
||||||
|
mod._default.FileSwapData.Add( gamePath, swapPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleMetaChanges( mod._default, mod.BasePath );
|
||||||
|
foreach( var group in mod.Groups )
|
||||||
|
{
|
||||||
|
IModGroup.SaveModGroup( group, mod.BasePath );
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.SaveDefaultMod();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertGroup( Mod2 mod, OptionGroupV0 group, ref int priority )
|
||||||
|
{
|
||||||
|
if( group.Options.Count == 0 )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = JObject.Parse( text );
|
switch( group.SelectionType )
|
||||||
var swaps = data[ "FileSwaps" ]?.ToObject< Dictionary< Utf8GamePath, FullPath > >()
|
{
|
||||||
?? new Dictionary< Utf8GamePath, FullPath >();
|
case SelectType.Multi:
|
||||||
var groups = data[ "Groups" ]?.ToObject< Dictionary< string, OptionGroupV0 > >() ?? new Dictionary< string, OptionGroupV0 >();
|
|
||||||
foreach( var group in groups.Values )
|
|
||||||
{ }
|
|
||||||
|
|
||||||
foreach( var swap in swaps )
|
var optionPriority = 0;
|
||||||
{ }
|
var newMultiGroup = new MultiModGroup()
|
||||||
|
{
|
||||||
|
Name = group.GroupName,
|
||||||
|
Priority = priority++,
|
||||||
|
Description = string.Empty,
|
||||||
|
};
|
||||||
|
mod._groups.Add( newMultiGroup );
|
||||||
|
foreach( var option in group.Options )
|
||||||
|
{
|
||||||
|
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod.BasePath, option ), optionPriority++ ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SelectType.Single:
|
||||||
|
if( group.Options.Count == 1 )
|
||||||
|
{
|
||||||
|
AddFilesToSubMod( mod._default, mod.BasePath, group.Options[ 0 ] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSingleGroup = new SingleModGroup()
|
||||||
|
{
|
||||||
|
Name = group.GroupName,
|
||||||
|
Priority = priority++,
|
||||||
|
Description = string.Empty,
|
||||||
|
};
|
||||||
|
mod._groups.Add( newSingleGroup );
|
||||||
|
foreach( var option in group.Options )
|
||||||
|
{
|
||||||
|
newSingleGroup.OptionData.Add( SubModFromOption( mod.BasePath, option ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddFilesToSubMod( SubMod mod, DirectoryInfo basePath, OptionV0 option )
|
||||||
|
{
|
||||||
|
foreach( var (relPath, gamePaths) in option.OptionFiles )
|
||||||
|
{
|
||||||
|
foreach( var gamePath in gamePaths )
|
||||||
|
{
|
||||||
|
mod.FileData.TryAdd( gamePath, new FullPath( basePath, relPath ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleMetaChanges( SubMod subMod, DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
foreach( var (key, file) in subMod.Files.ToList() )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch( file.Extension )
|
||||||
|
{
|
||||||
|
case ".meta":
|
||||||
|
subMod.FileData.Remove( key );
|
||||||
|
if( !file.Exists )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta = new TexToolsMeta( File.ReadAllBytes( file.FullName ) );
|
||||||
|
foreach( var manip in meta.EqpManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var manip in meta.EqdpManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var manip in meta.EstManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var manip in meta.GmpManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var manip in meta.ImcManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ".rgsp":
|
||||||
|
subMod.FileData.Remove( key );
|
||||||
|
if( !file.Exists )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ) );
|
||||||
|
foreach( var manip in rgsp.RspManipulations )
|
||||||
|
{
|
||||||
|
subMod.ManipulationData.Add( manip );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default: continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not migrate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubMod SubModFromOption( DirectoryInfo basePath, OptionV0 option )
|
||||||
|
{
|
||||||
|
var subMod = new SubMod() { Name = option.OptionName };
|
||||||
|
AddFilesToSubMod( subMod, basePath, option );
|
||||||
|
HandleMetaChanges( subMod, basePath );
|
||||||
|
return subMod;
|
||||||
|
}
|
||||||
|
|
||||||
private struct OptionV0
|
private struct OptionV0
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ public enum MetaChangeType : byte
|
||||||
Version = 0x08,
|
Version = 0x08,
|
||||||
Website = 0x10,
|
Website = 0x10,
|
||||||
Deletion = 0x20,
|
Deletion = 0x20,
|
||||||
|
Migration = 0x40,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed partial class Mod2
|
public sealed partial class Mod2
|
||||||
|
|
@ -29,19 +30,21 @@ public sealed partial class Mod2
|
||||||
public string Version { get; private set; } = string.Empty;
|
public string Version { get; private set; } = string.Empty;
|
||||||
public string Website { get; private set; } = string.Empty;
|
public string Website { get; private set; } = string.Empty;
|
||||||
|
|
||||||
private void SaveMeta()
|
private FileInfo MetaFile
|
||||||
=> SaveToFile( MetaFile );
|
=> new(Path.Combine( BasePath.FullName, "meta.json" ));
|
||||||
|
|
||||||
private MetaChangeType LoadMetaFromFile( FileInfo filePath )
|
private MetaChangeType LoadMeta()
|
||||||
{
|
{
|
||||||
if( !File.Exists( filePath.FullName ) )
|
var metaFile = MetaFile;
|
||||||
|
if( !File.Exists( metaFile.FullName ) )
|
||||||
{
|
{
|
||||||
|
PluginLog.Debug( "No mod meta found for {ModLocation}.", BasePath.Name );
|
||||||
return MetaChangeType.Deletion;
|
return MetaChangeType.Deletion;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var text = File.ReadAllText( filePath.FullName );
|
var text = File.ReadAllText( metaFile.FullName );
|
||||||
var json = JObject.Parse( text );
|
var json = JObject.Parse( text );
|
||||||
|
|
||||||
var newName = json[ nameof( Name ) ]?.Value< string >() ?? string.Empty;
|
var newName = json[ nameof( Name ) ]?.Value< string >() ?? string.Empty;
|
||||||
|
|
@ -52,12 +55,6 @@ public sealed partial class Mod2
|
||||||
var newFileVersion = json[ nameof( FileVersion ) ]?.Value< uint >() ?? 0;
|
var newFileVersion = json[ nameof( FileVersion ) ]?.Value< uint >() ?? 0;
|
||||||
|
|
||||||
MetaChangeType changes = 0;
|
MetaChangeType changes = 0;
|
||||||
if( newFileVersion < CurrentFileVersion )
|
|
||||||
{
|
|
||||||
Migration.Migrate( this, text );
|
|
||||||
FileVersion = newFileVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( Name != newName )
|
if( Name != newName )
|
||||||
{
|
{
|
||||||
changes |= MetaChangeType.Name;
|
changes |= MetaChangeType.Name;
|
||||||
|
|
@ -88,6 +85,14 @@ public sealed partial class Mod2
|
||||||
Website = newWebsite;
|
Website = newWebsite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( FileVersion != newFileVersion )
|
||||||
|
{
|
||||||
|
FileVersion = newFileVersion;
|
||||||
|
if( Migration.Migrate( this, json ) )
|
||||||
|
{
|
||||||
|
changes |= MetaChangeType.Migration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +103,9 @@ public sealed partial class Mod2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveToFile( FileInfo filePath )
|
private void SaveMeta()
|
||||||
{
|
{
|
||||||
|
var metaFile = MetaFile;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jObject = new JObject
|
var jObject = new JObject
|
||||||
|
|
@ -111,11 +117,11 @@ public sealed partial class Mod2
|
||||||
{ nameof( Version ), JToken.FromObject( Version ) },
|
{ nameof( Version ), JToken.FromObject( Version ) },
|
||||||
{ nameof( Website ), JToken.FromObject( Website ) },
|
{ nameof( Website ), JToken.FromObject( Website ) },
|
||||||
};
|
};
|
||||||
File.WriteAllText( filePath.FullName, jObject.ToString( Formatting.Indented ) );
|
File.WriteAllText( metaFile.FullName, jObject.ToString( Formatting.Indented ) );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"Could not write meta file for mod {Name} to {filePath.FullName}:\n{e}" );
|
PluginLog.Error( $"Could not write meta file for mod {Name} to {metaFile.FullName}:\n{e}" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
|
||||||
public sealed partial class Mod2
|
|
||||||
{
|
|
||||||
public Mod.SortOrder Order;
|
|
||||||
public override string ToString()
|
|
||||||
=> Order.FullPath;
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +1,52 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed class ModFileSystemA : FileSystem< Mod >
|
public sealed class ModFileSystemA : FileSystem< Mod >, IDisposable
|
||||||
{
|
{
|
||||||
|
// Save the current sort order.
|
||||||
|
// Does not save or copy the backup in the current mod directory,
|
||||||
|
// as this is done on mod directory changes only.
|
||||||
public void Save()
|
public void Save()
|
||||||
=> SaveToFile( new FileInfo( Mod.Manager.SortOrderFile ), SaveMod, true );
|
=> SaveToFile( new FileInfo( Mod.Manager.SortOrderFile ), SaveMod, true );
|
||||||
|
|
||||||
|
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||||
public static ModFileSystemA Load()
|
public static ModFileSystemA Load()
|
||||||
{
|
{
|
||||||
var x = new ModFileSystemA();
|
var ret = new ModFileSystemA();
|
||||||
if( x.Load( new FileInfo( Mod.Manager.SortOrderFile ), Penumbra.ModManager.Mods, ModToIdentifier, ModToName ) )
|
ret.Reload();
|
||||||
{
|
|
||||||
x.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
x.Changed += ( _1, _2, _3, _4 ) => x.Save();
|
ret.Changed += ret.OnChange;
|
||||||
|
Penumbra.ModManager.ModDiscoveryFinished += ret.Reload;
|
||||||
|
|
||||||
return x;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> Penumbra.ModManager.ModDiscoveryFinished -= Reload;
|
||||||
|
|
||||||
|
// Reload the whole filesystem from currently loaded mods and the current sort order file.
|
||||||
|
// Used on construction and on mod rediscoveries.
|
||||||
|
private void Reload()
|
||||||
|
{
|
||||||
|
if( Load( new FileInfo( Mod.Manager.SortOrderFile ), Penumbra.ModManager.Mods, ModToIdentifier, ModToName ) )
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the filesystem on every filesystem change except full reloading.
|
||||||
|
private void OnChange( FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3 )
|
||||||
|
{
|
||||||
|
if( type != FileSystemChangeType.Reload )
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for saving and loading.
|
||||||
private static string ModToIdentifier( Mod mod )
|
private static string ModToIdentifier( Mod mod )
|
||||||
=> mod.BasePath.Name;
|
=> mod.BasePath.Name;
|
||||||
|
|
||||||
|
|
@ -29,6 +55,7 @@ public sealed class ModFileSystemA : FileSystem< Mod >
|
||||||
|
|
||||||
private static (string, bool) SaveMod( Mod mod, string fullPath )
|
private static (string, bool) SaveMod( Mod mod, string fullPath )
|
||||||
{
|
{
|
||||||
|
// Only save pairs with non-default paths.
|
||||||
if( fullPath == ModToName( mod ) )
|
if( fullPath == ModToName( mod ) )
|
||||||
{
|
{
|
||||||
return ( string.Empty, false );
|
return ( string.Empty, false );
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public partial class Mod
|
||||||
|
|
||||||
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
|
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
|
||||||
|
|
||||||
public delegate void ModChangeDelegate( ChangeType type, int modIndex, Mod mod );
|
public delegate void ModChangeDelegate( ChangeType type, Mod mod );
|
||||||
|
|
||||||
public event ModChangeDelegate? ModChange;
|
public event ModChangeDelegate? ModChange;
|
||||||
public event Action? ModDiscoveryStarted;
|
public event Action? ModDiscoveryStarted;
|
||||||
|
|
@ -90,8 +90,14 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( !firstTime )
|
||||||
|
{
|
||||||
|
HandleSortOrderFiles( newDir );
|
||||||
|
}
|
||||||
|
|
||||||
BasePath = newDir;
|
BasePath = newDir;
|
||||||
Valid = true;
|
|
||||||
|
Valid = true;
|
||||||
if( Config.ModDirectory != BasePath.FullName )
|
if( Config.ModDirectory != BasePath.FullName )
|
||||||
{
|
{
|
||||||
Config.ModDirectory = BasePath.FullName;
|
Config.ModDirectory = BasePath.FullName;
|
||||||
|
|
@ -100,8 +106,37 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SortOrderFile = Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(),
|
private const string SortOrderFileName = "sort_order.json";
|
||||||
"sort_order.json" );
|
public static string SortOrderFile = Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), SortOrderFileName );
|
||||||
|
|
||||||
|
private void HandleSortOrderFiles( DirectoryInfo newDir )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mainFile = SortOrderFile;
|
||||||
|
// Copy old sort order to backup.
|
||||||
|
var oldSortOrderFile = Path.Combine( BasePath.FullName, SortOrderFileName );
|
||||||
|
PluginLog.Debug( "Copying current sort older file to {BackupFile}...", oldSortOrderFile );
|
||||||
|
File.Copy( mainFile, oldSortOrderFile, true );
|
||||||
|
BasePath = newDir;
|
||||||
|
var newSortOrderFile = Path.Combine( newDir.FullName, SortOrderFileName );
|
||||||
|
// Copy new sort order to main, if it exists.
|
||||||
|
if( File.Exists( newSortOrderFile ) )
|
||||||
|
{
|
||||||
|
File.Copy( newSortOrderFile, mainFile, true );
|
||||||
|
PluginLog.Debug( "Copying stored sort order file from {BackupFile}...", newSortOrderFile );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.Delete( mainFile );
|
||||||
|
PluginLog.Debug( "Deleting current sort order file...", newSortOrderFile );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not swap Sort Order files:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Manager()
|
public Manager()
|
||||||
{
|
{
|
||||||
|
|
@ -215,7 +250,7 @@ public partial class Mod
|
||||||
--_mods[ i ].Index;
|
--_mods[ i ].Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModChange?.Invoke( ChangeType.Removed, idx, mod );
|
ModChange?.Invoke( ChangeType.Removed, mod );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +276,7 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
_mods.Add( mod );
|
_mods.Add( mod );
|
||||||
ModChange?.Invoke( ChangeType.Added, _mods.Count - 1, mod );
|
ModChange?.Invoke( ChangeType.Added, mod );
|
||||||
|
|
||||||
return _mods.Count - 1;
|
return _mods.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
@ -287,7 +322,7 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: more specific mod changes?
|
// TODO: more specific mod changes?
|
||||||
ModChange?.Invoke( ChangeType.Changed, idx, mod );
|
ModChange?.Invoke( ChangeType.Changed, mod );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public static class ModManagerEditExtensions
|
||||||
manager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
|
manager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.Config.Save();
|
Penumbra.Config.Save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ public static class ModManagerEditExtensions
|
||||||
{
|
{
|
||||||
manager.TemporaryModSortOrder[ newDir.Name ] = manager.TemporaryModSortOrder[ oldBasePath.Name ];
|
manager.TemporaryModSortOrder[ newDir.Name ] = manager.TemporaryModSortOrder[ oldBasePath.Name ];
|
||||||
manager.TemporaryModSortOrder.Remove( oldBasePath.Name );
|
manager.TemporaryModSortOrder.Remove( oldBasePath.Name );
|
||||||
manager.Config.Save();
|
Penumbra.Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx = manager.Mods.IndexOf( mod );
|
var idx = manager.Mods.IndexOf( mod );
|
||||||
|
|
|
||||||
79
Penumbra/Mods/Subclasses/IModGroup.cs
Normal file
79
Penumbra/Mods/Subclasses/IModGroup.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public interface IModGroup : IEnumerable< ISubMod >
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public SelectType Type { get; }
|
||||||
|
public int Priority { get; }
|
||||||
|
|
||||||
|
public int OptionPriority( Index optionIdx );
|
||||||
|
|
||||||
|
public ISubMod this[ Index idx ] { get; }
|
||||||
|
|
||||||
|
public int Count { get; }
|
||||||
|
|
||||||
|
public bool IsOption
|
||||||
|
=> Type switch
|
||||||
|
{
|
||||||
|
SelectType.Single => Count > 1,
|
||||||
|
SelectType.Multi => Count > 0,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public string FileName( DirectoryInfo basePath )
|
||||||
|
=> Path.Combine( basePath.FullName, $"group_{Name.RemoveInvalidPathSymbols().ToLowerInvariant()}.json" );
|
||||||
|
|
||||||
|
public void DeleteFile( DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
var file = FileName( basePath );
|
||||||
|
if( !File.Exists( file ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete( file );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not delete file {file}:\n{e}" );
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveModGroup( IModGroup group, DirectoryInfo basePath )
|
||||||
|
{
|
||||||
|
var file = group.FileName( basePath );
|
||||||
|
using var s = File.Exists( file ) ? File.Open( file, FileMode.Truncate ) : File.Open( file, FileMode.CreateNew );
|
||||||
|
using var writer = new StreamWriter( s );
|
||||||
|
using var j = new JsonTextWriter( writer ) { Formatting = Formatting.Indented };
|
||||||
|
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||||
|
j.WriteStartObject();
|
||||||
|
j.WritePropertyName( nameof( group.Name ) );
|
||||||
|
j.WriteValue( group.Name );
|
||||||
|
j.WritePropertyName( nameof( group.Description ) );
|
||||||
|
j.WriteValue( group.Description );
|
||||||
|
j.WritePropertyName( nameof( group.Priority ) );
|
||||||
|
j.WriteValue( group.Priority );
|
||||||
|
j.WritePropertyName( nameof( Type ) );
|
||||||
|
j.WriteValue( group.Type.ToString() );
|
||||||
|
j.WritePropertyName( "Options" );
|
||||||
|
j.WriteStartArray();
|
||||||
|
for( var idx = 0; idx < group.Count; ++idx )
|
||||||
|
{
|
||||||
|
ISubMod.WriteSubMod( j, serializer, group[ idx ], basePath, group.Type == SelectType.Multi ? group.OptionPriority( idx ) : null );
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WriteEndArray();
|
||||||
|
j.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Penumbra/Mods/Subclasses/ISubMod.cs
Normal file
53
Penumbra/Mods/Subclasses/ISubMod.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public interface ISubMod
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
|
||||||
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
|
||||||
|
public IReadOnlySet< MetaManipulation > Manipulations { get; }
|
||||||
|
|
||||||
|
public static void WriteSubMod( JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority )
|
||||||
|
{
|
||||||
|
j.WriteStartObject();
|
||||||
|
j.WritePropertyName( nameof( Name ) );
|
||||||
|
j.WriteValue( mod.Name );
|
||||||
|
if( priority != null )
|
||||||
|
{
|
||||||
|
j.WritePropertyName( nameof( IModGroup.Priority ) );
|
||||||
|
j.WriteValue( priority.Value );
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WritePropertyName( nameof( mod.Files ) );
|
||||||
|
j.WriteStartObject();
|
||||||
|
foreach( var (gamePath, file) in mod.Files )
|
||||||
|
{
|
||||||
|
if( file.ToRelPath( basePath, out var relPath ) )
|
||||||
|
{
|
||||||
|
j.WritePropertyName( gamePath.ToString() );
|
||||||
|
j.WriteValue( relPath.ToString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WriteEndObject();
|
||||||
|
j.WritePropertyName( nameof( mod.FileSwaps ) );
|
||||||
|
j.WriteStartObject();
|
||||||
|
foreach( var (gamePath, file) in mod.FileSwaps )
|
||||||
|
{
|
||||||
|
j.WritePropertyName( gamePath.ToString() );
|
||||||
|
j.WriteValue( file.ToString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WriteEndObject();
|
||||||
|
j.WritePropertyName( nameof( mod.Manipulations ) );
|
||||||
|
serializer.Serialize( j, mod.Manipulations );
|
||||||
|
j.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,8 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ public partial class Mod2
|
||||||
|
|
||||||
public string Name { get; set; } = "Group";
|
public string Name { get; set; } = "Group";
|
||||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||||
public int Priority { get; set; } = 0;
|
public int Priority { get; set; }
|
||||||
|
|
||||||
public int OptionPriority( Index idx )
|
public int OptionPriority( Index idx )
|
||||||
=> PrioritizedOptions[ idx ].Priority;
|
=> PrioritizedOptions[ idx ].Priority;
|
||||||
|
|
@ -25,6 +25,7 @@ public partial class Mod2
|
||||||
public ISubMod this[ Index idx ]
|
public ISubMod this[ Index idx ]
|
||||||
=> PrioritizedOptions[ idx ].Mod;
|
=> PrioritizedOptions[ idx ].Mod;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public int Count
|
public int Count
|
||||||
=> PrioritizedOptions.Count;
|
=> PrioritizedOptions.Count;
|
||||||
|
|
||||||
|
|
@ -36,18 +37,31 @@ public partial class Mod2
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
public void Save( DirectoryInfo basePath )
|
public static MultiModGroup? Load( JObject json, DirectoryInfo basePath )
|
||||||
{
|
{
|
||||||
var path = ( ( IModGroup )this ).FileName( basePath );
|
var options = json[ "Options" ];
|
||||||
try
|
var ret = new MultiModGroup()
|
||||||
{
|
{
|
||||||
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
|
||||||
File.WriteAllText( path, text );
|
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
|
||||||
}
|
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
|
||||||
catch( Exception e )
|
};
|
||||||
|
if( ret.Name.Length == 0 )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"Could not save option group {Name} to {path}:\n{e}" );
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( options != null )
|
||||||
|
{
|
||||||
|
foreach( var child in options.Children() )
|
||||||
|
{
|
||||||
|
var subMod = new SubMod();
|
||||||
|
subMod.Load( basePath, child, out var priority );
|
||||||
|
ret.PrioritizedOptions.Add( ( subMod, priority ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ public partial class Mod2
|
||||||
|
|
||||||
public string Name { get; set; } = "Option";
|
public string Name { get; set; } = "Option";
|
||||||
public string Description { get; set; } = "A mutually exclusive group of settings.";
|
public string Description { get; set; } = "A mutually exclusive group of settings.";
|
||||||
public int Priority { get; set; } = 0;
|
public int Priority { get; set; }
|
||||||
|
|
||||||
public readonly List< SubMod > OptionData = new();
|
public readonly List< SubMod > OptionData = new();
|
||||||
|
|
||||||
|
|
@ -26,6 +26,7 @@ public partial class Mod2
|
||||||
public ISubMod this[ Index idx ]
|
public ISubMod this[ Index idx ]
|
||||||
=> OptionData[ idx ];
|
=> OptionData[ idx ];
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public int Count
|
public int Count
|
||||||
=> OptionData.Count;
|
=> OptionData.Count;
|
||||||
|
|
||||||
|
|
@ -35,18 +36,31 @@ public partial class Mod2
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
public void Save( DirectoryInfo basePath )
|
public static SingleModGroup? Load( JObject json, DirectoryInfo basePath )
|
||||||
{
|
{
|
||||||
var path = ( ( IModGroup )this ).FileName( basePath );
|
var options = json[ "Options" ];
|
||||||
try
|
var ret = new SingleModGroup
|
||||||
{
|
{
|
||||||
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
|
||||||
File.WriteAllText( path, text );
|
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
|
||||||
}
|
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
|
||||||
catch( Exception e )
|
};
|
||||||
|
if( ret.Name.Length == 0 )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"Could not save option group {Name} to {path}:\n{e}" );
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( options != null )
|
||||||
|
{
|
||||||
|
foreach( var child in options.Children() )
|
||||||
|
{
|
||||||
|
var subMod = new SubMod();
|
||||||
|
subMod.Load( basePath, child, out _ );
|
||||||
|
ret.OptionData.Add( subMod );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
117
Penumbra/Mods/Subclasses/Mod2.Files.SubMod.cs
Normal file
117
Penumbra/Mods/Subclasses/Mod2.Files.SubMod.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public partial class Mod2
|
||||||
|
{
|
||||||
|
private string DefaultFile
|
||||||
|
=> Path.Combine( BasePath.FullName, "default_mod.json" );
|
||||||
|
|
||||||
|
private void SaveDefaultMod()
|
||||||
|
{
|
||||||
|
var defaultFile = DefaultFile;
|
||||||
|
|
||||||
|
using var stream = File.Exists( defaultFile )
|
||||||
|
? File.Open( defaultFile, FileMode.Truncate )
|
||||||
|
: File.Open( defaultFile, FileMode.CreateNew );
|
||||||
|
|
||||||
|
using var w = new StreamWriter( stream );
|
||||||
|
using var j = new JsonTextWriter( w );
|
||||||
|
j.Formatting = Formatting.Indented;
|
||||||
|
var serializer = new JsonSerializer
|
||||||
|
{
|
||||||
|
Formatting = Formatting.Indented,
|
||||||
|
};
|
||||||
|
ISubMod.WriteSubMod( j, serializer, _default, BasePath, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDefaultOption()
|
||||||
|
{
|
||||||
|
var defaultFile = DefaultFile;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( !File.Exists( defaultFile ) )
|
||||||
|
{
|
||||||
|
_default.Load( BasePath, new JObject(), out _ );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_default.Load( BasePath, JObject.Parse( File.ReadAllText( defaultFile ) ), out _ );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not parse default file for {Name}:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private sealed class SubMod : ISubMod
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "Default";
|
||||||
|
|
||||||
|
public readonly Dictionary< Utf8GamePath, FullPath > FileData = new();
|
||||||
|
public readonly Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
||||||
|
public readonly HashSet< MetaManipulation > ManipulationData = new();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
||||||
|
=> FileData;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
|
||||||
|
=> FileSwapData;
|
||||||
|
|
||||||
|
public IReadOnlySet< MetaManipulation > Manipulations
|
||||||
|
=> ManipulationData;
|
||||||
|
|
||||||
|
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
||||||
|
{
|
||||||
|
FileData.Clear();
|
||||||
|
FileSwapData.Clear();
|
||||||
|
ManipulationData.Clear();
|
||||||
|
|
||||||
|
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||||
|
priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0;
|
||||||
|
|
||||||
|
var files = ( JObject? )json[ nameof( Files ) ];
|
||||||
|
if( files != null )
|
||||||
|
{
|
||||||
|
foreach( var property in files.Properties() )
|
||||||
|
{
|
||||||
|
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||||
|
{
|
||||||
|
FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var swaps = ( JObject? )json[ nameof( FileSwaps ) ];
|
||||||
|
if( swaps != null )
|
||||||
|
{
|
||||||
|
foreach( var property in swaps.Properties() )
|
||||||
|
{
|
||||||
|
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||||
|
{
|
||||||
|
FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var manips = json[ nameof( Manipulations ) ];
|
||||||
|
if( manips != null )
|
||||||
|
{
|
||||||
|
foreach( var s in manips.Children().Select( c => c.ToObject< MetaManipulation >() ) )
|
||||||
|
{
|
||||||
|
ManipulationData.Add( s );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -122,9 +122,11 @@ public class Penumbra : IDalamudPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
ResidentResources.Reload();
|
ResidentResources.Reload();
|
||||||
//var c = ModCollection2.LoadFromFile( new FileInfo(@"C:\Users\Ozy\AppData\Roaming\XIVLauncher\pluginConfigs\Penumbra\collections\Rayla.json"),
|
|
||||||
// out var inheritance );
|
foreach( var folder in ModManager.BasePath.EnumerateDirectories() )
|
||||||
//c?.Save();
|
{
|
||||||
|
var m = Mod2.LoadMod( folder );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Enable()
|
public bool Enable()
|
||||||
|
|
|
||||||
3
Penumbra/Penumbra.csproj.DotSettings
Normal file
3
Penumbra/Penumbra.csproj.DotSettings
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Cmanager/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Csubclasses/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
@ -20,7 +22,7 @@ public partial class SettingsInterface
|
||||||
public TabBrowser()
|
public TabBrowser()
|
||||||
{
|
{
|
||||||
_fileSystem = ModFileSystemA.Load();
|
_fileSystem = ModFileSystemA.Load();
|
||||||
_selector = new ModFileSystemSelector( _fileSystem );
|
_selector = new ModFileSystemSelector( _fileSystem, new HashSet<Mod>() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ public class ModListCache : IDisposable
|
||||||
PluginLog.Debug( "Resetting mod selector list..." );
|
PluginLog.Debug( "Resetting mod selector list..." );
|
||||||
if( _modsInOrder.Count == 0 )
|
if( _modsInOrder.Count == 0 )
|
||||||
{
|
{
|
||||||
foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) )
|
foreach( var modData in _manager.StructuredMods.AllMods( Penumbra.Config.SortFoldersFirst ) )
|
||||||
{
|
{
|
||||||
var idx = Penumbra.ModManager.Mods.IndexOf( modData );
|
var idx = Penumbra.ModManager.Mods.IndexOf( modData );
|
||||||
var mod = new FullMod( Penumbra.CollectionManager.Current[ idx ].Settings ?? ModSettings.DefaultSettings( modData.Meta ),
|
var mod = new FullMod( Penumbra.CollectionManager.Current[ idx ].Settings ?? ModSettings.DefaultSettings( modData.Meta ),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue