From e18fcafc51918d2f17fbe040df01240c70e44c6a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Jan 2022 12:36:19 +0100 Subject: [PATCH] Add option to disable disabling sound streaming. --- Penumbra/Configuration.cs | 1 + Penumbra/Mods/ModCollectionCache.cs | 660 ++++++++++++++-------------- Penumbra/Penumbra.cs | 428 +++++++++--------- Penumbra/UI/MenuTabs/TabSettings.cs | 28 ++ 4 files changed, 584 insertions(+), 533 deletions(-) diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 274a14fc..559ff2df 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -20,6 +20,7 @@ namespace Penumbra public bool DisableFileSystemNotifications { get; set; } + public bool DisableSoundStreaming { get; set; } = true; public bool EnableHttpApi { get; set; } public bool EnablePlayerWatch { get; set; } = false; public int WaitFrames { get; set; } = 30; diff --git a/Penumbra/Mods/ModCollectionCache.cs b/Penumbra/Mods/ModCollectionCache.cs index 1b3e6c1c..4322b73d 100644 --- a/Penumbra/Mods/ModCollectionCache.cs +++ b/Penumbra/Mods/ModCollectionCache.cs @@ -12,364 +12,384 @@ using Penumbra.Mod; using Penumbra.Structs; using Penumbra.Util; -namespace Penumbra.Mods +namespace Penumbra.Mods; + +// The ModCollectionCache contains all required temporary data to use a collection. +// It will only be setup if a collection gets activated in any way. +public class ModCollectionCache { - // The ModCollectionCache contains all required temporary data to use a collection. - // It will only be setup if a collection gets activated in any way. - public class ModCollectionCache + // Shared caches to avoid allocations. + private static readonly BitArray FileSeen = new(256); + private static readonly Dictionary< GamePath, Mod.Mod > RegisteredFiles = new(256); + + public readonly Dictionary< string, Mod.Mod > AvailableMods = new(); + + private readonly SortedList< string, object? > _changedItems = new(); + public readonly Dictionary< GamePath, FullPath > ResolvedFiles = new(); + public readonly Dictionary< GamePath, GamePath > SwappedFiles = new(); + public readonly HashSet< FullPath > MissingFiles = new(); + public readonly HashSet< ulong > Checksums = new(); + public readonly MetaManager MetaManipulations; + + public IReadOnlyDictionary< string, object? > ChangedItems { - // Shared caches to avoid allocations. - private static readonly BitArray FileSeen = new( 256 ); - private static readonly Dictionary< GamePath, Mod.Mod > RegisteredFiles = new( 256 ); - - public readonly Dictionary< string, Mod.Mod > AvailableMods = new(); - - private readonly SortedList< string, object? > _changedItems = new(); - public readonly Dictionary< GamePath, FullPath > ResolvedFiles = new(); - public readonly Dictionary< GamePath, GamePath > SwappedFiles = new(); - public readonly HashSet< FullPath > MissingFiles = new(); - public readonly HashSet< ulong > Checksums = new(); - public readonly MetaManager MetaManipulations; - - public IReadOnlyDictionary< string, object? > ChangedItems + get { - get + SetChangedItems(); + return _changedItems; + } + } + + public ModCollectionCache( string collectionName, DirectoryInfo tempDir ) + => MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir ); + + private static void ResetFileSeen( int size ) + { + if( size < FileSeen.Length ) + { + FileSeen.Length = size; + FileSeen.SetAll( false ); + } + else + { + FileSeen.SetAll( false ); + FileSeen.Length = size; + } + } + + public void CalculateEffectiveFileList() + { + ResolvedFiles.Clear(); + SwappedFiles.Clear(); + MissingFiles.Clear(); + RegisteredFiles.Clear(); + _changedItems.Clear(); + + foreach( var mod in AvailableMods.Values + .Where( m => m.Settings.Enabled ) + .OrderByDescending( m => m.Settings.Priority ) ) + { + mod.Cache.ClearFileConflicts(); + AddFiles( mod ); + AddSwaps( mod ); + } + + AddMetaFiles(); + Checksums.Clear(); + foreach( var file in ResolvedFiles ) + { + Checksums.Add( file.Value.Crc64 ); + } + } + + private void SetChangedItems() + { + if( _changedItems.Count > 0 || ResolvedFiles.Count + SwappedFiles.Count + MetaManipulations.Count == 0 ) + { + return; + } + + try + { + // Skip meta files because IMCs would result in far too many false-positive items, + // since they are per set instead of per item-slot/item/variant. + var metaFiles = MetaManipulations.Files.Select( p => p.Item1 ).ToHashSet(); + var identifier = GameData.GameData.GetIdentifier(); + foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) ) { - SetChangedItems(); - return _changedItems; + identifier.Identify( _changedItems, resolved ); + } + + foreach( var swapped in SwappedFiles.Keys ) + { + identifier.Identify( _changedItems, swapped ); + } + } + catch( Exception e ) + { + PluginLog.Error( $"Unknown Error:\n{e}" ); + } + } + + + private void AddFiles( Mod.Mod mod ) + { + ResetFileSeen( mod.Data.Resources.ModFiles.Count ); + // Iterate in reverse so that later groups take precedence before earlier ones. + foreach( var group in mod.Data.Meta.Groups.Values.Reverse() ) + { + switch( group.SelectionType ) + { + case SelectType.Single: + AddFilesForSingle( group, mod ); + break; + case SelectType.Multi: + AddFilesForMulti( group, mod ); + break; + default: throw new InvalidEnumArgumentException(); } } - public ModCollectionCache( string collectionName, DirectoryInfo tempDir ) - => MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir ); + AddRemainingFiles( mod ); + } - private static void ResetFileSeen( int size ) + private bool FilterFile( GamePath gamePath ) + { + // If audio streaming is not disabled, replacing .scd files crashes the game, + // so only add those files if it is disabled. + if( !Penumbra.Config.DisableSoundStreaming + && gamePath.ToString().EndsWith( ".scd", StringComparison.InvariantCultureIgnoreCase ) ) { - if( size < FileSeen.Length ) - { - FileSeen.Length = size; - FileSeen.SetAll( false ); - } - else - { - FileSeen.SetAll( false ); - FileSeen.Length = size; - } + return true; } - public void CalculateEffectiveFileList() + return false; + } + + + private void AddFile( Mod.Mod mod, GamePath gamePath, FullPath file ) + { + if( FilterFile( gamePath ) ) { - ResolvedFiles.Clear(); - SwappedFiles.Clear(); - MissingFiles.Clear(); - RegisteredFiles.Clear(); - _changedItems.Clear(); - - foreach( var mod in AvailableMods.Values - .Where( m => m.Settings.Enabled ) - .OrderByDescending( m => m.Settings.Priority ) ) - { - mod.Cache.ClearFileConflicts(); - AddFiles( mod ); - AddSwaps( mod ); - } - - AddMetaFiles(); - Checksums.Clear(); - foreach( var file in ResolvedFiles ) - Checksums.Add( file.Value.Crc64 ); + return; } - private void SetChangedItems() + if( !RegisteredFiles.TryGetValue( gamePath, out var oldMod ) ) { - if( _changedItems.Count > 0 || ResolvedFiles.Count + SwappedFiles.Count + MetaManipulations.Count == 0 ) + RegisteredFiles.Add( gamePath, mod ); + ResolvedFiles[ gamePath ] = file; + } + else + { + mod.Cache.AddConflict( oldMod, gamePath ); + if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) { + oldMod.Cache.AddConflict( mod, gamePath ); + } + } + } + + private void AddMissingFile( FullPath file ) + { + switch( file.Extension.ToLowerInvariant() ) + { + case ".meta": + case ".rgsp": return; + default: + MissingFiles.Add( file ); + return; + } + } + + private void AddPathsForOption( Option option, Mod.Mod mod, bool enabled ) + { + foreach( var (file, paths) in option.OptionFiles ) + { + var fullPath = new FullPath( mod.Data.BasePath, file ); + var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) ); + if( idx < 0 ) + { + AddMissingFile( fullPath ); + continue; } - try + var registeredFile = mod.Data.Resources.ModFiles[ idx ]; + if( !registeredFile.Exists ) { - // Skip meta files because IMCs would result in far too many false-positive items, - // since they are per set instead of per item-slot/item/variant. - var metaFiles = MetaManipulations.Files.Select( p => p.Item1 ).ToHashSet(); - var identifier = GameData.GameData.GetIdentifier(); - foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) ) - { - identifier.Identify( _changedItems, resolved ); - } - - foreach( var swapped in SwappedFiles.Keys ) - { - identifier.Identify( _changedItems, swapped ); - } + AddMissingFile( registeredFile ); + continue; } - catch( Exception e ) + + FileSeen.Set( idx, true ); + if( enabled ) { - PluginLog.Error( $"Unknown Error:\n{e}" ); + foreach( var path in paths ) + { + AddFile( mod, path, registeredFile ); + } } } + } + private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod ) + { + Debug.Assert( singleGroup.SelectionType == SelectType.Single ); - private void AddFiles( Mod.Mod mod ) + if( !mod.Settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) ) { - ResetFileSeen( mod.Data.Resources.ModFiles.Count ); - // Iterate in reverse so that later groups take precedence before earlier ones. - foreach( var group in mod.Data.Meta.Groups.Values.Reverse() ) - { - switch( group.SelectionType ) - { - case SelectType.Single: - AddFilesForSingle( group, mod ); - break; - case SelectType.Multi: - AddFilesForMulti( group, mod ); - break; - default: throw new InvalidEnumArgumentException(); - } - } - - AddRemainingFiles( mod ); + setting = 0; } - private void AddFile( Mod.Mod mod, GamePath gamePath, FullPath file ) + for( var i = 0; i < singleGroup.Options.Count; ++i ) { - if( !RegisteredFiles.TryGetValue( gamePath, out var oldMod ) ) + AddPathsForOption( singleGroup.Options[ i ], mod, setting == i ); + } + } + + private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod ) + { + Debug.Assert( multiGroup.SelectionType == SelectType.Multi ); + + if( !mod.Settings.Settings.TryGetValue( multiGroup.GroupName, out var setting ) ) + { + return; + } + + // Also iterate options in reverse so that later options take precedence before earlier ones. + for( var i = multiGroup.Options.Count - 1; i >= 0; --i ) + { + AddPathsForOption( multiGroup.Options[ i ], mod, ( setting & ( 1 << i ) ) != 0 ); + } + } + + private void AddRemainingFiles( Mod.Mod mod ) + { + for( var i = 0; i < mod.Data.Resources.ModFiles.Count; ++i ) + { + if( FileSeen.Get( i ) ) { - RegisteredFiles.Add( gamePath, mod ); - ResolvedFiles[ gamePath ] = file; + continue; + } + + var file = mod.Data.Resources.ModFiles[ i ]; + if( file.Exists ) + { + AddFile( mod, file.ToGamePath( mod.Data.BasePath ), file ); } else { - mod.Cache.AddConflict( oldMod, gamePath ); + MissingFiles.Add( file ); + } + } + } + + private void AddMetaFiles() + { + foreach( var (gamePath, file) in MetaManipulations.Files ) + { + if( RegisteredFiles.TryGetValue( gamePath, out var mod ) ) + { + PluginLog.Warning( + $"The meta manipulation file {gamePath} was already completely replaced by {mod.Data.Meta.Name}. This is probably a mistake. Using the custom file {file.FullName}." ); + } + + ResolvedFiles[ gamePath ] = file; + } + } + + private void AddSwaps( Mod.Mod mod ) + { + foreach( var (key, value) in mod.Data.Meta.FileSwaps.Where( kvp => !FilterFile( kvp.Key ) ) ) + { + if( !RegisteredFiles.TryGetValue( key, out var oldMod ) ) + { + RegisteredFiles.Add( key, mod ); + SwappedFiles.Add( key, value ); + } + else + { + mod.Cache.AddConflict( oldMod, key ); if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) { - oldMod.Cache.AddConflict( mod, gamePath ); + oldMod.Cache.AddConflict( mod, key ); } } } - - private void AddMissingFile( FullPath file ) - { - switch( file.Extension.ToLowerInvariant() ) - { - case ".meta": - case ".rgsp": - return; - default: - MissingFiles.Add( file ); - return; - } - } - - private void AddPathsForOption( Option option, Mod.Mod mod, bool enabled ) - { - foreach( var (file, paths) in option.OptionFiles ) - { - var fullPath = new FullPath(mod.Data.BasePath, file); - var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals(fullPath) ); - if( idx < 0 ) - { - AddMissingFile( fullPath ); - continue; - } - - var registeredFile = mod.Data.Resources.ModFiles[ idx ]; - if( !registeredFile.Exists ) - { - AddMissingFile( registeredFile ); - continue; - } - - FileSeen.Set( idx, true ); - if( enabled ) - { - foreach( var path in paths ) - { - AddFile( mod, path, registeredFile ); - } - } - } - } - - private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod ) - { - Debug.Assert( singleGroup.SelectionType == SelectType.Single ); - - if( !mod.Settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) ) - { - setting = 0; - } - - for( var i = 0; i < singleGroup.Options.Count; ++i ) - { - AddPathsForOption( singleGroup.Options[ i ], mod, setting == i ); - } - } - - private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod ) - { - Debug.Assert( multiGroup.SelectionType == SelectType.Multi ); - - if( !mod.Settings.Settings.TryGetValue( multiGroup.GroupName, out var setting ) ) - { - return; - } - - // Also iterate options in reverse so that later options take precedence before earlier ones. - for( var i = multiGroup.Options.Count - 1; i >= 0; --i ) - { - AddPathsForOption( multiGroup.Options[ i ], mod, ( setting & ( 1 << i ) ) != 0 ); - } - } - - private void AddRemainingFiles( Mod.Mod mod ) - { - for( var i = 0; i < mod.Data.Resources.ModFiles.Count; ++i ) - { - if( FileSeen.Get( i ) ) - { - continue; - } - - var file = mod.Data.Resources.ModFiles[ i ]; - if( file.Exists ) - { - AddFile( mod, file.ToGamePath( mod.Data.BasePath ), file ); - } - else - { - MissingFiles.Add( file ); - } - } - } - - private void AddMetaFiles() - { - foreach( var (gamePath, file) in MetaManipulations.Files ) - { - if( RegisteredFiles.TryGetValue( gamePath, out var mod ) ) - { - PluginLog.Warning( - $"The meta manipulation file {gamePath} was already completely replaced by {mod.Data.Meta.Name}. This is probably a mistake. Using the custom file {file.FullName}." ); - } - - ResolvedFiles[ gamePath ] = file; - } - } - - private void AddSwaps( Mod.Mod mod ) - { - foreach( var swap in mod.Data.Meta.FileSwaps ) - { - if( !RegisteredFiles.TryGetValue( swap.Key, out var oldMod ) ) - { - RegisteredFiles.Add( swap.Key, mod ); - SwappedFiles.Add( swap.Key, swap.Value ); - } - else - { - mod.Cache.AddConflict( oldMod, swap.Key ); - if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) - { - oldMod.Cache.AddConflict( mod, swap.Key ); - } - } - } - } - - private void AddManipulations( Mod.Mod mod ) - { - foreach( var manip in mod.Data.Resources.MetaManipulations.GetManipulationsForConfig( mod.Settings, mod.Data.Meta ) ) - { - if( !MetaManipulations.TryGetValue( manip, out var oldMod ) ) - { - MetaManipulations.ApplyMod( manip, mod ); - } - else - { - mod.Cache.AddConflict( oldMod, manip ); - if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) - { - oldMod.Cache.AddConflict( mod, manip ); - } - } - } - } - - public void UpdateMetaManipulations() - { - MetaManipulations.Reset( false ); - - foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) ) - { - mod.Cache.ClearMetaConflicts(); - AddManipulations( mod ); - } - - MetaManipulations.WriteNewFiles(); - } - - public void RemoveMod( DirectoryInfo basePath ) - { - if( AvailableMods.TryGetValue( basePath.Name, out var mod ) ) - { - AvailableMods.Remove( basePath.Name ); - if( mod.Settings.Enabled ) - { - CalculateEffectiveFileList(); - if( mod.Data.Resources.MetaManipulations.Count > 0 ) - { - UpdateMetaManipulations(); - } - } - } - } - - private class PriorityComparer : IComparer< Mod.Mod > - { - public int Compare( Mod.Mod? x, Mod.Mod? y ) - => ( x?.Settings.Priority ?? 0 ).CompareTo( y?.Settings.Priority ?? 0 ); - } - - private static readonly PriorityComparer Comparer = new(); - - public void AddMod( ModSettings settings, ModData data, bool updateFileList = true ) - { - if( !AvailableMods.TryGetValue( data.BasePath.Name, out var existingMod ) ) - { - var newMod = new Mod.Mod( settings, data ); - AvailableMods[ data.BasePath.Name ] = newMod; - - if( updateFileList && settings.Enabled ) - { - CalculateEffectiveFileList(); - if( data.Resources.MetaManipulations.Count > 0 ) - { - UpdateMetaManipulations(); - } - } - } - } - - public FullPath? GetCandidateForGameFile( GamePath gameResourcePath ) - { - if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) ) - { - return null; - } - - if( candidate.FullName.Length >= 260 || !candidate.Exists ) - { - return null; - } - - return candidate; - } - - public GamePath? GetSwappedFilePath( GamePath gameResourcePath ) - => SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null; - - public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath ) - => GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null; } + + private void AddManipulations( Mod.Mod mod ) + { + foreach( var manip in mod.Data.Resources.MetaManipulations.GetManipulationsForConfig( mod.Settings, mod.Data.Meta ) ) + { + if( !MetaManipulations.TryGetValue( manip, out var oldMod ) ) + { + MetaManipulations.ApplyMod( manip, mod ); + } + else + { + mod.Cache.AddConflict( oldMod, manip ); + if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) + { + oldMod.Cache.AddConflict( mod, manip ); + } + } + } + } + + public void UpdateMetaManipulations() + { + MetaManipulations.Reset( false ); + + foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) ) + { + mod.Cache.ClearMetaConflicts(); + AddManipulations( mod ); + } + + MetaManipulations.WriteNewFiles(); + } + + public void RemoveMod( DirectoryInfo basePath ) + { + if( AvailableMods.TryGetValue( basePath.Name, out var mod ) ) + { + AvailableMods.Remove( basePath.Name ); + if( mod.Settings.Enabled ) + { + CalculateEffectiveFileList(); + if( mod.Data.Resources.MetaManipulations.Count > 0 ) + { + UpdateMetaManipulations(); + } + } + } + } + + private class PriorityComparer : IComparer< Mod.Mod > + { + public int Compare( Mod.Mod? x, Mod.Mod? y ) + => ( x?.Settings.Priority ?? 0 ).CompareTo( y?.Settings.Priority ?? 0 ); + } + + private static readonly PriorityComparer Comparer = new(); + + public void AddMod( ModSettings settings, ModData data, bool updateFileList = true ) + { + if( !AvailableMods.TryGetValue( data.BasePath.Name, out var existingMod ) ) + { + var newMod = new Mod.Mod( settings, data ); + AvailableMods[ data.BasePath.Name ] = newMod; + + if( updateFileList && settings.Enabled ) + { + CalculateEffectiveFileList(); + if( data.Resources.MetaManipulations.Count > 0 ) + { + UpdateMetaManipulations(); + } + } + } + } + + public FullPath? GetCandidateForGameFile( GamePath gameResourcePath ) + { + if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) ) + { + return null; + } + + if( candidate.FullName.Length >= 260 || !candidate.Exists ) + { + return null; + } + + return candidate; + } + + public GamePath? GetSwappedFilePath( GamePath gameResourcePath ) + => SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null; + + public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath ) + => GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null; } \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index baa5f368..4f9e7715 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -14,244 +14,246 @@ using Penumbra.PlayerWatch; using Penumbra.UI; using Penumbra.Util; -namespace Penumbra +namespace Penumbra; + +public class Penumbra : IDalamudPlugin { - public class Penumbra : IDalamudPlugin + public string Name { get; } = "Penumbra"; + public string PluginDebugTitleStr { get; } = "Penumbra - Debug Build"; + + private const string CommandName = "/penumbra"; + + public static Configuration Config { get; private set; } = null!; + public static IPlayerWatcher PlayerWatcher { get; private set; } = null!; + + public ResourceLoader ResourceLoader { get; } + public SettingsInterface SettingsInterface { get; } + public MusicManager MusicManager { get; } + public ObjectReloader ObjectReloader { get; } + + public PenumbraApi Api { get; } + public PenumbraIpc Ipc { get; } + + private WebServer? _webServer; + + public Penumbra( DalamudPluginInterface pluginInterface ) { - public string Name { get; } = "Penumbra"; - public string PluginDebugTitleStr { get; } = "Penumbra - Debug Build"; + FFXIVClientStructs.Resolver.Initialize(); + Dalamud.Initialize( pluginInterface ); + GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage ); + Config = Configuration.Load(); - private const string CommandName = "/penumbra"; - - public static Configuration Config { get; private set; } = null!; - public static IPlayerWatcher PlayerWatcher { get; private set; } = null!; - - public ResourceLoader ResourceLoader { get; } - public SettingsInterface SettingsInterface { get; } - public MusicManager MusicManager { get; } - public ObjectReloader ObjectReloader { get; } - - public PenumbraApi Api { get; } - public PenumbraIpc Ipc { get; } - - private WebServer? _webServer; - - public Penumbra( DalamudPluginInterface pluginInterface ) + MusicManager = new MusicManager(); + if( Config.DisableSoundStreaming ) { - FFXIVClientStructs.Resolver.Initialize(); - Dalamud.Initialize( pluginInterface ); - GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage ); - Config = Configuration.Load(); - - MusicManager = new MusicManager(); MusicManager.DisableStreaming(); - - var gameUtils = Service< ResidentResources >.Set(); - PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects ); - Service< MetaDefaults >.Set(); - var modManager = Service< ModManager >.Set(); - - modManager.DiscoverMods(); - - ObjectReloader = new ObjectReloader( modManager, Config.WaitFrames ); - - ResourceLoader = new ResourceLoader( this ); - - Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) - { - HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", - } ); - - ResourceLoader.Init(); - ResourceLoader.Enable(); - - gameUtils.ReloadResidentResources(); - - SettingsInterface = new SettingsInterface( this ); - - if( Config.EnableHttpApi ) - { - CreateWebServer(); - } - - if( !Config.EnablePlayerWatch || !Config.IsEnabled ) - { - PlayerWatcher.Disable(); - } - - PlayerWatcher.PlayerChanged += p => - { - PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name ); - ObjectReloader.RedrawObject( p, RedrawType.OnlyWithSettings ); - }; - - Api = new PenumbraApi( this ); - SubscribeItemLinks(); - Ipc = new PenumbraIpc( pluginInterface, Api ); } - public bool Enable() + var gameUtils = Service< ResidentResources >.Set(); + PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects ); + Service< MetaDefaults >.Set(); + var modManager = Service< ModManager >.Set(); + + modManager.DiscoverMods(); + + ObjectReloader = new ObjectReloader( modManager, Config.WaitFrames ); + + ResourceLoader = new ResourceLoader( this ); + + Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) { - if( Config.IsEnabled ) - { - return false; - } + HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", + } ); - Config.IsEnabled = true; - Service< ResidentResources >.Get().ReloadResidentResources(); - if( Config.EnablePlayerWatch ) - { - PlayerWatcher.SetStatus( true ); - } + ResourceLoader.Init(); + ResourceLoader.Enable(); - Config.Save(); - ObjectReloader.RedrawAll( RedrawType.WithSettings ); - return true; + gameUtils.ReloadResidentResources(); + + SettingsInterface = new SettingsInterface( this ); + + if( Config.EnableHttpApi ) + { + CreateWebServer(); } - public bool Disable() + if( !Config.EnablePlayerWatch || !Config.IsEnabled ) { - if( !Config.IsEnabled ) - { - return false; - } - - Config.IsEnabled = false; - Service< ResidentResources >.Get().ReloadResidentResources(); - if( Config.EnablePlayerWatch ) - { - PlayerWatcher.SetStatus( false ); - } - - Config.Save(); - ObjectReloader.RedrawAll( RedrawType.WithoutSettings ); - return true; + PlayerWatcher.Disable(); } - public bool SetEnabled( bool enabled ) - => enabled ? Enable() : Disable(); - - private void SubscribeItemLinks() + PlayerWatcher.PlayerChanged += p => { - Api.ChangedItemTooltip += it => + PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name ); + ObjectReloader.RedrawObject( p, RedrawType.OnlyWithSettings ); + }; + + Api = new PenumbraApi( this ); + SubscribeItemLinks(); + Ipc = new PenumbraIpc( pluginInterface, Api ); + } + + public bool Enable() + { + if( Config.IsEnabled ) + { + return false; + } + + Config.IsEnabled = true; + Service< ResidentResources >.Get().ReloadResidentResources(); + if( Config.EnablePlayerWatch ) + { + PlayerWatcher.SetStatus( true ); + } + + Config.Save(); + ObjectReloader.RedrawAll( RedrawType.WithSettings ); + return true; + } + + public bool Disable() + { + if( !Config.IsEnabled ) + { + return false; + } + + Config.IsEnabled = false; + Service< ResidentResources >.Get().ReloadResidentResources(); + if( Config.EnablePlayerWatch ) + { + PlayerWatcher.SetStatus( false ); + } + + Config.Save(); + ObjectReloader.RedrawAll( RedrawType.WithoutSettings ); + return true; + } + + public bool SetEnabled( bool enabled ) + => enabled ? Enable() : Disable(); + + private void SubscribeItemLinks() + { + Api.ChangedItemTooltip += it => + { + if( it is Item ) { - if( it is Item ) + ImGui.Text( "Left Click to create an item link in chat." ); + } + }; + Api.ChangedItemClicked += ( button, it ) => + { + if( button == MouseButton.Left && it is Item item ) + { + ChatUtil.LinkItem( item ); + } + }; + } + + public void CreateWebServer() + { + const string prefix = "http://localhost:42069/"; + + ShutdownWebServer(); + + _webServer = new WebServer( o => o + .WithUrlPrefix( prefix ) + .WithMode( HttpListenerMode.EmbedIO ) ) + .WithCors( prefix ) + .WithWebApi( "/api", m => m + .WithController( () => new ModsController( this ) ) ); + + _webServer.StateChanged += ( s, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" ); + + _webServer.RunAsync(); + } + + public void ShutdownWebServer() + { + _webServer?.Dispose(); + _webServer = null; + } + + public void Dispose() + { + Ipc.Dispose(); + Api.Dispose(); + SettingsInterface.Dispose(); + ObjectReloader.Dispose(); + PlayerWatcher.Dispose(); + + Dalamud.Commands.RemoveHandler( CommandName ); + + ResourceLoader.Dispose(); + + ShutdownWebServer(); + } + + private void OnCommand( string command, string rawArgs ) + { + const string modsEnabled = "Your mods have now been enabled."; + const string modsDisabled = "Your mods have now been disabled."; + + var args = rawArgs.Split( new[] { ' ' }, 2 ); + if( args.Length > 0 && args[ 0 ].Length > 0 ) + { + switch( args[ 0 ] ) + { + case "reload": { - ImGui.Text( "Left Click to create an item link in chat." ); + Service< ModManager >.Get().DiscoverMods(); + Dalamud.Chat.Print( + $"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods.Count} mods." + ); + break; } - }; - Api.ChangedItemClicked += ( button, it ) => - { - if( button == MouseButton.Left && it is Item item ) + case "redraw": { - ChatUtil.LinkItem( item ); + if( args.Length > 1 ) + { + ObjectReloader.RedrawObject( args[ 1 ] ); + } + else + { + ObjectReloader.RedrawAll(); + } + + break; } - }; - } - - public void CreateWebServer() - { - const string prefix = "http://localhost:42069/"; - - ShutdownWebServer(); - - _webServer = new WebServer( o => o - .WithUrlPrefix( prefix ) - .WithMode( HttpListenerMode.EmbedIO ) ) - .WithCors( prefix ) - .WithWebApi( "/api", m => m - .WithController( () => new ModsController( this ) ) ); - - _webServer.StateChanged += ( s, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" ); - - _webServer.RunAsync(); - } - - public void ShutdownWebServer() - { - _webServer?.Dispose(); - _webServer = null; - } - - public void Dispose() - { - Ipc.Dispose(); - Api.Dispose(); - SettingsInterface.Dispose(); - ObjectReloader.Dispose(); - PlayerWatcher.Dispose(); - - Dalamud.Commands.RemoveHandler( CommandName ); - - ResourceLoader.Dispose(); - - ShutdownWebServer(); - } - - private void OnCommand( string command, string rawArgs ) - { - const string modsEnabled = "Your mods have now been enabled."; - const string modsDisabled = "Your mods have now been disabled."; - - var args = rawArgs.Split( new[] { ' ' }, 2 ); - if( args.Length > 0 && args[ 0 ].Length > 0 ) - { - switch( args[ 0 ] ) + case "debug": { - case "reload": - { - Service< ModManager >.Get().DiscoverMods(); - Dalamud.Chat.Print( - $"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods.Count} mods." - ); - break; - } - case "redraw": - { - if( args.Length > 1 ) - { - ObjectReloader.RedrawObject( args[ 1 ] ); - } - else - { - ObjectReloader.RedrawAll(); - } - - break; - } - case "debug": - { - SettingsInterface.MakeDebugTabVisible(); - break; - } - case "enable": - { - Dalamud.Chat.Print( Enable() - ? "Your mods are already enabled. To disable your mods, please run the following command instead: /penumbra disable" - : modsEnabled ); - break; - } - case "disable": - { - Dalamud.Chat.Print( Disable() - ? "Your mods are already disabled. To enable your mods, please run the following command instead: /penumbra enable" - : modsDisabled ); - break; - } - case "toggle": - { - SetEnabled( !Config.IsEnabled ); - Dalamud.Chat.Print( Config.IsEnabled - ? modsEnabled - : modsDisabled ); - break; - } + SettingsInterface.MakeDebugTabVisible(); + break; + } + case "enable": + { + Dalamud.Chat.Print( Enable() + ? "Your mods are already enabled. To disable your mods, please run the following command instead: /penumbra disable" + : modsEnabled ); + break; + } + case "disable": + { + Dalamud.Chat.Print( Disable() + ? "Your mods are already disabled. To enable your mods, please run the following command instead: /penumbra enable" + : modsDisabled ); + break; + } + case "toggle": + { + SetEnabled( !Config.IsEnabled ); + Dalamud.Chat.Print( Config.IsEnabled + ? modsEnabled + : modsDisabled ); + break; } - - return; } - SettingsInterface.FlipVisibility(); + return; } + + SettingsInterface.FlipVisibility(); } } \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs index 64e3f126..b82c14c6 100644 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ b/Penumbra/UI/MenuTabs/TabSettings.cs @@ -194,6 +194,33 @@ public partial class SettingsInterface "Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window." ); } + private void DrawDisableSoundStreamingBox() + { + var tmp = Penumbra.Config.DisableSoundStreaming; + if( ImGui.Checkbox( "Disable Audio Streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) + { + Penumbra.Config.DisableSoundStreaming = tmp; + _configChanged = true; + if( tmp ) + { + _base._penumbra.MusicManager.DisableStreaming(); + } + else + { + _base._penumbra.MusicManager.EnableStreaming(); + } + + _base.ReloadMods(); + } + + ImGui.SameLine(); + ImGuiComponents.HelpMarker( + "Disable streaming in the games audio engine.\n" + + "If you do not disable streaming, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n" + + "Only touch this if you experience sound problems.\n" + + "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash." ); + } + private void DrawLogLoadedFilesBox() { ImGui.Checkbox( "Log Loaded Files", ref _base._penumbra.ResourceLoader.LogAllFiles ); @@ -306,6 +333,7 @@ public partial class SettingsInterface private void DrawAdvancedSettings() { DrawTempFolder(); + DrawDisableSoundStreamingBox(); DrawLogLoadedFilesBox(); DrawDisableNotificationsBox(); DrawEnableHttpApiBox();