mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 13:14:17 +01:00
Let SubMods know their location in a mod.
This commit is contained in:
parent
0b2b0d1beb
commit
d6c0362404
18 changed files with 223 additions and 121 deletions
|
|
@ -270,7 +270,7 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
var mod = new Mod( modDirectory );
|
var mod = new Mod( modDirectory );
|
||||||
mod.Reload( out _ );
|
mod.Reload( out _ );
|
||||||
var editor = new Editor( mod, 0, 0 );
|
var editor = new Editor( mod, mod.Default );
|
||||||
editor.DuplicatesFinished = false;
|
editor.DuplicatesFinished = false;
|
||||||
editor.CheckDuplicates( editor.AvailableFiles.OrderByDescending( f => f.FileSize ).ToArray() );
|
editor.CheckDuplicates( editor.AvailableFiles.OrderByDescending( f => f.FileSize ).ToArray() );
|
||||||
editor.DeleteDuplicates( false );
|
editor.DeleteDuplicates( false );
|
||||||
|
|
|
||||||
|
|
@ -9,34 +9,16 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
public partial class Editor
|
public partial class Editor
|
||||||
{
|
{
|
||||||
public int GroupIdx { get; private set; } = -1;
|
private SubMod _subMod;
|
||||||
public int OptionIdx { get; private set; }
|
|
||||||
|
|
||||||
private IModGroup? _modGroup;
|
|
||||||
private SubMod _subMod;
|
|
||||||
|
|
||||||
public ISubMod CurrentOption
|
public ISubMod CurrentOption
|
||||||
=> _subMod;
|
=> _subMod;
|
||||||
|
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > CurrentSwaps = new();
|
public readonly Dictionary< Utf8GamePath, FullPath > CurrentSwaps = new();
|
||||||
|
|
||||||
public void SetSubMod( int groupIdx, int optionIdx )
|
public void SetSubMod( ISubMod? subMod )
|
||||||
{
|
{
|
||||||
GroupIdx = groupIdx;
|
_subMod = subMod as SubMod ?? _mod._default;
|
||||||
OptionIdx = optionIdx;
|
|
||||||
if( groupIdx >= 0 && groupIdx < _mod.Groups.Count && optionIdx >= 0 && optionIdx < _mod.Groups[ groupIdx ].Count )
|
|
||||||
{
|
|
||||||
_modGroup = _mod.Groups[ groupIdx ];
|
|
||||||
_subMod = ( SubMod )_modGroup![ optionIdx ];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GroupIdx = -1;
|
|
||||||
OptionIdx = 0;
|
|
||||||
_modGroup = null;
|
|
||||||
_subMod = _mod._default;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateFiles();
|
UpdateFiles();
|
||||||
RevertSwaps();
|
RevertSwaps();
|
||||||
RevertManipulations();
|
RevertManipulations();
|
||||||
|
|
@ -54,11 +36,16 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.ModManager.OptionSetFiles( _mod, GroupIdx, OptionIdx, dict );
|
Penumbra.ModManager.OptionSetFiles( _mod, _subMod.GroupIdx, _subMod.OptionIdx, dict );
|
||||||
if( num > 0 )
|
if( num > 0 )
|
||||||
|
{
|
||||||
RevertFiles();
|
RevertFiles();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
FileChanges = false;
|
FileChanges = false;
|
||||||
|
}
|
||||||
|
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +54,7 @@ public partial class Mod
|
||||||
|
|
||||||
public void ApplySwaps()
|
public void ApplySwaps()
|
||||||
{
|
{
|
||||||
Penumbra.ModManager.OptionSetFileSwaps( _mod, GroupIdx, OptionIdx, CurrentSwaps.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ) );
|
Penumbra.ModManager.OptionSetFileSwaps( _mod, _subMod.GroupIdx, _subMod.OptionIdx, CurrentSwaps.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RevertSwaps()
|
public void RevertSwaps()
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ public partial class Mod
|
||||||
|
|
||||||
public void ApplyManipulations()
|
public void ApplyManipulations()
|
||||||
{
|
{
|
||||||
Meta.Apply( _mod, GroupIdx, OptionIdx );
|
Meta.Apply( _mod, _subMod.GroupIdx, _subMod.OptionIdx );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,12 +10,11 @@ public partial class Mod : IMod
|
||||||
{
|
{
|
||||||
private readonly Mod _mod;
|
private readonly Mod _mod;
|
||||||
|
|
||||||
public Editor( Mod mod, int groupIdx, int optionIdx )
|
public Editor( Mod mod, ISubMod? option )
|
||||||
{
|
{
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
SetSubMod( groupIdx, optionIdx );
|
_subMod = null!;
|
||||||
GroupIdx = groupIdx;
|
SetSubMod( option );
|
||||||
_subMod = _mod._default;
|
|
||||||
UpdateFiles();
|
UpdateFiles();
|
||||||
ScanModels();
|
ScanModels();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -77,6 +78,14 @@ public sealed partial class Mod
|
||||||
{
|
{
|
||||||
if( mod._groups.Move( groupIdxFrom, groupIdxTo ) )
|
if( mod._groups.Move( groupIdxFrom, groupIdxTo ) )
|
||||||
{
|
{
|
||||||
|
foreach( var (group, groupIdx) in mod._groups.WithIndex().Skip( Math.Min( groupIdxFrom, groupIdxTo ) ) )
|
||||||
|
{
|
||||||
|
foreach( var (o, optionIdx) in group.OfType<SubMod>().WithIndex() )
|
||||||
|
{
|
||||||
|
o.SetPosition( groupIdx, optionIdx );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo );
|
ModOptionChanged.Invoke( ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,17 +171,19 @@ public sealed partial class Mod
|
||||||
|
|
||||||
public void AddOption( Mod mod, int groupIdx, string newName )
|
public void AddOption( Mod mod, int groupIdx, string newName )
|
||||||
{
|
{
|
||||||
switch( mod._groups[ groupIdx ] )
|
var group = mod._groups[groupIdx];
|
||||||
|
switch( group )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
s.OptionData.Add( new SubMod { Name = newName } );
|
s.OptionData.Add( new SubMod(mod) { Name = newName } );
|
||||||
break;
|
break;
|
||||||
case MultiModGroup m:
|
case MultiModGroup m:
|
||||||
m.PrioritizedOptions.Add( ( new SubMod { Name = newName }, 0 ) );
|
m.PrioritizedOptions.Add( ( new SubMod(mod) { Name = newName }, 0 ) );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, mod._groups[ groupIdx ].Count - 1, -1 );
|
group.UpdatePositions( group.Count - 1 );
|
||||||
|
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddOption( Mod mod, int groupIdx, ISubMod option, int priority = 0 )
|
public void AddOption( Mod mod, int groupIdx, ISubMod option, int priority = 0 )
|
||||||
|
|
@ -182,15 +193,16 @@ public sealed partial class Mod
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mod._groups[ groupIdx ].Count > 63 )
|
var group = mod._groups[ groupIdx ];
|
||||||
|
if( group.Count > 63 )
|
||||||
{
|
{
|
||||||
PluginLog.Error(
|
PluginLog.Error(
|
||||||
$"Could not add option {option.Name} to {mod._groups[ groupIdx ].Name} for mod {mod.Name}, "
|
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||||
+ "since only up to 64 options are supported in one group." );
|
+ "since only up to 64 options are supported in one group." );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch( mod._groups[ groupIdx ] )
|
switch( group )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
s.OptionData.Add( o );
|
s.OptionData.Add( o );
|
||||||
|
|
@ -199,23 +211,25 @@ public sealed partial class Mod
|
||||||
m.PrioritizedOptions.Add( ( o, priority ) );
|
m.PrioritizedOptions.Add( ( o, priority ) );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
group.UpdatePositions( group.Count - 1 );
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, mod._groups[ groupIdx ].Count - 1, -1 );
|
ModOptionChanged.Invoke( ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteOption( Mod mod, int groupIdx, int optionIdx )
|
public void DeleteOption( Mod mod, int groupIdx, int optionIdx )
|
||||||
{
|
{
|
||||||
|
var group = mod._groups[groupIdx];
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1 );
|
ModOptionChanged.Invoke( ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1 );
|
||||||
switch( mod._groups[ groupIdx ] )
|
switch( group )
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
case SingleModGroup s:
|
||||||
s.OptionData.RemoveAt( optionIdx );
|
s.OptionData.RemoveAt( optionIdx );
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case MultiModGroup m:
|
case MultiModGroup m:
|
||||||
m.PrioritizedOptions.RemoveAt( optionIdx );
|
m.PrioritizedOptions.RemoveAt( optionIdx );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
group.UpdatePositions( optionIdx );
|
||||||
ModOptionChanged.Invoke( ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1 );
|
ModOptionChanged.Invoke( ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ public partial class Mod
|
||||||
=> 0;
|
=> 0;
|
||||||
|
|
||||||
private Mod( DirectoryInfo modPath )
|
private Mod( DirectoryInfo modPath )
|
||||||
=> ModPath = modPath;
|
{
|
||||||
|
ModPath = modPath;
|
||||||
|
_default = new SubMod( this );
|
||||||
|
}
|
||||||
|
|
||||||
private static Mod? LoadMod( DirectoryInfo modPath )
|
private static Mod? LoadMod( DirectoryInfo modPath )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public partial class Mod
|
||||||
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
|
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
|
||||||
.Where( t => t.Item1 );
|
.Where( t => t.Item1 );
|
||||||
|
|
||||||
var mod = new SubMod
|
var mod = new SubMod(null!) // Mod is irrelevant here, only used for saving.
|
||||||
{
|
{
|
||||||
Name = option.Name,
|
Name = option.Name,
|
||||||
};
|
};
|
||||||
|
|
@ -112,7 +112,7 @@ public partial class Mod
|
||||||
|
|
||||||
// Create an empty sub mod for single groups with None options.
|
// Create an empty sub mod for single groups with None options.
|
||||||
internal static ISubMod CreateEmptySubMod( string name )
|
internal static ISubMod CreateEmptySubMod( string name )
|
||||||
=> new SubMod()
|
=> new SubMod(null! ) // Mod is irrelevant here, only used for saving.
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public partial class Mod
|
||||||
public IReadOnlyList< IModGroup > Groups
|
public IReadOnlyList< IModGroup > Groups
|
||||||
=> _groups;
|
=> _groups;
|
||||||
|
|
||||||
private readonly SubMod _default = new();
|
private readonly SubMod _default;
|
||||||
private readonly List< IModGroup > _groups = new();
|
private readonly List< IModGroup > _groups = new();
|
||||||
|
|
||||||
public int TotalFileCount { get; private set; }
|
public int TotalFileCount { get; private set; }
|
||||||
|
|
@ -70,7 +70,7 @@ public partial class Mod
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModGroup? LoadModGroup( FileInfo file, DirectoryInfo basePath )
|
private static IModGroup? LoadModGroup( Mod mod, FileInfo file, int groupIdx )
|
||||||
{
|
{
|
||||||
if( !File.Exists( file.FullName ) )
|
if( !File.Exists( file.FullName ) )
|
||||||
{
|
{
|
||||||
|
|
@ -82,8 +82,8 @@ public partial class Mod
|
||||||
var json = JObject.Parse( File.ReadAllText( file.FullName ) );
|
var json = JObject.Parse( File.ReadAllText( file.FullName ) );
|
||||||
switch( json[ nameof( Type ) ]?.ToObject< SelectType >() ?? SelectType.Single )
|
switch( json[ nameof( Type ) ]?.ToObject< SelectType >() ?? SelectType.Single )
|
||||||
{
|
{
|
||||||
case SelectType.Multi: return MultiModGroup.Load( json, basePath );
|
case SelectType.Multi: return MultiModGroup.Load( mod, json, groupIdx );
|
||||||
case SelectType.Single: return SingleModGroup.Load( json, basePath );
|
case SelectType.Single: return SingleModGroup.Load( mod, json, groupIdx );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -100,7 +100,7 @@ public partial class Mod
|
||||||
var changes = false;
|
var changes = false;
|
||||||
foreach( var file in GroupFiles )
|
foreach( var file in GroupFiles )
|
||||||
{
|
{
|
||||||
var group = LoadModGroup( file, ModPath );
|
var group = LoadModGroup( this, file, _groups.Count );
|
||||||
if( group != null && _groups.All( g => g.Name != group.Name ) )
|
if( group != null && _groups.All( g => g.Name != group.Name ) )
|
||||||
{
|
{
|
||||||
changes = changes || group.FileName( ModPath, _groups.Count ) != file.FullName;
|
changes = changes || group.FileName( ModPath, _groups.Count ) != file.FullName;
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ public sealed partial class Mod
|
||||||
mod._groups.Add( newMultiGroup );
|
mod._groups.Add( newMultiGroup );
|
||||||
foreach( var option in group.Options )
|
foreach( var option in group.Options )
|
||||||
{
|
{
|
||||||
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod.ModPath, option, seenMetaFiles ), optionPriority++ ) );
|
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod, option, seenMetaFiles ), optionPriority++ ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -161,7 +161,7 @@ public sealed partial class Mod
|
||||||
mod._groups.Add( newSingleGroup );
|
mod._groups.Add( newSingleGroup );
|
||||||
foreach( var option in group.Options )
|
foreach( var option in group.Options )
|
||||||
{
|
{
|
||||||
newSingleGroup.OptionData.Add( SubModFromOption( mod.ModPath, option, seenMetaFiles ) );
|
newSingleGroup.OptionData.Add( SubModFromOption( mod, option, seenMetaFiles ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -185,11 +185,11 @@ public sealed partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SubMod SubModFromOption( DirectoryInfo basePath, OptionV0 option, HashSet< FullPath > seenMetaFiles )
|
private static SubMod SubModFromOption( Mod mod, OptionV0 option, HashSet< FullPath > seenMetaFiles )
|
||||||
{
|
{
|
||||||
var subMod = new SubMod { Name = option.OptionName };
|
var subMod = new SubMod(mod) { Name = option.OptionName };
|
||||||
AddFilesToSubMod( subMod, basePath, option, seenMetaFiles );
|
AddFilesToSubMod( subMod, mod.ModPath, option, seenMetaFiles );
|
||||||
subMod.IncorporateMetaChanges( basePath, false );
|
subMod.IncorporateMetaChanges( mod.ModPath, false );
|
||||||
return subMod;
|
return subMod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ public sealed partial class Mod
|
||||||
public IEnumerable< ISubMod > AllSubMods
|
public IEnumerable< ISubMod > AllSubMods
|
||||||
=> new[] { Default };
|
=> new[] { Default };
|
||||||
|
|
||||||
private readonly SubMod _default = new();
|
private readonly SubMod _default;
|
||||||
|
|
||||||
|
public TemporaryMod()
|
||||||
|
=> _default = new SubMod( this );
|
||||||
|
|
||||||
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
|
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
|
||||||
=> _default.FileData[ gamePath ] = fullPath;
|
=> _default.FileData[ gamePath ] = fullPath;
|
||||||
|
|
|
||||||
|
|
@ -97,4 +97,5 @@ public interface IModGroup : IEnumerable< ISubMod >
|
||||||
|
|
||||||
public IModGroup Convert( SelectType type );
|
public IModGroup Convert( SelectType type );
|
||||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo );
|
public bool MoveOption( int optionIdxFrom, int optionIdxTo );
|
||||||
|
public void UpdatePositions(int from = 0);
|
||||||
}
|
}
|
||||||
|
|
@ -9,11 +9,14 @@ namespace Penumbra.Mods;
|
||||||
public interface ISubMod
|
public interface ISubMod
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
public string FullName { get; }
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
|
||||||
public IReadOnlySet< MetaManipulation > Manipulations { get; }
|
public IReadOnlySet< MetaManipulation > Manipulations { get; }
|
||||||
|
|
||||||
|
public bool IsDefault { get; }
|
||||||
|
|
||||||
public static void WriteSubMod( JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority )
|
public static void WriteSubMod( JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority )
|
||||||
{
|
{
|
||||||
j.WriteStartObject();
|
j.WriteStartObject();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
@ -40,7 +41,7 @@ public partial class Mod
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
public static MultiModGroup? Load( JObject json, DirectoryInfo basePath )
|
public static MultiModGroup? Load( Mod mod, JObject json, int groupIdx )
|
||||||
{
|
{
|
||||||
var options = json[ "Options" ];
|
var options = json[ "Options" ];
|
||||||
var ret = new MultiModGroup()
|
var ret = new MultiModGroup()
|
||||||
|
|
@ -60,11 +61,14 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
if( ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions )
|
if( ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions )
|
||||||
{
|
{
|
||||||
PluginLog.Warning($"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options." );
|
PluginLog.Warning(
|
||||||
|
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options." );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var subMod = new SubMod();
|
|
||||||
subMod.Load( basePath, child, out var priority );
|
var subMod = new SubMod( mod );
|
||||||
|
subMod.SetPosition( groupIdx, ret.PrioritizedOptions.Count );
|
||||||
|
subMod.Load( mod.ModPath, child, out var priority );
|
||||||
ret.PrioritizedOptions.Add( ( subMod, priority ) );
|
ret.PrioritizedOptions.Add( ( subMod, priority ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +95,22 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
||||||
=> PrioritizedOptions.Move( optionIdxFrom, optionIdxTo );
|
{
|
||||||
|
if( !PrioritizedOptions.Move( optionIdxFrom, optionIdxTo ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePositions( int from = 0 )
|
||||||
|
{
|
||||||
|
foreach( var ((o, _), i) in PrioritizedOptions.WithIndex().Skip( from ) )
|
||||||
|
{
|
||||||
|
o.SetPosition( o.GroupIdx, i );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
@ -39,7 +40,7 @@ public partial class Mod
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
public static SingleModGroup? Load( JObject json, DirectoryInfo basePath )
|
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
|
||||||
{
|
{
|
||||||
var options = json[ "Options" ];
|
var options = json[ "Options" ];
|
||||||
var ret = new SingleModGroup
|
var ret = new SingleModGroup
|
||||||
|
|
@ -57,8 +58,9 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
foreach( var child in options.Children() )
|
foreach( var child in options.Children() )
|
||||||
{
|
{
|
||||||
var subMod = new SubMod();
|
var subMod = new SubMod( mod );
|
||||||
subMod.Load( basePath, child, out _ );
|
subMod.SetPosition( groupIdx, ret.OptionData.Count );
|
||||||
|
subMod.Load( mod.ModPath, child, out _ );
|
||||||
ret.OptionData.Add( subMod );
|
ret.OptionData.Add( subMod );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +87,22 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
||||||
=> OptionData.Move( optionIdxFrom, optionIdxTo );
|
{
|
||||||
|
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePositions( int from = 0 )
|
||||||
|
{
|
||||||
|
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
|
||||||
|
{
|
||||||
|
o.SetPosition( o.GroupIdx, i );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ public partial class Mod
|
||||||
private void LoadDefaultOption()
|
private void LoadDefaultOption()
|
||||||
{
|
{
|
||||||
var defaultFile = DefaultFile;
|
var defaultFile = DefaultFile;
|
||||||
|
_default.SetPosition( -1, 0 );
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if( !File.Exists( defaultFile ) )
|
if( !File.Exists( defaultFile ) )
|
||||||
|
|
@ -72,10 +73,23 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = "Default";
|
public string Name { get; set; } = "Default";
|
||||||
|
|
||||||
|
public string FullName
|
||||||
|
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
|
||||||
|
|
||||||
|
internal IMod ParentMod { get; private init; }
|
||||||
|
internal int GroupIdx { get; private set; }
|
||||||
|
internal int OptionIdx { get; private set; }
|
||||||
|
|
||||||
|
public bool IsDefault
|
||||||
|
=> GroupIdx < 0;
|
||||||
|
|
||||||
public Dictionary< Utf8GamePath, FullPath > FileData = new();
|
public Dictionary< Utf8GamePath, FullPath > FileData = new();
|
||||||
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
||||||
public HashSet< MetaManipulation > ManipulationData = new();
|
public HashSet< MetaManipulation > ManipulationData = new();
|
||||||
|
|
||||||
|
public SubMod( IMod parentMod )
|
||||||
|
=> ParentMod = parentMod;
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
||||||
=> FileData;
|
=> FileData;
|
||||||
|
|
||||||
|
|
@ -85,25 +99,10 @@ public partial class Mod
|
||||||
public IReadOnlySet< MetaManipulation > Manipulations
|
public IReadOnlySet< MetaManipulation > Manipulations
|
||||||
=> ManipulationData;
|
=> ManipulationData;
|
||||||
|
|
||||||
// Insert all changes from the other submod.
|
public void SetPosition( int groupIdx, int optionIdx )
|
||||||
// Overwrites already existing changes in this mod.
|
|
||||||
public void MergeIn( ISubMod other )
|
|
||||||
{
|
{
|
||||||
foreach( var (key, value) in other.Files )
|
GroupIdx = groupIdx;
|
||||||
{
|
OptionIdx = optionIdx;
|
||||||
FileData[ key ] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var (key, value) in other.FileSwaps )
|
|
||||||
{
|
|
||||||
FileSwapData[ key ] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var manip in other.Manipulations )
|
|
||||||
{
|
|
||||||
ManipulationData.Remove( manip );
|
|
||||||
ManipulationData.Add( manip );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
|
@ -23,6 +24,7 @@ public partial class ModEditWindow
|
||||||
private int _fileIdx = -1;
|
private int _fileIdx = -1;
|
||||||
private int _pathIdx = -1;
|
private int _pathIdx = -1;
|
||||||
private int _folderSkip = 0;
|
private int _folderSkip = 0;
|
||||||
|
private bool _overviewMode = false;
|
||||||
|
|
||||||
private bool CheckFilter( Mod.Editor.FileRegistry registry )
|
private bool CheckFilter( Mod.Editor.FileRegistry registry )
|
||||||
=> _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.OrdinalIgnoreCase );
|
=> _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.OrdinalIgnoreCase );
|
||||||
|
|
@ -47,7 +49,73 @@ public partial class ModEditWindow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( _overviewMode )
|
||||||
|
DrawFilesOverviewMode();
|
||||||
|
else
|
||||||
|
DrawFilesNormalMode();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilesOverviewMode()
|
||||||
|
{
|
||||||
|
using var list = ImRaii.Table( "##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit );
|
||||||
|
|
||||||
|
if( !list )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
void Draw( Mod.Editor.FileRegistry registry )
|
||||||
|
{
|
||||||
|
if( registry.SubModUsage.Count == 0 )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( idx++ );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40000080 );
|
||||||
|
ImGui.Selectable( registry.RelPath.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40000080 );
|
||||||
|
ImGui.TextUnformatted( "Unused" );
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40000080 );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach( var (mod, path) in registry.SubModUsage )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( idx++ );
|
||||||
|
var color = mod == _editor.CurrentOption && _mod!.HasOptions;
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( color )
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40008000 );
|
||||||
|
ImGui.Selectable( registry.RelPath.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( color )
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40008000 );
|
||||||
|
ImGui.Selectable( path.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( color )
|
||||||
|
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, 0x40008000 );
|
||||||
|
ImGui.TextUnformatted( mod.Name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Filter( Mod.Editor.FileRegistry registry )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var skips = ImGuiClip.GetNecessarySkips( ImGui.GetTextLineHeight() );
|
||||||
|
var end = ImGuiClip.FilteredClippedDraw( _editor!.AvailableFiles, skips, Filter, Draw, 0 );
|
||||||
|
ImGuiClip.DrawEndDummy( end, ImGui.GetTextLineHeight() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilesNormalMode()
|
||||||
|
{
|
||||||
using var list = ImRaii.Table( "##table", 1 );
|
using var list = ImRaii.Table( "##table", 1 );
|
||||||
|
|
||||||
if( !list )
|
if( !list )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -68,7 +136,7 @@ public partial class ModEditWindow
|
||||||
using var indent = ImRaii.PushIndent( 50f );
|
using var indent = ImRaii.PushIndent( 50f );
|
||||||
for( var j = 0; j < registry.SubModUsage.Count; ++j )
|
for( var j = 0; j < registry.SubModUsage.Count; ++j )
|
||||||
{
|
{
|
||||||
var (subMod, gamePath) = registry.SubModUsage[ j ];
|
var (subMod, gamePath) = registry.SubModUsage[j];
|
||||||
if( subMod != _editor.CurrentOption )
|
if( subMod != _editor.CurrentOption )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -158,6 +226,7 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
|
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileIdx = -1;
|
_fileIdx = -1;
|
||||||
_pathIdx = -1;
|
_pathIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +249,7 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
|
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileIdx = -1;
|
_fileIdx = -1;
|
||||||
_pathIdx = -1;
|
_pathIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +311,13 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip( "Revert all revertible changes since the last file or option reload or data refresh." );
|
ImGuiUtil.HoverTooltip( "Revert all revertible changes since the last file or option reload or data refresh." );
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Checkbox( "Overview Mode", ref _overviewMode );
|
||||||
|
if( _overviewMode )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( 250 * ImGuiHelpers.GlobalScale );
|
ImGui.SetNextItemWidth( 250 * ImGuiHelpers.GlobalScale );
|
||||||
LowerString.InputWithHint( "##filter", "Filter paths...", ref _fileFilter, Utf8GamePath.MaxGamePathLength );
|
LowerString.InputWithHint( "##filter", "Filter paths...", ref _fileFilter, Utf8GamePath.MaxGamePathLength );
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
_editor?.Dispose();
|
_editor?.Dispose();
|
||||||
_editor = new Editor( mod, -1, 0 );
|
_editor = new Editor( mod, mod.Default );
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
|
|
||||||
SizeConstraints = new WindowSizeConstraints
|
SizeConstraints = new WindowSizeConstraints
|
||||||
|
|
@ -42,8 +42,8 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
_selectedFiles.Clear();
|
_selectedFiles.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeOption( int groupIdx, int optionIdx )
|
public void ChangeOption( ISubMod? subMod )
|
||||||
=> _editor?.SetSubMod( groupIdx, optionIdx );
|
=> _editor?.SetSubMod( subMod );
|
||||||
|
|
||||||
public override bool DrawConditions()
|
public override bool DrawConditions()
|
||||||
=> _editor != null;
|
=> _editor != null;
|
||||||
|
|
@ -437,56 +437,34 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
|
|
||||||
private void DrawOptionSelectHeader()
|
private void DrawOptionSelectHeader()
|
||||||
{
|
{
|
||||||
const string defaultOption = "Default Option";
|
const string defaultOption = "Default Option";
|
||||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ).Push( ImGuiStyleVar.FrameRounding, 0 );
|
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ).Push( ImGuiStyleVar.FrameRounding, 0 );
|
||||||
var width = new Vector2( ImGui.GetWindowWidth() / 3, 0 );
|
var width = new Vector2( ImGui.GetWindowWidth() / 3, 0 );
|
||||||
var isDefaultOption = _editor!.GroupIdx == -1 && _editor!.OptionIdx == 0;
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
|
if( ImGuiUtil.DrawDisabledButton( defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
|
||||||
isDefaultOption ) )
|
_editor!.CurrentOption.IsDefault ) )
|
||||||
{
|
{
|
||||||
_editor.SetSubMod( -1, 0 );
|
_editor.SetSubMod( _mod!.Default );
|
||||||
isDefaultOption = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false ) )
|
if( ImGuiUtil.DrawDisabledButton( "Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false ) )
|
||||||
{
|
{
|
||||||
_editor.SetSubMod( _editor.GroupIdx, _editor.OptionIdx );
|
_editor.SetSubMod( _editor.CurrentOption );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
string GetLabel()
|
using var combo = ImRaii.Combo( "##optionSelector", _editor.CurrentOption.FullName, ImGuiComboFlags.NoArrowButton );
|
||||||
{
|
|
||||||
if( isDefaultOption )
|
|
||||||
{
|
|
||||||
return defaultOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
var group = _mod!.Groups[ _editor!.GroupIdx ];
|
|
||||||
return $"{group.Name}: {group[ _editor.OptionIdx ].Name}";
|
|
||||||
}
|
|
||||||
|
|
||||||
using var combo = ImRaii.Combo( "##optionSelector", GetLabel(), ImGuiComboFlags.NoArrowButton );
|
|
||||||
if( !combo )
|
if( !combo )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.Selectable( $"{defaultOption}###-1_0", isDefaultOption ) )
|
foreach( var option in _mod!.AllSubMods )
|
||||||
{
|
{
|
||||||
_editor.SetSubMod( -1, 0 );
|
if( ImGui.Selectable( option.FullName, option == _editor.CurrentOption ) )
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var (group, groupIdx) in _mod!.Groups.WithIndex() )
|
|
||||||
{
|
|
||||||
foreach( var (option, optionIdx) in group.WithIndex() )
|
|
||||||
{
|
{
|
||||||
var name = $"{group.Name}: {option.Name}###{groupIdx}_{optionIdx}";
|
_editor.SetSubMod( option );
|
||||||
if( ImGui.Selectable( name, groupIdx == _editor.GroupIdx && optionIdx == _editor.OptionIdx ) )
|
|
||||||
{
|
|
||||||
_editor.SetSubMod( groupIdx, optionIdx );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public partial class ConfigWindow
|
||||||
if( ImGui.TabItemButton( "Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip ) )
|
if( ImGui.TabItemButton( "Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip ) )
|
||||||
{
|
{
|
||||||
_window.ModEditPopup.ChangeMod( _mod );
|
_window.ModEditPopup.ChangeMod( _mod );
|
||||||
_window.ModEditPopup.ChangeOption( -1, 0 );
|
_window.ModEditPopup.ChangeOption( _mod.Default );
|
||||||
_window.ModEditPopup.IsOpen = true;
|
_window.ModEditPopup.IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +142,7 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
var priority = conflict.Mod2.Index < 0
|
var priority = conflict.Mod2.Index < 0
|
||||||
? conflict.Mod2.Priority
|
? conflict.Mod2.Priority
|
||||||
: Penumbra.CollectionManager.Current[conflict.Mod2.Index].Settings!.Priority;
|
: Penumbra.CollectionManager.Current[ conflict.Mod2.Index ].Settings!.Priority;
|
||||||
ImGui.TextUnformatted( $"(Priority {priority})" );
|
ImGui.TextUnformatted( $"(Priority {priority})" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue