mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 12:44:19 +01:00
Add improved WIP edit windows for materials and models
This commit is contained in:
parent
e0a171051d
commit
4efdd6d834
12 changed files with 969 additions and 146 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 09dcd012a3106862f20f045b9ff9e33d168047c4
|
Subproject commit 88bf221852d4a1ac26f5ffbfb5e497220aef75c4
|
||||||
6
Penumbra.GameData/Files/IWritable.cs
Normal file
6
Penumbra.GameData/Files/IWritable.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Penumbra.GameData.Files;
|
||||||
|
|
||||||
|
public interface IWritable
|
||||||
|
{
|
||||||
|
public byte[] Write();
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ using Lumina.Extensions;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Files;
|
namespace Penumbra.GameData.Files;
|
||||||
|
|
||||||
public partial class MdlFile
|
public partial class MdlFile : IWritable
|
||||||
{
|
{
|
||||||
public const uint NumVertices = 17;
|
public const uint NumVertices = 17;
|
||||||
public const uint FileHeaderSize = 0x44;
|
public const uint FileHeaderSize = 0x44;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -22,24 +23,38 @@ public partial class MtrlFile
|
||||||
cumulativeStringOffset += ( ushort )( texture.Path.Length + 1 );
|
cumulativeStringOffset += ( ushort )( texture.Path.Length + 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var colorSet in UvColorSets.Concat( ColorSets ) )
|
foreach( var set in UvSets )
|
||||||
{
|
{
|
||||||
w.Write( cumulativeStringOffset );
|
w.Write( cumulativeStringOffset );
|
||||||
w.Write( colorSet.Index );
|
w.Write( set.Index );
|
||||||
cumulativeStringOffset += ( ushort )( colorSet.Name.Length + 1 );
|
cumulativeStringOffset += ( ushort )( set.Name.Length + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var set in ColorSets )
|
||||||
|
{
|
||||||
|
w.Write( cumulativeStringOffset );
|
||||||
|
w.Write( set.Index );
|
||||||
|
cumulativeStringOffset += ( ushort )( set.Name.Length + 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var text in Textures.Select( t => t.Path )
|
foreach( var text in Textures.Select( t => t.Path )
|
||||||
.Concat( UvColorSets.Concat( ColorSets ).Select( c => c.Name ).Append( ShaderPackage.Name ) ) )
|
.Concat( UvSets.Select( c => c.Name ) )
|
||||||
|
.Concat( ColorSets.Select( c => c.Name ) )
|
||||||
|
.Append( ShaderPackage.Name ) )
|
||||||
{
|
{
|
||||||
w.Write( Encoding.UTF8.GetBytes( text ) );
|
w.Write( Encoding.UTF8.GetBytes( text ) );
|
||||||
w.Write( ( byte )'\0' );
|
w.Write( ( byte )'\0' );
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write( AdditionalData );
|
w.Write( AdditionalData );
|
||||||
foreach( var color in ColorSetData )
|
foreach( var row in ColorSets.Select( c => c.Rows ) )
|
||||||
{
|
{
|
||||||
w.Write( color );
|
w.Write( row.AsBytes() );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var row in ColorDyeSets.Select( c => c.Rows ) )
|
||||||
|
{
|
||||||
|
w.Write( row.AsBytes() );
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write( ( ushort )( ShaderPackage.ShaderValues.Length * 4 ) );
|
w.Write( ( ushort )( ShaderPackage.ShaderValues.Length * 4 ) );
|
||||||
|
|
@ -85,11 +100,12 @@ public partial class MtrlFile
|
||||||
w.BaseStream.Seek( 0, SeekOrigin.Begin );
|
w.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||||
w.Write( Version );
|
w.Write( Version );
|
||||||
w.Write( fileSize );
|
w.Write( fileSize );
|
||||||
w.Write( ( ushort )( ColorSetData.Length * 2 ) );
|
w.Write( ( ushort )( ColorSets.Length * ColorSet.RowArray.NumRows * ColorSet.Row.Size
|
||||||
|
+ ColorDyeSets.Length * ColorDyeSet.RowArray.NumRows * 2 ) );
|
||||||
w.Write( ( ushort )( shaderPackageNameOffset + ShaderPackage.Name.Length + 1 ) );
|
w.Write( ( ushort )( shaderPackageNameOffset + ShaderPackage.Name.Length + 1 ) );
|
||||||
w.Write( shaderPackageNameOffset );
|
w.Write( shaderPackageNameOffset );
|
||||||
w.Write( ( byte )Textures.Length );
|
w.Write( ( byte )Textures.Length );
|
||||||
w.Write( ( byte )UvColorSets.Length );
|
w.Write( ( byte )UvSets.Length );
|
||||||
w.Write( ( byte )ColorSets.Length );
|
w.Write( ( byte )ColorSets.Length );
|
||||||
w.Write( ( byte )AdditionalData.Length );
|
w.Write( ( byte )AdditionalData.Length );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,234 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Lumina.Data.Parsing;
|
using Lumina.Data.Parsing;
|
||||||
using Lumina.Extensions;
|
using Lumina.Extensions;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Files;
|
namespace Penumbra.GameData.Files;
|
||||||
|
|
||||||
public partial class MtrlFile
|
public partial class MtrlFile : IWritable
|
||||||
{
|
{
|
||||||
public struct ColorSet
|
public struct UvSet
|
||||||
{
|
{
|
||||||
public string Name;
|
public string Name;
|
||||||
public ushort Index;
|
public ushort Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe struct ColorSet
|
||||||
|
{
|
||||||
|
public struct Row
|
||||||
|
{
|
||||||
|
public const int Size = 32;
|
||||||
|
|
||||||
|
private fixed ushort _data[16];
|
||||||
|
|
||||||
|
public Vector3 Diffuse
|
||||||
|
{
|
||||||
|
get => new(ToFloat( 0 ), ToFloat( 1 ), ToFloat( 2 ));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[ 0 ] = FromFloat( value.X );
|
||||||
|
_data[ 1 ] = FromFloat( value.Y );
|
||||||
|
_data[ 2 ] = FromFloat( value.Z );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Specular
|
||||||
|
{
|
||||||
|
get => new(ToFloat( 4 ), ToFloat( 5 ), ToFloat( 6 ));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[ 4 ] = FromFloat( value.X );
|
||||||
|
_data[ 5 ] = FromFloat( value.Y );
|
||||||
|
_data[ 6 ] = FromFloat( value.Z );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Emissive
|
||||||
|
{
|
||||||
|
get => new(ToFloat( 8 ), ToFloat( 9 ), ToFloat( 10 ));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[ 8 ] = FromFloat( value.X );
|
||||||
|
_data[ 9 ] = FromFloat( value.Y );
|
||||||
|
_data[ 10 ] = FromFloat( value.Z );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MaterialRepeat
|
||||||
|
{
|
||||||
|
get => new(ToFloat( 12 ), ToFloat( 15 ));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[ 12 ] = FromFloat( value.X );
|
||||||
|
_data[ 15 ] = FromFloat( value.Y );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MaterialSkew
|
||||||
|
{
|
||||||
|
get => new(ToFloat( 13 ), ToFloat( 14 ));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_data[ 13 ] = FromFloat( value.X );
|
||||||
|
_data[ 14 ] = FromFloat( value.Y );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float SpecularStrength
|
||||||
|
{
|
||||||
|
get => ToFloat( 3 );
|
||||||
|
set => _data[ 3 ] = FromFloat( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GlossStrength
|
||||||
|
{
|
||||||
|
get => ToFloat( 7 );
|
||||||
|
set => _data[ 7 ] = FromFloat( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort TileSet
|
||||||
|
{
|
||||||
|
get => (ushort) (ToFloat(11) * 64f);
|
||||||
|
set => _data[ 11 ] = FromFloat(value / 64f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float ToFloat( int idx )
|
||||||
|
=> ( float )BitConverter.UInt16BitsToHalf( _data[ idx ] );
|
||||||
|
|
||||||
|
private static ushort FromFloat( float x )
|
||||||
|
=> BitConverter.HalfToUInt16Bits( ( Half )x );
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RowArray : IEnumerable< Row >
|
||||||
|
{
|
||||||
|
public const int NumRows = 16;
|
||||||
|
private fixed byte _rowData[NumRows * Row.Size];
|
||||||
|
|
||||||
|
public ref Row this[ int i ]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed( byte* ptr = _rowData )
|
||||||
|
{
|
||||||
|
return ref ( ( Row* )ptr )[ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator< Row > GetEnumerator()
|
||||||
|
{
|
||||||
|
for( var i = 0; i < NumRows; ++i )
|
||||||
|
{
|
||||||
|
yield return this[ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public ReadOnlySpan< byte > AsBytes()
|
||||||
|
{
|
||||||
|
fixed( byte* ptr = _rowData )
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan< byte >( ptr, NumRows * Row.Size );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RowArray Rows;
|
||||||
|
public string Name;
|
||||||
|
public ushort Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe struct ColorDyeSet
|
||||||
|
{
|
||||||
|
public struct Row
|
||||||
|
{
|
||||||
|
private ushort _data;
|
||||||
|
|
||||||
|
public ushort Template
|
||||||
|
{
|
||||||
|
get => ( ushort )( _data >> 5 );
|
||||||
|
set => _data = ( ushort )( ( _data & 0x1F ) | ( value << 5 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Diffuse
|
||||||
|
{
|
||||||
|
get => ( _data & 0x01 ) != 0;
|
||||||
|
set => _data = ( ushort )( value ? _data | 0x01 : _data & 0xFFFE );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Specular
|
||||||
|
{
|
||||||
|
get => ( _data & 0x02 ) != 0;
|
||||||
|
set => _data = ( ushort )( value ? _data | 0x02 : _data & 0xFFFD );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Emissive
|
||||||
|
{
|
||||||
|
get => ( _data & 0x04 ) != 0;
|
||||||
|
set => _data = ( ushort )( value ? _data | 0x04 : _data & 0xFFFB );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Gloss
|
||||||
|
{
|
||||||
|
get => ( _data & 0x08 ) != 0;
|
||||||
|
set => _data = ( ushort )( value ? _data | 0x08 : _data & 0xFFF7 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SpecularStrength
|
||||||
|
{
|
||||||
|
get => ( _data & 0x10 ) != 0;
|
||||||
|
set => _data = ( ushort )( value ? _data | 0x10 : _data & 0xFFEF );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RowArray : IEnumerable< Row >
|
||||||
|
{
|
||||||
|
public const int NumRows = 16;
|
||||||
|
private fixed ushort _rowData[NumRows];
|
||||||
|
|
||||||
|
public ref Row this[ int i ]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed( ushort* ptr = _rowData )
|
||||||
|
{
|
||||||
|
return ref ( ( Row* )ptr )[ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator< Row > GetEnumerator()
|
||||||
|
{
|
||||||
|
for( var i = 0; i < NumRows; ++i )
|
||||||
|
{
|
||||||
|
yield return this[ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public ReadOnlySpan< byte > AsBytes()
|
||||||
|
{
|
||||||
|
fixed( ushort* ptr = _rowData )
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan< byte >( ptr, NumRows * sizeof( ushort ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RowArray Rows;
|
||||||
|
public string Name;
|
||||||
|
public ushort Index;
|
||||||
|
}
|
||||||
|
|
||||||
public struct Texture
|
public struct Texture
|
||||||
{
|
{
|
||||||
public string Path;
|
public string Path;
|
||||||
|
|
@ -37,12 +252,12 @@ public partial class MtrlFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public uint Version;
|
public readonly uint Version;
|
||||||
|
|
||||||
public Texture[] Textures;
|
public Texture[] Textures;
|
||||||
public ColorSet[] UvColorSets;
|
public UvSet[] UvSets;
|
||||||
public ColorSet[] ColorSets;
|
public ColorSet[] ColorSets;
|
||||||
public ushort[] ColorSetData;
|
public ColorDyeSet[] ColorDyeSets;
|
||||||
public ShaderPackageData ShaderPackage;
|
public ShaderPackageData ShaderPackage;
|
||||||
public byte[] AdditionalData;
|
public byte[] AdditionalData;
|
||||||
|
|
||||||
|
|
@ -61,9 +276,9 @@ public partial class MtrlFile
|
||||||
var colorSetCount = r.ReadByte();
|
var colorSetCount = r.ReadByte();
|
||||||
var additionalDataSize = r.ReadByte();
|
var additionalDataSize = r.ReadByte();
|
||||||
|
|
||||||
Textures = ReadTextureOffsets( r, textureCount, out var textureOffsets );
|
Textures = ReadTextureOffsets( r, textureCount, out var textureOffsets );
|
||||||
UvColorSets = ReadColorSetOffsets( r, uvSetCount, out var uvOffsets );
|
UvSets = ReadUvSetOffsets( r, uvSetCount, out var uvOffsets );
|
||||||
ColorSets = ReadColorSetOffsets( r, colorSetCount, out var colorOffsets );
|
ColorSets = ReadColorSetOffsets( r, colorSetCount, out var colorOffsets );
|
||||||
|
|
||||||
var strings = r.ReadBytes( stringTableSize );
|
var strings = r.ReadBytes( stringTableSize );
|
||||||
for( var i = 0; i < textureCount; ++i )
|
for( var i = 0; i < textureCount; ++i )
|
||||||
|
|
@ -73,7 +288,7 @@ public partial class MtrlFile
|
||||||
|
|
||||||
for( var i = 0; i < uvSetCount; ++i )
|
for( var i = 0; i < uvSetCount; ++i )
|
||||||
{
|
{
|
||||||
UvColorSets[ i ].Name = UseOffset( strings, uvOffsets[ i ] );
|
UvSets[ i ].Name = UseOffset( strings, uvOffsets[ i ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( var i = 0; i < colorSetCount; ++i )
|
for( var i = 0; i < colorSetCount; ++i )
|
||||||
|
|
@ -81,10 +296,22 @@ public partial class MtrlFile
|
||||||
ColorSets[ i ].Name = UseOffset( strings, colorOffsets[ i ] );
|
ColorSets[ i ].Name = UseOffset( strings, colorOffsets[ i ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorDyeSets = ColorSets.Length * ColorSet.RowArray.NumRows * ColorSet.Row.Size < dataSetSize
|
||||||
|
? ColorSets.Select( c => new ColorDyeSet { Index = c.Index, Name = c.Name } ).ToArray()
|
||||||
|
: Array.Empty< ColorDyeSet >();
|
||||||
|
|
||||||
ShaderPackage.Name = UseOffset( strings, shaderPackageNameOffset );
|
ShaderPackage.Name = UseOffset( strings, shaderPackageNameOffset );
|
||||||
|
|
||||||
AdditionalData = r.ReadBytes( additionalDataSize );
|
AdditionalData = r.ReadBytes( additionalDataSize );
|
||||||
ColorSetData = r.ReadStructuresAsArray< ushort >( dataSetSize / 2 );
|
for( var i = 0; i < ColorSets.Length; ++i )
|
||||||
|
{
|
||||||
|
ColorSets[ i ].Rows = r.ReadStructure< ColorSet.RowArray >();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < ColorDyeSets.Length; ++i )
|
||||||
|
{
|
||||||
|
ColorDyeSets[ i ].Rows = r.ReadStructure< ColorDyeSet.RowArray >();
|
||||||
|
}
|
||||||
|
|
||||||
var shaderValueListSize = r.ReadUInt16();
|
var shaderValueListSize = r.ReadUInt16();
|
||||||
var shaderKeyCount = r.ReadUInt16();
|
var shaderKeyCount = r.ReadUInt16();
|
||||||
|
|
@ -111,6 +338,19 @@ public partial class MtrlFile
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UvSet[] ReadUvSetOffsets( BinaryReader r, int count, out ushort[] offsets )
|
||||||
|
{
|
||||||
|
var ret = new UvSet[count];
|
||||||
|
offsets = new ushort[count];
|
||||||
|
for( var i = 0; i < count; ++i )
|
||||||
|
{
|
||||||
|
offsets[ i ] = r.ReadUInt16();
|
||||||
|
ret[ i ].Index = r.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private static ColorSet[] ReadColorSetOffsets( BinaryReader r, int count, out ushort[] offsets )
|
private static ColorSet[] ReadColorSetOffsets( BinaryReader r, int count, out ushort[] offsets )
|
||||||
{
|
{
|
||||||
var ret = new ColorSet[count];
|
var ret = new ColorSet[count];
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ public static class GameData
|
||||||
internal static ObjectIdentification? Identification;
|
internal static ObjectIdentification? Identification;
|
||||||
internal static readonly GamePathParser GamePathParser = new();
|
internal static readonly GamePathParser GamePathParser = new();
|
||||||
|
|
||||||
public static IObjectIdentifier GetIdentifier( DataManager dataManager, ClientLanguage clientLanguage )
|
public static IObjectIdentifier GetIdentifier( DataManager dataManager )
|
||||||
{
|
{
|
||||||
Identification ??= new ObjectIdentification( dataManager, clientLanguage );
|
Identification ??= new ObjectIdentification( dataManager, dataManager.Language );
|
||||||
return Identification;
|
return Identification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,10 @@ public readonly struct WeaponType : IEquatable< WeaponType >
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> Value.GetHashCode();
|
=> Value.GetHashCode();
|
||||||
|
|
||||||
|
public static bool operator ==( WeaponType lhs, WeaponType rhs )
|
||||||
|
=> lhs.Value == rhs.Value;
|
||||||
|
|
||||||
|
public static bool operator !=( WeaponType lhs, WeaponType rhs )
|
||||||
|
=> lhs.Value != rhs.Value;
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +74,8 @@ public partial class Mod
|
||||||
|
|
||||||
public bool FileChanges { get; private set; }
|
public bool FileChanges { get; private set; }
|
||||||
private List< FileRegistry > _availableFiles = null!;
|
private List< FileRegistry > _availableFiles = null!;
|
||||||
|
private List< FileRegistry > _mtrlFiles = null!;
|
||||||
|
private List< FileRegistry > _mdlFiles = null!;
|
||||||
private readonly HashSet< Utf8GamePath > _usedPaths = new();
|
private readonly HashSet< Utf8GamePath > _usedPaths = new();
|
||||||
|
|
||||||
// All paths that are used in
|
// All paths that are used in
|
||||||
|
|
@ -82,6 +84,12 @@ public partial class Mod
|
||||||
public IReadOnlySet< FullPath > MissingFiles
|
public IReadOnlySet< FullPath > MissingFiles
|
||||||
=> _missingFiles;
|
=> _missingFiles;
|
||||||
|
|
||||||
|
public IReadOnlyList< FileRegistry > MtrlFiles
|
||||||
|
=> _mtrlFiles;
|
||||||
|
|
||||||
|
public IReadOnlyList< FileRegistry > MdlFiles
|
||||||
|
=> _mdlFiles;
|
||||||
|
|
||||||
// Remove all path redirections where the pointed-to file does not exist.
|
// Remove all path redirections where the pointed-to file does not exist.
|
||||||
public void RemoveMissingPaths()
|
public void RemoveMissingPaths()
|
||||||
{
|
{
|
||||||
|
|
@ -121,6 +129,8 @@ public partial class Mod
|
||||||
.OfType< FileRegistry >() )
|
.OfType< FileRegistry >() )
|
||||||
.ToList();
|
.ToList();
|
||||||
_usedPaths.Clear();
|
_usedPaths.Clear();
|
||||||
|
_mtrlFiles = _availableFiles.Where( f => f.File.FullName.EndsWith( ".mtrl", StringComparison.OrdinalIgnoreCase ) ).ToList();
|
||||||
|
_mdlFiles = _availableFiles.Where( f => f.File.FullName.EndsWith( ".mdl", StringComparison.OrdinalIgnoreCase ) ).ToList();
|
||||||
FileChanges = false;
|
FileChanges = false;
|
||||||
foreach( var subMod in _mod.AllSubMods )
|
foreach( var subMod in _mod.AllSubMods )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ public partial class Mod
|
||||||
public partial class Editor
|
public partial class Editor
|
||||||
{
|
{
|
||||||
private static readonly Regex MaterialRegex = new(@"/mt_c(?'RaceCode'\d{4})b0001_(?'Suffix'.*?)\.mtrl", RegexOptions.Compiled);
|
private static readonly Regex MaterialRegex = new(@"/mt_c(?'RaceCode'\d{4})b0001_(?'Suffix'.*?)\.mtrl", RegexOptions.Compiled);
|
||||||
private readonly List< MaterialInfo > _modelFiles = new();
|
private readonly List< ModelMaterialInfo > _modelFiles = new();
|
||||||
public IReadOnlyList< MaterialInfo > ModelFiles
|
|
||||||
|
public IReadOnlyList< ModelMaterialInfo > ModelFiles
|
||||||
=> _modelFiles;
|
=> _modelFiles;
|
||||||
|
|
||||||
// Non-ASCII encoding can not be used.
|
// Non-ASCII encoding can not be used.
|
||||||
|
|
@ -50,7 +51,9 @@ public partial class Mod
|
||||||
public void ReplaceAllMaterials( string toSuffix, string fromSuffix = "", GenderRace raceCode = GenderRace.Unknown )
|
public void ReplaceAllMaterials( string toSuffix, string fromSuffix = "", GenderRace raceCode = GenderRace.Unknown )
|
||||||
{
|
{
|
||||||
if( !ValidString( toSuffix ) )
|
if( !ValidString( toSuffix ) )
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach( var info in _modelFiles )
|
foreach( var info in _modelFiles )
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +65,7 @@ public partial class Mod
|
||||||
&& ( raceCode == GenderRace.Unknown || raceCode.ToRaceCode() == match.Groups[ "RaceCode" ].Value )
|
&& ( raceCode == GenderRace.Unknown || raceCode.ToRaceCode() == match.Groups[ "RaceCode" ].Value )
|
||||||
&& ( fromSuffix.Length == 0 || fromSuffix == match.Groups[ "Suffix" ].Value ) )
|
&& ( fromSuffix.Length == 0 || fromSuffix == match.Groups[ "Suffix" ].Value ) )
|
||||||
{
|
{
|
||||||
info.SetMaterial( $"/mt_c{match.Groups["RaceCode"].Value}b0001_{toSuffix}.mtrl", i );
|
info.SetMaterial( $"/mt_c{match.Groups[ "RaceCode" ].Value}b0001_{toSuffix}.mtrl", i );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ public partial class Mod
|
||||||
public void ScanModels()
|
public void ScanModels()
|
||||||
{
|
{
|
||||||
_modelFiles.Clear();
|
_modelFiles.Clear();
|
||||||
foreach( var file in AvailableFiles.Where( f => f.File.Extension == ".mdl" ) )
|
foreach( var file in _mdlFiles.Where( f => f.File.Extension == ".mdl" ) )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -82,7 +85,7 @@ public partial class Mod
|
||||||
.Select( p => p.Item2 ).ToArray();
|
.Select( p => p.Item2 ).ToArray();
|
||||||
if( materials.Length > 0 )
|
if( materials.Length > 0 )
|
||||||
{
|
{
|
||||||
_modelFiles.Add( new MaterialInfo( file.File, mdlFile, materials ) );
|
_modelFiles.Add( new ModelMaterialInfo( file.File, mdlFile, materials ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -93,22 +96,22 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// A class that collects information about skin materials in a model file and handle changes on them.
|
// A class that collects information about skin materials in a model file and handle changes on them.
|
||||||
public class MaterialInfo
|
public class ModelMaterialInfo
|
||||||
{
|
{
|
||||||
public readonly FullPath Path;
|
public readonly FullPath Path;
|
||||||
private readonly MdlFile _file;
|
public readonly MdlFile File;
|
||||||
private readonly string[] _currentMaterials;
|
private readonly string[] _currentMaterials;
|
||||||
private readonly IReadOnlyList<int> _materialIndices;
|
private readonly IReadOnlyList< int > _materialIndices;
|
||||||
public bool Changed { get; private set; }
|
public bool Changed { get; private set; }
|
||||||
|
|
||||||
public IReadOnlyList<string> CurrentMaterials
|
public IReadOnlyList< string > CurrentMaterials
|
||||||
=> _currentMaterials;
|
=> _currentMaterials;
|
||||||
|
|
||||||
private IEnumerable<string> DefaultMaterials
|
private IEnumerable< string > DefaultMaterials
|
||||||
=> _materialIndices.Select( i => _file.Materials[i] );
|
=> _materialIndices.Select( i => File.Materials[ i ] );
|
||||||
|
|
||||||
public (string Current, string Default) this[int idx]
|
public (string Current, string Default) this[ int idx ]
|
||||||
=> (_currentMaterials[idx], _file.Materials[_materialIndices[idx]]);
|
=> ( _currentMaterials[ idx ], File.Materials[ _materialIndices[ idx ] ] );
|
||||||
|
|
||||||
public int Count
|
public int Count
|
||||||
=> _materialIndices.Count;
|
=> _materialIndices.Count;
|
||||||
|
|
@ -116,8 +119,8 @@ public partial class Mod
|
||||||
// Set the skin material to a new value and flag changes appropriately.
|
// Set the skin material to a new value and flag changes appropriately.
|
||||||
public void SetMaterial( string value, int materialIdx )
|
public void SetMaterial( string value, int materialIdx )
|
||||||
{
|
{
|
||||||
var mat = _file.Materials[_materialIndices[materialIdx]];
|
var mat = File.Materials[ _materialIndices[ materialIdx ] ];
|
||||||
_currentMaterials[materialIdx] = value;
|
_currentMaterials[ materialIdx ] = value;
|
||||||
if( mat != value )
|
if( mat != value )
|
||||||
{
|
{
|
||||||
Changed = true;
|
Changed = true;
|
||||||
|
|
@ -138,12 +141,12 @@ public partial class Mod
|
||||||
|
|
||||||
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
||||||
{
|
{
|
||||||
_file.Materials[idx] = _currentMaterials[i];
|
File.Materials[ idx ] = _currentMaterials[ i ];
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.WriteAllBytes( Path.FullName, _file.Write() );
|
System.IO.File.WriteAllBytes( Path.FullName, File.Write() );
|
||||||
Changed = false;
|
Changed = false;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -157,23 +160,25 @@ public partial class Mod
|
||||||
public void Restore()
|
public void Restore()
|
||||||
{
|
{
|
||||||
if( !Changed )
|
if( !Changed )
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
||||||
{
|
{
|
||||||
_currentMaterials[i] = _file.Materials[idx];
|
_currentMaterials[ i ] = File.Materials[ idx ];
|
||||||
}
|
}
|
||||||
|
|
||||||
Changed = false;
|
Changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MaterialInfo( FullPath path, MdlFile file, IReadOnlyList<int> indices )
|
public ModelMaterialInfo( FullPath path, MdlFile file, IReadOnlyList< int > indices )
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
_file = file;
|
File = file;
|
||||||
_materialIndices = indices;
|
_materialIndices = indices;
|
||||||
_currentMaterials = DefaultMaterials.ToArray();
|
_currentMaterials = DefaultMaterials.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
public Penumbra( DalamudPluginInterface pluginInterface )
|
public Penumbra( DalamudPluginInterface pluginInterface )
|
||||||
{
|
{
|
||||||
Dalamud.Initialize( pluginInterface );
|
Dalamud.Initialize( pluginInterface );
|
||||||
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
GameData.GameData.GetIdentifier( Dalamud.GameData );
|
||||||
DevPenumbraExists = CheckDevPluginPenumbra();
|
DevPenumbraExists = CheckDevPluginPenumbra();
|
||||||
IsNotInstalledPenumbra = CheckIsNotInstalled();
|
IsNotInstalledPenumbra = CheckIsNotInstalled();
|
||||||
|
|
||||||
|
|
|
||||||
607
Penumbra/UI/Classes/ModEditWindow.FileEdit.cs
Normal file
607
Penumbra/UI/Classes/ModEditWindow.FileEdit.cs
Normal file
|
|
@ -0,0 +1,607 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Functions = Penumbra.GameData.Util.Functions;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
public partial class ModEditWindow
|
||||||
|
{
|
||||||
|
private readonly FileEditor< MtrlFile > _materialTab;
|
||||||
|
private readonly FileEditor< MdlFile > _modelTab;
|
||||||
|
|
||||||
|
private class FileEditor< T > where T : class, IWritable
|
||||||
|
{
|
||||||
|
private readonly string _tabName;
|
||||||
|
private readonly string _fileType;
|
||||||
|
private readonly Func< IReadOnlyList< Mod.Editor.FileRegistry > > _getFiles;
|
||||||
|
private readonly Func< T, bool, bool > _drawEdit;
|
||||||
|
|
||||||
|
private Mod.Editor.FileRegistry? _currentPath;
|
||||||
|
private T? _currentFile;
|
||||||
|
private bool _changed;
|
||||||
|
|
||||||
|
private string _defaultPath = string.Empty;
|
||||||
|
private bool _inInput = false;
|
||||||
|
private T? _defaultFile;
|
||||||
|
|
||||||
|
private IReadOnlyList< Mod.Editor.FileRegistry > _list = null!;
|
||||||
|
|
||||||
|
public FileEditor( string tabName, string fileType, Func< IReadOnlyList< Mod.Editor.FileRegistry > > getFiles,
|
||||||
|
Func< T, bool, bool > drawEdit )
|
||||||
|
{
|
||||||
|
_tabName = tabName;
|
||||||
|
_fileType = fileType;
|
||||||
|
_getFiles = getFiles;
|
||||||
|
_drawEdit = drawEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
_list = _getFiles();
|
||||||
|
if( _list.Count == 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var tab = ImRaii.TabItem( _tabName );
|
||||||
|
if( !tab )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
DrawFileSelectCombo();
|
||||||
|
SaveButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ResetButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DefaultInput();
|
||||||
|
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
|
||||||
|
|
||||||
|
DrawFilePanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DefaultInput()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||||
|
ImGui.InputTextWithHint( "##defaultInput", "Input game path to compare...", ref _defaultPath, Utf8GamePath.MaxGamePathLength );
|
||||||
|
_inInput = ImGui.IsItemActive();
|
||||||
|
if( ImGui.IsItemDeactivatedAfterEdit() && _defaultPath.Length > 0 )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = Dalamud.GameData.GetFile( _defaultPath );
|
||||||
|
if( file != null )
|
||||||
|
{
|
||||||
|
_defaultFile = Activator.CreateInstance( typeof( T ), file.Data ) as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_defaultFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_currentPath = null;
|
||||||
|
_currentFile = null;
|
||||||
|
_changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFileSelectCombo()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||||
|
using var combo = ImRaii.Combo( "##fileSelect", _currentPath?.RelPath.ToString() ?? $"Select {_fileType} File..." );
|
||||||
|
if( !combo )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var file in _list )
|
||||||
|
{
|
||||||
|
if( ImGui.Selectable( file.RelPath.ToString(), ReferenceEquals( file, _currentPath ) ) )
|
||||||
|
{
|
||||||
|
UpdateCurrentFile( file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentFile( Mod.Editor.FileRegistry path )
|
||||||
|
{
|
||||||
|
if( ReferenceEquals( _currentPath, path ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_changed = false;
|
||||||
|
_currentPath = path;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = File.ReadAllBytes( _currentPath.File.FullName );
|
||||||
|
_currentFile = Activator.CreateInstance( typeof( T ), bytes ) as T;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not parse {_fileType} file {_currentPath.File.FullName}:\n{e}" );
|
||||||
|
_currentFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton()
|
||||||
|
{
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Save to File", Vector2.Zero,
|
||||||
|
$"Save the selected {_fileType} file with all changes applied. This is not revertible.", !_changed ) )
|
||||||
|
{
|
||||||
|
File.WriteAllBytes( _currentPath!.File.FullName, _currentFile!.Write() );
|
||||||
|
_changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetButton()
|
||||||
|
{
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Reset Changes", Vector2.Zero,
|
||||||
|
$"Reset all changes made to the {_fileType} file.", !_changed ) )
|
||||||
|
{
|
||||||
|
var tmp = _currentPath;
|
||||||
|
_currentPath = null;
|
||||||
|
UpdateCurrentFile( tmp! );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilePanel()
|
||||||
|
{
|
||||||
|
using var child = ImRaii.Child( "##filePanel", -Vector2.One, true );
|
||||||
|
if( !child )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _currentPath != null )
|
||||||
|
{
|
||||||
|
if( _currentFile == null )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( $"Could not parse selected {_fileType} file." );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( 0 );
|
||||||
|
_changed |= _drawEdit( _currentFile, false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !_inInput && _defaultPath.Length > 0 )
|
||||||
|
{
|
||||||
|
if( _currentPath != null )
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.TextUnformatted( $"Preview of {_defaultPath}:" );
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _defaultFile == null )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( $"Could not parse provided {_fileType} game file." );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( 1 );
|
||||||
|
_drawEdit( _defaultFile, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DrawModelPanel( MdlFile file, bool disabled )
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
for( var i = 0; i < file.Materials.Length; ++i )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( i );
|
||||||
|
var tmp = file.Materials[ i ];
|
||||||
|
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||||
|
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None )
|
||||||
|
&& tmp.Length > 0
|
||||||
|
&& tmp != file.Materials[ i ] )
|
||||||
|
{
|
||||||
|
file.Materials[ i ] = tmp;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled && ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool DrawMaterialPanel( MtrlFile file, bool disabled )
|
||||||
|
{
|
||||||
|
var ret = DrawMaterialTextureChange( file, disabled );
|
||||||
|
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ret |= DrawMaterialColorSetChange( file, disabled );
|
||||||
|
|
||||||
|
return disabled && ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DrawMaterialTextureChange( MtrlFile file, bool disabled )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( "Textures" );
|
||||||
|
var ret = false;
|
||||||
|
for( var i = 0; i < file.Textures.Length; ++i )
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.PushId( i );
|
||||||
|
var tmp = file.Textures[ i ].Path;
|
||||||
|
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||||
|
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||||
|
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None )
|
||||||
|
&& tmp.Length > 0
|
||||||
|
&& tmp != file.Textures[ i ].Path )
|
||||||
|
{
|
||||||
|
ret = true;
|
||||||
|
file.Textures[ i ].Path = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DrawMaterialColorSetChange( MtrlFile file, bool disabled )
|
||||||
|
{
|
||||||
|
if( file.ColorSets.Length == 0 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var table = ImRaii.Table( "##ColorSets", 10,
|
||||||
|
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV );
|
||||||
|
if( !table )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( string.Empty );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Row" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Diffuse" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Specular" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Emissive" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Gloss" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Tile" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Repeat" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Skew" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader( "Dye" );
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
|
for( var j = 0; j < file.ColorSets.Length; ++j )
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.PushId( j );
|
||||||
|
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
|
||||||
|
{
|
||||||
|
ret |= DrawColorSetRow( file, j, i, disabled );
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void ColorSetCopyClipboardButton( MtrlFile.ColorSet.Row row, MtrlFile.ColorDyeSet.Row dye )
|
||||||
|
{
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||||
|
"Export this row to your clipboard.", false, true ) )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
||||||
|
fixed( byte* ptr = data )
|
||||||
|
{
|
||||||
|
Functions.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
|
||||||
|
Functions.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = Convert.ToBase64String( data );
|
||||||
|
ImGui.SetClipboardText( text );
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool ColorSetPasteFromClipboardButton( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||||
|
{
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Paste.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||||
|
"Import an exported row from your clipboard onto this row.", disabled, true ) )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = ImGui.GetClipboardText();
|
||||||
|
var data = Convert.FromBase64String( text );
|
||||||
|
if( data.Length != MtrlFile.ColorSet.Row.Size + 2
|
||||||
|
|| file.ColorSets.Length <= colorSetIdx )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed( byte* ptr = data )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorSet.Row* )ptr;
|
||||||
|
if( file.ColorDyeSets.Length <= colorSetIdx )
|
||||||
|
{
|
||||||
|
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] = *( MtrlFile.ColorDyeSet.Row* )( ptr + MtrlFile.ColorSet.Row.Size );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( rowIdx );
|
||||||
|
var row = file.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
|
||||||
|
var hasDye = file.ColorDyeSets.Length > colorSetIdx;
|
||||||
|
var dye = hasDye ? file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] : new MtrlFile.ColorDyeSet.Row();
|
||||||
|
var floatSize = 70 * ImGuiHelpers.GlobalScale;
|
||||||
|
var intSize = 45 * ImGuiHelpers.GlobalScale;
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ColorSetCopyClipboardButton( row, dye );
|
||||||
|
ImGui.SameLine();
|
||||||
|
var ret = ColorSetPasteFromClipboardButton( file, colorSetIdx, rowIdx, disabled );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted( $"#{rowIdx + 1:D2}" );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using var dis = ImRaii.Disabled(disabled);
|
||||||
|
ret |= ColorPicker( "##Diffuse", "Diffuse Color", row.Diffuse, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = c );
|
||||||
|
if( hasDye )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ret |= ImGuiUtil.Checkbox( "##dyeDiffuse", "Apply Diffuse Color on Dye", dye.Diffuse,
|
||||||
|
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Diffuse = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ret |= ColorPicker( "##Specular", "Specular Color", row.Specular, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Specular = c );
|
||||||
|
ImGui.SameLine();
|
||||||
|
var tmpFloat = row.SpecularStrength;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##SpecularStrength", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.SpecularStrength )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = tmpFloat;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Specular Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
if( hasDye )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ret |= ImGuiUtil.Checkbox( "##dyeSpecular", "Apply Specular Color on Dye", dye.Specular,
|
||||||
|
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Specular = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
ImGui.SameLine();
|
||||||
|
ret |= ImGuiUtil.Checkbox( "##dyeSpecularStrength", "Apply Specular Strength on Dye", dye.SpecularStrength,
|
||||||
|
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].SpecularStrength = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ret |= ColorPicker( "##Emissive", "Emissive Color", row.Emissive, c => file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = c );
|
||||||
|
if( hasDye )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ret |= ImGuiUtil.Checkbox( "##dyeEmissive", "Apply Emissive Color on Dye", dye.Emissive,
|
||||||
|
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Emissive = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
tmpFloat = row.GlossStrength;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##GlossStrength", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.GlossStrength )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].GlossStrength = tmpFloat;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Gloss Strength", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
if( hasDye )
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ret |= ImGuiUtil.Checkbox( "##dyeGloss", "Apply Gloss Strength on Dye", dye.Gloss,
|
||||||
|
b => file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Gloss = b, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
int tmpInt = row.TileSet;
|
||||||
|
ImGui.SetNextItemWidth( intSize );
|
||||||
|
if( ImGui.InputInt( "##TileSet", ref tmpInt, 0, 0 ) && tmpInt != row.TileSet && tmpInt is >= 0 and <= ushort.MaxValue )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].TileSet = ( ushort )tmpInt;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Tile Set", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
tmpFloat = row.MaterialRepeat.X;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##RepeatX", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.MaterialRepeat.X )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { X = tmpFloat };
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Repeat X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
ImGui.SameLine();
|
||||||
|
tmpFloat = row.MaterialRepeat.Y;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##RepeatY", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.MaterialRepeat.Y )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialRepeat = row.MaterialRepeat with { Y = tmpFloat };
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Repeat Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
tmpFloat = row.MaterialSkew.X;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##SkewX", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.MaterialSkew.X )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { X = tmpFloat };
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Skew X", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
tmpFloat = row.MaterialSkew.Y;
|
||||||
|
ImGui.SetNextItemWidth( floatSize );
|
||||||
|
if( ImGui.DragFloat( "##SkewY", ref tmpFloat, 0.1f, 0f ) && tmpFloat != row.MaterialSkew.Y )
|
||||||
|
{
|
||||||
|
file.ColorSets[ colorSetIdx ].Rows[ rowIdx ].MaterialSkew = row.MaterialSkew with { Y = tmpFloat };
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Skew Y", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( hasDye )
|
||||||
|
{
|
||||||
|
tmpInt = dye.Template;
|
||||||
|
ImGui.SetNextItemWidth( intSize );
|
||||||
|
if( ImGui.InputInt( "##DyeTemplate", ref tmpInt, 0, 0 )
|
||||||
|
&& tmpInt != dye.Template
|
||||||
|
&& tmpInt is >= 0 and <= ushort.MaxValue )
|
||||||
|
{
|
||||||
|
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = ( ushort )tmpInt;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( "Dye Template", ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ColorPicker( string label, string tooltip, Vector3 input, Action< Vector3 > setter )
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var tmp = input;
|
||||||
|
if( ImGui.ColorEdit3( label, ref tmp,
|
||||||
|
ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.DisplayRGB | ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.NoTooltip )
|
||||||
|
&& tmp != input )
|
||||||
|
{
|
||||||
|
setter( tmp );
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( tooltip, ImGuiHoveredFlags.AllowWhenDisabled );
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMaterialReassignmentTab()
|
||||||
|
{
|
||||||
|
if( _editor!.ModelFiles.Count == 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var tab = ImRaii.TabItem( "Material Reassignment" );
|
||||||
|
if( !tab )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
MaterialSuffix.Draw( _editor, ImGuiHelpers.ScaledVector2( 175, 0 ) );
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
using var child = ImRaii.Child( "##mdlFiles", -Vector2.One, true );
|
||||||
|
if( !child )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var table = ImRaii.Table( "##files", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.One );
|
||||||
|
if( !table )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconSize = ImGui.GetFrameHeight() * Vector2.One;
|
||||||
|
foreach( var (info, idx) in _editor.ModelFiles.WithIndex() )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( idx );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Save.ToIconString(), iconSize,
|
||||||
|
"Save the changed mdl file.\nUse at own risk!", !info.Changed, true ) )
|
||||||
|
{
|
||||||
|
info.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Recycle.ToIconString(), iconSize,
|
||||||
|
"Restore current changes to default.", !info.Changed, true ) )
|
||||||
|
{
|
||||||
|
info.Restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted( info.Path.FullName[ ( _mod!.ModPath.FullName.Length + 1 ).. ] );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
|
||||||
|
var tmp = info.CurrentMaterials[ 0 ];
|
||||||
|
if( ImGui.InputText( "##0", ref tmp, 64 ) )
|
||||||
|
{
|
||||||
|
info.SetMaterial( tmp, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( var i = 1; i < info.Count; ++i )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
|
||||||
|
tmp = info.CurrentMaterials[ i ];
|
||||||
|
if( ImGui.InputText( $"##{i}", ref tmp, 64 ) )
|
||||||
|
{
|
||||||
|
info.SetMaterial( tmp, i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using static Penumbra.Mods.Mod;
|
using static Penumbra.Mods.Mod;
|
||||||
|
|
@ -40,6 +41,8 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
MaximumSize = 4000 * Vector2.One,
|
MaximumSize = 4000 * Vector2.One,
|
||||||
};
|
};
|
||||||
_selectedFiles.Clear();
|
_selectedFiles.Clear();
|
||||||
|
_modelTab.Reset();
|
||||||
|
_materialTab.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeOption( ISubMod? subMod )
|
public void ChangeOption( ISubMod? subMod )
|
||||||
|
|
@ -132,7 +135,9 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
DrawSwapTab();
|
DrawSwapTab();
|
||||||
DrawMissingFilesTab();
|
DrawMissingFilesTab();
|
||||||
DrawDuplicatesTab();
|
DrawDuplicatesTab();
|
||||||
DrawMaterialChangeTab();
|
DrawMaterialReassignmentTab();
|
||||||
|
_modelTab.Draw();
|
||||||
|
_materialTab.Draw();
|
||||||
DrawTextureTab();
|
DrawTextureTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,118 +228,41 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMaterialChangeTab()
|
private void DrawMissingFilesTab()
|
||||||
{
|
{
|
||||||
using var tab = ImRaii.TabItem( "Model Materials" );
|
if( _editor!.MissingFiles.Count == 0 )
|
||||||
if( !tab )
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( _editor!.ModelFiles.Count == 0 )
|
|
||||||
{
|
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.TextUnformatted( "No .mdl files detected." );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.NewLine();
|
|
||||||
MaterialSuffix.Draw( _editor, ImGuiHelpers.ScaledVector2( 175, 0 ) );
|
|
||||||
ImGui.NewLine();
|
|
||||||
using var child = ImRaii.Child( "##mdlFiles", -Vector2.One, true );
|
|
||||||
if( !child )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var table = ImRaii.Table( "##files", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.One );
|
|
||||||
if( !table )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconSize = ImGui.GetFrameHeight() * Vector2.One;
|
|
||||||
foreach( var (info, idx) in _editor.ModelFiles.WithIndex() )
|
|
||||||
{
|
|
||||||
using var id = ImRaii.PushId( idx );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Save.ToIconString(), iconSize,
|
|
||||||
"Save the changed mdl file.\nUse at own risk!", !info.Changed, true ) )
|
|
||||||
{
|
|
||||||
info.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Recycle.ToIconString(), iconSize,
|
|
||||||
"Restore current changes to default.", !info.Changed, true ) )
|
|
||||||
{
|
|
||||||
info.Restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted( info.Path.FullName[ ( _mod!.ModPath.FullName.Length + 1 ).. ] );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
|
|
||||||
var tmp = info.CurrentMaterials[ 0 ];
|
|
||||||
if( ImGui.InputText( "##0", ref tmp, 64 ) )
|
|
||||||
{
|
|
||||||
info.SetMaterial( tmp, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
for( var i = 1; i < info.Count; ++i )
|
|
||||||
{
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
|
|
||||||
tmp = info.CurrentMaterials[ i ];
|
|
||||||
if( ImGui.InputText( $"##{i}", ref tmp, 64 ) )
|
|
||||||
{
|
|
||||||
info.SetMaterial( tmp, i );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawMissingFilesTab()
|
|
||||||
{
|
|
||||||
using var tab = ImRaii.TabItem( "Missing Files" );
|
using var tab = ImRaii.TabItem( "Missing Files" );
|
||||||
if( !tab )
|
if( !tab )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( _editor!.MissingFiles.Count == 0 )
|
ImGui.NewLine();
|
||||||
|
if( ImGui.Button( "Remove Missing Files from Mod" ) )
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
_editor.RemoveMissingPaths();
|
||||||
ImGui.TextUnformatted( "No missing files detected." );
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
using var child = ImRaii.Child( "##unusedFiles", -Vector2.One, true );
|
||||||
|
if( !child )
|
||||||
{
|
{
|
||||||
if( ImGui.Button( "Remove Missing Files from Mod" ) )
|
return;
|
||||||
{
|
}
|
||||||
_editor.RemoveMissingPaths();
|
|
||||||
}
|
|
||||||
|
|
||||||
using var child = ImRaii.Child( "##unusedFiles", -Vector2.One, true );
|
using var table = ImRaii.Table( "##missingFiles", 1, ImGuiTableFlags.RowBg, -Vector2.One );
|
||||||
if( !child )
|
if( !table )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var table = ImRaii.Table( "##missingFiles", 1, ImGuiTableFlags.RowBg, -Vector2.One );
|
foreach( var path in _editor.MissingFiles )
|
||||||
if( !table )
|
{
|
||||||
{
|
ImGui.TableNextColumn();
|
||||||
return;
|
ImGui.TextUnformatted( path.FullName );
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var path in _editor.MissingFiles )
|
|
||||||
{
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted( path.FullName );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -575,7 +503,12 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
|
|
||||||
public ModEditWindow()
|
public ModEditWindow()
|
||||||
: base( WindowBaseLabel )
|
: base( WindowBaseLabel )
|
||||||
{ }
|
{
|
||||||
|
_materialTab = new FileEditor< MtrlFile >( "Materials (WIP)", ".mtrl", () => _editor?.MtrlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
||||||
|
DrawMaterialPanel );
|
||||||
|
_modelTab = new FileEditor< MdlFile >( "Models (WIP)", ".mdl", () => _editor?.MdlFiles ?? Array.Empty< Editor.FileRegistry >(),
|
||||||
|
DrawModelPanel );
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue