Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces.

This commit is contained in:
Ottermandias 2023-09-18 16:56:16 +02:00
parent 53adb6fa54
commit 2b4a01df06
155 changed files with 1620 additions and 1614 deletions

View file

@ -59,7 +59,7 @@ public class DuplicateManager
public void Clear()
{
_cancellationTokenSource.Cancel();
Worker = Task.CompletedTask;
Worker = Task.CompletedTask;
_duplicates.Clear();
SavedSpace = 0;
}

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;

View file

@ -7,14 +7,14 @@ public interface IMod
{
LowerString Name { get; }
public int Index { get; }
public int Index { get; }
public int Priority { get; }
public ISubMod Default { get; }
public IReadOnlyList< IModGroup > Groups { get; }
public ISubMod Default { get; }
public IReadOnlyList<IModGroup> Groups { get; }
public IEnumerable< SubMod > AllSubMods { get; }
// Cache
public IEnumerable<SubMod> AllSubMods { get; }
// Cache
public int TotalManipulations { get; }
}
}

View file

@ -1,25 +1,26 @@
using System.IO.Compression;
using OtterGui.Tasks;
using OtterGui.Tasks;
using Penumbra.Mods.Manager;
namespace Penumbra.Mods.Editor;
/// <summary> Utility to create and apply a zipped backup of a mod. </summary>
public class ModBackup
{
{
/// <summary> Set when reading Config and migrating from v4 to v5. </summary>
public static bool MigrateModBackups = false;
public static bool CreatingBackup { get; private set; }
private readonly Mod _mod;
public readonly string Name;
public readonly bool Exists;
private readonly Mod _mod;
public readonly string Name;
public readonly bool Exists;
public ModBackup(ModExportManager modExportManager, Mod mod)
{
_mod = mod;
Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp";
Exists = File.Exists(Name);
{
_mod = mod;
Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp";
Exists = File.Exists(Name);
}
/// <summary> Migrate file extensions. </summary>

View file

@ -1,4 +1,5 @@
using OtterGui;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor;

View file

@ -1,5 +1,6 @@
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
@ -7,7 +8,7 @@ namespace Penumbra.Mods;
public class ModFileEditor
{
private readonly ModFileCollection _files;
private readonly ModManager _modManager;
private readonly ModManager _modManager;
public bool Changes { get; private set; }

View file

@ -5,6 +5,7 @@ using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.UI.ModsTab;
@ -174,7 +175,7 @@ public class ModMerger : IDisposable
ret = new FullPath(MergeToMod!.ModPath, relPath);
return true;
}
foreach (var originalOption in mergeOptions)
{
foreach (var manip in originalOption.Manipulations)

View file

@ -1,5 +1,6 @@
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
namespace Penumbra.Mods;
@ -146,6 +147,7 @@ public class ModMetaEditor
}
}
}
Split(currentOption.Manipulations);
}

View file

@ -2,6 +2,7 @@ using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Tasks;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;

View file

@ -1,11 +1,12 @@
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
using Penumbra.Util;
public class ModSwapEditor
{
private readonly ModManager _modManager;
private readonly ModManager _modManager;
private readonly Dictionary<Utf8GamePath, FullPath> _swaps = new();
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps

View file

@ -10,27 +10,29 @@ namespace Penumbra.Mods.ItemSwap;
public static class CustomizationSwap
{
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
public static FileSwap CreateMdl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo )
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, SetId idTo)
{
if( idFrom.Id > byte.MaxValue )
{
throw new Exception( $"The Customization ID {idFrom} is too large for {slot}." );
}
if (idFrom.Id > byte.MaxValue)
throw new Exception($"The Customization ID {idFrom} is too large for {slot}.");
var mdlPathFrom = GamePaths.Character.Mdl.Path( race, slot, idFrom, slot.ToCustomizationType() );
var mdlPathTo = GamePaths.Character.Mdl.Path( race, slot, idTo, slot.ToCustomizationType() );
var mdlPathFrom = GamePaths.Character.Mdl.Path(race, slot, idFrom, slot.ToCustomizationType());
var mdlPathTo = GamePaths.Character.Mdl.Path(race, slot, idTo, slot.ToCustomizationType());
var mdl = FileSwap.CreateSwap( manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo );
var range = slot == BodySlot.Tail && race is GenderRace.HrothgarMale or GenderRace.HrothgarFemale or GenderRace.HrothgarMaleNpc or GenderRace.HrothgarMaleNpc ? 5 : 1;
var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo);
var range = slot == BodySlot.Tail
&& race is GenderRace.HrothgarMale or GenderRace.HrothgarFemale or GenderRace.HrothgarMaleNpc or GenderRace.HrothgarMaleNpc
? 5
: 1;
foreach( ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan() )
foreach (ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan())
{
var name = materialFileName;
foreach( var variant in Enumerable.Range( 1, range ) )
foreach (var variant in Enumerable.Range(1, range))
{
name = materialFileName;
var mtrl = CreateMtrl( manager, redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged );
mdl.ChildSwaps.Add( mtrl );
var mtrl = CreateMtrl(manager, redirections, slot, race, idFrom, idTo, (byte)variant, ref name, ref mdl.DataWasChanged);
mdl.ChildSwaps.Add(mtrl);
}
materialFileName = name;
@ -39,71 +41,75 @@ public static class CustomizationSwap
return mdl;
}
public static FileSwap CreateMtrl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant,
ref string fileName, ref bool dataWasChanged )
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, SetId idTo, byte variant,
ref string fileName, ref bool dataWasChanged)
{
variant = slot is BodySlot.Face or BodySlot.Zear ? byte.MaxValue : variant;
var mtrlFromPath = GamePaths.Character.Mtrl.Path( race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant );
var mtrlToPath = GamePaths.Character.Mtrl.Path( race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant );
var mtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant);
var mtrlToPath = GamePaths.Character.Mtrl.Path(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant);
var newFileName = fileName;
newFileName = ItemSwap.ReplaceRace( newFileName, gameRaceTo, race, gameRaceTo != race );
newFileName = ItemSwap.ReplaceBody( newFileName, slot, idTo, idFrom, idFrom != idTo );
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_c{race.ToRaceCode()}", gameRaceFrom != race || MaterialHandling.IsSpecialCase( race, idFrom ) );
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom );
newFileName = ItemSwap.ReplaceRace(newFileName, gameRaceTo, race, gameRaceTo != race);
newFileName = ItemSwap.ReplaceBody(newFileName, slot, idTo, idFrom, idFrom != idTo);
newFileName = ItemSwap.AddSuffix(newFileName, ".mtrl", $"_c{race.ToRaceCode()}",
gameRaceFrom != race || MaterialHandling.IsSpecialCase(race, idFrom));
newFileName = ItemSwap.AddSuffix(newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom);
var actualMtrlFromPath = mtrlFromPath;
if( newFileName != fileName )
if (newFileName != fileName)
{
actualMtrlFromPath = GamePaths.Character.Mtrl.Path( race, slot, idFrom, newFileName, out _, out _, variant );
actualMtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, newFileName, out _, out _, variant);
fileName = newFileName;
dataWasChanged = true;
}
var mtrl = FileSwap.CreateSwap( manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath );
var shpk = CreateShader( manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged );
mtrl.ChildSwaps.Add( shpk );
var mtrl = FileSwap.CreateSwap(manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath);
var shpk = CreateShader(manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged);
mtrl.ChildSwaps.Add(shpk);
foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() )
foreach (ref var texture in mtrl.AsMtrl()!.Textures.AsSpan())
{
var tex = CreateTex( manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged );
mtrl.ChildSwaps.Add( tex );
var tex = CreateTex(manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged);
mtrl.ChildSwaps.Add(tex);
}
return mtrl;
}
public static FileSwap CreateTex( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture,
ref bool dataWasChanged )
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, ref MtrlFile.Texture texture,
ref bool dataWasChanged)
{
var path = texture.Path;
var addedDashes = false;
if( texture.DX11 )
if (texture.DX11)
{
var fileName = Path.GetFileName( path );
if( !fileName.StartsWith( "--" ) )
var fileName = Path.GetFileName(path);
if (!fileName.StartsWith("--"))
{
path = path.Replace( fileName, $"--{fileName}" );
path = path.Replace(fileName, $"--{fileName}");
addedDashes = true;
}
}
var newPath = ItemSwap.ReplaceAnyRace( path, race );
newPath = ItemSwap.ReplaceAnyBody( newPath, slot, idFrom );
newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}", true );
if( newPath != path )
var newPath = ItemSwap.ReplaceAnyRace(path, race);
newPath = ItemSwap.ReplaceAnyBody(newPath, slot, idFrom);
newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}", true);
if (newPath != path)
{
texture.Path = addedDashes ? newPath.Replace( "--", string.Empty ) : newPath;
texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath;
dataWasChanged = true;
}
return FileSwap.CreateSwap( manager, ResourceType.Tex, redirections, newPath, path, path );
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
}
public static FileSwap CreateShader( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged )
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName,
ref bool dataWasChanged)
{
var path = $"shader/sm5/shpk/{shaderName}";
return FileSwap.CreateSwap( manager, ResourceType.Shpk, redirections, path, path );
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
}
}
}

