mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add option to disable disabling sound streaming.
This commit is contained in:
parent
ac2f2cf3b9
commit
e18fcafc51
4 changed files with 584 additions and 533 deletions
|
|
@ -20,6 +20,7 @@ namespace Penumbra
|
||||||
|
|
||||||
public bool DisableFileSystemNotifications { get; set; }
|
public bool DisableFileSystemNotifications { get; set; }
|
||||||
|
|
||||||
|
public bool DisableSoundStreaming { get; set; } = true;
|
||||||
public bool EnableHttpApi { get; set; }
|
public bool EnableHttpApi { get; set; }
|
||||||
public bool EnablePlayerWatch { get; set; } = false;
|
public bool EnablePlayerWatch { get; set; } = false;
|
||||||
public int WaitFrames { get; set; } = 30;
|
public int WaitFrames { get; set; } = 30;
|
||||||
|
|
|
||||||
|
|
@ -12,364 +12,384 @@ using Penumbra.Mod;
|
||||||
using Penumbra.Structs;
|
using Penumbra.Structs;
|
||||||
using Penumbra.Util;
|
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.
|
// Shared caches to avoid allocations.
|
||||||
// It will only be setup if a collection gets activated in any way.
|
private static readonly BitArray FileSeen = new(256);
|
||||||
public class ModCollectionCache
|
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.
|
get
|
||||||
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
|
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();
|
identifier.Identify( _changedItems, resolved );
|
||||||
return _changedItems;
|
}
|
||||||
|
|
||||||
|
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 )
|
AddRemainingFiles( mod );
|
||||||
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir );
|
}
|
||||||
|
|
||||||
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 )
|
return true;
|
||||||
{
|
|
||||||
FileSeen.Length = size;
|
|
||||||
FileSeen.SetAll( false );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FileSeen.SetAll( false );
|
|
||||||
FileSeen.Length = size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CalculateEffectiveFileList()
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddFile( Mod.Mod mod, GamePath gamePath, FullPath file )
|
||||||
|
{
|
||||||
|
if( FilterFile( gamePath ) )
|
||||||
{
|
{
|
||||||
ResolvedFiles.Clear();
|
return;
|
||||||
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( !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;
|
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,
|
AddMissingFile( registeredFile );
|
||||||
// since they are per set instead of per item-slot/item/variant.
|
continue;
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 );
|
setting = 0;
|
||||||
// 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 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 );
|
continue;
|
||||||
ResolvedFiles[ gamePath ] = file;
|
}
|
||||||
|
|
||||||
|
var file = mod.Data.Resources.ModFiles[ i ];
|
||||||
|
if( file.Exists )
|
||||||
|
{
|
||||||
|
AddFile( mod, file.ToGamePath( mod.Data.BasePath ), file );
|
||||||
}
|
}
|
||||||
else
|
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 )
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -14,244 +14,246 @@ using Penumbra.PlayerWatch;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using Penumbra.Util;
|
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";
|
FFXIVClientStructs.Resolver.Initialize();
|
||||||
public string PluginDebugTitleStr { get; } = "Penumbra - Debug Build";
|
Dalamud.Initialize( pluginInterface );
|
||||||
|
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
||||||
|
Config = Configuration.Load();
|
||||||
|
|
||||||
private const string CommandName = "/penumbra";
|
MusicManager = new MusicManager();
|
||||||
|
if( Config.DisableSoundStreaming )
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
FFXIVClientStructs.Resolver.Initialize();
|
|
||||||
Dalamud.Initialize( pluginInterface );
|
|
||||||
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
|
||||||
Config = Configuration.Load();
|
|
||||||
|
|
||||||
MusicManager = new MusicManager();
|
|
||||||
MusicManager.DisableStreaming();
|
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 )
|
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
|
||||||
{
|
} );
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.IsEnabled = true;
|
ResourceLoader.Init();
|
||||||
Service< ResidentResources >.Get().ReloadResidentResources();
|
ResourceLoader.Enable();
|
||||||
if( Config.EnablePlayerWatch )
|
|
||||||
{
|
|
||||||
PlayerWatcher.SetStatus( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.Save();
|
gameUtils.ReloadResidentResources();
|
||||||
ObjectReloader.RedrawAll( RedrawType.WithSettings );
|
|
||||||
return true;
|
SettingsInterface = new SettingsInterface( this );
|
||||||
|
|
||||||
|
if( Config.EnableHttpApi )
|
||||||
|
{
|
||||||
|
CreateWebServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Disable()
|
if( !Config.EnablePlayerWatch || !Config.IsEnabled )
|
||||||
{
|
{
|
||||||
if( !Config.IsEnabled )
|
PlayerWatcher.Disable();
|
||||||
{
|
|
||||||
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 )
|
PlayerWatcher.PlayerChanged += p =>
|
||||||
=> enabled ? Enable() : Disable();
|
|
||||||
|
|
||||||
private void SubscribeItemLinks()
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
};
|
case "redraw":
|
||||||
Api.ChangedItemClicked += ( button, it ) =>
|
|
||||||
{
|
|
||||||
if( button == MouseButton.Left && it is Item item )
|
|
||||||
{
|
{
|
||||||
ChatUtil.LinkItem( item );
|
if( args.Length > 1 )
|
||||||
|
{
|
||||||
|
ObjectReloader.RedrawObject( args[ 1 ] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ObjectReloader.RedrawAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
case "debug":
|
||||||
}
|
|
||||||
|
|
||||||
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":
|
SettingsInterface.MakeDebugTabVisible();
|
||||||
{
|
break;
|
||||||
Service< ModManager >.Get().DiscoverMods();
|
}
|
||||||
Dalamud.Chat.Print(
|
case "enable":
|
||||||
$"Reloaded Penumbra mods. You have {Service< ModManager >.Get()?.Mods.Count} mods."
|
{
|
||||||
);
|
Dalamud.Chat.Print( Enable()
|
||||||
break;
|
? "Your mods are already enabled. To disable your mods, please run the following command instead: /penumbra disable"
|
||||||
}
|
: modsEnabled );
|
||||||
case "redraw":
|
break;
|
||||||
{
|
}
|
||||||
if( args.Length > 1 )
|
case "disable":
|
||||||
{
|
{
|
||||||
ObjectReloader.RedrawObject( args[ 1 ] );
|
Dalamud.Chat.Print( Disable()
|
||||||
}
|
? "Your mods are already disabled. To enable your mods, please run the following command instead: /penumbra enable"
|
||||||
else
|
: modsDisabled );
|
||||||
{
|
break;
|
||||||
ObjectReloader.RedrawAll();
|
}
|
||||||
}
|
case "toggle":
|
||||||
|
{
|
||||||
break;
|
SetEnabled( !Config.IsEnabled );
|
||||||
}
|
Dalamud.Chat.Print( Config.IsEnabled
|
||||||
case "debug":
|
? modsEnabled
|
||||||
{
|
: modsDisabled );
|
||||||
SettingsInterface.MakeDebugTabVisible();
|
break;
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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." );
|
"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()
|
private void DrawLogLoadedFilesBox()
|
||||||
{
|
{
|
||||||
ImGui.Checkbox( "Log Loaded Files", ref _base._penumbra.ResourceLoader.LogAllFiles );
|
ImGui.Checkbox( "Log Loaded Files", ref _base._penumbra.ResourceLoader.LogAllFiles );
|
||||||
|
|
@ -306,6 +333,7 @@ public partial class SettingsInterface
|
||||||
private void DrawAdvancedSettings()
|
private void DrawAdvancedSettings()
|
||||||
{
|
{
|
||||||
DrawTempFolder();
|
DrawTempFolder();
|
||||||
|
DrawDisableSoundStreamingBox();
|
||||||
DrawLogLoadedFilesBox();
|
DrawLogLoadedFilesBox();
|
||||||
DrawDisableNotificationsBox();
|
DrawDisableNotificationsBox();
|
||||||
DrawEnableHttpApiBox();
|
DrawEnableHttpApiBox();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue