Merge pull request #17 from Ottermandias/SmallFixes

Small fixes
This commit is contained in:
Adam 2021-02-17 10:19:51 +11:00 committed by GitHub
commit 7d23505bfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2709 additions and 2195 deletions

View file

@ -2,7 +2,7 @@
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=false
trim_trailing_whitespace=true
insert_final_newline=false
indent_style=space
indent_size=4
@ -10,6 +10,7 @@ indent_size=4
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_prefer_braces=true:none
csharp_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
@ -30,9 +31,26 @@ dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties
resharper_align_multiline_binary_expressions_chain=false
resharper_align_multiline_calls_chain=false
resharper_autodetect_indent_settings=true
resharper_braces_redundant=true
resharper_constructor_or_destructor_body=expression_body
resharper_csharp_empty_block_style=together
resharper_csharp_max_line_length=144
resharper_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true
resharper_int_align_assignments=true
resharper_int_align_comments=true
resharper_int_align_fields=true
resharper_int_align_invocations=false
resharper_int_align_nested_ternary=true
resharper_int_align_properties=false
resharper_int_align_switch_expressions=true
resharper_int_align_switch_sections=true
resharper_int_align_variables=true
resharper_local_function_body=expression_body
resharper_method_or_operator_body=expression_body
resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true

View file

@ -9,10 +9,7 @@ namespace Penumbra.API
{
private readonly Plugin _plugin;
public ModsController( Plugin plugin )
{
_plugin = plugin;
}
public ModsController( Plugin plugin ) => _plugin = plugin;
[Route( HttpVerbs.Get, "/mods" )]
public object GetMods()

View file

@ -1,7 +1,7 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using Dalamud.Configuration;
using Dalamud.Plugin;
namespace Penumbra
{

View file

@ -38,10 +38,7 @@ namespace Penumbra
{
public IntPtr Handle { get; set; }
public DialogHandle( IntPtr handle )
{
Handle = handle;
}
public DialogHandle( IntPtr handle ) => Handle = handle;
}
public class HiddenForm : Form
@ -52,9 +49,9 @@ namespace Penumbra
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{
this._form = form;
this._owner = owner;
this._taskSource = taskSource;
_form = form;
_owner = owner;
_taskSource = taskSource;
Opacity = 0;
FormBorderStyle = FormBorderStyle.None;

View file

@ -25,7 +25,8 @@ namespace Penumbra.Extensions
/// <typeparam name="TField">The type of the underlying field</typeparam>
/// <returns>A delegate that will return a reference to a particular field - zero copy</returns>
/// <exception cref="MissingFieldException"></exception>
private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName ) where TField : unmanaged
private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName )
where TField : unmanaged
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;

View file

@ -14,11 +14,11 @@ namespace Penumbra.Game
public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager );
public LoadPlayerResourcesPrototype LoadPlayerResources { get; private set; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; private set; }
public LoadPlayerResourcesPrototype LoadPlayerResources { get; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; }
// Object addresses
private IntPtr _playerResourceManagerAddress;
private readonly IntPtr _playerResourceManagerAddress;
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
public GameUtils( DalamudPluginInterface pluginInterface )

View file

@ -1,9 +1,10 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using System.Threading.Tasks;
namespace Penumbra
namespace Penumbra.Game
{
public static class RefreshActors
{
@ -12,43 +13,49 @@ namespace Penumbra
private const int RenderTaskOtherDelay = 25;
private const int ModelInvisibilityFlag = 0b10;
private static async void Redraw(Actor actor)
private static async void Redraw( Actor actor )
{
var ptr = actor.Address;
var renderModePtr = ptr + RenderModeOffset;
var renderStatus = Marshal.ReadInt32(renderModePtr);
var renderStatus = Marshal.ReadInt32( renderModePtr );
async void DrawObject(int delay)
async void DrawObject( int delay )
{
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag);
await Task.Delay(delay);
Marshal.WriteInt32(renderModePtr, renderStatus & ~ModelInvisibilityFlag);
Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag );
await Task.Delay( delay );
Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag );
}
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player)
if( actor.ObjectKind == ObjectKind.Player )
{
DrawObject(RenderTaskPlayerDelay);
await Task.Delay(RenderTaskPlayerDelay);
DrawObject( RenderTaskPlayerDelay );
await Task.Delay( RenderTaskPlayerDelay );
}
else
DrawObject(RenderTaskOtherDelay);
{
DrawObject( RenderTaskOtherDelay );
}
}
public static void RedrawSpecific(ActorTable actors, string name)
public static void RedrawSpecific( ActorTable actors, string name )
{
if (name?.Length == 0)
RedrawAll(actors);
foreach (var actor in actors)
if (actor.Name == name)
Redraw(actor);
if( name?.Length == 0 )
{
RedrawAll( actors );
}
public static void RedrawAll(ActorTable actors)
foreach( var actor in actors.Where( A => A.Name == name ) )
{
foreach (var actor in actors)
Redraw(actor);
Redraw( actor );
}
}
public static void RedrawAll( ActorTable actors )
{
foreach( var actor in actors )
{
Redraw( actor );
}
}
}
}

View file

@ -8,10 +8,7 @@ namespace Penumbra.Importer
{
private readonly FileStream _fileStream;
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream )
{
_fileStream = stream;
}
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
public new void Dispose()
{

View file

@ -21,7 +21,7 @@ namespace Penumbra.Importer
public ImporterState State { get; private set; }
public long TotalProgress { get; private set; }
public long TotalProgress { get; private set; } = 0;
public long CurrentProgress { get; private set; }
public float Progress
@ -50,19 +50,7 @@ namespace Penumbra.Importer
{
CurrentModPack = modPackFile.Name;
switch( modPackFile.Extension )
{
case ".ttmp":
ImportV1ModPack( modPackFile );
break;
case ".ttmp2":
ImportV2ModPack( modPackFile );
break;
default:
throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) );
}
VerifyVersionAndImport( modPackFile );
State = ImporterState.Done;
}
@ -88,15 +76,39 @@ namespace Penumbra.Importer
return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
}
private void ImportV1ModPack( FileInfo modPackFile )
private void VerifyVersionAndImport( FileInfo modPackFile )
{
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
// At least a better validation than going by the extension.
if( modRaw.Contains( "\"TTMPVersion\":" ) )
{
if( modPackFile.Extension != ".ttmp2" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension." );
}
ImportV2ModPack( modPackFile, extractedModPack, modRaw );
}
else
{
if( modPackFile.Extension != ".ttmp" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension." );
}
ImportV1ModPack( modPackFile, extractedModPack, modRaw );
}
}
private void ImportV1ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{
PluginLog.Log( " -> Importing V1 ModPack" );
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modListRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ).Split(
var modListRaw = modRaw.Split(
new[] { "\r\n", "\r", "\n" },
StringSplitOptions.None
);
@ -129,33 +141,26 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList, modData );
}
private void ImportV2ModPack( FileInfo modPackFile )
private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
if( modList.TTMPVersion.EndsWith( "s" ) )
{
ImportSimpleV2ModPack( extractedModPack );
ImportSimpleV2ModPack( extractedModPack, modList );
return;
}
if( modList.TTMPVersion.EndsWith( "w" ) )
{
ImportExtendedV2ModPack( extractedModPack );
ImportExtendedV2ModPack( extractedModPack, modRaw );
}
}
private void ImportSimpleV2ModPack( ZipFile extractedModPack )
private void ImportSimpleV2ModPack( ZipFile extractedModPack, SimpleModPack modList )
{
PluginLog.Log( " -> Importing Simple V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
// Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta
{
@ -179,12 +184,11 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
private void ImportExtendedV2ModPack( ZipFile extractedModPack )
private void ImportExtendedV2ModPack( ZipFile extractedModPack, string modRaw )
{
PluginLog.Log( " -> Importing Extended V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw );
// Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta
@ -202,28 +206,32 @@ namespace Penumbra.Importer
var newModFolder = new DirectoryInfo(
Path.Combine( _outDirectory.FullName,
Path.GetFileNameWithoutExtension( modList.Name )
Path.GetFileNameWithoutExtension( modList.Name ).ReplaceInvalidPathSymbols()
)
);
newModFolder.Create();
if( modList.SimpleModsList != null )
{
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
if( modList.ModPackPages == null )
{
return;
}
// Iterate through all pages
foreach( var page in modList.ModPackPages)
foreach( var group in modList.ModPackPages.SelectMany( page => page.ModGroups ) )
{
foreach(var group in page.ModGroups) {
var groupFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, group.GroupName));
foreach(var option in group.OptionList) {
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name ) );
var groupFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols() ) );
foreach( var option in group.OptionList )
{
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta(newModFolder, groupFolder, group, modMeta);
}
AddMeta( newModFolder, groupFolder, group, modMeta );
}
File.WriteAllText(
@ -232,32 +240,35 @@ namespace Penumbra.Importer
);
}
private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder,ModGroup group, ModMeta meta)
private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder, ModGroup group, ModMeta meta )
{
var Inf = new InstallerInfo
{
SelectionType = group.SelectionType,
GroupName = group.GroupName,
Options = new List<Option>(),
Options = new List< Option >()
};
foreach( var opt in group.OptionList )
{
var optio = new Option
var option = new Option
{
OptionName = opt.Name,
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description,
OptionFiles = new Dictionary<string, HashSet<string>>()
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
OptionFiles = new Dictionary< string, HashSet< string > >()
};
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name));
if (optDir.Exists)
var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols() ) );
if( !optDir.Exists )
{
foreach ( var file in optDir.EnumerateFiles("*.*", SearchOption.AllDirectories) )
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{
optio.AddFile(file.FullName.Substring(baseFolder.FullName.Length).TrimStart('\\'), file.FullName.Substring(optDir.FullName.Length).TrimStart('\\').Replace('\\','/'));
option.AddFile( file.FullName.Substring( baseFolder.FullName.Length ).TrimStart( '\\' ),
file.FullName.Substring( optDir.FullName.Length ).TrimStart( '\\' ).Replace( '\\', '/' ) );
}
}
Inf.Options.Add( optio );
Inf.Options.Add( option );
}
meta.Groups.Add( group.GroupName, Inf );
}
@ -273,17 +284,11 @@ namespace Penumbra.Importer
// haha allocation go brr
var wtf = mods.ToList();
TotalProgress = wtf.LongCount();
TotalProgress += wtf.LongCount();
// Extract each SimpleMod into the new mod folder
foreach( var simpleMod in wtf )
foreach( var simpleMod in wtf.Where( M => M != null ) )
{
if( simpleMod == null )
{
// do we increment here too???? can this even happen?????
continue;
}
ExtractMod( outDirectory, simpleMod, dataStream );
CurrentProgress++;
}
@ -301,13 +306,15 @@ namespace Penumbra.Importer
extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( "mdl" ) )
{
ProcessMdl( data.Data );
}
File.WriteAllBytes( extractedFile.FullName, data.Data );
}
catch( Exception ex )
{
PluginLog.LogError( ex, "Could not export mod." );
PluginLog.LogError( ex, "Could not extract mod." );
}
}
@ -326,10 +333,7 @@ namespace Penumbra.Importer
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry )
{
return file.GetInputStream( entry );
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) => file.GetInputStream( entry );
private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding )
{

View file

@ -1,163 +1,170 @@
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.IO;
using System.Linq;
using System.Collections;
using System.Security.Cryptography;
using Dalamud.Plugin;
namespace Penumbra.Models
{
public class Deduplicator
{
private DirectoryInfo baseDir;
private int baseDirLength;
private ModMeta mod;
private SHA256 hasher = null;
private readonly DirectoryInfo _baseDir;
private readonly int _baseDirLength;
private readonly ModMeta _mod;
private SHA256 _hasher;
private Dictionary<long, List<FileInfo>> filesBySize;
private readonly Dictionary< long, List< FileInfo > > _filesBySize = new();
private ref SHA256 Sha()
{
if (hasher == null)
hasher = SHA256.Create();
return ref hasher;
_hasher ??= SHA256.Create();
return ref _hasher;
}
public Deduplicator(DirectoryInfo baseDir, ModMeta mod)
public Deduplicator( DirectoryInfo baseDir, ModMeta mod )
{
this.baseDir = baseDir;
this.baseDirLength = baseDir.FullName.Length;
this.mod = mod;
filesBySize = new();
_baseDir = baseDir;
_baseDirLength = baseDir.FullName.Length;
_mod = mod;
BuildDict();
}
private void BuildDict()
{
foreach( var file in baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
foreach( var file in _baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{
var fileLength = file.Length;
if (filesBySize.TryGetValue(fileLength, out var files))
files.Add(file);
if( _filesBySize.TryGetValue( fileLength, out var files ) )
{
files.Add( file );
}
else
filesBySize[fileLength] = new(){ file };
{
_filesBySize[ fileLength ] = new List< FileInfo >() { file };
}
}
}
public void Run()
{
foreach (var pair in filesBySize)
foreach( var pair in _filesBySize.Where( pair => pair.Value.Count >= 2 ) )
{
if (pair.Value.Count < 2)
continue;
if (pair.Value.Count == 2)
if( pair.Value.Count == 2 )
{
if (CompareFilesDirectly(pair.Value[0], pair.Value[1]))
ReplaceFile(pair.Value[0], pair.Value[1]);
if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) )
{
ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] );
}
}
else
{
var deleted = Enumerable.Repeat(false, pair.Value.Count).ToArray();
var hashes = pair.Value.Select( F => ComputeHash(F)).ToArray();
var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray();
var hashes = pair.Value.Select( ComputeHash ).ToArray();
for (var i = 0; i < pair.Value.Count; ++i)
for( var i = 0; i < pair.Value.Count; ++i )
{
if (deleted[i])
continue;
for (var j = i + 1; j < pair.Value.Count; ++j)
if( deleted[ i ] )
{
if (deleted[j])
continue;
if (!CompareHashes(hashes[i], hashes[j]))
continue;
ReplaceFile(pair.Value[i], pair.Value[j]);
deleted[j] = true;
}
}
}
}
ClearEmptySubDirectories(baseDir);
}
private void ReplaceFile(FileInfo f1, FileInfo f2)
for( var j = i + 1; j < pair.Value.Count; ++j )
{
var relName1 = f1.FullName.Substring(baseDirLength).TrimStart('\\');
var relName2 = f2.FullName.Substring(baseDirLength).TrimStart('\\');
if( deleted[ j ] || !CompareHashes( hashes[ i ], hashes[ j ] ) )
{
continue;
}
ReplaceFile( pair.Value[ i ], pair.Value[ j ] );
deleted[ j ] = true;
}
}
}
}
ClearEmptySubDirectories( _baseDir );
}
private void ReplaceFile( FileInfo f1, FileInfo f2 )
{
var relName1 = f1.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var relName2 = f2.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var inOption = false;
foreach (var group in mod.Groups.Select( g => g.Value.Options))
foreach( var group in _mod.Groups.Select( g => g.Value.Options ) )
{
foreach (var option in group)
foreach( var option in group )
{
if (option.OptionFiles.TryGetValue(relName2, out var values))
if( option.OptionFiles.TryGetValue( relName2, out var values ) )
{
inOption = true;
foreach (var value in values)
option.AddFile(relName1, value);
option.OptionFiles.Remove(relName2);
foreach( var value in values )
{
option.AddFile( relName1, value );
}
option.OptionFiles.Remove( relName2 );
}
}
}
if (!inOption)
if( !inOption )
{
const string duplicates = "Duplicates";
if (!mod.Groups.ContainsKey(duplicates))
if( !_mod.Groups.ContainsKey( duplicates ) )
{
InstallerInfo info = new()
{
GroupName = duplicates,
SelectionType = SelectType.Single,
Options = new()
Options = new List< Option >()
{
new()
{
OptionName = "Required",
OptionDesc = "",
OptionFiles = new()
OptionFiles = new Dictionary< string, HashSet< string > >()
}
}
};
mod.Groups.Add(duplicates, info);
_mod.Groups.Add( duplicates, info );
}
mod.Groups[duplicates].Options[0].AddFile(relName1, relName2.Replace('\\', '/'));
mod.Groups[duplicates].Options[0].AddFile(relName1, relName1.Replace('\\', '/'));
_mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName2.Replace( '\\', '/' ) );
_mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName1.Replace( '\\', '/' ) );
}
PluginLog.Information($"File {relName1} and {relName2} are identical. Deleting the second.");
PluginLog.Information( $"File {relName1} and {relName2} are identical. Deleting the second." );
f2.Delete();
}
public static bool CompareFilesDirectly(FileInfo f1, FileInfo f2)
{
return File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
}
public static bool CompareFilesDirectly( FileInfo f1, FileInfo f2 )
=> File.ReadAllBytes( f1.FullName ).SequenceEqual( File.ReadAllBytes( f2.FullName ) );
public static bool CompareHashes(byte[] f1, byte[] f2)
{
return StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
}
public static bool CompareHashes( byte[] f1, byte[] f2 )
=> StructuralComparisons.StructuralEqualityComparer.Equals( f1, f2 );
public byte[] ComputeHash(FileInfo f)
public byte[] ComputeHash( FileInfo f )
{
var stream = File.OpenRead( f.FullName );
var ret = Sha().ComputeHash(stream);
var ret = Sha().ComputeHash( stream );
stream.Dispose();
return ret;
}
// Does not delete the base directory itself even if it is completely empty at the end.
public static void ClearEmptySubDirectories(DirectoryInfo baseDir)
public static void ClearEmptySubDirectories( DirectoryInfo baseDir )
{
foreach (var subDir in baseDir.GetDirectories())
foreach( var subDir in baseDir.GetDirectories() )
{
ClearEmptySubDirectories( subDir );
if( subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0 )
{
ClearEmptySubDirectories(subDir);
if (subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0)
subDir.Delete();
}
}
}
}
}

View file

@ -5,30 +5,37 @@ namespace Penumbra.Models
{
public enum SelectType
{
Single, Multi
Single,
Multi
}
public struct Option
{
public string OptionName;
public string OptionDesc;
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))]
public Dictionary<string, HashSet<string>> OptionFiles;
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< string > ) )]
public Dictionary< string, HashSet< string > > OptionFiles;
public bool AddFile(string filePath, string gamePath)
public bool AddFile( string filePath, string gamePath )
{
if (OptionFiles.TryGetValue(filePath, out var set))
return set.Add(gamePath);
else
OptionFiles[filePath] = new(){ gamePath };
if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath );
}
OptionFiles[ filePath ] = new HashSet< string >() { gamePath };
return true;
}
}
public struct InstallerInfo {
public struct InstallerInfo
{
public string GroupName;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType;
public List<Option> Options;
public List< Option > Options;
}
}

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;
using Penumbra.Mods;
namespace Penumbra.Models
@ -9,7 +9,7 @@ namespace Penumbra.Models
public string FolderName { get; set; }
public bool Enabled { get; set; }
public int Priority { get; set; }
public Dictionary<string, int> Conf {get;set;}
public Dictionary< string, int > Conf { get; set; }
[JsonIgnore]
public ResourceMod Mod { get; set; }

View file

@ -1,8 +1,8 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace Penumbra.Models
{
@ -21,21 +21,24 @@ namespace Penumbra.Models
public Dictionary< string, string > FileSwaps { get; } = new();
public Dictionary<string, InstallerInfo> Groups { get; set; } = new();
public Dictionary< string, InstallerInfo > Groups { get; set; } = new();
[JsonIgnore]
public bool HasGroupWithConfig { get; set; } = false;
public static ModMeta LoadFromFile(string filePath)
public static ModMeta LoadFromFile( string filePath )
{
try
{
var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) );
meta.HasGroupWithConfig = meta.Groups != null && meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1);
meta.HasGroupWithConfig =
meta.Groups != null
&& meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
return meta;
}
catch( Exception)
catch( Exception )
{
return null;
// todo: handle broken mods properly

View file

@ -16,10 +16,7 @@ namespace Penumbra.Mods
public ResourceMod[] EnabledMods { get; set; }
public ModCollection( DirectoryInfo basePath )
{
_basePath = basePath;
}
public ModCollection( DirectoryInfo basePath ) => _basePath = basePath;
public void Load( bool invertOrder = false )
{
@ -51,7 +48,7 @@ namespace Penumbra.Mods
}
#endif
ModSettings ??= new();
ModSettings ??= new List< ModInfo >();
var foundMods = new List< string >();
foreach( var modDir in _basePath.EnumerateDirectories() )

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using Penumbra.Models;
namespace Penumbra.Mods
@ -16,10 +17,7 @@ namespace Penumbra.Mods
private DirectoryInfo _basePath;
public ModManager( Plugin plugin )
{
_plugin = plugin;
}
public ModManager( Plugin plugin ) => _plugin = plugin;
public void DiscoverMods()
{
@ -113,7 +111,7 @@ namespace Penumbra.Mods
mod.FileConflicts?.Clear();
if( settings.Conf == null )
{
settings.Conf = new();
settings.Conf = new Dictionary< string, int >();
_plugin.ModManager.Mods.Save();
}
@ -154,13 +152,14 @@ namespace Penumbra.Mods
{
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
bool doNotAdd = false;
var doNotAdd = false;
HashSet< string > paths;
foreach( var group in mod.Meta.Groups.Select( G => G.Value ) )
{
if( !settings.Conf.TryGetValue( group.GroupName, out var setting )
|| ( group.SelectionType == SelectType.Single && settings.Conf[ group.GroupName ] >= group.Options.Count ) )
|| group.SelectionType == SelectType.Single
&& settings.Conf[ group.GroupName ] >= group.Options.Count )
{
settings.Conf[ group.GroupName ] = 0;
_plugin.ModManager.Mods.Save();
@ -168,10 +167,14 @@ namespace Penumbra.Mods
}
if( group.Options.Count == 0 )
{
continue;
}
if( group.SelectionType == SelectType.Multi )
settings.Conf[ group.GroupName ] &= ( ( 1 << group.Options.Count ) - 1 );
{
settings.Conf[ group.GroupName ] &= ( 1 << group.Options.Count ) - 1;
}
switch( group.SelectionType )
{
@ -182,15 +185,10 @@ namespace Penumbra.Mods
}
else
{
for( var i = 0; i < group.Options.Count; ++i )
{
if( i == setting )
continue;
if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
if( group.Options.Where( ( o, i ) => i != setting )
.Any( option => option.OptionFiles.ContainsKey( relativeFilePath ) ) )
{
doNotAdd = true;
break;
}
}
}
@ -206,8 +204,10 @@ namespace Penumbra.Mods
}
}
else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true;
}
}
break;
}
@ -215,7 +215,7 @@ namespace Penumbra.Mods
if( !doNotAdd )
{
AddFiles( new() { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
AddFiles( new HashSet< string > { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
}
}
}
@ -245,8 +245,19 @@ namespace Penumbra.Mods
}
public void DeleteMod( ResourceMod mod )
{
if( mod?.ModBasePath?.Exists ?? false )
{
try
{
Directory.Delete( mod.ModBasePath.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
}
}
DiscoverMods();
}
@ -267,9 +278,7 @@ namespace Penumbra.Mods
}
public string GetSwappedFilePath( string gameResourcePath )
{
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
}
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{

View file

@ -21,6 +21,10 @@
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>

View file

@ -57,6 +57,7 @@ namespace Penumbra
GameUtils.ReloadPlayerResources();
SettingsInterface = new SettingsInterface( this );
PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw;
PluginDebugTitleStr = $"{Name} - Debug Build";
@ -122,10 +123,15 @@ namespace Penumbra
}
case "redraw":
{
if (args.Length > 1)
RefreshActors.RedrawSpecific(PluginInterface.ClientState.Actors, string.Join(" ", args.Skip(1)));
if( args.Length > 1 )
{
RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
}
else
RefreshActors.RedrawAll(PluginInterface.ClientState.Actors);
{
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
}
break;
}
}

View file

@ -1,14 +1,14 @@
using System;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Plugin;
using Penumbra.Structs;
using Penumbra.Util;
using FileMode = Penumbra.Structs.FileMode;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X64;
using FileMode = Penumbra.Structs.FileMode;
namespace Penumbra
{
@ -129,7 +129,7 @@ namespace Penumbra
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
}
if (Plugin.Configuration.IsEnabled)
if( Plugin.Configuration.IsEnabled )
{
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
@ -192,7 +192,9 @@ namespace Penumbra
public void Enable()
{
if( IsEnabled )
{
return;
}
ReadSqpackHook.Activate();
GetResourceSyncHook.Activate();
@ -208,7 +210,9 @@ namespace Penumbra
public void Disable()
{
if( !IsEnabled )
{
return;
}
ReadSqpackHook.Disable();
GetResourceSyncHook.Disable();
@ -220,7 +224,9 @@ namespace Penumbra
public void Dispose()
{
if( IsEnabled )
{
Disable();
}
// ReadSqpackHook.Disable();
// GetResourceSyncHook.Disable();

View file

@ -1,9 +1,10 @@
namespace Penumbra.Structs
namespace Penumbra.Structs
{
public enum FileMode : uint
{
LoadUnpackedResource = 0,
LoadFileResource = 1, // Shit in My Games uses this
// some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug?
LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Penumbra.Structs
{

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Penumbra.Structs
{

View file

@ -7,124 +7,134 @@ namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
public static void BeginFramedGroup(string label) => BeginFramedGroupInternal(ref label, ZeroVector, false);
public static void BeginFramedGroup(string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, false);
public static void BeginFramedGroup( string label ) => BeginFramedGroupInternal( ref label, ZeroVector, false );
public static void BeginFramedGroup( string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, false );
public static bool BeginFramedGroupEdit(ref string label) => BeginFramedGroupInternal(ref label, ZeroVector, true);
public static bool BeginFramedGroupEdit(ref string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, true);
public static bool BeginFramedGroupEdit( ref string label ) => BeginFramedGroupInternal( ref label, ZeroVector, true );
public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, true );
private static bool BeginFramedGroupInternal(ref string label, Vector2 minSize, bool edit)
private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit )
{
var itemSpacing = ImGui.GetStyle().ItemSpacing;
var frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2(ImGui.GetFrameHeight() / 2, 0);
var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.BeginGroup(); // First group
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.BeginGroup(); // Second group
var effectiveSize = minSize;
if (effectiveSize.X < 0)
if( effectiveSize.X < 0 )
{
effectiveSize.X = ImGui.GetContentRegionAvail().X;
}
// Ensure width.
ImGui.Dummy(new(effectiveSize.X, 0));
ImGui.Dummy( new Vector2( effectiveSize.X, 0 ) );
// Ensure left half boundary width/distance.
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
ImGui.SameLine();
ImGui.BeginGroup(); // Third group.
// Ensure right half of boundary width/distance
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
// Label block
ImGui.SameLine();
var ret = false;
if (edit)
ret = ImGuiCustom.ResizingTextInput(ref label, 1024);
if( edit )
{
ret = ResizingTextInput( ref label, 1024 );
}
else
ImGui.TextUnformatted(label);
{
ImGui.TextUnformatted( label );
}
var labelMin = ImGui.GetItemRectMin();
var labelMax = ImGui.GetItemRectMax();
ImGui.SameLine();
// Ensure height and distance to label.
ImGui.Dummy(new Vector2(0, frameHeight + itemSpacing.Y));
ImGui.Dummy( new Vector2( 0, frameHeight + itemSpacing.Y ) );
ImGui.BeginGroup(); // Fourth Group.
ImGui.PopStyleVar(2);
ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize(new Vector2(ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y));
ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y ) );
var itemWidth = ImGui.CalcItemWidth();
ImGui.PushItemWidth(Math.Max(0f, itemWidth - frameHeight));
ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) );
labelStack.Add((labelMin, labelMax));
labelStack.Add( ( labelMin, labelMax ) );
return ret;
}
private static void DrawClippedRect(Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness)
private static void DrawClippedRect( Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness )
{
ImGui.PushClipRect(clipMin, clipMax, true);
ImGui.GetWindowDrawList().AddRect(drawMin, drawMax, color, thickness);
ImGui.PushClipRect( clipMin, clipMax, true );
ImGui.GetWindowDrawList().AddRect( drawMin, drawMax, color, thickness );
ImGui.PopClipRect();
}
public static void EndFramedGroup()
{
uint borderColor = ImGui.ColorConvertFloat4ToU32(ImGui.GetStyle().Colors[(int)ImGuiCol.Border]);
Vector2 itemSpacing = ImGui.GetStyle().ItemSpacing;
float frameHeight = ImGui.GetFrameHeight();
Vector2 halfFrameHeight = new(ImGui.GetFrameHeight() / 2, 0);
var borderColor = ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Border ] );
var itemSpacing = ImGui.GetStyle().ItemSpacing;
var frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.PopItemWidth();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.EndGroup(); // Close fourth group
ImGui.EndGroup(); // Close third group
ImGui.SameLine();
// Ensure right distance.
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
// Ensure bottom distance
ImGui.Dummy(new Vector2(0, frameHeight/2 - itemSpacing.Y));
ImGui.Dummy( new Vector2( 0, frameHeight / 2 - itemSpacing.Y ) );
ImGui.EndGroup(); // Close second group
var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax();
var (currentLabelMin, currentLabelMax) = labelStack[labelStack.Count - 1];
labelStack.RemoveAt(labelStack.Count - 1);
var (currentLabelMin, currentLabelMax) = labelStack[ labelStack.Count - 1 ];
labelStack.RemoveAt( labelStack.Count - 1 );
var halfFrame = new Vector2(frameHeight / 8, frameHeight / 2);
var halfFrame = new Vector2( frameHeight / 8, frameHeight / 2 );
currentLabelMin.X -= itemSpacing.X;
currentLabelMax.X += itemSpacing.X;
var frameMin = itemMin + halfFrame;
var frameMax = itemMax - new Vector2(halfFrame.X, 0);
var frameMax = itemMax - new Vector2( halfFrame.X, 0 );
// Left
DrawClippedRect(new(-float.MaxValue , -float.MaxValue ), new(currentLabelMin.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X);
DrawClippedRect( new Vector2( -float.MaxValue, -float.MaxValue ), new Vector2( currentLabelMin.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Right
DrawClippedRect(new(currentLabelMax.X, -float.MaxValue ), new(float.MaxValue , float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X);
DrawClippedRect( new Vector2( currentLabelMax.X, -float.MaxValue ), new Vector2( float.MaxValue, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Top
DrawClippedRect(new(currentLabelMin.X, -float.MaxValue ), new(currentLabelMax.X, currentLabelMin.Y), frameMin, frameMax, borderColor, halfFrame.X);
DrawClippedRect( new Vector2( currentLabelMin.X, -float.MaxValue ), new Vector2( currentLabelMax.X, currentLabelMin.Y ), frameMin,
frameMax, borderColor, halfFrame.X );
// Bottom
DrawClippedRect(new(currentLabelMin.X, currentLabelMax.Y), new(currentLabelMax.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X);
DrawClippedRect( new Vector2( currentLabelMin.X, currentLabelMax.Y ), new Vector2( currentLabelMax.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
ImGui.PopStyleVar(2);
ImGui.SetWindowSize(new Vector2(ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y));
ImGui.Dummy(ZeroVector);
ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) );
ImGui.Dummy( ZeroVector );
ImGui.EndGroup(); // Close first group
}
private static readonly Vector2 ZeroVector = new(0, 0);
private static readonly Vector2 ZeroVector = new( 0, 0 );
private static List<(Vector2, Vector2)> labelStack = new();
private static readonly List< (Vector2, Vector2) > labelStack = new();
}
}

View file

@ -4,39 +4,50 @@ namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
public static bool RenameableCombo(string label, ref int currentItem, ref string newName, string[] items, int numItems)
public static bool RenameableCombo( string label, ref int currentItem, out string newName, string[] items, int numItems )
{
var ret = false;
newName = "";
var newOption = "";
if (ImGui.BeginCombo(label, (numItems > 0) ? items[currentItem] : newOption))
if( !ImGui.BeginCombo( label, numItems > 0 ? items[ currentItem ] : newOption ) )
{
for (var i = 0; i < numItems; ++i)
return false;
}
for( var i = 0; i < numItems; ++i )
{
var isSelected = i == currentItem;
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText($"##{label}_{i}", ref items[i], 64, ImGuiInputTextFlags.EnterReturnsTrue))
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_{i}", ref items[ i ], 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
currentItem = i;
newName = items[i];
newName = items[ i ];
ret = true;
ImGui.CloseCurrentPopup();
}
if (isSelected)
if( isSelected )
{
ImGui.SetItemDefaultFocus();
}
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText($"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
}
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
currentItem = numItems;
newName = newOption;
ret = true;
ImGui.CloseCurrentPopup();
}
if (numItems == 0)
if( numItems == 0 )
{
ImGui.SetItemDefaultFocus();
ImGui.EndCombo();
}
ImGui.EndCombo();
return ret;
}
}

View file

@ -5,36 +5,45 @@ namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
public static bool InputOrText(bool editable, string label, ref string text, uint maxLength)
public static bool InputOrText( bool editable, string label, ref string text, uint maxLength )
{
if (editable)
return ResizingTextInput(label, ref text, maxLength);
if( editable )
{
return ResizingTextInput( label, ref text, maxLength );
}
ImGui.Text(text);
ImGui.Text( text );
return false;
}
public static bool ResizingTextInput(string label, ref string input, uint maxLength) => ResizingTextInputIntern(label, ref input, maxLength).Item1;
public static bool ResizingTextInput(ref string input, uint maxLength)
public static bool ResizingTextInput( string label, ref string input, uint maxLength ) =>
ResizingTextInputIntern( label, ref input, maxLength ).Item1;
public static bool ResizingTextInput( ref string input, uint maxLength )
{
var (ret, id) = ResizingTextInputIntern($"##{input}", ref input, maxLength);
if (ret)
_textInputWidths.Remove(id);
var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength );
if( ret )
{
TextInputWidths.Remove( id );
}
return ret;
}
private static (bool, uint) ResizingTextInputIntern(string label, ref string input, uint maxLength)
private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength )
{
var id = ImGui.GetID(label);
if (!_textInputWidths.TryGetValue(id, out var width))
width = ImGui.CalcTextSize(input).X + 10;
ImGui.SetNextItemWidth(width);
var ret = ImGui.InputText(label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue);
_textInputWidths[id] = ImGui.CalcTextSize(input).X + 10;
return (ret, id);
var id = ImGui.GetID( label );
if( !TextInputWidths.TryGetValue( id, out var width ) )
{
width = ImGui.CalcTextSize( input ).X + 10;
}
private static readonly Dictionary<uint, float> _textInputWidths = new();
ImGui.SetNextItemWidth( width );
var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue );
TextInputWidths[ id ] = ImGui.CalcTextSize( input ).X + 10;
return ( ret, id );
}
private static readonly Dictionary< uint, float > TextInputWidths = new();
}
}

View file

@ -4,23 +4,22 @@ namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
public static void VerticalDistance(float distance)
public static void VerticalDistance( float distance )
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + distance);
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + distance );
}
public static void RightJustifiedText(float pos, string text)
public static void RightJustifiedText( float pos, string text )
{
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - 2 * ImGui.GetStyle().ItemSpacing.X);
ImGui.Text(text);
ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - 2 * ImGui.GetStyle().ItemSpacing.X );
ImGui.Text( text );
}
public static void RightJustifiedLabel(float pos, string text)
public static void RightJustifiedLabel( float pos, string text )
{
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - ImGui.GetStyle().ItemSpacing.X / 2);
ImGui.Text(text);
ImGui.SameLine(pos);
ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().ItemSpacing.X / 2 );
ImGui.Text( text );
ImGui.SameLine( pos );
}
}
}

View file

@ -14,10 +14,11 @@ namespace Penumbra.UI
private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods";
private static readonly Vector2 WindowSize = new(Width, Height);
private static readonly Vector2 WindowPosOffset = new(Padding + Width, Padding + Height);
private static readonly Vector2 WindowSize = new( Width, Height );
private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
private readonly ImGuiWindowFlags ButtonFlags = ImGuiWindowFlags.AlwaysAutoResize
private const ImGuiWindowFlags ButtonFlags =
ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove
@ -28,7 +29,7 @@ namespace Penumbra.UI
private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton(SettingsInterface ui)
public LaunchButton( SettingsInterface ui )
{
_base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition;
@ -36,21 +37,27 @@ namespace Penumbra.UI
public void Draw()
{
if( !_condition.Any() && !_base._menu.Visible )
if( _condition.Any() || _base._menu.Visible )
{
return;
}
var ss = ImGui.GetIO().DisplaySize;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( ImGui.Begin(MenuButtonsName, ButtonFlags) )
if( !ImGui.Begin( MenuButtonsName, ButtonFlags ) )
{
return;
}
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
{
_base.FlipVisibility();
}
ImGui.End();
}
}
}
}
}
}

View file

@ -10,27 +10,41 @@ namespace Penumbra.UI
private const string MenuItemToggle = "Toggle UI";
private const string SlashCommand = "/penumbra";
private const string MenuItemRediscover = "Rediscover Mods";
private const string MenuItemHide = "Hide Menu Bar";
#if DEBUG
private const bool _showDebugBar = true;
private bool _showDebugBar = true;
#else
private const bool _showDebugBar = false;
#endif
private readonly SettingsInterface _base;
public MenuBar(SettingsInterface ui) => _base = ui;
public MenuBar( SettingsInterface ui ) => _base = ui;
public void Draw()
{
if( _showDebugBar && ImGui.BeginMainMenuBar() )
if( !_showDebugBar || !ImGui.BeginMainMenuBar() )
{
return;
}
if( ImGui.BeginMenu( MenuLabel ) )
{
if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) )
{
_base.FlipVisibility();
}
if( ImGui.MenuItem( MenuItemRediscover ) )
{
_base.ReloadMods();
}
#if DEBUG
if( ImGui.MenuItem( MenuItemHide ) )
{
_showDebugBar = false;
}
#endif
ImGui.EndMenu();
}
@ -39,5 +53,4 @@ namespace Penumbra.UI
}
}
}
}
}

View file

@ -7,8 +7,8 @@ namespace Penumbra.UI
{
private const float DefaultVerticalSpace = 20f;
private static readonly Vector2 AutoFillSize = new(-1, -1);
private static readonly Vector2 ZeroVector = new( 0, 0);
private static readonly Vector2 AutoFillSize = new( -1, -1 );
private static readonly Vector2 ZeroVector = new( 0, 0 );
private readonly Plugin _plugin;
@ -19,9 +19,9 @@ namespace Penumbra.UI
public SettingsInterface( Plugin plugin )
{
_plugin = plugin;
_launchButton = new(this);
_menuBar = new(this);
_menu = new(this);
_launchButton = new LaunchButton( this );
_menuBar = new MenuBar( this );
_menu = new SettingsMenu( this );
}
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
@ -35,12 +35,14 @@ namespace Penumbra.UI
private void ReloadMods()
{
_menu._installedTab._selector.ClearSelection();
_menu.InstalledTab.Selector.ResetModNamesLower();
_menu.InstalledTab.Selector.ClearSelection();
// create the directory if it doesn't exist
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced);
_menu.EffectiveTab.RebuildFileList( _plugin.Configuration.ShowAdvanced );
_menu.InstalledTab.Selector.ResetModNamesLower();
}
}
}

View file

@ -13,20 +13,20 @@ namespace Penumbra.UI
private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 );
private readonly SettingsInterface _base;
public readonly TabSettings _settingsTab;
public readonly TabImport _importTab;
public readonly TabBrowser _browserTab;
public readonly TabInstalled _installedTab;
public readonly TabEffective _effectiveTab;
private readonly TabSettings _settingsTab;
private readonly TabImport _importTab;
private readonly TabBrowser _browserTab;
public readonly TabInstalled InstalledTab;
public readonly TabEffective EffectiveTab;
public SettingsMenu(SettingsInterface ui)
public SettingsMenu( SettingsInterface ui )
{
_base = ui;
_settingsTab = new(_base);
_importTab = new(_base);
_browserTab = new();
_installedTab = new(_base);
_effectiveTab = new(_base);
_settingsTab = new TabSettings( _base );
_importTab = new TabImport( _base );
_browserTab = new TabBrowser();
InstalledTab = new TabInstalled( _base );
EffectiveTab = new TabEffective( _base );
}
#if DEBUG
@ -39,7 +39,9 @@ namespace Penumbra.UI
public void Draw()
{
if( !Visible )
{
return;
}
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
#if DEBUG
@ -48,7 +50,9 @@ namespace Penumbra.UI
var ret = ImGui.Begin( _base._plugin.Name, ref Visible );
#endif
if( !ret )
{
return;
}
ImGui.BeginTabBar( PenumbraSettingsLabel );
@ -58,10 +62,12 @@ namespace Penumbra.UI
if( !_importTab.IsImporting() )
{
_browserTab.Draw();
_installedTab.Draw();
InstalledTab.Draw();
if( _base._plugin.Configuration.ShowAdvanced )
_effectiveTab.Draw();
{
EffectiveTab.Draw();
}
}
ImGui.EndTabBar();

View file

@ -12,7 +12,9 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( "Available Mods" );
if( !ret )
{
return;
}
ImGui.Text( "woah" );
ImGui.EndTabItem();

View file

@ -11,22 +11,22 @@ namespace Penumbra.UI
private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f;
private ModManager _mods;
private (string, string)[] _fileList = null;
private float _maxGamePath = 0f;
private readonly ModManager _mods;
private (string, string)[] _fileList;
private float _maxGamePath;
public TabEffective(SettingsInterface ui)
public TabEffective( SettingsInterface ui )
{
_mods = ui._plugin.ModManager;
RebuildFileList(ui._plugin.Configuration.ShowAdvanced);
RebuildFileList( ui._plugin.Configuration.ShowAdvanced );
}
public void RebuildFileList(bool advanced)
public void RebuildFileList( bool advanced )
{
if (advanced)
if( advanced )
{
_fileList = _mods.ResolvedFiles.Select( P => (P.Value.FullName, P.Key) ).ToArray();
_maxGamePath = ((_fileList.Length > 0) ? _fileList.Max( P => ImGui.CalcTextSize(P.Item2).X ) : 0f) + TextSizePadding;
_fileList = _mods.ResolvedFiles.Select( P => ( P.Value.FullName, P.Key ) ).ToArray();
_maxGamePath = ( _fileList.Length > 0 ? _fileList.Max( P => ImGui.CalcTextSize( P.Item2 ).X ) : 0f ) + TextSizePadding;
}
else
{
@ -35,26 +35,30 @@ namespace Penumbra.UI
}
}
private void DrawFileLine((string, string) file)
private void DrawFileLine( (string, string) file )
{
ImGui.Selectable(file.Item2);
ImGui.Selectable( file.Item2 );
ImGui.SameLine();
ImGui.SetCursorPosX(_maxGamePath);
ImGui.TextUnformatted(" <-- ");
ImGui.SetCursorPosX( _maxGamePath );
ImGui.TextUnformatted( " <-- " );
ImGui.SameLine();
ImGui.Selectable(file.Item1);
ImGui.Selectable( file.Item1 );
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
{
foreach( var file in _fileList )
DrawFileLine(file);
{
DrawFileLine( file );
}
ImGui.ListBoxFooter();
}

View file

@ -1,11 +1,11 @@
using ImGuiNET;
using System;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System;
using Penumbra.Importer;
using Dalamud.Plugin;
using System.Numerics;
using ImGuiNET;
using Penumbra.Importer;
namespace Penumbra.UI
{
@ -15,9 +15,9 @@ namespace Penumbra.UI
{
private const string LabelTab = "Import Mods";
private const string LabelImportButton = "Import TexTools Modpacks";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string LabelFileDialog = "Pick one or more modpacks.";
private const string LabelFileImportRunning = "Import in progress...";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string TooltipModpack1 = "Writing modpack to disk before extracting...";
private const string FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report.";
@ -30,7 +30,7 @@ namespace Penumbra.UI
private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base;
public TabImport(SettingsInterface ui) => _base = ui;
public TabImport( SettingsInterface ui ) => _base = ui;
public bool IsImporting() => _isImportRunning;
@ -55,7 +55,7 @@ namespace Penumbra.UI
foreach( var fileName in picker.FileNames )
{
PluginLog.Log( $"-> {fileName} START");
PluginLog.Log( $"-> {fileName} START" );
try
{
@ -74,6 +74,7 @@ namespace Penumbra.UI
_texToolsImport = null;
_base.ReloadMods();
}
_isImportRunning = false;
} );
}
@ -90,8 +91,11 @@ namespace Penumbra.UI
{
ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null )
if( _texToolsImport == null )
{
return;
}
switch( _texToolsImport.State )
{
case ImporterState.None:
@ -113,9 +117,8 @@ namespace Penumbra.UI
throw new ArgumentOutOfRangeException();
}
}
}
private void DrawFailedImportMessage()
private static void DrawFailedImportMessage()
{
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
ImGui.Text( FailedImport );
@ -126,15 +129,23 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( !_isImportRunning )
{
DrawImportButton();
}
else
{
DrawImportProgress();
}
if (_hasError)
if( _hasError )
{
DrawFailedImportMessage();
}
ImGui.EndTabItem();
}

View file

@ -4,25 +4,25 @@ namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class TabInstalled
private class TabInstalled
{
private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base;
public readonly Selector _selector;
public readonly ModPanel _modPanel;
public readonly Selector Selector;
public readonly ModPanel ModPanel;
public TabInstalled(SettingsInterface ui)
public TabInstalled( SettingsInterface ui )
{
_base = ui;
_selector = new(_base);
_modPanel = new(_base, _selector);
Selector = new Selector( _base );
ModPanel = new ModPanel( _base, Selector );
}
private void DrawNoModsAvailable()
private static void DrawNoModsAvailable()
{
ImGui.Text( "You don't have any mods :(" );
ImGuiCustom.VerticalDistance(20f);
ImGuiCustom.VerticalDistance( 20f );
ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." );
ImGui.Text( "For example: D:/ffxiv/mods/" );
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
@ -33,20 +33,22 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
return;
if (_base._plugin.ModManager.Mods != null)
{
_selector.Draw();
return;
}
if( _base._plugin.ModManager.Mods != null )
{
Selector.Draw();
ImGui.SameLine();
_modPanel.Draw();
ModPanel.Draw();
}
else
{
DrawNoModsAvailable();
}
ImGui.EndTabItem();
return;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,355 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class PluginDetails
{
private const string LabelDescEdit = "##descedit";
private const string LabelNewSingleGroup = "New Single Group";
private const string LabelNewSingleGroupEdit = "##newSingleGroup";
private const string LabelNewMultiGroup = "New Multi Group";
private const string LabelGamePathsEdit = "Game Paths";
private const string LabelGamePathsEditBox = "##gamePathsEdit";
private const string ButtonAddToGroup = "Add to Group";
private const string ButtonRemoveFromGroup = "Remove from Group";
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string TextNoOptionAvailable = "[Not Available]";
private const string TextDefaultGamePath = "default";
private const char GamePathsSeparator = ';';
private static readonly string TooltipFilesTabEdit =
$"{TooltipFilesTab}\n" +
$"Red Files are replaced in another group or a different option in this group, but not contained in the current option.";
private static readonly string TooltipGamePathsEdit =
$"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" +
$"Use '{TextDefaultGamePath}' to add the original file path.";
private const float MultiEditBoxWidth = 300f;
private bool DrawEditGroupSelector()
{
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( Meta.Groups.Count == 0 )
{
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 );
return false;
}
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
, Meta.Groups.Values.Select( G => G.GroupName ).ToArray()
, Meta.Groups.Count ) )
{
SelectGroup();
SelectOption( 0 );
}
return true;
}
private bool DrawEditOptionSelector()
{
ImGui.SameLine();
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 )
{
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 );
return false;
}
var group = ( InstallerInfo )_selectedGroup;
if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( O => O.OptionName ).ToArray(),
group.Options.Count ) )
{
SelectOption();
}
return true;
}
private void DrawFileListTabEdit()
{
if( ImGui.BeginTabItem( LabelFileListTab ) )
{
UpdateFilenameList();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
{
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
DrawFileAndGamePaths( i );
}
}
ImGui.ListBoxFooter();
DrawGroupRow();
ImGui.EndTabItem();
}
else
{
_fullFilenameList = null;
}
}
private bool DrawMultiSelectorEditBegin( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
&& groupName != group.GroupName && !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
if( groupName.Length > 0 )
{
Meta.Groups[ groupName ] = new InstallerInfo()
{ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private void DrawMultiSelectorEditAdd( InstallerInfo group, float nameBoxStart )
{
var newOption = "";
ImGui.SetCursorPosX( nameBoxStart );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& newOption.Length != 0 )
{
group.Options.Add( new Option()
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
_selector.SaveCurrentMod();
}
}
private void DrawMultiSelectorEdit( InstallerInfo group )
{
var nameBoxStart = CheckMarkSize;
var flag = Mod.Conf[ group.GroupName ];
var modChanged = DrawMultiSelectorEditBegin( group );
for( var i = 0; i < group.Options.Count; ++i )
{
var opt = group.Options[ i ];
var label = $"##{opt.OptionName}_{group.GroupName}";
DrawMultiSelectorCheckBox( group, i, flag, label );
ImGui.SameLine();
var newName = opt.OptionName;
if( nameBoxStart == CheckMarkSize )
{
nameBoxStart = ImGui.GetCursorPosX();
}
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
if( newName.Length == 0 )
{
group.Options.RemoveAt( i );
var bitmaskFront = ( 1 << i ) - 1;
Mod.Conf[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & ~bitmaskFront ) >> 1 );
modChanged = true;
}
else if( newName != opt.OptionName )
{
group.Options[ i ] = new Option()
{ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod();
}
}
}
DrawMultiSelectorEditAdd( group, nameBoxStart );
if( modChanged )
{
_selector.SaveCurrentMod();
Save();
}
ImGuiCustom.EndFramedGroup();
}
private bool DrawSingleSelectorEditGroup( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
if( groupName != group.GroupName )
{
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
}
if( groupName.Length > 0 )
{
Meta.Groups.Add( groupName,
new InstallerInfo() { GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private float DrawSingleSelectorEdit( InstallerInfo group )
{
var code = Mod.Conf[ group.GroupName ];
var selectionChanged = false;
var modChanged = false;
if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{
if( code == group.Options.Count )
{
if( newName.Length > 0 )
{
selectionChanged = true;
modChanged = true;
Mod.Conf[ group.GroupName ] = code;
group.Options.Add( new Option()
{ OptionName = newName, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
}
}
else
{
if( newName.Length == 0 )
{
modChanged = true;
group.Options.RemoveAt( code );
if( code >= group.Options.Count )
{
code = 0;
}
}
else if( newName != group.Options[ code ].OptionName )
{
modChanged = true;
group.Options[ code ] = new Option()
{
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
OptionFiles = group.Options[ code ].OptionFiles
};
}
if( Mod.Conf[ group.GroupName ] != code )
{
selectionChanged = true;
Mod.Conf[ group.GroupName ] = code;
}
}
}
ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX();
modChanged |= DrawSingleSelectorEditGroup( group );
if( modChanged )
{
_selector.SaveCurrentMod();
}
if( selectionChanged )
{
Save();
}
return labelEditPos;
}
private void AddNewGroup( string newGroup, SelectType selectType )
{
if( Meta.Groups.ContainsKey( newGroup ) || newGroup.Length <= 0 )
{
return;
}
Meta.Groups[ newGroup ] = new InstallerInfo()
{
GroupName = newGroup,
SelectionType = selectType,
Options = new List< Option >()
};
Mod.Conf[ newGroup ] = 0;
_selector.SaveCurrentMod();
Save();
}
private void DrawAddSingleGroupField( float labelEditPos )
{
var newGroup = "";
if( labelEditPos == CheckMarkSize )
{
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
else
{
ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
if( ImGui.InputText( LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
}
private void DrawAddMultiGroupField()
{
var newGroup = "";
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Multi );
}
}
private void DrawGroupSelectorsEdit()
{
var labelEditPos = CheckMarkSize;
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
{
labelEditPos = DrawSingleSelectorEdit( g );
}
DrawAddSingleGroupField( labelEditPos );
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelectorEdit( g );
}
DrawAddMultiGroupField();
}
}
}
}

View file

@ -1,8 +1,8 @@
using ImGuiNET;
using Dalamud.Plugin;
using System;
using System.Numerics;
using System.Diagnostics;
using System.Numerics;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI
@ -16,46 +16,48 @@ namespace Penumbra.UI
private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite";
private const string ButtonOpenWebsite = "Open Website";
private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing";
private const string ButtonOpenWebsite = "Open Website";
private const string ButtonOpenModFolder = "Open Mod Folder";
private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string ButtonEditJson = "Edit JSON";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string ButtonReloadJson = "Reload JSON";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string ButtonDeduplicate = "Deduplicate";
private const string TooltipDeduplicate = "Try to find identical files and remove duplicate occurences to reduce the mods disk size. Introduces an invisible single-option Group \"Duplicates\".";
private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string TooltipDeduplicate =
"Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" +
"Introduces an invisible single-option Group \"Duplicates\".";
private const float HeaderLineDistance = 10f;
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base;
private readonly Selector _selector;
public readonly PluginDetails _details;
public readonly PluginDetails Details;
private bool _editMode = false;
private bool _editMode;
private string _currentWebsite;
private bool _validWebsite;
public ModPanel(SettingsInterface ui, Selector s)
public ModPanel( SettingsInterface ui, Selector s )
{
_base = ui;
_selector = s;
_details = new(_base, _selector);
Details = new PluginDetails( _base, _selector );
_currentWebsite = Meta?.Website;
}
private ModInfo Mod { get{ return _selector.Mod(); } }
private ModMeta Meta { get{ return Mod?.Mod.Meta; } }
private ModInfo Mod => _selector.Mod();
private ModMeta Meta => Mod?.Mod.Meta;
#region Header Line Functions
private void DrawName()
{
var name = Meta.Name;
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64)
&& name.Length > 0 && name != Meta.Name)
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
&& name.Length > 0 && name != Meta.Name )
{
Meta.Name = name;
_selector.SaveCurrentMod();
@ -64,27 +66,27 @@ namespace Penumbra.UI
private void DrawVersion()
{
if (_editMode)
if( _editMode )
{
ImGui.BeginGroup();
ImGui.Text("(Version ");
ImGui.Text( "(Version " );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.SameLine();
var version = Meta.Version ?? "";
if (ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16)
&& version != Meta.Version)
if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
&& version != Meta.Version )
{
Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod();
}
ImGui.SameLine();
ImGui.Text(")");
ImGui.Text( ")" );
ImGui.PopStyleVar();
ImGui.EndGroup();
}
else if ((Meta.Version?.Length ?? 0) > 0)
else if( ( Meta.Version?.Length ?? 0 ) > 0 )
{
ImGui.Text( $"(Version {Meta.Version})" );
}
@ -97,59 +99,63 @@ namespace Penumbra.UI
ImGui.SameLine();
var author = Meta.Author ?? "";
if (ImGuiCustom.InputOrText(_editMode, LabelEditAuthor, ref author, 64)
&& author != Meta.Author)
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
&& author != Meta.Author )
{
Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod();
}
ImGui.EndGroup();
}
private void DrawWebsite()
{
ImGui.BeginGroup();
if (_editMode)
if( _editMode )
{
ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine();
var website = Meta.Website ?? "";
if (ImGuiCustom.ResizingTextInput(LabelEditWebsite, ref website, 512)
&& website != Meta.Website)
if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
&& website != Meta.Website )
{
Meta.Website = website.Length > 0 ? website : null;
_selector.SaveCurrentMod();
}
}
else if (( Meta.Website?.Length ?? 0 ) > 0)
else if( ( Meta.Website?.Length ?? 0 ) > 0 )
{
if (_currentWebsite != Meta.Website)
if( _currentWebsite != Meta.Website )
{
_currentWebsite = Meta.Website;
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
}
if( _validWebsite )
{
if( ImGui.SmallButton( ButtonOpenWebsite ) )
{
try
{
var process = new ProcessStartInfo(Meta.Website)
var process = new ProcessStartInfo( Meta.Website )
{
UseShellExecute = true
};
Process.Start(process);
Process.Start( process );
}
catch(System.ComponentModel.Win32Exception)
catch( System.ComponentModel.Win32Exception )
{
// Do nothing.
}
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website );
}
}
else
{
ImGui.TextColored( GreyColor, "from" );
@ -157,6 +163,7 @@ namespace Penumbra.UI
ImGui.Text( Meta.Website );
}
}
ImGui.EndGroup();
}
@ -170,9 +177,7 @@ namespace Penumbra.UI
ImGui.SameLine();
DrawWebsite();
}
#endregion
#region Enabled Checkmarks
private void DrawEnabledMark()
{
var enabled = Mod.Enabled;
@ -181,26 +186,27 @@ namespace Penumbra.UI
Mod.Enabled = enabled;
_base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
}
private void DrawEditableMark()
{
ImGui.Checkbox( LabelEditingEnabled, ref _editMode);
ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
}
#endregion
#region Edit Line Functions
private void DrawOpenModFolderButton()
{
if( ImGui.Button( ButtonOpenModFolder ) )
{
Process.Start( Mod.Mod.ModBasePath.FullName );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipOpenModFolder );
}
}
private void DrawEditJsonButton()
{
@ -208,9 +214,12 @@ namespace Penumbra.UI
{
Process.Start( _selector.SaveCurrentMod() );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipEditJson );
}
}
private void DrawReloadJsonButton()
{
@ -218,23 +227,29 @@ namespace Penumbra.UI
{
_selector.ReloadCurrentMod();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipReloadJson );
}
}
private void DrawDeduplicateButton()
{
if( ImGui.Button( ButtonDeduplicate ) )
{
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run();
new Deduplicator( Mod.Mod.ModBasePath, Meta ).Run();
_selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDeduplicate );
}
}
private void DrawEditLine()
{
@ -246,35 +261,41 @@ namespace Penumbra.UI
ImGui.SameLine();
DrawDeduplicateButton();
}
#endregion
public void Draw()
{
if( Mod != null )
if( Mod == null )
{
return;
}
try
{
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if (!ret)
if( !ret )
{
return;
}
DrawHeaderLine();
// Next line with fixed distance.
ImGuiCustom.VerticalDistance(HeaderLineDistance);
ImGuiCustom.VerticalDistance( HeaderLineDistance );
DrawEnabledMark();
if (_base._plugin.Configuration.ShowAdvanced)
if( _base._plugin.Configuration.ShowAdvanced )
{
ImGui.SameLine();
DrawEditableMark();
}
// Next line, if editable.
if (_editMode)
if( _editMode )
{
DrawEditLine();
}
_details.Draw(_editMode);
Details.Draw( _editMode );
ImGui.EndChild();
}
@ -285,5 +306,4 @@ namespace Penumbra.UI
}
}
}
}
}

View file

@ -1,11 +1,11 @@
using System.Numerics;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using ImGuiNET;
using Penumbra.Mods;
using Penumbra.Models;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
using Newtonsoft.Json;
using Penumbra.Models;
using Penumbra.Mods;
namespace Penumbra.UI
{
@ -14,6 +14,8 @@ namespace Penumbra.UI
private class Selector
{
private const string LabelSelectorList = "##availableModList";
private const string LabelModFilter = "##ModFilter";
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
private const string TooltipMoveDown = "Move the selected mod down in priority";
private const string TooltipMoveUp = "Move the selected mod up in priority";
private const string TooltipDelete = "Delete the selected mod";
@ -25,31 +27,41 @@ namespace Penumbra.UI
private const uint DisabledModColor = 0xFF666666;
private const uint ConflictingModColor = 0xFFAAAAFF;
private static readonly Vector2 SelectorButtonSizes = new(60, 0);
private static readonly Vector2 SelectorButtonSizes = new( 60, 0 );
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
private readonly SettingsInterface _base;
private ModCollection Mods{ get{ return _base._plugin.ModManager.Mods; } }
private ModCollection Mods => _base._plugin.ModManager.Mods;
private ModInfo _mod = null;
private int _index = 0;
private int? _deleteIndex = null;
private ModInfo _mod;
private int _index;
private int? _deleteIndex;
private string _modFilter = "";
private string[] _modNamesLower;
public Selector(SettingsInterface ui)
public Selector( SettingsInterface ui )
{
_base = ui;
ResetModNamesLower();
}
private void DrawPriorityChangeButton(string iconString, bool up, int unavailableWhen)
public void ResetModNamesLower()
{
_modNamesLower = Mods.ModSettings.Select( I => I.Mod.Meta.Name.ToLowerInvariant() ).ToArray();
}
private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen )
{
ImGui.PushFont( UiBuilder.IconFont );
if( _index != unavailableWhen )
{
if( ImGui.Button( iconString, SelectorButtonSizes ) )
{
SetSelection(_index);
SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1;
}
}
@ -82,10 +94,12 @@ namespace Penumbra.UI
ImGui.PopFont();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDelete );
}
}
private void DrawModAddButton()
private static void DrawModAddButton()
{
ImGui.PushFont( UiBuilder.IconFont );
@ -97,8 +111,25 @@ namespace Penumbra.UI
ImGui.PopFont();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAdd );
}
}
private void DrawModsSelectorFilter()
{
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
var tmp = _modFilter;
if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
{
_modFilter = tmp.ToLowerInvariant();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipModFilter );
}
}
private void DrawModsSelectorButtons()
{
@ -106,9 +137,9 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
DrawPriorityChangeButton(ArrowUpString, false, 0);
DrawPriorityChangeButton( ArrowUpString, false, 0 );
ImGui.SameLine();
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0);
DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0 );
ImGui.SameLine();
DrawModTrashButton();
ImGui.SameLine();
@ -117,19 +148,26 @@ namespace Penumbra.UI
ImGui.PopStyleVar( 3 );
}
void DrawDeleteModal()
private void DrawDeleteModal()
{
if( _deleteIndex != null )
if( _deleteIndex == null )
{
return;
}
ImGui.OpenPopup( DialogDeleteMod );
var ret = ImGui.BeginPopupModal( DialogDeleteMod );
if( !ret )
{
return;
}
if( _mod?.Mod == null )
{
ImGui.CloseCurrentPopup();
ImGui.EndPopup();
return;
}
ImGui.Text( "Are you sure you want to delete the following mod:" );
@ -158,19 +196,28 @@ namespace Penumbra.UI
public void Draw()
{
if (Mods == null)
if( Mods == null )
{
return;
}
// Selector pane
ImGui.BeginGroup();
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
DrawModsSelectorFilter();
// Inlay selector list
ImGui.BeginChild( LabelSelectorList, new Vector2(SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
{
var settings = Mods.ModSettings[ modIndex ];
var modName = settings.Mod.Meta.Name;
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue;
}
var changedColour = false;
if( !settings.Enabled )
@ -186,18 +233,22 @@ namespace Penumbra.UI
#if DEBUG
var selected = ImGui.Selectable(
$"id={modIndex} {settings.Mod.Meta.Name}",
$"id={modIndex} {modName}",
modIndex == _index
);
#else
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _index );
var selected = ImGui.Selectable( modName, modIndex == _index );
#endif
if( changedColour )
{
ImGui.PopStyleColor();
}
if( selected )
SetSelection(modIndex, settings);
{
SetSelection( modIndex, settings );
}
}
ImGui.EndChild();
@ -210,26 +261,36 @@ namespace Penumbra.UI
public ModInfo Mod() => _mod;
private void SetSelection(int idx, ModInfo info)
private void SetSelection( int idx, ModInfo info )
{
_mod = info;
if (idx != _index)
_base._menu._installedTab._modPanel._details.ResetState();
if( idx != _index )
{
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_index = idx;
_deleteIndex = null;
}
public void SetSelection(int idx)
private void SetSelection( int idx )
{
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
{
if (idx >= (Mods?.ModSettings?.Count ?? 0))
idx = -1;
if (idx < 0)
SetSelection(0, null);
else
SetSelection(idx, Mods.ModSettings[idx]);
}
public void ClearSelection() => SetSelection(-1);
if( idx < 0 )
{
SetSelection( 0, null );
}
else
{
SetSelection( idx, Mods.ModSettings[ idx ] );
}
}
public void ClearSelection() => SetSelection( -1 );
public void SelectModByName( string name )
{
@ -238,38 +299,42 @@ namespace Penumbra.UI
var mod = Mods.ModSettings[ modIndex ];
if( mod.Mod.Meta.Name != name )
{
continue;
}
SetSelection(modIndex, mod);
SetSelection( modIndex, mod );
return;
}
}
private string GetCurrentModMetaFile()
{
if( _mod == null )
return "";
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
}
=> _mod == null ? "" : Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
public void ReloadCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0 && File.Exists(metaPath))
if( metaPath.Length > 0 && File.Exists( metaPath ) )
{
_mod.Mod.Meta = ModMeta.LoadFromFile(metaPath) ?? _mod.Mod.Meta;
_base._menu._installedTab._modPanel._details.ResetState();
_mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta;
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower();
}
public string SaveCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0)
if( metaPath.Length > 0 )
{
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
_base._menu._installedTab._modPanel._details.ResetState();
}
_base._menu.InstalledTab.ModPanel.Details.ResetState();
return metaPath;
}
}

View file

@ -23,7 +23,7 @@ namespace Penumbra.UI
private readonly Configuration _config;
private bool _configChanged;
public TabSettings(SettingsInterface ui)
public TabSettings( SettingsInterface ui )
{
_base = ui;
_config = _base._plugin.Configuration;
@ -45,7 +45,7 @@ namespace Penumbra.UI
if( ImGui.Button( LabelRediscoverButton ) )
{
_base.ReloadMods();
_base._menu._installedTab._selector.ClearSelection();
_base._menu.InstalledTab.Selector.ClearSelection();
}
}
@ -64,7 +64,7 @@ namespace Penumbra.UI
{
_config.IsEnabled = enabled;
_configChanged = true;
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors);
Game.RefreshActors.RedrawAll( _base._plugin.PluginInterface.ClientState.Actors );
}
}
@ -86,15 +86,17 @@ namespace Penumbra.UI
{
_config.ShowAdvanced = showAdvanced;
_configChanged = true;
_base._menu._effectiveTab.RebuildFileList(showAdvanced);
_base._menu.EffectiveTab.RebuildFileList( showAdvanced );
}
}
private void DrawLogLoadedFilesBox()
{
if( _base._plugin.ResourceLoader != null )
{
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
}
}
private void DrawDisableNotificationsBox()
{
@ -112,9 +114,13 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
{
if( http )
{
_base._plugin.CreateWebServer();
}
else
{
_base._plugin.ShutdownWebServer();
}
_config.EnableHttpApi = http;
_configChanged = true;
@ -141,7 +147,9 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
DrawRootFolder();
@ -149,17 +157,19 @@ namespace Penumbra.UI
ImGui.SameLine();
DrawOpenModsButton();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawInvertModOrderBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawShowAdvancedBox();
if( _config.ShowAdvanced )
{
DrawAdvancedSettings();
}
if( _configChanged )
{

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
namespace Penumbra
{
public static class ArrayExtensions
{
public static void Swap< T >( this T[] array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static void Swap< T >( this List< T > array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static int IndexOf< T >( this T[] array, Predicate< T > match )
{
for( var i = 0; i < array.Length; ++i )
{
if( match( array[ i ] ) )
{
return i;
}
}
return -1;
}
public static void Swap< T >( this T[] array, T lhs, T rhs )
{
var idx1 = Array.IndexOf( array, lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = Array.IndexOf( array, rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
public static void Swap< T >( this List< T > array, T lhs, T rhs )
{
var idx1 = array.IndexOf( lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = array.IndexOf( rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
}
}

View file

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Penumbra.Util
@ -15,7 +15,9 @@ namespace Penumbra.Util
{
var k = ( uint )i;
for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k;
} ).ToArray();
@ -40,8 +42,10 @@ namespace Penumbra.Util
public void Update( byte[] data )
{
foreach( var b in data )
{
Update( b );
}
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b )

View file

@ -3,21 +3,16 @@ using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class SingleOrArrayConverter<T> : JsonConverter
public class SingleOrArrayConverter< T > : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return (objectType == typeof(HashSet<T>));
}
public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<HashSet<T>>();
}
return new HashSet<T>{ token.ToObject<T>() };
var token = JToken.Load( reader );
return token.Type == JTokenType.Array
? token.ToObject< HashSet< T > >()
: new HashSet< T > { token.ToObject< T >() };
}
public override bool CanWrite => false;
@ -28,22 +23,20 @@ public class SingleOrArrayConverter<T> : JsonConverter
}
}
public class DictSingleOrArrayConverter<T,U> : JsonConverter
public class DictSingleOrArrayConverter< T, U > : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return (objectType == typeof(Dictionary<T, HashSet<U>>));
}
public override bool CanConvert( Type objectType ) => objectType == typeof( Dictionary< T, HashSet< U > > );
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load(reader);
var token = JToken.Load( reader );
if (token.Type == JTokenType.Array)
if( token.Type == JTokenType.Array )
{
return token.ToObject<HashSet<T>>();
return token.ToObject< HashSet< T > >();
}
return new HashSet<T>{ token.ToObject<T>() };
return new HashSet< T > { token.ToObject< T >() };
}
public override bool CanWrite => false;

View file

@ -0,0 +1,15 @@
using System.IO;
namespace Penumbra
{
public static class StringPathExtensions
{
private static readonly char[] _invalid = Path.GetInvalidFileNameChars();
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
=> string.Join( replacement, s.Split( _invalid ) );
public static string RemoveInvalidPathSymbols( this string s )
=> string.Concat( s.Split( _invalid ) );
}
}