View file

@ -249,10 +249,10 @@ public static class EquipmentSwap
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
SetId idFrom, SetId idTo, Variant variantFrom)
{
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry);
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry);
EquipItem[] items;
Variant[] variants;
Variant[] variants;
if (idFrom == idTo)
{
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
@ -264,8 +264,9 @@ public static class EquipmentSwap
else
{
items = identifier.Identify(slotFrom.IsEquipment()
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray();
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>()
.ToArray();
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
}
@ -283,11 +284,13 @@ public static class EquipmentSwap
return new MetaSwap(manips, manipFrom, manipTo);
}
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips,
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips,
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
{
@ -401,7 +404,8 @@ public static class EquipmentSwap
ref MtrlFile.Texture texture, ref bool dataWasChanged)
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom,
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
EquipSlot slotTo, SetId idFrom,
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
{
var path = texture.Path;
@ -428,13 +432,15 @@ public static class EquipmentSwap
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
}
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName, ref bool dataWasChanged)
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName,
ref bool dataWasChanged)
{
var path = $"shader/sm5/shpk/{shaderName}";
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
}
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath, ref bool dataWasChanged)
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath,
ref bool dataWasChanged)
{
var oldPath = filePath;
filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}");

View file

@ -20,47 +20,45 @@ public static class ItemSwap
{
public readonly ResourceType Type;
public MissingFileException( ResourceType type, object path )
public MissingFileException(ResourceType type, object path)
: base($"Could not load {type} File Data for \"{path}\".")
=> Type = type;
}
private static bool LoadFile( MetaFileManager manager, FullPath path, out byte[] data )
private static bool LoadFile(MetaFileManager manager, FullPath path, out byte[] data)
{
if( path.FullName.Length > 0 )
{
if (path.FullName.Length > 0)
try
{
if( path.IsRooted )
if (path.IsRooted)
{
data = File.ReadAllBytes( path.FullName );
data = File.ReadAllBytes(path.FullName);
return true;
}
var file = manager.GameData.GetFile( path.InternalName.ToString() );
if( file != null )
var file = manager.GameData.GetFile(path.InternalName.ToString());
if (file != null)
{
data = file.Data;
return true;
}
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Debug( $"Could not load file {path}:\n{e}" );
Penumbra.Log.Debug($"Could not load file {path}:\n{e}");
}
}
data = Array.Empty< byte >();
data = Array.Empty<byte>();
return false;
}
public class GenericFile : IWritable
{
public readonly byte[] Data;
public bool Valid { get; }
public bool Valid { get; }
public GenericFile( MetaFileManager manager, FullPath path )
=> Valid = LoadFile( manager, path, out Data );
public GenericFile(MetaFileManager manager, FullPath path)
=> Valid = LoadFile(manager, path, out Data);
public byte[] Write()
=> Data;
@ -68,69 +66,67 @@ public static class ItemSwap
public static readonly GenericFile Invalid = new(null!, FullPath.Empty);
}
public static bool LoadFile( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out GenericFile? file )
public static bool LoadFile(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out GenericFile? file)
{
file = new GenericFile( manager, path );
if( file.Valid )
{
file = new GenericFile(manager, path);
if (file.Valid)
return true;
}
file = null;
return false;
}
public static bool LoadMdl( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MdlFile? file )
public static bool LoadMdl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MdlFile? file)
{
try
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new MdlFile( data );
file = new MdlFile(data);
return true;
}
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Debug( $"Could not parse file {path} to Mdl:\n{e}" );
Penumbra.Log.Debug($"Could not parse file {path} to Mdl:\n{e}");
}
file = null;
return false;
}
public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MtrlFile? file )
public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MtrlFile? file)
{
try
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new MtrlFile( data );
file = new MtrlFile(data);
return true;
}
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Debug( $"Could not parse file {path} to Mtrl:\n{e}" );
Penumbra.Log.Debug($"Could not parse file {path} to Mtrl:\n{e}");
}
file = null;
return false;
}
public static bool LoadAvfx( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out AvfxFile? file )
public static bool LoadAvfx(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out AvfxFile? file)
{
try
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new AvfxFile( data );
file = new AvfxFile(data);
return true;
}
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Debug( $"Could not parse file {path} to Avfx:\n{e}" );
Penumbra.Log.Debug($"Could not parse file {path} to Avfx:\n{e}");
}
file = null;
@ -138,40 +134,41 @@ public static class ItemSwap
}
public static FileSwap CreatePhyb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry )
public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
GenderRace race, ushort estEntry)
{
var phybPath = GamePaths.Skeleton.Phyb.Path( race, EstManipulation.ToName( type ), estEntry );
return FileSwap.CreateSwap( manager, ResourceType.Phyb, redirections, phybPath, phybPath );
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry);
return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
}
public static FileSwap CreateSklb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry )
public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
GenderRace race, ushort estEntry)
{
var sklbPath = GamePaths.Skeleton.Sklb.Path( race, EstManipulation.ToName( type ), estEntry );
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath );
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry);
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
}
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
public static MetaSwap? CreateEst( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl )
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl)
{
if( type == 0 )
{
if (type == 0)
return null;
}
var (gender, race) = genderRace.Split();
var fromDefault = new EstManipulation( gender, race, type, idFrom, EstFile.GetDefault( manager, type, genderRace, idFrom ) );
var toDefault = new EstManipulation( gender, race, type, idTo, EstFile.GetDefault( manager, type, genderRace, idTo ) );
var est = new MetaSwap( manips, fromDefault, toDefault );
var fromDefault = new EstManipulation(gender, race, type, idFrom, EstFile.GetDefault(manager, type, genderRace, idFrom));
var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo));
var est = new MetaSwap(manips, fromDefault, toDefault);
if( ownMdl && est.SwapApplied.Est.Entry >= 2 )
if (ownMdl && est.SwapApplied.Est.Entry >= 2)
{
var phyb = CreatePhyb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry );
est.ChildSwaps.Add( phyb );
var sklb = CreateSklb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry );
est.ChildSwaps.Add( sklb );
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add(phyb);
var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add(sklb);
}
else if( est.SwapAppliedIsDefault )
else if (est.SwapAppliedIsDefault)
{
return null;
}
@ -179,57 +176,55 @@ public static class ItemSwap
return est;
}
public static int GetStableHashCode( this string str )
public static int GetStableHashCode(this string str)
{
unchecked
{
var hash1 = 5381;
var hash2 = hash1;
for( var i = 0; i < str.Length && str[ i ] != '\0'; i += 2 )
for (var i = 0; i < str.Length && str[i] != '\0'; i += 2)
{
hash1 = ( ( hash1 << 5 ) + hash1 ) ^ str[ i ];
if( i == str.Length - 1 || str[ i + 1 ] == '\0' )
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1 || str[i + 1] == '\0')
break;
}
hash2 = ( ( hash2 << 5 ) + hash2 ) ^ str[ i + 1 ];
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return hash1 + hash2 * 1566083941;
}
}
public static string ReplaceAnyId( string path, char idType, SetId id, bool condition = true )
public static string ReplaceAnyId(string path, char idType, SetId id, bool condition = true)
=> condition
? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}" )
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
: path;
public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true )
=> ReplaceAnyId( path, 'c', ( ushort )to, condition );
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
=> ReplaceAnyId(path, 'c', (ushort)to, condition);
public static string ReplaceAnyBody( string path, BodySlot slot, SetId to, bool condition = true )
=> ReplaceAnyId( path, slot.ToAbbreviation(), to, condition );
public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true)
=> ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
public static string ReplaceId( string path, char type, SetId idFrom, SetId idTo, bool condition = true )
public static string ReplaceId(string path, char type, SetId idFrom, SetId idTo, bool condition = true)
=> condition
? path.Replace( $"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}" )
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
: path;
public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true )
public static string ReplaceSlot(string path, EquipSlot from, EquipSlot to, bool condition = true)
=> condition
? path.Replace( $"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_" )
? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_")
: path;
public static string ReplaceRace( string path, GenderRace from, GenderRace to, bool condition = true )
=> ReplaceId( path, 'c', ( ushort )from, ( ushort )to, condition );
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
public static string ReplaceBody( string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true )
=> ReplaceId( path, slot.ToAbbreviation(), idFrom, idTo, condition );
public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true)
=> ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
public static string AddSuffix( string path, string ext, string suffix, bool condition = true )
public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
=> condition
? path.Replace( ext, suffix + ext )
? path.Replace(ext, suffix + ext)
: path;
}
}

