mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-19 14:27:50 +01:00
Move Mod.Manager and ModCollection.Manager to outer scope and required changes.
This commit is contained in:
parent
ccdafcf85d
commit
1253079968
59 changed files with 2562 additions and 2615 deletions
|
|
@ -15,103 +15,105 @@ namespace Penumbra.Mods;
|
|||
|
||||
public partial class Mod
|
||||
{
|
||||
// Groups that allow all available options to be selected at once.
|
||||
private sealed class MultiModGroup : IModGroup
|
||||
|
||||
}
|
||||
|
||||
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
||||
public sealed class MultiModGroup : IModGroup
|
||||
{
|
||||
public GroupType Type
|
||||
=> GroupType.Multi;
|
||||
|
||||
public string Name { get; set; } = "Group";
|
||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||
public int Priority { get; set; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
|
||||
public int OptionPriority(Index idx)
|
||||
=> PrioritizedOptions[idx].Priority;
|
||||
|
||||
public ISubMod this[Index idx]
|
||||
=> PrioritizedOptions[idx].Mod;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> PrioritizedOptions.Count;
|
||||
|
||||
public readonly List<(SubMod Mod, int Priority)> PrioritizedOptions = new();
|
||||
|
||||
public IEnumerator<ISubMod> GetEnumerator()
|
||||
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||
{
|
||||
public GroupType Type
|
||||
=> GroupType.Multi;
|
||||
|
||||
public string Name { get; set; } = "Group";
|
||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||
public int Priority { get; set; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
|
||||
public int OptionPriority(Index idx)
|
||||
=> PrioritizedOptions[idx].Priority;
|
||||
|
||||
public ISubMod this[Index idx]
|
||||
=> PrioritizedOptions[idx].Mod;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> PrioritizedOptions.Count;
|
||||
|
||||
public readonly List<(SubMod Mod, int Priority)> PrioritizedOptions = new();
|
||||
|
||||
public IEnumerator<ISubMod> GetEnumerator()
|
||||
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||
var ret = new MultiModGroup()
|
||||
{
|
||||
var ret = new MultiModGroup()
|
||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
|
||||
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0,
|
||||
};
|
||||
if (ret.Name.Length == 0)
|
||||
return null;
|
||||
|
||||
var options = json["Options"];
|
||||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
{
|
||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
|
||||
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0,
|
||||
};
|
||||
if (ret.Name.Length == 0)
|
||||
return null;
|
||||
|
||||
var options = json["Options"];
|
||||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
|
||||
{
|
||||
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
|
||||
{
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning",
|
||||
NotificationType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
var subMod = new SubMod(mod);
|
||||
subMod.SetPosition(groupIdx, ret.PrioritizedOptions.Count);
|
||||
subMod.Load(mod.ModPath, child, out var priority);
|
||||
ret.PrioritizedOptions.Add((subMod, priority));
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning",
|
||||
NotificationType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
ret.DefaultSettings = (uint)(ret.DefaultSettings & ((1ul << ret.Count) - 1));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IModGroup Convert(GroupType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GroupType.Multi: return this;
|
||||
case GroupType.Single:
|
||||
var multi = new SingleModGroup()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = (uint)Math.Max(Math.Min(Count - 1, BitOperations.TrailingZeroCount(DefaultSettings)), 0),
|
||||
};
|
||||
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
|
||||
return multi;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
var subMod = new SubMod(mod);
|
||||
subMod.SetPosition(groupIdx, ret.PrioritizedOptions.Count);
|
||||
subMod.Load(mod.ModPath, child, out var priority);
|
||||
ret.PrioritizedOptions.Add((subMod, priority));
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
||||
ret.DefaultSettings = (uint)(ret.DefaultSettings & ((1ul << ret.Count) - 1));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IModGroup Convert(GroupType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
|
||||
return false;
|
||||
|
||||
DefaultSettings = Functions.MoveBit(DefaultSettings, optionIdxFrom, optionIdxTo);
|
||||
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePositions(int from = 0)
|
||||
{
|
||||
foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from))
|
||||
o.SetPosition(o.GroupIdx, i);
|
||||
case GroupType.Multi: return this;
|
||||
case GroupType.Single:
|
||||
var multi = new SingleModGroup()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = (uint)Math.Max(Math.Min(Count - 1, BitOperations.TrailingZeroCount(DefaultSettings)), 0),
|
||||
};
|
||||
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
|
||||
return multi;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
||||
{
|
||||
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
|
||||
return false;
|
||||
|
||||
DefaultSettings = Functions.MoveBit(DefaultSettings, optionIdxFrom, optionIdxTo);
|
||||
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePositions(int from = 0)
|
||||
{
|
||||
foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from))
|
||||
o.SetPosition(o.GroupIdx, i);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,122 +10,119 @@ using Penumbra.Api.Enums;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
/// <summary> Groups that allow only one of their available options to be selected. </summary>
|
||||
public sealed class SingleModGroup : IModGroup
|
||||
{
|
||||
// Groups that allow only one of their available options to be selected.
|
||||
private sealed class SingleModGroup : IModGroup
|
||||
public GroupType Type
|
||||
=> GroupType.Single;
|
||||
|
||||
public string Name { get; set; } = "Option";
|
||||
public string Description { get; set; } = "A mutually exclusive group of settings.";
|
||||
public int Priority { get; set; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
|
||||
public readonly List< SubMod > OptionData = new();
|
||||
|
||||
public int OptionPriority( Index _ )
|
||||
=> Priority;
|
||||
|
||||
public ISubMod this[ Index idx ]
|
||||
=> OptionData[ idx ];
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> OptionData.Count;
|
||||
|
||||
public IEnumerator< ISubMod > GetEnumerator()
|
||||
=> OptionData.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
|
||||
{
|
||||
public GroupType Type
|
||||
=> GroupType.Single;
|
||||
|
||||
public string Name { get; set; } = "Option";
|
||||
public string Description { get; set; } = "A mutually exclusive group of settings.";
|
||||
public int Priority { get; set; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
|
||||
public readonly List< SubMod > OptionData = new();
|
||||
|
||||
public int OptionPriority( Index _ )
|
||||
=> Priority;
|
||||
|
||||
public ISubMod this[ Index idx ]
|
||||
=> OptionData[ idx ];
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> OptionData.Count;
|
||||
|
||||
public IEnumerator< ISubMod > GetEnumerator()
|
||||
=> OptionData.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
|
||||
var options = json[ "Options" ];
|
||||
var ret = new SingleModGroup
|
||||
{
|
||||
var options = json[ "Options" ];
|
||||
var ret = new SingleModGroup
|
||||
{
|
||||
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
|
||||
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
|
||||
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
|
||||
DefaultSettings = json[ nameof( DefaultSettings ) ]?.ToObject< uint >() ?? 0u,
|
||||
};
|
||||
if( ret.Name.Length == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
|
||||
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
|
||||
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
|
||||
DefaultSettings = json[ nameof( DefaultSettings ) ]?.ToObject< uint >() ?? 0u,
|
||||
};
|
||||
if( ret.Name.Length == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( options != null )
|
||||
if( options != null )
|
||||
{
|
||||
foreach( var child in options.Children() )
|
||||
{
|
||||
foreach( var child in options.Children() )
|
||||
var subMod = new SubMod( mod );
|
||||
subMod.SetPosition( groupIdx, ret.OptionData.Count );
|
||||
subMod.Load( mod.ModPath, child, out _ );
|
||||
ret.OptionData.Add( subMod );
|
||||
}
|
||||
}
|
||||
|
||||
if( ( int )ret.DefaultSettings >= ret.Count )
|
||||
ret.DefaultSettings = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IModGroup Convert( GroupType type )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case GroupType.Single: return this;
|
||||
case GroupType.Multi:
|
||||
var multi = new MultiModGroup()
|
||||
{
|
||||
var subMod = new SubMod( mod );
|
||||
subMod.SetPosition( groupIdx, ret.OptionData.Count );
|
||||
subMod.Load( mod.ModPath, child, out _ );
|
||||
ret.OptionData.Add( subMod );
|
||||
}
|
||||
}
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = 1u << ( int )DefaultSettings,
|
||||
};
|
||||
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
|
||||
return multi;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
}
|
||||
}
|
||||
|
||||
if( ( int )ret.DefaultSettings >= ret.Count )
|
||||
ret.DefaultSettings = 0;
|
||||
|
||||
return ret;
|
||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
||||
{
|
||||
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IModGroup Convert( GroupType type )
|
||||
// Update default settings with the move.
|
||||
if( DefaultSettings == optionIdxFrom )
|
||||
{
|
||||
switch( type )
|
||||
DefaultSettings = ( uint )optionIdxTo;
|
||||
}
|
||||
else if( optionIdxFrom < optionIdxTo )
|
||||
{
|
||||
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
|
||||
{
|
||||
case GroupType.Single: return this;
|
||||
case GroupType.Multi:
|
||||
var multi = new MultiModGroup()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = 1u << ( int )DefaultSettings,
|
||||
};
|
||||
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
|
||||
return multi;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
--DefaultSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
||||
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
|
||||
{
|
||||
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update default settings with the move.
|
||||
if( DefaultSettings == optionIdxFrom )
|
||||
{
|
||||
DefaultSettings = ( uint )optionIdxTo;
|
||||
}
|
||||
else if( optionIdxFrom < optionIdxTo )
|
||||
{
|
||||
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
|
||||
{
|
||||
--DefaultSettings;
|
||||
}
|
||||
}
|
||||
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
|
||||
{
|
||||
++DefaultSettings;
|
||||
}
|
||||
|
||||
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
|
||||
return true;
|
||||
++DefaultSettings;
|
||||
}
|
||||
|
||||
public void UpdatePositions( int from = 0 )
|
||||
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePositions( int from = 0 )
|
||||
{
|
||||
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
|
||||
{
|
||||
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
|
||||
{
|
||||
o.SetPosition( o.GroupIdx, i );
|
||||
}
|
||||
o.SetPosition( o.GroupIdx, i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public partial class Mod
|
|||
ISubMod.WriteSubMod( j, serializer, _default, ModPath, 0 );
|
||||
}
|
||||
|
||||
private void SaveDefaultModDelayed()
|
||||
internal void SaveDefaultModDelayed()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveDefaultMod ) + ModPath.Name, SaveDefaultMod );
|
||||
|
||||
private void LoadDefaultOption()
|
||||
|
|
@ -92,233 +92,237 @@ public partial class Mod
|
|||
}
|
||||
|
||||
|
||||
// A sub mod is a collection of
|
||||
// - file replacements
|
||||
// - file swaps
|
||||
// - meta manipulations
|
||||
// that can be used either as an option or as the default data for a mod.
|
||||
// It can be loaded and reloaded from Json.
|
||||
// Nothing is checked for existence or validity when loading.
|
||||
// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
|
||||
public sealed class SubMod : ISubMod
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A sub mod is a collection of
|
||||
/// - file replacements
|
||||
/// - file swaps
|
||||
/// - meta manipulations
|
||||
/// that can be used either as an option or as the default data for a mod.
|
||||
/// It can be loaded and reloaded from Json.
|
||||
/// Nothing is checked for existence or validity when loading.
|
||||
/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
|
||||
/// </summary>
|
||||
public sealed class SubMod : ISubMod
|
||||
{
|
||||
public string Name { get; set; } = "Default";
|
||||
|
||||
public string FullName
|
||||
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
internal IMod ParentMod { get; private init; }
|
||||
internal int GroupIdx { get; private set; }
|
||||
internal int OptionIdx { get; private set; }
|
||||
|
||||
public bool IsDefault
|
||||
=> GroupIdx < 0;
|
||||
|
||||
public Dictionary< Utf8GamePath, FullPath > FileData = new();
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
||||
public HashSet< MetaManipulation > ManipulationData = new();
|
||||
|
||||
public SubMod( IMod parentMod )
|
||||
=> ParentMod = parentMod;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
||||
=> FileData;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
|
||||
=> FileSwapData;
|
||||
|
||||
public IReadOnlySet< MetaManipulation > Manipulations
|
||||
=> ManipulationData;
|
||||
|
||||
public void SetPosition( int groupIdx, int optionIdx )
|
||||
{
|
||||
public string Name { get; set; } = "Default";
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
public string FullName
|
||||
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
|
||||
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
||||
{
|
||||
FileData.Clear();
|
||||
FileSwapData.Clear();
|
||||
ManipulationData.Clear();
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
// Every option has a name, but priorities are only relevant for multi group options.
|
||||
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
Description = json[ nameof( ISubMod.Description ) ]?.ToObject< string >() ?? string.Empty;
|
||||
priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0;
|
||||
|
||||
internal IMod ParentMod { get; private init; }
|
||||
internal int GroupIdx { get; private set; }
|
||||
internal int OptionIdx { get; private set; }
|
||||
|
||||
public bool IsDefault
|
||||
=> GroupIdx < 0;
|
||||
|
||||
public Dictionary< Utf8GamePath, FullPath > FileData = new();
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
||||
public HashSet< MetaManipulation > ManipulationData = new();
|
||||
|
||||
public SubMod( IMod parentMod )
|
||||
=> ParentMod = parentMod;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
||||
=> FileData;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
|
||||
=> FileSwapData;
|
||||
|
||||
public IReadOnlySet< MetaManipulation > Manipulations
|
||||
=> ManipulationData;
|
||||
|
||||
public void SetPosition( int groupIdx, int optionIdx )
|
||||
var files = ( JObject? )json[ nameof( Files ) ];
|
||||
if( files != null )
|
||||
{
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
||||
{
|
||||
FileData.Clear();
|
||||
FileSwapData.Clear();
|
||||
ManipulationData.Clear();
|
||||
|
||||
// Every option has a name, but priorities are only relevant for multi group options.
|
||||
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
Description = json[ nameof( ISubMod.Description ) ]?.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() )
|
||||
{
|
||||
foreach( var property in files.Properties() )
|
||||
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||
{
|
||||
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 >() ).Where( m => m.ManipulationType != MetaManipulation.Type.Unknown ) )
|
||||
{
|
||||
ManipulationData.Add( s );
|
||||
FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 swaps = ( JObject? )json[ nameof( FileSwaps ) ];
|
||||
if( swaps != null )
|
||||
{
|
||||
var deleteList = new List< string >();
|
||||
var oldSize = ManipulationData.Count;
|
||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||
foreach( var (key, file) in Files.ToList() )
|
||||
foreach( var property in swaps.Properties() )
|
||||
{
|
||||
var ext1 = key.Extension().AsciiToLower().ToString();
|
||||
var ext2 = file.Extension.ToLowerInvariant();
|
||||
try
|
||||
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||
{
|
||||
if( ext1 == ".meta" || ext2 == ".meta" )
|
||||
{
|
||||
FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = new TexToolsMeta( 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( 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 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var file in deleteList )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( file );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" );
|
||||
FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
|
||||
var manips = json[ nameof( Manipulations ) ];
|
||||
if( manips != null )
|
||||
{
|
||||
var files = TexToolsMeta.ConvertToTexTools( Manipulations );
|
||||
|
||||
foreach( var (file, data) in files )
|
||||
foreach( var s in manips.Children().Select( c => c.ToObject< MetaManipulation >() ).Where( m => m.ManipulationType != MetaManipulation.Type.Unknown ) )
|
||||
{
|
||||
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( files );
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "DEBUG" )]
|
||||
private void TestMetaWriting( 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( file, data, Penumbra.Config.KeepDefaultMetaChanges )
|
||||
: new TexToolsMeta( 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." );
|
||||
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.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( 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 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var file in deleteList )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( file );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
|
||||
{
|
||||
var files = TexToolsMeta.ConvertToTexTools( 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( files );
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG" )]
|
||||
private void TestMetaWriting( 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( file, data, Penumbra.Config.KeepDefaultMetaChanges )
|
||||
: new TexToolsMeta( 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." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Contains the settings for a given mod.
|
||||
/// <summary> Contains the settings for a given mod. </summary>
|
||||
public class ModSettings
|
||||
{
|
||||
public static readonly ModSettings Empty = new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue