More fixes, some cleanup.

This commit is contained in:
Ottermandias 2022-03-17 14:03:57 +01:00
parent 581b91b337
commit e6752ade04
20 changed files with 193 additions and 361 deletions

View file

@ -49,7 +49,7 @@ public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf
return true;
}
if( !Utf8String.FromString( substring, out var ascii ) || !ascii.IsAscii )
if( !Utf8String.FromString( substring, out var ascii, true ) || !ascii.IsAscii )
{
return false;
}
@ -66,7 +66,7 @@ public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf
return false;
}
var substring = file.FullName[ baseDir.FullName.Length.. ];
var substring = file.FullName[ (baseDir.FullName.Length + 1).. ];
return FromString( substring, out path );
}
@ -78,7 +78,7 @@ public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf
return false;
}
var substring = file.FullName[ baseDir.FullName.Length.. ];
var substring = file.FullName[ (baseDir.FullName.Length + 1).. ];
return FromString( substring, out path );
}

View file

@ -97,7 +97,7 @@ public sealed unsafe partial class Utf8String : IDisposable
}
Marshal.FreeHGlobal( ( IntPtr )_path );
GC.RemoveMemoryPressure( Length );
GC.RemoveMemoryPressure( Length + 1 );
_length = AsciiCheckedFlag | AsciiFlag | AsciiLowerCheckedFlag | AsciiLowerFlag | NullTerminatedFlag;
_path = Null.NullBytePtr;
_crc32 = 0;

View file

@ -31,8 +31,6 @@ public class Configuration : IPluginConfiguration
public bool DisableSoundStreaming { get; set; } = true;
public bool EnableHttpApi { get; set; }
public bool EnablePlayerWatch { get; set; } = false;
public int WaitFrames { get; set; } = 30;
public string ModDirectory { get; set; } = string.Empty;

View file

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Enums;
@ -16,18 +14,13 @@ public unsafe class ObjectReloader : IDisposable
{
private delegate void ManipulateDraw( IntPtr actor );
private const int RenderModeOffset = 0x0104;
private const int UnloadAllRedrawDelay = 250;
private const uint NpcObjectId = unchecked( ( uint )-536870912 );
public const int GPosePlayerIdx = 201;
public const int GPoseEndIdx = GPosePlayerIdx + 48;
private const uint NpcObjectId = unchecked( ( uint )-536870912 );
public const int GPosePlayerIdx = 201;
public const int GPoseEndIdx = GPosePlayerIdx + 48;
private readonly ModManager _mods;
private readonly Queue< (uint actorId, string name, RedrawType s) > _actorIds = new();
internal int DefaultWaitFrames;
private int _waitFrames;
private int _currentFrame;
private bool _changedSettings;
private uint _currentObjectId = uint.MaxValue;
@ -46,11 +39,8 @@ public unsafe class ObjectReloader : IDisposable
private static delegate*< IntPtr, void > GetEnableDraw( GameObject actor )
=> ( ( delegate*< IntPtr, void >** )actor.Address )[ 0 ][ 16 ];
public ObjectReloader( ModManager mods, int defaultWaitFrames )
{
_mods = mods;
DefaultWaitFrames = defaultWaitFrames;
}
public ObjectReloader( ModManager mods )
=> _mods = mods;
private void ChangeSettings()
{
@ -289,13 +279,6 @@ public unsafe class ObjectReloader : IDisposable
|| Dalamud.Conditions[ ConditionFlag.BetweenAreas ]
|| Dalamud.Conditions[ ConditionFlag.OccupiedInCutSceneEvent ] )
{
_waitFrames = DefaultWaitFrames;
return;
}
if( _waitFrames > 0 )
{
--_waitFrames;
return;
}

View file

@ -68,10 +68,12 @@ public unsafe partial class PathResolver
CharacterBaseCreateHook?.Enable();
EnableDrawHook?.Enable();
CharacterBaseDestructorHook?.Enable();
Penumbra.ModManager.Collections.CollectionChanged += CheckCollections;
}
private void DisableDataHooks()
{
Penumbra.ModManager.Collections.CollectionChanged -= CheckCollections;
CharacterBaseCreateHook?.Disable();
EnableDrawHook?.Disable();
CharacterBaseDestructorHook?.Disable();
@ -178,8 +180,13 @@ public unsafe partial class PathResolver
}
// Update collections linked to Game/DrawObjects due to a change in collection configuration.
private void CheckCollections()
private void CheckCollections( ModCollection? _1, ModCollection? _2, CollectionType type, string? name )
{
if( type is not (CollectionType.Character or CollectionType.Default) )
{
return;
}
foreach( var (key, (_, idx)) in DrawObjectToObject.ToArray() )
{
if( !VerifyEntry( key, idx, out var obj ) )
@ -187,8 +194,8 @@ public unsafe partial class PathResolver
DrawObjectToObject.Remove( key );
}
var collection = IdentifyCollection( obj );
DrawObjectToObject[ key ] = ( collection, idx );
var newCollection = IdentifyCollection( obj );
DrawObjectToObject[ key ] = ( newCollection, idx );
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.GameData.ByteString;
@ -64,6 +65,7 @@ public unsafe partial class PathResolver
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
var collection = PathCollections.TryGetValue( mtrlPath, out var c ) ? c : null;
var x = PathCollections.ToList();
for( var i = 0; i < mtrl->NumTex; ++i )
{
var texString = new Utf8String( mtrl->TexString( i ) );

View file

@ -92,16 +92,17 @@ public unsafe partial class PathResolver
}
}
// GMP
public delegate void ApplyVisorDelegate( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 );
// GMP. This gets called every time when changing visor state, and it accesses the gmp file itself,
// but it only applies a changed gmp file after a redraw for some reason.
public delegate byte SetupVisorDelegate( IntPtr drawObject, ushort modelId, byte visorState );
[Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "ApplyVisorDetour" )]
public Hook< ApplyVisorDelegate >? ApplyVisorHook;
[Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "SetupVisorDetour" )]
public Hook< SetupVisorDelegate >? SetupVisorHook;
private void ApplyVisorDetour( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 )
private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState )
{
using var gmp = MetaChanger.ChangeGmp( this, drawObject );
ApplyVisorHook!.Original( drawObject, unk1, unk2, unk3, unk4, unk5 );
return SetupVisorHook!.Original( drawObject, modelId, visorState );
}
// RSP
@ -132,7 +133,7 @@ public unsafe partial class PathResolver
OnModelLoadCompleteHook?.Enable();
#endif
#if USE_GMP
ApplyVisorHook?.Enable();
SetupVisorHook?.Enable();
#endif
#if USE_CMP
RspSetupCharacterHook?.Enable();
@ -144,7 +145,7 @@ public unsafe partial class PathResolver
GetEqpIndirectHook?.Disable();
UpdateModelsHook?.Disable();
OnModelLoadCompleteHook?.Disable();
ApplyVisorHook?.Disable();
SetupVisorHook?.Disable();
RspSetupCharacterHook?.Disable();
}
@ -153,7 +154,7 @@ public unsafe partial class PathResolver
GetEqpIndirectHook?.Dispose();
UpdateModelsHook?.Dispose();
OnModelLoadCompleteHook?.Dispose();
ApplyVisorHook?.Dispose();
SetupVisorHook?.Dispose();
RspSetupCharacterHook?.Dispose();
}

View file

@ -49,6 +49,12 @@ public partial class PathResolver : IDisposable
resolved = Penumbra.ModManager.Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gamePath );
if( resolved == null )
{
// We also need to handle defaulted materials against a non-default collection.
if( nonDefault && gamePath.Path.EndsWith( 'm', 't', 'r', 'l' ) )
{
SetCollection( gamePath.Path, collection );
}
return ( null, collection );
}

View file

@ -37,19 +37,31 @@ public unsafe struct ResourceHandle
public ReadOnlySpan< byte > FileNameSpan()
=> new(FileName(), FileNameLength);
[FieldOffset( 0x00 )]
public void** VTable;
[FieldOffset( 0x48 )]
public byte* FileNameData;
[FieldOffset( 0x58 )]
public int FileNameLength;
// May return null.
public static byte* GetData( ResourceHandle* handle )
=> ( ( delegate*< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
public static ulong GetLength( ResourceHandle* handle )
=> ( ( delegate*< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
// Only use these if you know what you are doing.
// Those are actually only sure to be accessible for DefaultResourceHandles.
[FieldOffset( 0xB0 )]
public DataIndirection* Data;
[FieldOffset( 0xB8 )]
public uint DataLength;
public (IntPtr Data, int Length) GetData()
=> Data != null
? ( ( IntPtr )Data->DataPtr, ( int )Data->DataLength )

View file

@ -121,7 +121,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public static EqdpEntry GetDefault( int fileIdx, int setIdx )
{
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address;
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( fileIdx ).Address;
var blockSize = *( ushort* )( data + IdentifierSize );
var totalBlockCount = *( ushort* )( data + IdentifierSize + 2 );
@ -139,7 +139,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 );
var x = new ReadOnlySpan< ushort >( blockData, blockSize );
return (EqdpEntry) (*( blockData + setIdx % blockSize ));
return ( EqdpEntry )( *( blockData + setIdx % blockSize ) );
}
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )

View file

@ -201,7 +201,9 @@ public unsafe class ImcFile : MetaBaseFile
var file = Dalamud.GameData.GetFile( path.ToString() );
if( file == null )
{
throw new Exception();
throw new Exception(
"Could not obtain default Imc File.\n"
+ "Either the default file does not exist (possibly for offhand files from TexTools) or the installation is corrupted." );
}
fixed( byte* ptr = file.Data )

View file

@ -24,8 +24,10 @@ public unsafe class MetaBaseFile : IDisposable
{
Length = length;
Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length );
;
GC.AddMemoryPressure( length );
if( length > 0 )
{
GC.AddMemoryPressure( length );
}
}
// Free memory.
@ -33,7 +35,11 @@ public unsafe class MetaBaseFile : IDisposable
{
var ptr = ( IntPtr )Data;
MemoryHelper.GameFree( ref ptr, ( ulong )Length );
GC.RemoveMemoryPressure( Length );
if( Length > 0 )
{
GC.RemoveMemoryPressure( Length );
}
Length = 0;
Data = null;
}

View file

@ -1,14 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.Interop.Structs;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
public enum CollectionType : byte
{
Inactive,
Default,
Forced,
Character,
Current,
}
public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, CollectionType type,
string? characterName = null );
// Contains all collections and respective functions, as well as the collection settings.
public class CollectionManager
{
@ -21,7 +31,10 @@ public class CollectionManager
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;
public ModCollection DefaultCollection { get; private set; } = ModCollection.Empty;
public ModCollection ForcedCollection { get; private set; } = ModCollection.Empty;
public ModCollection ActiveCollection { get; private set; }
public ModCollection ActiveCollection { get; private set; } = ModCollection.Empty;
// Is invoked after the collections actually changed.
public event CollectionChangeDelegate? CollectionChanged;
public CollectionManager( ModManager manager )
{
@ -29,7 +42,7 @@ public class CollectionManager
ReadCollections();
LoadConfigCollections( Penumbra.Config );
ActiveCollection = DefaultCollection;
SetActiveCollection( DefaultCollection, string.Empty );
}
public bool SetActiveCollection( ModCollection newActive, string name )
@ -44,14 +57,7 @@ public class CollectionManager
{
ActiveCollection = newActive;
Penumbra.ResidentResources.Reload();
if( ActiveCollection.Cache == null )
{
Penumbra.CharacterUtility.ResetAll();
}
else
{
ActiveCollection.Cache.MetaManipulations.SetFiles();
}
ActiveCollection.SetFiles();
}
else
{
@ -131,8 +137,9 @@ public class CollectionManager
var newCollection = new ModCollection( name, settings );
Collections.Add( name, newCollection );
CollectionChanged?.Invoke( null, newCollection, CollectionType.Inactive );
newCollection.Save();
SetCurrentCollection( newCollection );
SetCollection( newCollection, CollectionType.Current );
return true;
}
@ -149,26 +156,27 @@ public class CollectionManager
return false;
}
CollectionChanged?.Invoke( collection, null, CollectionType.Inactive );
if( CurrentCollection == collection )
{
SetCurrentCollection( Collections[ ModCollection.DefaultCollection ] );
SetCollection( Collections[ ModCollection.DefaultCollection ], CollectionType.Current );
}
if( ForcedCollection == collection )
{
SetForcedCollection( ModCollection.Empty );
SetCollection( ModCollection.Empty, CollectionType.Forced );
}
if( DefaultCollection == collection )
{
SetDefaultCollection( ModCollection.Empty );
SetCollection( ModCollection.Empty, CollectionType.Default );
}
foreach( var (characterName, characterCollection) in CharacterCollection.ToArray() )
{
if( characterCollection == collection )
{
SetCharacterCollection( characterName, ModCollection.Empty );
SetCollection( ModCollection.Empty, CollectionType.Character, characterName );
}
}
@ -196,51 +204,63 @@ public class CollectionManager
}
}
private void SetCollection( ModCollection newCollection, ModCollection oldCollection, Action< ModCollection > setter,
Action< string > configSetter )
public void SetCollection( ModCollection newCollection, CollectionType type, string? characterName = null )
{
if( newCollection.Name == oldCollection.Name )
var oldCollection = type switch
{
CollectionType.Default => DefaultCollection,
CollectionType.Forced => ForcedCollection,
CollectionType.Current => CurrentCollection,
CollectionType.Character => characterName?.Length > 0
? CharacterCollection.TryGetValue( characterName, out var c )
? c
: ModCollection.Empty
: null,
_ => null,
};
if( oldCollection == null || newCollection.Name == oldCollection.Name )
{
return;
}
AddCache( newCollection );
setter( newCollection );
RemoveCache( oldCollection );
configSetter( newCollection.Name );
Penumbra.Config.Save();
}
public void SetDefaultCollection( ModCollection newCollection )
=> SetCollection( newCollection, DefaultCollection, c =>
switch( type )
{
if( CollectionChangedTo.Length == 0 )
{
SetActiveCollection( c, string.Empty );
}
DefaultCollection = c;
}, s => Penumbra.Config.DefaultCollection = s );
public void SetForcedCollection( ModCollection newCollection )
=> SetCollection( newCollection, ForcedCollection, c => ForcedCollection = c, s => Penumbra.Config.ForcedCollection = s );
public void SetCurrentCollection( ModCollection newCollection )
=> SetCollection( newCollection, CurrentCollection, c => CurrentCollection = c, s => Penumbra.Config.CurrentCollection = s );
public void SetCharacterCollection( string characterName, ModCollection newCollection )
=> SetCollection( newCollection,
CharacterCollection.TryGetValue( characterName, out var oldCollection ) ? oldCollection : ModCollection.Empty,
c =>
{
if( CollectionChangedTo == characterName && CharacterCollection.TryGetValue( characterName, out var collection ) )
case CollectionType.Default:
DefaultCollection = newCollection;
Penumbra.Config.DefaultCollection = newCollection.Name;
if( CollectionChangedTo.Length == 0 )
{
SetActiveCollection( c, CollectionChangedTo );
SetActiveCollection( newCollection, string.Empty );
}
CharacterCollection[ characterName ] = c;
}, s => Penumbra.Config.CharacterCollections[ characterName ] = s );
break;
case CollectionType.Forced:
ForcedCollection = newCollection;
Penumbra.Config.ForcedCollection = newCollection.Name;
break;
case CollectionType.Current:
CurrentCollection = newCollection;
Penumbra.Config.CurrentCollection = newCollection.Name;
break;
case CollectionType.Character:
if( CollectionChangedTo == characterName && CharacterCollection.ContainsKey( characterName ) )
{
SetActiveCollection( newCollection, CollectionChangedTo );
}
CharacterCollection[ characterName! ] = newCollection;
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
break;
}
CollectionChanged?.Invoke( oldCollection, newCollection, type, characterName );
Penumbra.Config.Save();
}
public bool CreateCharacterCollection( string characterName )
{
@ -252,7 +272,7 @@ public class CollectionManager
CharacterCollection[ characterName ] = ModCollection.Empty;
Penumbra.Config.CharacterCollections[ characterName ] = string.Empty;
Penumbra.Config.Save();
Penumbra.PlayerWatcher.AddPlayerToWatch( characterName );
CollectionChanged?.Invoke( null, ModCollection.Empty, CollectionType.Character, characterName );
return true;
}
@ -262,7 +282,7 @@ public class CollectionManager
{
RemoveCache( collection );
CharacterCollection.Remove( characterName );
Penumbra.PlayerWatcher.RemovePlayerFromWatch( characterName );
CollectionChanged?.Invoke( collection, null, CollectionType.Character, characterName );
}
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
@ -338,7 +358,6 @@ public class CollectionManager
var configChanged = false;
foreach( var (player, collectionName) in config.CharacterCollections.ToArray() )
{
Penumbra.PlayerWatcher.AddPlayerToWatch( player );
if( collectionName.Length == 0 )
{
CharacterCollection.Add( player, ModCollection.Empty );

View file

@ -146,6 +146,7 @@ public class ModManager
}
Collections.RecreateCaches();
Collections.DefaultCollection.SetFiles();
}
public void DeleteMod( DirectoryInfo modFolder )

View file

@ -34,7 +34,6 @@ public class Penumbra : IDalamudPlugin
private const string CommandName = "/penumbra";
public static Configuration Config { get; private set; } = null!;
public static IPlayerWatcher PlayerWatcher { get; private set; } = null!;
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
public static CharacterUtility CharacterUtility { get; private set; } = null!;
@ -72,10 +71,9 @@ public class Penumbra : IDalamudPlugin
MetaDefaults = new MetaDefaults();
ResourceLoader = new ResourceLoader( this );
ResourceLogger = new ResourceLogger( ResourceLoader );
PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects );
ModManager = new ModManager();
ModManager.DiscoverMods();
ObjectReloader = new ObjectReloader( ModManager, Config.WaitFrames );
ObjectReloader = new ObjectReloader( ModManager );
PathResolver = new PathResolver( ResourceLoader );
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
@ -101,17 +99,6 @@ public class Penumbra : IDalamudPlugin
CreateWebServer();
}
if( !Config.EnablePlayerWatch || !Config.EnableMods )
{
PlayerWatcher.Disable();
}
PlayerWatcher.PlayerChanged += p =>
{
PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name );
ObjectReloader.RedrawObject( p, RedrawType.OnlyWithSettings );
};
ResourceLoader.EnableHooks();
if( Config.EnableMods )
{
@ -127,17 +114,6 @@ public class Penumbra : IDalamudPlugin
{
ResourceLoader.EnableFullLogging();
}
unsafe
{
PluginLog.Information( $"MetaManipulation: {sizeof( MetaManipulation )}" );
PluginLog.Information( $"EqpManipulation: {sizeof( EqpManipulation )}" );
PluginLog.Information( $"GmpManipulation: {sizeof( GmpManipulation )}" );
PluginLog.Information( $"EqdpManipulation: {sizeof( EqdpManipulation )}" );
PluginLog.Information( $"EstManipulation: {sizeof( EstManipulation )}" );
PluginLog.Information( $"RspManipulation: {sizeof( RspManipulation )}" );
PluginLog.Information( $"ImcManipulation: {sizeof( ImcManipulation )}" );
}
}
public bool Enable()
@ -150,10 +126,6 @@ public class Penumbra : IDalamudPlugin
Config.EnableMods = true;
ResourceLoader.EnableReplacements();
ResidentResources.Reload();
if( Config.EnablePlayerWatch )
{
PlayerWatcher.SetStatus( true );
}
Config.Save();
ObjectReloader.RedrawAll( RedrawType.WithSettings );
@ -170,10 +142,6 @@ public class Penumbra : IDalamudPlugin
Config.EnableMods = false;
ResourceLoader.DisableReplacements();
ResidentResources.Reload();
if( Config.EnablePlayerWatch )
{
PlayerWatcher.SetStatus( false );
}
Config.Save();
ObjectReloader.RedrawAll( RedrawType.WithoutSettings );
@ -232,7 +200,6 @@ public class Penumbra : IDalamudPlugin
Api.Dispose();
SettingsInterface.Dispose();
ObjectReloader.Dispose();
PlayerWatcher.Dispose();
Dalamud.Commands.RemoveHandler( CommandName );
@ -268,7 +235,7 @@ public class Penumbra : IDalamudPlugin
return false;
}
ModManager.Collections.SetDefaultCollection( collection );
ModManager.Collections.SetCollection( collection, CollectionType.Default );
Dalamud.Chat.Print( $"Set {collection.Name} as default collection." );
SettingsInterface.ResetDefaultCollection();
return true;
@ -279,7 +246,7 @@ public class Penumbra : IDalamudPlugin
return false;
}
ModManager.Collections.SetForcedCollection( collection );
ModManager.Collections.SetCollection( collection, CollectionType.Forced );
Dalamud.Chat.Print( $"Set {collection.Name} as forced collection." );
SettingsInterface.ResetForcedCollection();
return true;