View file

@ -6,6 +6,7 @@ using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes;
using Penumbra.Meta;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
namespace Penumbra.Mods.ItemSwap;
@ -13,18 +14,18 @@ namespace Penumbra.Mods.ItemSwap;
public class ItemSwapContainer
{
private readonly MetaFileManager _manager;
private readonly IdentifierService _identifier;
private readonly IdentifierService _identifier;
private Dictionary< Utf8GamePath, FullPath > _modRedirections = new();
private HashSet< MetaManipulation > _modManipulations = new();
private Dictionary<Utf8GamePath, FullPath> _modRedirections = new();
private HashSet<MetaManipulation> _modManipulations = new();
public IReadOnlyDictionary< Utf8GamePath, FullPath > ModRedirections
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
=> _modRedirections;
public IReadOnlySet< MetaManipulation > ModManipulations
public IReadOnlySet<MetaManipulation> ModManipulations
=> _modManipulations;
public readonly List< Swap > Swaps = new();
public readonly List<Swap> Swaps = new();
public bool Loaded { get; private set; }
@ -40,72 +41,69 @@ public class ItemSwapContainer
NoSwaps,
}
public bool WriteMod( ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 )
public bool WriteMod(ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null,
int groupIndex = -1, int optionIndex = 0)
{
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
var convertedManips = new HashSet<MetaManipulation>(Swaps.Count);
var convertedFiles = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
var convertedSwaps = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
directory ??= mod.ModPath;
try
{
foreach( var swap in Swaps.SelectMany( s => s.WithChildren() ) )
foreach (var swap in Swaps.SelectMany(s => s.WithChildren()))
{
switch( swap )
switch (swap)
{
case FileSwap file:
// Skip, nothing to do
if( file.SwapToModdedEqualsOriginal )
{
if (file.SwapToModdedEqualsOriginal)
continue;
}
if( writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged )
if (writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged)
{
convertedSwaps.TryAdd( file.SwapFromRequestPath, file.SwapToModded );
convertedSwaps.TryAdd(file.SwapFromRequestPath, file.SwapToModded);
}
else
{
var path = file.GetNewPath( directory.FullName );
var path = file.GetNewPath(directory.FullName);
var bytes = file.FileData.Write();
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
_manager.Compactor.WriteAllBytes( path, bytes );
convertedFiles.TryAdd( file.SwapFromRequestPath, new FullPath( path ) );
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
_manager.Compactor.WriteAllBytes(path, bytes);
convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path));
}
break;
case MetaSwap meta:
if( !meta.SwapAppliedIsDefault )
{
convertedManips.Add( meta.SwapApplied );
}
if (!meta.SwapAppliedIsDefault)
convertedManips.Add(meta.SwapApplied);
break;
}
}
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
manager.OptionEditor.OptionSetFiles(mod, groupIndex, optionIndex, convertedFiles);
manager.OptionEditor.OptionSetFileSwaps(mod, groupIndex, optionIndex, convertedSwaps);
manager.OptionEditor.OptionSetManipulations(mod, groupIndex, optionIndex, convertedManips);
return true;
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Could not write FileSwapContainer to {mod.ModPath}:\n{e}" );
Penumbra.Log.Error($"Could not write FileSwapContainer to {mod.ModPath}:\n{e}");
return false;
}
}
public void LoadMod( Mod? mod, ModSettings? settings )
public void LoadMod(Mod? mod, ModSettings? settings)
{
Clear();
if( mod == null )
if (mod == null)
{
_modRedirections = new Dictionary< Utf8GamePath, FullPath >();
_modManipulations = new HashSet< MetaManipulation >();
_modRedirections = new Dictionary<Utf8GamePath, FullPath>();
_modManipulations = new HashSet<MetaManipulation>();
}
else
{
( _modRedirections, _modManipulations ) = ModSettings.GetResolveData( mod, settings );
(_modRedirections, _modManipulations) = ModSettings.GetResolveData(mod, settings);
}
}
@ -113,59 +111,61 @@ public class ItemSwapContainer
{
_manager = manager;
_identifier = identifier;
LoadMod( null, null );
LoadMod(null, null);
}
private Func< Utf8GamePath, FullPath > PathResolver( ModCollection? collection )
private Func<Utf8GamePath, FullPath> PathResolver(ModCollection? collection)
=> collection != null
? p => collection.ResolvePath( p ) ?? new FullPath( p )
: p => ModRedirections.TryGetValue( p, out var path ) ? path : new FullPath( p );
? p => collection.ResolvePath(p) ?? new FullPath(p)
: p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p);
private Func< MetaManipulation, MetaManipulation > MetaResolver( ModCollection? collection )
private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection)
{
var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations;
return m => set.TryGetValue( m, out var a ) ? a : m;
return m => set.TryGetValue(m, out var a) ? a : m;
}
public EquipItem[] LoadEquipment( EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, bool useLeftRing = true )
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
bool useLeftRing = true)
{
Swaps.Clear();
Loaded = false;
var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing );
var ret = EquipmentSwap.CreateItemSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
from, to, useRightRing, useLeftRing);
Loaded = true;
return ret;
}
public EquipItem[] LoadTypeSwap( EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null )
public EquipItem[] LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null)
{
Swaps.Clear();
Loaded = false;
var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to );
var ret = EquipmentSwap.CreateTypeSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
slotFrom, from, slotTo, to);
Loaded = true;
return ret;
}
public bool LoadCustomization( MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null )
public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to,
ModCollection? collection = null)
{
var pathResolver = PathResolver( collection );
var mdl = CustomizationSwap.CreateMdl( manager, pathResolver, slot, race, from, to );
var pathResolver = PathResolver(collection);
var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
var type = slot switch
{
BodySlot.Hair => EstManipulation.EstType.Hair,
BodySlot.Face => EstManipulation.EstType.Face,
_ => ( EstManipulation.EstType )0,
_ => (EstManipulation.EstType)0,
};
var metaResolver = MetaResolver( collection );
var est = ItemSwap.CreateEst( manager, pathResolver, metaResolver, type, race, from, to, true );
var metaResolver = MetaResolver(collection);
var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true);
Swaps.Add( mdl );
if( est != null )
{
Swaps.Add( est );
}
Swaps.Add(mdl);
if (est != null)
Swaps.Add(est);
Loaded = true;
return true;
}
}
}

View file

@ -89,4 +89,4 @@ public class ModExportManager : IDisposable
new ModBackup(this, mod).Move(null, newDirectory.Name);
mod.ModPath = newDirectory;
}
}
}

View file

@ -2,11 +2,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Manager;
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
{

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Interface.Internal.Notifications;
using Penumbra.Import;

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Penumbra.Communication;
using Penumbra.Mods.Editor;
using Penumbra.Services;
@ -16,7 +15,7 @@ public enum NewDirectoryState
Identical,
Empty,
}
/// <summary> Describes the state of a changed mod event. </summary>
public enum ModPathChangeType
{
@ -25,7 +24,7 @@ public enum ModPathChangeType
Moved,
Reloaded,
StartingReload,
}
}
public sealed class ModManager : ModStorage, IDisposable
{
@ -46,8 +45,8 @@ public sealed class ModManager : ModStorage, IDisposable
_communicator = communicator;
DataEditor = dataEditor;
OptionEditor = optionEditor;
Creator = creator;
SetBaseDirectory(config.ModDirectory, true);
Creator = creator;
SetBaseDirectory(config.ModDirectory, true);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager);
DiscoverMods();
}
@ -242,7 +241,7 @@ public sealed class ModManager : ModStorage, IDisposable
{
switch (type)
{
case ModPathChangeType.Added:
case ModPathChangeType.Added:
SetNew(mod);
break;
case ModPathChangeType.Deleted:

View file

@ -6,7 +6,6 @@ using Penumbra.Api.Enums;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods.Manager;
@ -19,7 +18,9 @@ public static partial class ModMigration
private static partial Regex GroupStartRegex();
public static bool Migrate(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
=> MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion) || MigrateV1ToV2(saveService, mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
=> MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion)
|| MigrateV1ToV2(saveService, mod, ref fileVersion)
|| MigrateV2ToV3(mod, ref fileVersion);
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
{
@ -63,8 +64,8 @@ public static partial class ModMigration
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;
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
var priority = 1;
var seenMetaFiles = new HashSet<FullPath>();
foreach (var group in groups.Values)
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
@ -128,8 +129,8 @@ public static partial class ModMigration
var optionPriority = 0;
var newMultiGroup = new MultiModGroup()
{
Name = group.GroupName,
Priority = priority++,
Name = group.GroupName,
Priority = priority++,
Description = string.Empty,
};
mod.Groups.Add(newMultiGroup);
@ -146,8 +147,8 @@ public static partial class ModMigration
var newSingleGroup = new SingleModGroup()
{
Name = group.GroupName,
Priority = priority++,
Name = group.GroupName,
Priority = priority++,
Description = string.Empty,
};
mod.Groups.Add(newSingleGroup);

View file

@ -5,7 +5,7 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods;
public sealed partial class Mod : IMod
public sealed class Mod : IMod
{
public static readonly TemporaryMod ForcedFiles = new()
{

View file

@ -113,9 +113,7 @@ public partial class ModCreator
}
if (changes)
{
_saveService.SaveAllOptionGroups(mod, true);
}
}
/// <summary> Load the default option for a given mod.</summary>

View file

@ -2,7 +2,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods;

View file

@ -1,7 +1,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods;

View file

@ -1,58 +1,57 @@
using Newtonsoft.Json;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Subclasses;
public interface ISubMod
{
public string Name { get; }
public string FullName { get; }
public string Name { get; }
public string FullName { get; }
public string Description { get; }
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
public IReadOnlySet< MetaManipulation > Manipulations { get; }
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files { get; }
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps { get; }
public IReadOnlySet<MetaManipulation> Manipulations { get; }
public bool IsDefault { get; }
public static void WriteSubMod( JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority )
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority)
{
j.WriteStartObject();
j.WritePropertyName( nameof( Name ) );
j.WriteValue( mod.Name );
j.WritePropertyName( nameof(Description) );
j.WriteValue( mod.Description );
if( priority != null )
j.WritePropertyName(nameof(Name));
j.WriteValue(mod.Name);
j.WritePropertyName(nameof(Description));
j.WriteValue(mod.Description);
if (priority != null)
{
j.WritePropertyName( nameof( IModGroup.Priority ) );
j.WriteValue( priority.Value );
j.WritePropertyName(nameof(IModGroup.Priority));
j.WriteValue(priority.Value);
}
j.WritePropertyName( nameof( mod.Files ) );
j.WritePropertyName(nameof(mod.Files));
j.WriteStartObject();
foreach( var (gamePath, file) in mod.Files )
foreach (var (gamePath, file) in mod.Files)
{
if( file.ToRelPath( basePath, out var relPath ) )
if (file.ToRelPath(basePath, out var relPath))
{
j.WritePropertyName( gamePath.ToString() );
j.WriteValue( relPath.ToString() );
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
}
}
j.WriteEndObject();
j.WritePropertyName( nameof( mod.FileSwaps ) );
j.WritePropertyName(nameof(mod.FileSwaps));
j.WriteStartObject();
foreach( var (gamePath, file) in mod.FileSwaps )
foreach (var (gamePath, file) in mod.FileSwaps)
{
j.WritePropertyName( gamePath.ToString() );
j.WriteValue( file.ToString() );
j.WritePropertyName(gamePath.ToString());
j.WriteValue(file.ToString());
}
j.WriteEndObject();
j.WritePropertyName( nameof( mod.Manipulations ) );
serializer.Serialize( j, mod.Manipulations );
j.WritePropertyName(nameof(mod.Manipulations));
serializer.Serialize(j, mod.Manipulations);
j.WriteEndObject();
}
}
}

View file

@ -3,10 +3,9 @@ using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Subclasses;
/// <summary> Contains the settings for a given mod. </summary>
public class ModSettings
@ -267,4 +266,4 @@ public class ModSettings
return ( Enabled, Priority, dict );
}
}
}

View file

@ -4,10 +4,9 @@ using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Mods.Subclasses;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Subclasses;
/// <summary> Groups that allow all available options to be selected at once. </summary>
public sealed class MultiModGroup : IModGroup
{

View file

@ -3,9 +3,8 @@ using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Mods.Subclasses;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Subclasses;
/// <summary> Groups that allow only one of their available options to be selected. </summary>
public sealed class SingleModGroup : IModGroup
@ -18,59 +17,55 @@ public sealed class SingleModGroup : IModGroup
public int Priority { get; set; }
public uint DefaultSettings { get; set; }
public readonly List< SubMod > OptionData = new();
public readonly List<SubMod> OptionData = new();
public int OptionPriority( Index _ )
public int OptionPriority(Index _)
=> Priority;
public ISubMod this[ Index idx ]
=> OptionData[ idx ];
public ISubMod this[Index idx]
=> OptionData[idx];
[JsonIgnore]
public int Count
=> OptionData.Count;
public IEnumerator< ISubMod > GetEnumerator()
public IEnumerator<ISubMod> GetEnumerator()
=> OptionData.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
public static SingleModGroup? Load(Mod mod, JObject json, int groupIdx)
{
var options = json[ "Options" ];
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,
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 )
{
if (ret.Name.Length == 0)
return null;
}
if( options != null )
{
foreach( var child in options.Children() )
if (options != null)
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 );
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 )
if ((int)ret.DefaultSettings >= ret.Count)
ret.DefaultSettings = 0;
return ret;
}
public IModGroup Convert( GroupType type )
public IModGroup Convert(GroupType type)
{
switch( type )
switch (type)
{
case GroupType.Single: return this;
case GroupType.Multi:
@ -79,47 +74,41 @@ public sealed class SingleModGroup : IModGroup
Name = Name,
Description = Description,
Priority = Priority,
DefaultSettings = 1u << ( int )DefaultSettings,
DefaultSettings = 1u << (int)DefaultSettings,
};
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
multi.PrioritizedOptions.AddRange(OptionData.Select((o, i) => (o, i)));
return multi;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
{
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
{
if (!OptionData.Move(optionIdxFrom, optionIdxTo))
return false;
}
// Update default settings with the move.
if( DefaultSettings == optionIdxFrom )
if (DefaultSettings == optionIdxFrom)
{
DefaultSettings = ( uint )optionIdxTo;
DefaultSettings = (uint)optionIdxTo;
}
else if( optionIdxFrom < optionIdxTo )
else if (optionIdxFrom < optionIdxTo)
{
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
{
if (DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo)
--DefaultSettings;
}
}
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
else if (DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo)
{
++DefaultSettings;
}
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
return true;
}
public void UpdatePositions( int from = 0 )
public void UpdatePositions(int from = 0)
{
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
{
o.SetPosition( o.GroupIdx, i );
}
foreach (var (o, i) in OptionData.WithIndex().Skip(from))
o.SetPosition(o.GroupIdx, i);
}
}
}

