Add option to disable disabling sound streaming.

This commit is contained in:
Ottermandias 2022-01-28 12:36:19 +01:00
parent ac2f2cf3b9
commit e18fcafc51
4 changed files with 584 additions and 533 deletions

View file

@ -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;

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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();