Move mod creation functions to own subclass.

This commit is contained in:
Ottermandias 2023-03-03 13:43:00 +01:00
parent c2ac745d72
commit 1f942491ac
13 changed files with 237 additions and 233 deletions

View file

@ -150,11 +150,11 @@ public partial class Mod
foreach( var (group, groupIdx) in _mod.Groups.WithIndex() )
{
_redirections[ groupIdx + 1 ] = new Dictionary< Utf8GamePath, FullPath >[group.Count];
var groupDir = CreateModFolder( directory, group.Name );
var groupDir = Creator.CreateModFolder( directory, group.Name );
foreach( var option in group.OfType< SubMod >() )
{
var optionDir = CreateModFolder( groupDir, option.Name );
var optionDir = Creator.CreateModFolder( groupDir, option.Name );
newDict = new Dictionary< Utf8GamePath, FullPath >( option.FileData.Count );
_redirections[ groupIdx + 1 ][ option.OptionIdx ] = newDict;
foreach( var (gamePath, fullPath) in option.FileData )

View file

@ -64,12 +64,6 @@ public static class EquipmentSwap
var imcFileTo = new ImcFile( imcManip);
var isAccessory = slot.IsAccessory();
var estType = slot switch
{
EquipSlot.Head => EstManipulation.EstType.Head,
EquipSlot.Body => EstManipulation.EstType.Body,
_ => ( EstManipulation.EstType )0,
};
var skipFemale = false;
var skipMale = false;
@ -89,12 +83,6 @@ public static class EquipmentSwap
continue;
}
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo );
if( est != null )
{
swaps.Add( est );
}
try
{
var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo );
@ -140,6 +128,19 @@ public static class EquipmentSwap
{
var mdl = CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo );
meta.ChildSwaps.Add( mdl );
var estType = slot switch
{
EquipSlot.Head => EstManipulation.EstType.Head,
EquipSlot.Body => EstManipulation.EstType.Body,
_ => ( EstManipulation.EstType )0,
};
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo );
if( est != null )
{
meta.ChildSwaps.Add( est );
}
}
else if( !ownMtrl && meta.SwapAppliedIsDefault )
{

View file

@ -175,7 +175,7 @@ public partial class Mod
return NewDirectoryState.Identical;
}
var fixedNewName = ReplaceBadXivSymbols( newName );
var fixedNewName = Creator.ReplaceBadXivSymbols( newName );
if( fixedNewName != newName )
{
return NewDirectoryState.ContainsInvalidSymbols;

View file

@ -1,187 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Import;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
public partial class Mod
{
/// <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>
internal 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>
internal 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 );
}
// Create the file containing the meta information about a mod from scratch.
internal static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website )
{
var mod = new Mod( directory );
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
mod.Author = author != null ? new LowerString( author ) : mod.Author;
mod.Description = description ?? mod.Description;
mod.Version = version ?? mod.Version;
mod.Website = website ?? mod.Website;
mod.SaveMetaFile(); // Not delayed.
}
// Create a file for an option group from given data.
internal 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 ) ) );
IModGroup.Save( group, baseFolder, index );
break;
}
case GroupType.Single:
{
var group = new SingleModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.OptionData.AddRange( subMods.OfType< SubMod >() );
IModGroup.Save( group, baseFolder, index );
break;
}
}
}
// Create the data for a given sub mod from its data and the folder it is based on.
internal static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option )
{
var list = optionFolder.EnumerateFiles( "*.*", SearchOption.AllDirectories )
.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;
}
// Create an empty sub mod for single groups with None options.
internal static ISubMod CreateEmptySubMod( string name )
=> new SubMod( null! ) // Mod is irrelevant here, only used for saving.
{
Name = name,
};
// Create the default data file from all unused files that were not handled before
// and are used in sub mods.
internal static void CreateDefaultFiles( DirectoryInfo directory )
{
var mod = new Mod( directory );
mod.Reload( 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 );
mod.SaveDefaultMod();
}
// Return the name of a new valid directory based on the base directory and the given name.
private static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName )
=> new(Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) ));
// Normalize for nicer names, and remove invalid symbols or invalid paths.
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
{
if( s == "." )
{
return replacement;
}
if( s == ".." )
{
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();
}
}

View file

@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Import;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
public partial class Mod
{
internal static class Creator
{
/// <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 the file containing the meta information about a mod from scratch. </summary>
public static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website )
{
var mod = new Mod( directory );
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
mod.Author = author != null ? new LowerString( author ) : mod.Author;
mod.Description = description ?? mod.Description;
mod.Version = version ?? mod.Version;
mod.Website = website ?? mod.Website;
mod.SaveMetaFile(); // Not delayed.
}
/// <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 ) ) );
IModGroup.Save( group, baseFolder, index );
break;
}
case GroupType.Single:
{
var group = new SingleModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.OptionData.AddRange( subMods.OfType< SubMod >() );
IModGroup.Save( group, baseFolder, 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.EnumerateFiles( "*.*", SearchOption.AllDirectories )
.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( 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 );
mod.SaveDefaultMod();
}
/// <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();
}
}
}

View file

@ -51,9 +51,9 @@ public sealed partial class Mod
DirectoryInfo? dir = null;
try
{
dir = CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
dir = Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
Creator.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 = mod._default;

View file

@ -3,11 +3,13 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Internal.Notifications;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Util;
namespace Penumbra.Mods;
@ -63,8 +65,7 @@ public partial class Mod
{
if( ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions )
{
Penumbra.Log.Warning(
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options." );
ChatUtil.NotificationMessage( $"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning", NotificationType.Warning );
break;
}

View file

@ -67,7 +67,7 @@ public partial class Mod
_default.WriteTexToolsMeta( ModPath );
foreach( var group in Groups )
{
var dir = NewOptionDirectory( ModPath, group.Name );
var dir = Creator.NewOptionDirectory( ModPath, group.Name );
if( !dir.Exists )
{
dir.Create();
@ -75,7 +75,7 @@ public partial class Mod
foreach( var option in group.OfType< SubMod >() )
{
var optionDir = NewOptionDirectory( dir, option.Name );
var optionDir = Creator.NewOptionDirectory( dir, option.Name );
if( !optionDir.Exists )
{
optionDir.Create();