View file

@ -1,11 +1,8 @@
using Newtonsoft.Json.Linq;
using Penumbra.Import;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Subclasses;
/// <summary>
/// A sub mod is a collection of

View file

@ -5,7 +5,6 @@ using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods;
@ -21,80 +20,84 @@ public class TemporaryMod : IMod
public readonly SubMod Default;
ISubMod IMod.Default
=> Default;
=> Default;
public IReadOnlyList< IModGroup > Groups
=> Array.Empty< IModGroup >();
public IReadOnlyList<IModGroup> Groups
=> Array.Empty<IModGroup>();
public IEnumerable< SubMod > AllSubMods
=> new[] { Default };
public IEnumerable<SubMod> AllSubMods
=> new[]
{
Default,
};
public TemporaryMod()
=> Default = new SubMod( this );
=> Default = new SubMod(this);
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
=> Default.FileData[ gamePath ] = fullPath;
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
=> Default.FileData[gamePath] = fullPath;
public bool SetManipulation( MetaManipulation manip )
=> Default.ManipulationData.Remove( manip ) | Default.ManipulationData.Add( manip );
public bool SetManipulation(MetaManipulation manip)
=> Default.ManipulationData.Remove(manip) | Default.ManipulationData.Add(manip);
public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips )
public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, HashSet<MetaManipulation> manips)
{
Default.FileData = dict;
Default.ManipulationData = manips;
}
public static void SaveTempCollection( Configuration config, SaveService saveService, ModManager modManager, ModCollection collection, string? character = null )
public static void SaveTempCollection(Configuration config, SaveService saveService, ModManager modManager, ModCollection collection,
string? character = null)
{
DirectoryInfo? dir = null;
try
{
dir = ModCreator.CreateModFolder( modManager.BasePath, collection.Name );
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? config.DefaultModAuthor,
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
var mod = new Mod( dir );
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name);
var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files"));
modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor,
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null);
var mod = new Mod(dir);
var defaultMod = mod.Default;
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
foreach (var (gamePath, fullPath) in collection.ResolvedFiles)
{
if( gamePath.Path.EndsWith( ".imc"u8 ) )
if (gamePath.Path.EndsWith(".imc"u8))
{
continue;
}
var targetPath = fullPath.Path.FullName;
if( fullPath.Path.Name.StartsWith( '|' ) )
if (fullPath.Path.Name.StartsWith('|'))
{
targetPath = targetPath.Split( '|', 3, StringSplitOptions.RemoveEmptyEntries ).Last();
targetPath = targetPath.Split('|', 3, StringSplitOptions.RemoveEmptyEntries).Last();
}
if( Path.IsPathRooted(targetPath) )
if (Path.IsPathRooted(targetPath))
{
var target = Path.Combine( fileDir.FullName, Path.GetFileName(targetPath) );
File.Copy( targetPath, target, true );
defaultMod.FileData[ gamePath ] = new FullPath( target );
var target = Path.Combine(fileDir.FullName, Path.GetFileName(targetPath));
File.Copy(targetPath, target, true);
defaultMod.FileData[gamePath] = new FullPath(target);
}
else
{
defaultMod.FileSwapData[ gamePath ] = new FullPath(targetPath);
defaultMod.FileSwapData[gamePath] = new FullPath(targetPath);
}
}
foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() )
defaultMod.ManipulationData.Add( manip );
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
defaultMod.ManipulationData.Add(manip);
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
modManager.AddMod( dir );
Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." );
modManager.AddMod(dir);
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}.");
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}" );
if( dir != null && Directory.Exists( dir.FullName ) )
Penumbra.Log.Error($"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}");
if (dir != null && Directory.Exists(dir.FullName))
{
try
{
Directory.Delete( dir.FullName, true );
Directory.Delete(dir.FullName, true);
}
catch
{
@ -103,4 +106,4 @@ public class TemporaryMod : IMod
}
}
}
}
}