View file

@ -169,7 +169,7 @@ public partial class SettingsInterface
return;
}
Penumbra.ModManager.Collections.SetCurrentCollection( _collections[ idx + 1 ] );
Penumbra.ModManager.Collections.SetCollection( _collections[ idx + 1 ], CollectionType.Current );
_currentCollectionIndex = idx;
_selector.Cache.TriggerListReset();
if( _selector.Mod != null )
@ -208,7 +208,7 @@ public partial class SettingsInterface
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( "##Default Collection", ref index, _collectionNamesWithNone ) && index != _currentDefaultIndex )
{
Penumbra.ModManager.Collections.SetDefaultCollection( _collections[ index ] );
Penumbra.ModManager.Collections.SetCollection( _collections[ index ], CollectionType.Default );
_currentDefaultIndex = index;
}
@ -231,7 +231,7 @@ public partial class SettingsInterface
&& index != _currentForcedIndex
&& manager.Collections.CharacterCollection.Count > 0 )
{
manager.Collections.SetForcedCollection( _collections[ index ] );
manager.Collections.SetCollection( _collections[ index ], CollectionType.Forced );
_currentForcedIndex = index;
}
@ -352,7 +352,7 @@ public partial class SettingsInterface
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp )
{
manager.Collections.SetCharacterCollection( name, _collections[ tmp ] );
manager.Collections.SetCollection( _collections[ tmp ], CollectionType.Character, name );
_currentCharacterIndices[ name ] = tmp;
}

View file

@ -22,104 +22,6 @@ namespace Penumbra.UI;
public partial class SettingsInterface
{
private static void DrawDebugTabPlayers()
{
if( !ImGui.CollapsingHeader( "Players##Debug" ) )
{
return;
}
var players = Penumbra.PlayerWatcher.WatchedPlayers().ToArray();
var count = players.Sum( s => Math.Max( 1, s.Item2.Length ) );
if( count == 0 )
{
return;
}
if( !ImGui.BeginTable( "##ObjectTable", 13, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollX,
new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 4 * count ) ) )
{
return;
}
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable );
var identifier = GameData.GameData.GetIdentifier();
foreach( var (actor, equip) in players.SelectMany( kvp => kvp.Item2.Any()
? kvp.Item2
.Select( x => ( $"{kvp.Item1} ({x.Item1})", x.Item2 ) )
: new[] { ( kvp.Item1, new CharacterEquipment() ) } ) )
{
// @formatter:off
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text( actor );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.MainHand}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Head}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Body}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Hands}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Legs}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Feet}" );
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (equip.IsSet == 0)
{
ImGui.Text( "(not set)" );
}
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, EquipSlot.MainHand )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Head.Set, 0, equip.Head.Variant, EquipSlot.Head )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Body.Set, 0, equip.Body.Variant, EquipSlot.Body )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Hands.Set, 0, equip.Hands.Variant, EquipSlot.Hands )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Legs.Set, 0, equip.Legs.Variant, EquipSlot.Legs )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Feet.Set, 0, equip.Feet.Variant, EquipSlot.Feet )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.Text( $"{equip.OffHand}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Ears}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Neck}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.Wrists}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.LFinger}" );
ImGui.TableNextColumn();
ImGui.Text( $"{equip.RFinger}" );
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, EquipSlot.OffHand )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Ears.Set, 0, equip.Ears.Variant, EquipSlot.Ears )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Neck.Set, 0, equip.Neck.Variant, EquipSlot.Neck )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.Wrists.Set, 0, equip.Wrists.Variant, EquipSlot.Wrists )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.LFinger.Set, 0, equip.LFinger.Variant, EquipSlot.LFinger )?.Name.ToString() ?? "Unknown" );
ImGui.TableNextColumn();
ImGui.Text( identifier.Identify( equip.RFinger.Set, 0, equip.RFinger.Variant, EquipSlot.LFinger )?.Name.ToString() ?? "Unknown" );
// @formatter:on
}
}
private static void PrintValue( string name, string value )
{
ImGui.TableNextRow();
@ -431,8 +333,8 @@ public partial class SettingsInterface
{
var def = ExpandedGmpFile.GetDefault( gmp );
var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Gmp.File?[ gmp ] ?? def;
ImGui.Text( def.Value.ToString("X") );
ImGui.Text( val.Value.ToString("X") );
ImGui.Text( def.Value.ToString( "X" ) );
ImGui.Text( val.Value.ToString( "X" ) );
}
catch
{ }
@ -557,8 +459,6 @@ public partial class SettingsInterface
ImGui.NewLine();
DrawDebugTabRedraw();
ImGui.NewLine();
DrawDebugTabPlayers();
ImGui.NewLine();
DrawDebugTabIpc();
ImGui.NewLine();
}

View file

@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.ComponentModel;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Interface;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Files;
@ -77,22 +75,12 @@ public partial class SettingsInterface
( Gender.FemaleNpc.ToName(), Gender.FemaleNpc ),
};
private static readonly (string, ObjectType)[] ObjectTypes =
private static readonly (string, EstManipulation.EstType)[] EstTypes =
{
( "Equipment", ObjectType.Equipment ),
( "Customization", ObjectType.Character ),
};
private static readonly (string, EquipSlot)[] EstEquipSlots =
{
EqpEquipSlots[ 0 ],
EqpEquipSlots[ 1 ],
};
private static readonly (string, BodySlot)[] EstBodySlots =
{
( "Hair", BodySlot.Hair ),
( "Face", BodySlot.Face ),
( "Hair", EstManipulation.EstType.Hair ),
( "Face", EstManipulation.EstType.Face ),
( "Body", EstManipulation.EstType.Body ),
( "Head", EstManipulation.EstType.Head ),
};
private static readonly (string, SubRace)[] Subraces =
@ -133,9 +121,11 @@ public partial class SettingsInterface
( RspAttribute.FemaleMaxTail.ToFullString(), RspAttribute.FemaleMaxTail ),
};
private static readonly (string, ObjectType)[] ImcObjectType =
{
ObjectTypes[ 0 ],
( "Equipment", ObjectType.Equipment ),
( "Customization", ObjectType.Character ),
( "Weapon", ObjectType.Weapon ),
( "Demihuman", ObjectType.DemiHuman ),
( "Monster", ObjectType.Monster ),
@ -143,8 +133,8 @@ public partial class SettingsInterface
private static readonly (string, BodySlot)[] ImcBodySlots =
{
EstBodySlots[ 0 ],
EstBodySlots[ 1 ],
( "Hair", BodySlot.Hair ),
( "Face", BodySlot.Face ),
( "Body", BodySlot.Body ),
( "Tail", BodySlot.Tail ),
( "Ears", BodySlot.Zear ),
@ -646,24 +636,26 @@ public partial class SettingsInterface
RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue );
RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue );
CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType );
EquipSlot equipSlot = default;
ImcManipulation imc = new();
switch( objectType )
{
case ObjectType.Equipment:
CustomCombo( "Equipment Slot", EqdpEquipSlots, out equipSlot, ref _newManipEquipSlot );
//newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant,
// new ImcFile.ImageChangeData() );
CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot );
imc = new ImcManipulation( equipSlot, _newManipVariant, _newManipSetId, new ImcEntry() );
break;
case ObjectType.DemiHuman:
case ObjectType.Weapon:
case ObjectType.Monster:
RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue );
CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot );
//newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId,
// _newManipVariant, new ImcFile.ImageChangeData() );
imc = new ImcManipulation( objectType, bodySlot, _newManipSetId, _newManipSecondaryId,
_newManipVariant, new ImcEntry() );
break;
}
newManip = new MetaManipulation( new ImcManipulation( imc.ObjectType, imc.BodySlot, imc.PrimaryId, imc.SecondaryId,
imc.Variant, imc.EquipSlot, ImcFile.GetDefault( imc.GamePath(), imc.EquipSlot, imc.Variant ) ) );
break;
}
case MetaManipulation.Type.Eqdp:
@ -672,76 +664,50 @@ public partial class SettingsInterface
CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot );
CustomCombo( "Race", Races, out var race, ref _newManipRace );
CustomCombo( "Gender", Genders, out var gender, ref _newManipGender );
//newManip = MetaManipulation.Eqdp( equipSlot, Names.CombinedRace( gender, race ), ( ushort )_newManipSetId,
// new EqdpEntry() );
var eqdp = new EqdpManipulation( new EqdpEntry(), equipSlot, gender, race, _newManipSetId );
newManip = new MetaManipulation( new EqdpManipulation( ExpandedEqdpFile.GetDefault( eqdp.FileIndex(), eqdp.SetId ),
equipSlot, gender, race, _newManipSetId ) );
break;
}
case MetaManipulation.Type.Eqp:
{
RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue );
CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot );
//newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 );
newManip = new MetaManipulation( new EqpManipulation( ExpandedEqpFile.GetDefault( _newManipSetId ) & Eqp.Mask( equipSlot ),
equipSlot, _newManipSetId ) );
break;
}
case MetaManipulation.Type.Est:
{
RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue );
CustomCombo( "Object Type", ObjectTypes, out var objectType, ref _newManipObjectType );
EquipSlot equipSlot = default;
BodySlot bodySlot = default;
switch( ( ObjectType )_newManipObjectType )
{
case ObjectType.Equipment:
CustomCombo( "Equipment Slot", EstEquipSlots, out equipSlot, ref _newManipEquipSlot );
break;
case ObjectType.Character:
CustomCombo( "Body Slot", EstBodySlots, out bodySlot, ref _newManipBodySlot );
break;
}
CustomCombo( "Est Type", EstTypes, out var estType, ref _newManipObjectType );
CustomCombo( "Race", Races, out var race, ref _newManipRace );
CustomCombo( "Gender", Genders, out var gender, ref _newManipGender );
//newManip = MetaManipulation.Est( objectType, equipSlot, Names.CombinedRace( gender, race ), bodySlot,
// ( ushort )_newManipSetId, 0 );
newManip = new MetaManipulation( new EstManipulation( gender, race, estType, _newManipSetId,
EstFile.GetDefault( estType, Names.CombinedRace( gender, race ), _newManipSetId ) ) );
break;
}
case MetaManipulation.Type.Gmp:
RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue );
//newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() );
newManip = new MetaManipulation( new GmpManipulation( ExpandedGmpFile.GetDefault( _newManipSetId ), _newManipSetId ) );
break;
case MetaManipulation.Type.Rsp:
CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace );
CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute );
//newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f );
newManip = new MetaManipulation( new RspManipulation( subRace, rspAttribute,
CmpFile.GetDefault( subRace, rspAttribute ) ) );
break;
}
//if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 )
// && newManip != null
// && list.All( m => m.Identifier != newManip.Value.Identifier ) )
//{
// var def = Penumbra.MetaDefaults.GetDefaultValue( newManip.Value );
// if( def != null )
// {
// var manip = newManip.Value.Type switch
// {
// MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
// MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
// MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, (ushort) def ),
// MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
// MetaType.Imc => new MetaManipulation( newManip.Value.Identifier,
// ( ( ImcFile.ImageChangeData )def ).ToInteger() ),
// MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace,
// newManip.Value.RspIdentifier.Attribute, ( float )def ),
// _ => throw new InvalidEnumArgumentException(),
// };
// list.Add( manip );
// change = true;
// ++count;
// }
//
// ImGui.CloseCurrentPopup();
//}
if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 )
&& newManip != null
&& list.All( m => !m.Equals( newManip ) ) )
{
list.Add( newManip.Value );
change = true;
++count;
ImGui.CloseCurrentPopup();
}
return change;
}

View file

@ -87,9 +87,10 @@ public partial class SettingsInterface
if( ImGui.IsItemClicked() )
{
var data = ( ( Interop.Structs.ResourceHandle* )r )->GetData();
var data = Interop.Structs.ResourceHandle.GetData( ( Interop.Structs.ResourceHandle* )r );
var length = ( int )Interop.Structs.ResourceHandle.GetLength( ( Interop.Structs.ResourceHandle* )r );
ImGui.SetClipboardText( string.Join( " ",
new ReadOnlySpan< byte >( ( byte* )data.Data, data.Length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
new ReadOnlySpan< byte >( data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
//ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) );
}

View file

@ -27,10 +27,10 @@ public partial class SettingsInterface
public TabSettings( SettingsInterface ui )
{
_base = ui;
_config = Penumbra.Config;
_configChanged = false;
_newModDirectory = _config.ModDirectory;
_base = ui;
_config = Penumbra.Config;
_configChanged = false;
_newModDirectory = _config.ModDirectory;
}
private static bool DrawPressEnterWarning( string old )
@ -239,44 +239,6 @@ public partial class SettingsInterface
ImGuiComponents.HelpMarker( "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
}
private void DrawEnabledPlayerWatcher()
{
var enabled = _config.EnablePlayerWatch;
if( ImGui.Checkbox( "Enable Automatic Character Redraws", ref enabled ) )
{
_config.EnablePlayerWatch = enabled;
_configChanged = true;
Penumbra.PlayerWatcher.SetStatus( enabled );
}
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"If this setting is enabled, Penumbra will keep tabs on characters that have a corresponding character collection setup in the Collections tab.\n"
+ "Penumbra will try to automatically redraw those characters using their collection when they first appear in an instance, or when they change their current equip.\n" );
if( !_config.EnablePlayerWatch || !_config.ShowAdvanced )
{
return;
}
var waitFrames = _config.WaitFrames;
ImGui.SameLine();
ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
if( ImGui.InputInt( "Wait Frames", ref waitFrames, 0, 0 )
&& waitFrames != _config.WaitFrames
&& waitFrames is > 0 and < 3000 )
{
_base._penumbra.ObjectReloader.DefaultWaitFrames = waitFrames;
_config.WaitFrames = waitFrames;
_configChanged = true;
}
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"The number of frames penumbra waits after some events (like zone changes) until it starts trying to redraw actors again, in a range of [1, 3001].\n"
+ "Keep this as low as possible while producing stable results." );
}
private static void DrawReloadResourceButton()
{
if( ImGui.Button( "Reload Resident Resources" ) )
@ -388,7 +350,6 @@ public partial class SettingsInterface
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox();
DrawEnabledPlayerWatcher();
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawScaleModSelectorBox();