mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-01-02 13:53:42 +01:00
Allow item swapping between from accessories and hats to other accessory types.
This commit is contained in:
parent
19dde3cbc4
commit
1b7360f8be
6 changed files with 243 additions and 38 deletions
|
|
@ -33,6 +33,69 @@ public static class EquipmentSwap
|
|||
: Array.Empty< EquipSlot >();
|
||||
}
|
||||
|
||||
public static Item[] CreateTypeSwap( List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips,
|
||||
EquipSlot slotFrom, Item itemFrom, EquipSlot slotTo, Item itemTo )
|
||||
{
|
||||
LookupItem( itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom );
|
||||
LookupItem( itemTo, out var actualSlotTo, out var idTo, out var variantTo );
|
||||
if( actualSlotFrom != slotFrom.ToSlot() || actualSlotTo != slotTo.ToSlot() )
|
||||
{
|
||||
throw new ItemSwap.InvalidItemTypeException();
|
||||
}
|
||||
|
||||
var ( imcFileFrom, variants, affectedItems ) = GetVariants( slotFrom, idFrom, idTo, variantFrom );
|
||||
var imcManip = new ImcManipulation( slotTo, variantTo, idTo.Value, default );
|
||||
var imcFileTo = new ImcFile( imcManip );
|
||||
var skipFemale = false;
|
||||
var skipMale = false;
|
||||
var mtrlVariantTo = manips( imcManip.Copy( imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo ) ) ).Imc.Entry.MaterialId;
|
||||
foreach( var gr in Enum.GetValues< GenderRace >() )
|
||||
{
|
||||
switch( gr.Split().Item1 )
|
||||
{
|
||||
case Gender.Male when skipMale: continue;
|
||||
case Gender.Female when skipFemale: continue;
|
||||
case Gender.MaleNpc when skipMale: continue;
|
||||
case Gender.FemaleNpc when skipFemale: continue;
|
||||
}
|
||||
|
||||
if( CharacterUtility.EqdpIdx( gr, true ) < 0 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var eqdp = CreateEqdp( redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo );
|
||||
if( eqdp != null )
|
||||
{
|
||||
swaps.Add( eqdp );
|
||||
}
|
||||
}
|
||||
catch( ItemSwap.MissingFileException e )
|
||||
{
|
||||
switch( gr )
|
||||
{
|
||||
case GenderRace.MidlanderMale when e.Type == ResourceType.Mdl:
|
||||
skipMale = true;
|
||||
continue;
|
||||
case GenderRace.MidlanderFemale when e.Type == ResourceType.Mdl:
|
||||
skipFemale = true;
|
||||
continue;
|
||||
default: throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var variant in variants )
|
||||
{
|
||||
var imc = CreateImc( redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo );
|
||||
swaps.Add( imc );
|
||||
}
|
||||
|
||||
return affectedItems;
|
||||
}
|
||||
|
||||
public static Item[] CreateItemSwap( List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, Item itemFrom,
|
||||
Item itemTo, bool rFinger = true, bool lFinger = true )
|
||||
{
|
||||
|
|
@ -59,9 +122,9 @@ public static class EquipmentSwap
|
|||
var affectedItems = Array.Empty< Item >();
|
||||
foreach( var slot in ConvertSlots( slotFrom, rFinger, lFinger ) )
|
||||
{
|
||||
(var imcFileFrom, var variants, affectedItems) = GetVariants( slot, idFrom, idTo, variantFrom );
|
||||
( var imcFileFrom, var variants, affectedItems ) = GetVariants( slot, idFrom, idTo, variantFrom );
|
||||
var imcManip = new ImcManipulation( slot, variantTo, idTo.Value, default );
|
||||
var imcFileTo = new ImcFile( imcManip);
|
||||
var imcFileTo = new ImcFile( imcManip );
|
||||
|
||||
var isAccessory = slot.IsAccessory();
|
||||
var estType = slot switch
|
||||
|
|
@ -89,7 +152,7 @@ public static class EquipmentSwap
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo );
|
||||
|
|
@ -99,7 +162,7 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits( slot ).Item2 ?? false;
|
||||
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo, ownMdl );
|
||||
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo, ownMdl );
|
||||
if( est != null )
|
||||
{
|
||||
swaps.Add( est );
|
||||
|
|
@ -132,15 +195,18 @@ public static class EquipmentSwap
|
|||
|
||||
public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo )
|
||||
=> CreateEqdp( redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo );
|
||||
public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, 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 );
|
||||
var eqdpFrom = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slotFrom.IsAccessory(), idFrom.Value ), slotFrom, gender, race, idFrom.Value );
|
||||
var eqdpTo = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slotTo.IsAccessory(), idTo.Value ), slotTo, gender, race, idTo.Value );
|
||||
var meta = new MetaSwap( manips, eqdpFrom, eqdpTo );
|
||||
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits( slot );
|
||||
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits( slotFrom );
|
||||
if( ownMdl )
|
||||
{
|
||||
var mdl = CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo );
|
||||
var mdl = CreateMdl( redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo );
|
||||
meta.ChildSwaps.Add( mdl );
|
||||
}
|
||||
else if( !ownMtrl && meta.SwapAppliedIsDefault )
|
||||
|
|
@ -152,15 +218,17 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo )
|
||||
=> CreateMdl( redirections, slot, slot, gr, idFrom, idTo, mtrlTo );
|
||||
|
||||
public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo )
|
||||
{
|
||||
var accessory = slot.IsAccessory();
|
||||
var mdlPathFrom = accessory ? GamePaths.Accessory.Mdl.Path( idFrom, gr, slot ) : GamePaths.Equipment.Mdl.Path( idFrom, gr, slot );
|
||||
var mdlPathTo = accessory ? GamePaths.Accessory.Mdl.Path( idTo, gr, slot ) : GamePaths.Equipment.Mdl.Path( idTo, gr, slot );
|
||||
var mdlPathFrom = slotFrom.IsAccessory() ? GamePaths.Accessory.Mdl.Path( idFrom, gr, slotFrom ) : GamePaths.Equipment.Mdl.Path( idFrom, gr, slotFrom );
|
||||
var mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path( idTo, gr, slotTo ) : GamePaths.Equipment.Mdl.Path( idTo, gr, slotTo );
|
||||
var mdl = FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo );
|
||||
|
||||
foreach( ref var fileName in mdl.AsMdl()!.Materials.AsSpan() )
|
||||
{
|
||||
var mtrl = CreateMtrl( redirections, slot, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged );
|
||||
var mtrl = CreateMtrl( redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged );
|
||||
if( mtrl != null )
|
||||
{
|
||||
mdl.ChildSwaps.Add( mtrl );
|
||||
|
|
@ -182,22 +250,22 @@ public static class EquipmentSwap
|
|||
variant = ( byte )( ( Quad )i.ModelMain ).B;
|
||||
}
|
||||
|
||||
private static (ImcFile, byte[], Item[]) GetVariants( EquipSlot slot, SetId idFrom, SetId idTo, byte variantFrom )
|
||||
private static (ImcFile, byte[], Item[]) GetVariants( EquipSlot slotFrom, SetId idFrom, SetId idTo, byte variantFrom )
|
||||
{
|
||||
var entry = new ImcManipulation( slot, variantFrom, idFrom.Value, default );
|
||||
var entry = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, default );
|
||||
var imc = new ImcFile( entry );
|
||||
Item[] items;
|
||||
byte[] variants;
|
||||
if( idFrom.Value == idTo.Value )
|
||||
{
|
||||
items = Penumbra.Identifier.Identify( idFrom, variantFrom, slot ).ToArray();
|
||||
items = Penumbra.Identifier.Identify( idFrom, variantFrom, slotFrom ).ToArray();
|
||||
variants = new[] { variantFrom };
|
||||
}
|
||||
else
|
||||
{
|
||||
items = Penumbra.Identifier.Identify( slot.IsEquipment()
|
||||
? GamePaths.Equipment.Mdl.Path( idFrom, GenderRace.MidlanderMale, slot )
|
||||
: GamePaths.Accessory.Mdl.Path( idFrom, GenderRace.MidlanderMale, slot ) ).Select( kvp => kvp.Value ).OfType< Item >().ToArray();
|
||||
items = Penumbra.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< Item >().ToArray();
|
||||
variants = Enumerable.Range( 0, imc.Count + 1 ).Select( i => ( byte )i ).ToArray();
|
||||
}
|
||||
|
||||
|
|
@ -218,11 +286,15 @@ public static class EquipmentSwap
|
|||
|
||||
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 )
|
||||
=> CreateImc( redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo );
|
||||
|
||||
public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, 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 );
|
||||
var entryFrom = imcFileFrom.GetEntry( ImcFile.PartIndex( slotFrom ), variantFrom );
|
||||
var entryTo = imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo );
|
||||
var manipulationFrom = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, entryFrom );
|
||||
var manipulationTo = new ImcManipulation( slotTo, variantTo, idTo.Value, entryTo );
|
||||
var imc = new MetaSwap( manips, manipulationFrom, manipulationTo );
|
||||
|
||||
var decal = CreateDecal( redirections, imc.SwapToModded.Imc.Entry.DecalId );
|
||||
|
|
@ -292,18 +364,23 @@ public static class EquipmentSwap
|
|||
|
||||
public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged )
|
||||
=> CreateMtrl( redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged );
|
||||
|
||||
public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged )
|
||||
{
|
||||
var prefix = slot.IsAccessory() ? 'a' : 'e';
|
||||
var prefix = slotTo.IsAccessory() ? 'a' : 'e';
|
||||
if( !fileName.Contains( $"{prefix}{idTo.Value:D4}" ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var folderTo = slot.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo );
|
||||
var folderTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo );
|
||||
var pathTo = $"{folderTo}{fileName}";
|
||||
|
||||
var folderFrom = slot.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idFrom, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idFrom, variantTo );
|
||||
var folderFrom = slotFrom.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idFrom, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idFrom, variantTo );
|
||||
var newFileName = ItemSwap.ReplaceId( fileName, prefix, idTo, idFrom );
|
||||
newFileName = ItemSwap.ReplaceSlot( newFileName, slotTo, slotFrom, slotTo != slotFrom );
|
||||
var pathFrom = $"{folderFrom}{newFileName}";
|
||||
|
||||
if( newFileName != fileName )
|
||||
|
|
@ -318,7 +395,7 @@ public static class EquipmentSwap
|
|||
|
||||
foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() )
|
||||
{
|
||||
var tex = CreateTex( redirections, prefix, idFrom, idTo, ref texture, ref mtrl.DataWasChanged );
|
||||
var tex = CreateTex( redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged );
|
||||
mtrl.ChildSwaps.Add( tex );
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +403,9 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, char prefix, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged )
|
||||
=> CreateTex( redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged );
|
||||
|
||||
public static FileSwap CreateTex( 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;
|
||||
var addedDashes = false;
|
||||
|
|
@ -340,6 +420,7 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
var newPath = ItemSwap.ReplaceAnyId( path, prefix, idFrom );
|
||||
newPath = ItemSwap.ReplaceSlot( newPath, slotTo, slotFrom, slotTo != slotFrom );
|
||||
newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}" );
|
||||
if( newPath != path )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,12 +17,6 @@ public static class ItemSwap
|
|||
public class InvalidItemTypeException : Exception
|
||||
{ }
|
||||
|
||||
public class InvalidImcException : Exception
|
||||
{ }
|
||||
|
||||
public class IdUnavailableException : Exception
|
||||
{ }
|
||||
|
||||
public class MissingFileException : Exception
|
||||
{
|
||||
public readonly ResourceType Type;
|
||||
|
|
@ -224,6 +218,11 @@ public static class ItemSwap
|
|||
? path.Replace( $"{type}{idFrom.Value:D4}", $"{type}{idTo.Value:D4}" )
|
||||
: path;
|
||||
|
||||
public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true )
|
||||
=> condition
|
||||
? 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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using System;
|
||||
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;
|
||||
using Penumbra.String.Classes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mods.ItemSwap;
|
||||
|
||||
|
|
@ -133,6 +133,15 @@ public class ItemSwapContainer
|
|||
return ret;
|
||||
}
|
||||
|
||||
public Item[] LoadTypeSwap( EquipSlot slotFrom, Item from, EquipSlot slotTo, Item to, ModCollection? collection = null )
|
||||
{
|
||||
Swaps.Clear();
|
||||
Loaded = false;
|
||||
var ret = EquipmentSwap.CreateTypeSwap( Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to );
|
||||
Loaded = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool LoadCustomization( BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null )
|
||||
{
|
||||
var pathResolver = PathResolver( collection );
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue