From 45ec212b781e53caf0617a97163516e8908c432c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 1 Jan 2023 22:06:01 +0100 Subject: [PATCH] Change Item Swaps to use exceptions for actual error messages. --- Penumbra/Meta/Manager/MetaManager.cs | 1 - Penumbra/Mods/ItemSwap/CustomizationSwap.cs | 55 ++--- Penumbra/Mods/ItemSwap/EquipmentSwap.cs | 227 ++++++-------------- Penumbra/Mods/ItemSwap/ItemSwap.cs | 34 ++- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 46 ++-- Penumbra/Mods/ItemSwap/Swaps.cs | 46 ++-- Penumbra/UI/Classes/ItemSwapWindow.cs | 25 ++- 7 files changed, 149 insertions(+), 285 deletions(-) diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 0469dc09..a37af335 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using OtterGui; using Penumbra.Collections; diff --git a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs index f650818a..9b346c00 100644 --- a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs +++ b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; @@ -14,22 +12,17 @@ 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 bool CreateMdl( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, out FileSwap mdl ) + public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo ) { if( idFrom.Value > byte.MaxValue ) { - mdl = new FileSwap(); - return false; + 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() ); - if( !FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo, out mdl ) ) - { - return false; - } - + var mdl = FileSwap.CreateSwap( 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() ) @@ -38,22 +31,18 @@ public static class CustomizationSwap foreach( var variant in Enumerable.Range( 1, range ) ) { name = materialFileName; - if( !CreateMtrl( redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged, out var mtrl ) ) - { - return false; - } - + var mtrl = CreateMtrl( redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged ); mdl.ChildSwaps.Add( mtrl ); } materialFileName = name; } - return true; + return mdl; } - public static bool CreateMtrl( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant, - ref string fileName, ref bool dataWasChanged, out FileSwap mtrl ) + public static FileSwap CreateMtrl( 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 ); @@ -73,33 +62,21 @@ public static class CustomizationSwap dataWasChanged = true; } - if( !FileSwap.CreateSwap( ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, out mtrl, actualMtrlFromPath ) ) - { - return false; - } - - if( !CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged, out var shpk ) ) - { - return false; - } - + var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath ); + var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged ); mtrl.ChildSwaps.Add( shpk ); foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() ) { - if( !CreateTex( redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged, out var tex ) ) - { - return false; - } - + var tex = CreateTex( redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged ); mtrl.ChildSwaps.Add( tex ); } - return true; + return mtrl; } - public static bool CreateTex( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture, - ref bool dataWasChanged, out FileSwap tex ) + public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture, + ref bool dataWasChanged ) { var path = texture.Path; var addedDashes = false; @@ -122,13 +99,13 @@ public static class CustomizationSwap dataWasChanged = true; } - return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, out tex, path ); + return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, path ); } - public static bool CreateShader( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged, out FileSwap shpk ) + public static FileSwap CreateShader( Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged ) { var path = $"shader/sm5/shpk/{shaderName}"; - return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path, out shpk ); + return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path ); } } \ No newline at end of file diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 301108f2..3bdc99a9 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -17,7 +17,7 @@ namespace Penumbra.Mods.ItemSwap; public static class EquipmentSwap { - public static Item[] CreateItemSwap( List< Swap > swaps, IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, HashSet< MetaManipulation > manips, Item itemFrom, + public static Item[] CreateItemSwap( List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, Item itemFrom, Item itemTo ) { // Check actual ids, variants and slots. We only support using the same slot. @@ -28,21 +28,13 @@ public static class EquipmentSwap throw new ItemSwap.InvalidItemTypeException(); } - if( !CreateEqp( manips, slotFrom, idFrom, idTo, out var eqp ) ) - { - throw new Exception( "Could not get Eqp Entry for Swap." ); - } - + var eqp = CreateEqp( manips, slotFrom, idFrom, idTo ); if( eqp != null ) { swaps.Add( eqp ); } - if( !CreateGmp( manips, slotFrom, idFrom, idTo, out var gmp ) ) - { - throw new Exception( "Could not get Gmp Entry for Swap." ); - } - + var gmp = CreateGmp( manips, slotFrom, idFrom, idTo); if( gmp != null ) { swaps.Add( gmp ); @@ -68,21 +60,13 @@ public static class EquipmentSwap continue; } - if( !ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo, out var est ) ) - { - throw new Exception( "Could not get Est Entry for Swap." ); - } - + var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo ); if( est != null ) { swaps.Add( est ); } - if( !CreateEqdp( redirections, manips, slotFrom, gr, idFrom, idTo, mtrlVariantTo, out var eqdp ) ) - { - throw new Exception( "Could not get Eqdp Entry for Swap." ); - } - + var eqdp = CreateEqdp( redirections, manips, slotFrom, gr, idFrom, idTo, mtrlVariantTo ); if( eqdp != null ) { swaps.Add( eqdp ); @@ -91,11 +75,7 @@ public static class EquipmentSwap foreach( var variant in variants ) { - if( !CreateImc( redirections, manips, slotFrom, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo, out var imc ) ) - { - throw new Exception( "Could not get IMC Entry for Swap." ); - } - + var imc = CreateImc( redirections, manips, slotFrom, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo ); swaps.Add( imc ); } @@ -103,21 +83,17 @@ public static class EquipmentSwap return affectedItems; } - public static bool CreateEqdp( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, HashSet< MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom, - SetId idTo, byte mtrlTo, out MetaSwap? meta ) + public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom, + SetId idTo, byte mtrlTo ) { var (gender, race) = gr.Split(); var eqdpFrom = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slot.IsAccessory(), idFrom.Value ), slot, gender, race, idFrom.Value ); var eqdpTo = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slot.IsAccessory(), idTo.Value ), slot, gender, race, idTo.Value ); - meta = new MetaSwap( manips, eqdpFrom, eqdpTo ); + var meta = new MetaSwap( manips, eqdpFrom, eqdpTo ); var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits( slot ); if( ownMdl ) { - if( !CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo, out var mdl ) ) - { - return false; - } - + var mdl = CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo ); meta.ChildSwaps.Add( mdl ); } else if( !ownMtrl && meta.SwapAppliedIsDefault ) @@ -125,64 +101,25 @@ public static class EquipmentSwap meta = null; } - return true; + return meta; } - public static bool CreateMdl( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, EquipSlot slot, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo, - out FileSwap mdl ) + public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo ) { var mdlPathFrom = GamePaths.Equipment.Mdl.Path( idFrom, gr, slot ); var mdlPathTo = GamePaths.Equipment.Mdl.Path( idTo, gr, slot ); - if( !FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo, out mdl ) ) - { - return false; - } + var mdl = FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo ); foreach( ref var fileName in mdl.AsMdl()!.Materials.AsSpan() ) { - if( !CreateMtrl( redirections, slot, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged, out var mtrl ) ) - { - return false; - } - + var mtrl = CreateMtrl( redirections, slot, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged ); if( mtrl != null ) { mdl.ChildSwaps.Add( mtrl ); } } - return true; - } - - private static (GenderRace, GenderRace) TraverseEqdpTree( GenderRace genderRace, SetId modelId, EquipSlot slot ) - { - var model = GenderRace.Unknown; - var material = GenderRace.Unknown; - var accessory = slot.IsAccessory(); - foreach( var gr in genderRace.Dependencies() ) - { - var entry = ExpandedEqdpFile.GetDefault( gr, accessory, modelId.Value ); - var (b1, b2) = entry.ToBits( slot ); - if( b1 && material == GenderRace.Unknown ) - { - material = gr; - if( model != GenderRace.Unknown ) - { - return ( model, material ); - } - } - - if( b2 && model == GenderRace.Unknown ) - { - model = gr; - if( material != GenderRace.Unknown ) - { - return ( model, material ); - } - } - } - - return ( GenderRace.MidlanderMale, GenderRace.MidlanderMale ); + return mdl; } private static void LookupItem( Item i, out EquipSlot slot, out SetId modelId, out byte variant ) @@ -219,115 +156,99 @@ public static class EquipmentSwap return ( imc, variants, items ); } - public static bool CreateGmp( HashSet< MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo, out MetaSwap? gmp ) + public static MetaSwap? CreateGmp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo ) { if( slot is not EquipSlot.Head ) { - gmp = null; - return true; + return null; } var manipFrom = new GmpManipulation( ExpandedGmpFile.GetDefault( idFrom.Value ), idFrom.Value ); var manipTo = new GmpManipulation( ExpandedGmpFile.GetDefault( idTo.Value ), idTo.Value ); - gmp = new MetaSwap( manips, manipFrom, manipTo ); - return true; + return new MetaSwap( manips, manipFrom, manipTo ); } - public static bool CreateImc( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, HashSet< MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo, - byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo, out MetaSwap imc ) + public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo, + byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo ) { var entryFrom = imcFileFrom.GetEntry( ImcFile.PartIndex( slot ), variantFrom ); var entryTo = imcFileTo.GetEntry( ImcFile.PartIndex( slot ), variantTo ); var manipulationFrom = new ImcManipulation( slot, variantFrom, idFrom.Value, entryFrom ); var manipulationTo = new ImcManipulation( slot, variantTo, idTo.Value, entryTo ); - imc = new MetaSwap( manips, manipulationFrom, manipulationTo ); + var imc = new MetaSwap( manips, manipulationFrom, manipulationTo ); - if( !AddDecal( redirections, imc.SwapToModded.Imc.Entry.DecalId, imc ) ) + var decal = CreateDecal( redirections, imc.SwapToModded.Imc.Entry.DecalId ); + if( decal != null ) { - return false; + imc.ChildSwaps.Add( decal ); } - if( !AddAvfx( redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId, imc ) ) + var avfx = CreateAvfx( redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId ); + if( avfx != null ) { - return false; + imc.ChildSwaps.Add( avfx ); } // IMC also controls sound, Example: Dodore Doublet, but unknown what it does? // IMC also controls some material animation, Example: The Howling Spirit and The Wailing Spirit, but unknown what it does. - - return true; + return imc; } - + // Example: Crimson Standard Bracelet - public static bool AddDecal( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, byte decalId, MetaSwap imc ) + public static FileSwap? CreateDecal( Func< Utf8GamePath, FullPath > redirections, byte decalId ) { - if( decalId != 0 ) + if( decalId == 0 ) { - var decalPath = GamePaths.Equipment.Decal.Path( decalId ); - if( !FileSwap.CreateSwap( ResourceType.Tex, redirections, decalPath, decalPath, out var swap ) ) - { - return false; - } - - imc.ChildSwaps.Add( swap ); + return null; } - return true; + var decalPath = GamePaths.Equipment.Decal.Path( decalId ); + return FileSwap.CreateSwap( ResourceType.Tex, redirections, decalPath, decalPath ); } - + // Example: Abyssos Helm / Body - public static bool AddAvfx( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId, MetaSwap imc ) + public static FileSwap? CreateAvfx( Func< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId ) { - if( vfxId != 0 ) + if( vfxId == 0 ) { - var vfxPathFrom = GamePaths.Equipment.Avfx.Path( idFrom.Value, vfxId ); - var vfxPathTo = GamePaths.Equipment.Avfx.Path( idTo.Value, vfxId ); - if( !FileSwap.CreateSwap( ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo, out var swap ) ) - { - return false; - } - - foreach( ref var filePath in swap.AsAvfx()!.Textures.AsSpan() ) - { - if( !CreateAtex( redirections, ref filePath, ref swap.DataWasChanged, out var atex ) ) - { - return false; - } - - swap.ChildSwaps.Add( atex ); - } - - imc.ChildSwaps.Add( swap ); + return null; } - return true; + var vfxPathFrom = GamePaths.Equipment.Avfx.Path( idFrom.Value, vfxId ); + var vfxPathTo = GamePaths.Equipment.Avfx.Path( idTo.Value, vfxId ); + var avfx = FileSwap.CreateSwap( ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo ); + + foreach( ref var filePath in avfx.AsAvfx()!.Textures.AsSpan() ) + { + var atex = CreateAtex( redirections, ref filePath, ref avfx.DataWasChanged ); + avfx.ChildSwaps.Add( atex ); + } + + return avfx; } - public static bool CreateEqp( HashSet< MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo, out MetaSwap? eqp ) + public static MetaSwap? CreateEqp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo ) { if( slot.IsAccessory() ) { - eqp = null; - return true; + return null; } var eqpValueFrom = ExpandedEqpFile.GetDefault( idFrom.Value ); var eqpValueTo = ExpandedEqpFile.GetDefault( idTo.Value ); var eqpFrom = new EqpManipulation( eqpValueFrom, slot, idFrom.Value ); var eqpTo = new EqpManipulation( eqpValueTo, slot, idFrom.Value ); - eqp = new MetaSwap( manips, eqpFrom, eqpTo ); - return true; + return new MetaSwap( manips, eqpFrom, eqpTo ); } - public static bool CreateMtrl( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, EquipSlot slot, SetId idFrom, SetId idTo, byte variantTo, ref string fileName, - ref bool dataWasChanged, out FileSwap? mtrl ) + public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, SetId idFrom, SetId idTo, byte variantTo, ref string fileName, + ref bool dataWasChanged ) { var prefix = slot.IsAccessory() ? 'a' : 'e'; if( !fileName.Contains( $"{prefix}{idTo.Value:D4}" ) ) { - mtrl = null; - return true; + return null; } var folderTo = slot.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo ); @@ -343,34 +264,20 @@ public static class EquipmentSwap dataWasChanged = true; } - if( !FileSwap.CreateSwap( ResourceType.Mtrl, redirections, pathFrom, pathTo, out mtrl ) ) - { - return false; - } - - if( !CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged, out var shader ) ) - { - return false; - } - - mtrl.ChildSwaps.Add( shader ); + var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, pathFrom, pathTo ); + var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged ); + mtrl.ChildSwaps.Add( shpk ); foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() ) { - if( !CreateTex( redirections, prefix, idFrom, idTo, ref texture, ref mtrl.DataWasChanged, out var swap ) ) - { - return false; - } - - mtrl.ChildSwaps.Add( swap ); + var tex = CreateTex( redirections, prefix, idFrom, idTo, ref texture, ref mtrl.DataWasChanged ); + mtrl.ChildSwaps.Add( tex ); } - return true; + return mtrl; } - public static bool CreateTex( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, char prefix, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, - ref bool dataWasChanged, - out FileSwap tex ) + public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, char prefix, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged ) { var path = texture.Path; var addedDashes = false; @@ -392,21 +299,21 @@ public static class EquipmentSwap dataWasChanged = true; } - return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, out tex, path ); + return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, path ); } - public static bool CreateShader( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged, out FileSwap shpk ) + public static FileSwap CreateShader( Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged ) { var path = $"shader/sm5/shpk/{shaderName}"; - return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path, out shpk ); + return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path ); } - public static bool CreateAtex( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, ref string filePath, ref bool dataWasChanged, out FileSwap atex ) + public static FileSwap CreateAtex( Func< Utf8GamePath, FullPath > redirections, ref string filePath, ref bool dataWasChanged ) { var oldPath = filePath; filePath = ItemSwap.AddSuffix( filePath, ".atex", $"_{Path.GetFileName( filePath ).GetStableHashCode():x8}", true ); dataWasChanged = true; - return FileSwap.CreateSwap( ResourceType.Atex, redirections, filePath, oldPath, out atex, oldPath ); + return FileSwap.CreateSwap( ResourceType.Atex, redirections, filePath, oldPath, oldPath ); } } \ No newline at end of file diff --git a/Penumbra/Mods/ItemSwap/ItemSwap.cs b/Penumbra/Mods/ItemSwap/ItemSwap.cs index d32e2073..d8e8809a 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwap.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwap.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.RegularExpressions; @@ -137,54 +136,45 @@ public static class ItemSwap } - public static bool CreatePhyb( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry, out FileSwap phyb ) + public static FileSwap CreatePhyb( 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( ResourceType.Phyb, redirections, phybPath, phybPath, out phyb ); + return FileSwap.CreateSwap( ResourceType.Phyb, redirections, phybPath, phybPath ); } - public static bool CreateSklb( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry, out FileSwap sklb ) + public static FileSwap CreateSklb( 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( ResourceType.Sklb, redirections, sklbPath, sklbPath, out sklb ); + return FileSwap.CreateSwap( ResourceType.Sklb, redirections, sklbPath, sklbPath ); } /// metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. - public static bool CreateEst( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, HashSet< MetaManipulation > manips, EstManipulation.EstType type, - GenderRace genderRace, SetId idFrom, SetId idTo, out MetaSwap? est ) + public static MetaSwap? CreateEst( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type, + GenderRace genderRace, SetId idFrom, SetId idTo ) { if( type == 0 ) { - est = null; - return true; + return null; } var (gender, race) = genderRace.Split(); var fromDefault = new EstManipulation( gender, race, type, idFrom.Value, EstFile.GetDefault( type, genderRace, idFrom.Value ) ); var toDefault = new EstManipulation( gender, race, type, idTo.Value, EstFile.GetDefault( type, genderRace, idTo.Value ) ); - est = new MetaSwap( manips, fromDefault, toDefault ); + var est = new MetaSwap( manips, fromDefault, toDefault ); if( est.SwapApplied.Est.Entry >= 2 ) { - if( !CreatePhyb( redirections, type, genderRace, est.SwapApplied.Est.Entry, out var phyb ) ) - { - return false; - } - - if( !CreateSklb( redirections, type, genderRace, est.SwapApplied.Est.Entry, out var sklb ) ) - { - return false; - } - + var phyb = CreatePhyb( redirections, type, genderRace, est.SwapApplied.Est.Entry ); est.ChildSwaps.Add( phyb ); + var sklb = CreateSklb( redirections, type, genderRace, est.SwapApplied.Est.Entry ); est.ChildSwaps.Add( sklb ); } else if( est.SwapAppliedIsDefault ) { - est = null; + return null; } - return true; + return est; } public static int GetStableHashCode( this string str ) diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index eae8a60b..66ceb930 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Lumina.Excel.GeneratedSheets; +using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Meta.Manipulations; @@ -112,40 +113,39 @@ public class ItemSwapContainer LoadMod( null, null ); } - public Item[] LoadEquipment( Item from, Item to ) + 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 ); + + private Func< MetaManipulation, MetaManipulation > MetaResolver( ModCollection? collection ) { - try - { - Swaps.Clear(); - var ret = EquipmentSwap.CreateItemSwap( Swaps, ModRedirections, _modManipulations, from, to ); - Loaded = true; - return ret; - } - catch - { - Swaps.Clear(); - Loaded = false; - return Array.Empty< Item >(); - } + var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations; + return m => set.TryGetValue( m, out var a ) ? a : m; } - public bool LoadCustomization( BodySlot slot, GenderRace race, SetId from, SetId to ) + public Item[] LoadEquipment( Item from, Item to, ModCollection? collection = null ) { - if( !CustomizationSwap.CreateMdl( ModRedirections, slot, race, from, to, out var mdl ) ) - { - return false; - } + Swaps.Clear(); + Loaded = false; + var ret = EquipmentSwap.CreateItemSwap( Swaps, PathResolver( collection ), MetaResolver( collection ), from, to ); + Loaded = true; + return ret; + } + public bool LoadCustomization( BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null ) + { + var pathResolver = PathResolver( collection ); + var mdl = CustomizationSwap.CreateMdl( pathResolver, slot, race, from, to ); var type = slot switch { BodySlot.Hair => EstManipulation.EstType.Hair, BodySlot.Face => EstManipulation.EstType.Face, _ => ( EstManipulation.EstType )0, }; - if( !ItemSwap.CreateEst( ModRedirections, _modManipulations, type, race, from, to, out var est ) ) - { - return false; - } + + var metaResolver = MetaResolver( collection ); + var est = ItemSwap.CreateEst( pathResolver, metaResolver, type, race, from, to ); Swaps.Add( mdl ); if( est != null ) diff --git a/Penumbra/Mods/ItemSwap/Swaps.cs b/Penumbra/Mods/ItemSwap/Swaps.cs index 5018f2a7..5e40f9ac 100644 --- a/Penumbra/Mods/ItemSwap/Swaps.cs +++ b/Penumbra/Mods/ItemSwap/Swaps.cs @@ -1,3 +1,4 @@ +using System; using Penumbra.GameData.Files; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -41,25 +42,16 @@ public sealed class MetaSwap : Swap /// /// Create a new MetaSwap from the original meta identifier and the target meta identifier. /// - /// A set of modded meta manipulations to consider. This is not manipulated, but can not be IReadOnly because TryGetValue is not available for that. + /// A function that converts the given manipulation to the modded one. /// The original meta identifier with its default value. /// The target meta identifier with its default value. - public MetaSwap( HashSet< MetaManipulation > manipulations, MetaManipulation manipFrom, MetaManipulation manipTo ) + public MetaSwap( Func< MetaManipulation, MetaManipulation > manipulations, MetaManipulation manipFrom, MetaManipulation manipTo ) { SwapFrom = manipFrom; SwapToDefault = manipTo; - if( manipulations.TryGetValue( manipTo, out var actual ) ) - { - SwapToModded = actual; - SwapToIsDefault = false; - } - else - { - SwapToModded = manipTo; - SwapToIsDefault = true; - } - + SwapToModded = manipulations( manipTo ); + SwapToIsDefault = manipTo.EntryEquals( SwapToModded ); SwapApplied = SwapFrom.WithEntryOf( SwapToModded ); SwapAppliedIsDefault = SwapApplied.EntryEquals( SwapFrom ); } @@ -117,15 +109,14 @@ public sealed class FileSwap : Swap /// Create a full swap container for a specific file type using a modded redirection set, the actually requested path and the game file it should load instead after the swap. /// /// The file type. Mdl and Mtrl have special file loading treatment. - /// The set of redirections that need to be considered. + /// A function either returning the path after mod application. /// The path the game is going to request when loading the file. /// The unmodded path to the file the game is supposed to load instead. /// A full swap container with the actual file in memory. /// True if everything could be read correctly, false otherwise. - public static bool CreateSwap( ResourceType type, IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, string swapFromRequest, string swapToRequest, out FileSwap swap, - string? swapFromPreChange = null ) + public static FileSwap CreateSwap( ResourceType type, Func< Utf8GamePath, FullPath > redirections, string swapFromRequest, string swapToRequest, string? swapFromPreChange = null ) { - swap = new FileSwap + var swap = new FileSwap { Type = type, FileData = ItemSwap.GenericFile.Invalid, @@ -142,22 +133,22 @@ public sealed class FileSwap : Swap || !Utf8GamePath.FromString( swapToRequest, out swap.SwapToRequestPath ) || !Utf8GamePath.FromString( swapFromRequest, out swap.SwapFromRequestPath ) ) { - return false; + throw new Exception( $"Could not create UTF8 String for \"{swapFromRequest}\" or \"{swapToRequest}\"." ); } - swap.SwapToModded = redirections.TryGetValue( swap.SwapToRequestPath, out var p ) ? p : new FullPath( swap.SwapToRequestPath ); + swap.SwapToModded = redirections( swap.SwapToRequestPath ); swap.SwapToModdedExistsInGame = !swap.SwapToModded.IsRooted && Dalamud.GameData.FileExists( swap.SwapToModded.InternalName.ToString() ); swap.SwapToModdedEqualsOriginal = !swap.SwapToModded.IsRooted && swap.SwapToModded.InternalName.Equals( swap.SwapFromRequestPath.Path ); swap.FileData = type switch { - ResourceType.Mdl => ItemSwap.LoadMdl( swap.SwapToModded, out var f ) ? f : ItemSwap.GenericFile.Invalid, - ResourceType.Mtrl => ItemSwap.LoadMtrl( swap.SwapToModded, out var f ) ? f : ItemSwap.GenericFile.Invalid, - ResourceType.Avfx => ItemSwap.LoadAvfx( swap.SwapToModded, out var f ) ? f : ItemSwap.GenericFile.Invalid, - _ => ItemSwap.LoadFile( swap.SwapToModded, out var f ) ? f : ItemSwap.GenericFile.Invalid, + ResourceType.Mdl => ItemSwap.LoadMdl( swap.SwapToModded, out var f ) ? f : throw new Exception( $"Could not load file data for {swap.SwapToModded}." ), + ResourceType.Mtrl => ItemSwap.LoadMtrl( swap.SwapToModded, out var f ) ? f : throw new Exception( $"Could not load file data for {swap.SwapToModded}." ), + ResourceType.Avfx => ItemSwap.LoadAvfx( swap.SwapToModded, out var f ) ? f : throw new Exception( $"Could not load file data for {swap.SwapToModded}." ), + _ => ItemSwap.LoadFile( swap.SwapToModded, out var f ) ? f : throw new Exception( $"Could not load file data for {swap.SwapToModded}." ), }; - return swap.SwapToModdedExists; + return swap; } @@ -168,17 +159,14 @@ public sealed class FileSwap : Swap /// The in- and output path for a file /// Will be set to true if was changed. /// Will be updated. - public static bool CreateShaRedirection( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, ref string path, ref bool dataWasChanged, ref FileSwap swap ) + public static bool CreateShaRedirection( Func< Utf8GamePath, FullPath > redirections, ref string path, ref bool dataWasChanged, ref FileSwap swap ) { var oldFilename = Path.GetFileName( path ); var hash = SHA256.HashData( swap.FileData.Write() ); var name = $"{( oldFilename.StartsWith( "--" ) ? "--" : string.Empty )}{string.Join( null, hash.Select( c => c.ToString( "x2" ) ) )}.{swap.Type.ToString().ToLowerInvariant()}"; var newPath = path.Replace( oldFilename, name ); - if( !CreateSwap( swap.Type, redirections, newPath, swap.SwapToRequestPath.ToString(), out var newSwap ) ) - { - return false; - } + var newSwap = CreateSwap( swap.Type, redirections, newPath, swap.SwapToRequestPath.ToString()); path = newPath; dataWasChanged = true; diff --git a/Penumbra/UI/Classes/ItemSwapWindow.cs b/Penumbra/UI/Classes/ItemSwapWindow.cs index 2e59b540..124908f6 100644 --- a/Penumbra/UI/Classes/ItemSwapWindow.cs +++ b/Penumbra/UI/Classes/ItemSwapWindow.cs @@ -102,12 +102,13 @@ public class ItemSwapWindow : IDisposable private int _sourceId = 0; private Exception? _loadException = null; - private string _newModName = string.Empty; - private string _newGroupName = "Swaps"; - private string _newOptionName = string.Empty; - private IModGroup? _selectedGroup = null; - private bool _subModValid = false; - private bool _useFileSwaps = true; + private string _newModName = string.Empty; + private string _newGroupName = "Swaps"; + private string _newOptionName = string.Empty; + private IModGroup? _selectedGroup = null; + private bool _subModValid = false; + private bool _useFileSwaps = true; + private bool _useCurrentCollection = false; private Item[]? _affectedItems; @@ -157,21 +158,21 @@ public class ItemSwapWindow : IDisposable var values = _selectors[ _lastTab ]; if( values.Source.CurrentSelection.Item2 != null && values.Target.CurrentSelection.Item2 != null ) { - _affectedItems = _swapData.LoadEquipment( values.Target.CurrentSelection.Item2, values.Source.CurrentSelection.Item2 ); + _affectedItems = _swapData.LoadEquipment( values.Target.CurrentSelection.Item2, values.Source.CurrentSelection.Item2, _useCurrentCollection ? Penumbra.CollectionManager.Current : null ); } break; case SwapType.Hair when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId ); + _swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId, _useCurrentCollection ? Penumbra.CollectionManager.Current : null ); break; case SwapType.Face when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization( BodySlot.Face, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId ); + _swapData.LoadCustomization( BodySlot.Face, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId, _useCurrentCollection ? Penumbra.CollectionManager.Current : null ); break; case SwapType.Ears when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization( BodySlot.Zear, Names.CombinedRace( _currentGender, ModelRace.Viera ), ( SetId )_sourceId, ( SetId )_targetId ); + _swapData.LoadCustomization( BodySlot.Zear, Names.CombinedRace( _currentGender, ModelRace.Viera ), ( SetId )_sourceId, ( SetId )_targetId, _useCurrentCollection ? Penumbra.CollectionManager.Current : null ); break; case SwapType.Tail when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId ); + _swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId, _useCurrentCollection ? Penumbra.CollectionManager.Current : null ); break; case SwapType.Weapon: break; } @@ -180,6 +181,8 @@ public class ItemSwapWindow : IDisposable { Penumbra.Log.Error( $"Could not get Customization Data container for {_lastTab}:\n{e}" ); _loadException = e; + _affectedItems = null; + _swapData.Clear(); } _dirty = false;