mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-31 21:03:48 +01:00
Add more edit options, some small fixes.
This commit is contained in:
parent
65bbece9cf
commit
54460c39f3
13 changed files with 697 additions and 227 deletions
|
|
@ -48,11 +48,6 @@ public partial class Mod
|
|||
_duplicates.Clear();
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
DuplicatesFinished = true;
|
||||
}
|
||||
|
||||
private void HandleDuplicate( FullPath duplicate, FullPath remaining )
|
||||
{
|
||||
void HandleSubMod( ISubMod subMod, int groupIdx, int optionIdx )
|
||||
|
|
@ -94,8 +89,11 @@ public partial class Mod
|
|||
|
||||
public void StartDuplicateCheck()
|
||||
{
|
||||
DuplicatesFinished = false;
|
||||
Task.Run( CheckDuplicates );
|
||||
if( DuplicatesFinished )
|
||||
{
|
||||
DuplicatesFinished = false;
|
||||
Task.Run( CheckDuplicates );
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDuplicates()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -9,20 +10,23 @@ public partial class Mod
|
|||
{
|
||||
public partial class Editor
|
||||
{
|
||||
private int _groupIdx = -1;
|
||||
private int _optionIdx = 0;
|
||||
public int GroupIdx { get; private set; } = -1;
|
||||
public int OptionIdx { get; private set; } = 0;
|
||||
|
||||
private IModGroup? _modGroup;
|
||||
private SubMod _subMod;
|
||||
|
||||
public ISubMod CurrentOption
|
||||
=> _subMod;
|
||||
|
||||
public readonly Dictionary< Utf8GamePath, FullPath > CurrentFiles = new();
|
||||
public readonly Dictionary< Utf8GamePath, FullPath > CurrentSwaps = new();
|
||||
public readonly HashSet< MetaManipulation > CurrentManipulations = new();
|
||||
|
||||
public void SetSubMod( int groupIdx, int optionIdx )
|
||||
{
|
||||
_groupIdx = groupIdx;
|
||||
_optionIdx = optionIdx;
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
if( groupIdx >= 0 )
|
||||
{
|
||||
_modGroup = _mod.Groups[ groupIdx ];
|
||||
|
|
@ -34,8 +38,38 @@ public partial class Mod
|
|||
_subMod = _mod._default;
|
||||
}
|
||||
|
||||
RevertFiles();
|
||||
RevertSwaps();
|
||||
RevertManipulations();
|
||||
}
|
||||
|
||||
public void ApplyFiles()
|
||||
{
|
||||
Penumbra.ModManager.OptionSetFiles( _mod, GroupIdx, OptionIdx, CurrentFiles.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ) );
|
||||
}
|
||||
|
||||
public void RevertFiles()
|
||||
{
|
||||
CurrentFiles.SetTo( _subMod.Files );
|
||||
}
|
||||
|
||||
public void ApplySwaps()
|
||||
{
|
||||
Penumbra.ModManager.OptionSetFileSwaps( _mod, GroupIdx, OptionIdx, CurrentSwaps.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ) );
|
||||
}
|
||||
|
||||
public void RevertSwaps()
|
||||
{
|
||||
CurrentSwaps.SetTo( _subMod.FileSwaps );
|
||||
}
|
||||
|
||||
public void ApplyManipulations()
|
||||
{
|
||||
Penumbra.ModManager.OptionSetManipulations( _mod, GroupIdx, OptionIdx, CurrentManipulations.ToHashSet() );
|
||||
}
|
||||
|
||||
public void RevertManipulations()
|
||||
{
|
||||
CurrentManipulations.Clear();
|
||||
CurrentManipulations.UnionWith( _subMod.Manipulations );
|
||||
}
|
||||
|
|
|
|||
179
Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs
Normal file
179
Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public partial class Editor
|
||||
{
|
||||
private static readonly Regex MaterialRegex = new(@"/mt_c(?'RaceCode'\d{4})b0001_(?'Suffix'.*?)\.mtrl", RegexOptions.Compiled);
|
||||
private readonly List< MaterialInfo > _modelFiles = new();
|
||||
public IReadOnlyList< MaterialInfo > ModelFiles
|
||||
=> _modelFiles;
|
||||
|
||||
// Non-ASCII encoding can not be used.
|
||||
public static bool ValidString( string to )
|
||||
=> to.Length != 0
|
||||
&& to.Length < 16
|
||||
&& Encoding.UTF8.GetByteCount( to ) == to.Length;
|
||||
|
||||
public void SaveAllModels()
|
||||
{
|
||||
foreach( var info in _modelFiles )
|
||||
{
|
||||
info.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreAllModels()
|
||||
{
|
||||
foreach( var info in _modelFiles )
|
||||
{
|
||||
info.Restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the currently loaded files and replace all appropriate suffices.
|
||||
// Does nothing if toSuffix is invalid.
|
||||
// If raceCode is Unknown, apply to all raceCodes.
|
||||
// If fromSuffix is empty, apply to all suffices.
|
||||
public void ReplaceAllMaterials( string toSuffix, string fromSuffix = "", GenderRace raceCode = GenderRace.Unknown )
|
||||
{
|
||||
if( !ValidString( toSuffix ) )
|
||||
return;
|
||||
|
||||
foreach( var info in _modelFiles )
|
||||
{
|
||||
for( var i = 0; i < info.Count; ++i )
|
||||
{
|
||||
var (_, def) = info[ i ];
|
||||
var match = MaterialRegex.Match( def );
|
||||
if( match.Success
|
||||
&& ( raceCode == GenderRace.Unknown || raceCode.ToRaceCode() == match.Groups[ "RaceCode" ].Value )
|
||||
&& ( fromSuffix.Length == 0 || fromSuffix == match.Groups[ "Suffix" ].Value ) )
|
||||
{
|
||||
info.SetMaterial( $"/mt_c{match.Groups["RaceCode"].Value}b0001_{toSuffix}.mtrl", i );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all model files in the mod that contain skin materials.
|
||||
private void ScanModels()
|
||||
{
|
||||
_modelFiles.Clear();
|
||||
foreach( var (file, _) in AvailableFiles.Where( f => f.Item1.Extension == ".mdl" ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = File.ReadAllBytes( file.FullName );
|
||||
var mdlFile = new MdlFile( bytes );
|
||||
var materials = mdlFile.Materials.WithIndex().Where( p => MaterialRegex.IsMatch( p.Item1 ) )
|
||||
.Select( p => p.Item2 ).ToArray();
|
||||
if( materials.Length > 0 )
|
||||
{
|
||||
_modelFiles.Add( new MaterialInfo( file, mdlFile, materials ) );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Unexpected error scanning {_mod.Name}'s {file.FullName} for materials:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A class that collects information about skin materials in a model file and handle changes on them.
|
||||
public class MaterialInfo
|
||||
{
|
||||
public readonly FullPath Path;
|
||||
private readonly MdlFile _file;
|
||||
private readonly string[] _currentMaterials;
|
||||
private readonly IReadOnlyList<int> _materialIndices;
|
||||
public bool Changed { get; private set; } = false;
|
||||
|
||||
public IReadOnlyList<string> CurrentMaterials
|
||||
=> _currentMaterials;
|
||||
|
||||
private IEnumerable<string> DefaultMaterials
|
||||
=> _materialIndices.Select( i => _file.Materials[i] );
|
||||
|
||||
public (string Current, string Default) this[int idx]
|
||||
=> (_currentMaterials[idx], _file.Materials[_materialIndices[idx]]);
|
||||
|
||||
public int Count
|
||||
=> _materialIndices.Count;
|
||||
|
||||
// Set the skin material to a new value and flag changes appropriately.
|
||||
public void SetMaterial( string value, int materialIdx )
|
||||
{
|
||||
var mat = _file.Materials[_materialIndices[materialIdx]];
|
||||
_currentMaterials[materialIdx] = value;
|
||||
if( mat != value )
|
||||
{
|
||||
Changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Changed = !_currentMaterials.SequenceEqual( DefaultMaterials );
|
||||
}
|
||||
}
|
||||
|
||||
// Save a changed .mdl file.
|
||||
public void Save()
|
||||
{
|
||||
if( !Changed )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
||||
{
|
||||
_file.Materials[idx] = _currentMaterials[i];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes( Path.FullName, _file.Write() );
|
||||
Changed = false;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Restore();
|
||||
PluginLog.Error( $"Could not write manipulated .mdl file {Path.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
// Revert all current changes.
|
||||
public void Restore()
|
||||
{
|
||||
if( !Changed )
|
||||
return;
|
||||
|
||||
foreach( var (idx, i) in _materialIndices.WithIndex() )
|
||||
{
|
||||
_currentMaterials[i] = _file.Materials[idx];
|
||||
}
|
||||
Changed = false;
|
||||
}
|
||||
|
||||
public MaterialInfo( FullPath path, MdlFile file, IReadOnlyList<int> indices )
|
||||
{
|
||||
Path = path;
|
||||
_file = file;
|
||||
_materialIndices = indices;
|
||||
_currentMaterials = DefaultMaterials.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -21,13 +21,17 @@ public partial class Mod
|
|||
_missingPaths = new SortedSet< FullPath >( UsedPaths.Where( f => !f.Exists ) );
|
||||
_unusedFiles = new SortedSet< FullPath >( AvailableFiles.Where( p => !UsedPaths.Contains( p.Item1 ) ).Select( p => p.Item1 ) );
|
||||
_subMod = _mod._default;
|
||||
ScanModels();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Cancel()
|
||||
{
|
||||
DuplicatesFinished = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Cancel();
|
||||
|
||||
// Does not delete the base directory itself even if it is completely empty at the end.
|
||||
private static void ClearEmptySubDirectories( DirectoryInfo baseDir )
|
||||
{
|
||||
|
|
@ -49,7 +53,7 @@ public partial class Mod
|
|||
{
|
||||
for( var optionIdx = 0; optionIdx < group.Count; ++optionIdx )
|
||||
{
|
||||
action( @group[ optionIdx ], groupIdx, optionIdx );
|
||||
action( group[ optionIdx ], groupIdx, optionIdx );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public partial class Mod
|
|||
// - Containing no symbols invalid for FFXIV or windows paths.
|
||||
internal static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension( modListName );
|
||||
var name = modListName;
|
||||
if( name.Length == 0 )
|
||||
{
|
||||
name = "_";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue