diff --git a/Penumbra/UI/Classes/ModEditWindow.Files.cs b/Penumbra/UI/Classes/ModEditWindow.Files.cs index 9b9c16d2..18f17711 100644 --- a/Penumbra/UI/Classes/ModEditWindow.Files.cs +++ b/Penumbra/UI/Classes/ModEditWindow.Files.cs @@ -26,7 +26,6 @@ public partial class ModEditWindow private LowerString _fileOverviewFilter2 = LowerString.Empty; private LowerString _fileOverviewFilter3 = LowerString.Empty; - private bool CheckFilter( Mod.Editor.FileRegistry registry ) => _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.OrdinalIgnoreCase ); diff --git a/Penumbra/UI/Classes/ModEditWindow.cs b/Penumbra/UI/Classes/ModEditWindow.cs index 123d0b58..83d58be4 100644 --- a/Penumbra/UI/Classes/ModEditWindow.cs +++ b/Penumbra/UI/Classes/ModEditWindow.cs @@ -20,10 +20,10 @@ namespace Penumbra.UI.Classes; public partial class ModEditWindow : Window, IDisposable { - private const string WindowBaseLabel = "###SubModEdit"; - private Editor? _editor; - private Mod? _mod; - private Vector2 _iconSize = Vector2.Zero; + private const string WindowBaseLabel = "###SubModEdit"; + private Editor? _editor; + private Mod? _mod; + private Vector2 _iconSize = Vector2.Zero; public void ChangeMod( Mod mod ) { @@ -85,37 +85,37 @@ public partial class ModEditWindow : Window, IDisposable sb.Append( _mod!.Name ); if( subMods > 1 ) { - sb.AppendFormat( " | {0} Options", subMods ); + sb.Append( $" | {subMods} Options" ); } if( size > 0 ) { - sb.AppendFormat( " | {0} Files ({1})", _editor.AvailableFiles.Count, Functions.HumanReadableSize( size ) ); + sb.Append( $" | {_editor.AvailableFiles.Count} Files ({Functions.HumanReadableSize( size )})" ); } if( unused > 0 ) { - sb.AppendFormat( " | {0} Unused Files", unused ); + sb.Append( $" | {unused} Unused Files" ); } if( _editor.MissingFiles.Count > 0 ) { - sb.AppendFormat( " | {0} Missing Files", _editor.MissingFiles.Count ); + sb.Append( $" | {_editor.MissingFiles.Count} Missing Files" ); } if( redirections > 0 ) { - sb.AppendFormat( " | {0} Redirections", redirections ); + sb.Append( $" | {redirections} Redirections" ); } if( manipulations > 0 ) { - sb.AppendFormat( " | {0} Manipulations", manipulations ); + sb.Append( $" | {manipulations} Manipulations" ); } if( swaps > 0 ) { - sb.AppendFormat( " | {0} Swaps", swaps ); + sb.Append( $" | {swaps} Swaps" ); } sb.Append( WindowBaseLabel ); diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs new file mode 100644 index 00000000..8d7eba65 --- /dev/null +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs @@ -0,0 +1,212 @@ +using System.Collections.Generic; +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.Collections; +using System.Linq; +using System.Numerics; +using Dalamud.Game.ClientState.Objects.Enums; +using OtterGui.Widgets; +using Penumbra.GameData.Actors; + +namespace Penumbra.UI; + +public partial class ConfigWindow +{ + private partial class CollectionsTab + { + private sealed class WorldCombo : FilterComboCache< KeyValuePair< ushort, string > > + { + private static readonly KeyValuePair< ushort, string > AllWorldPair = new(ushort.MaxValue, "All Worlds"); + + public WorldCombo( IReadOnlyDictionary< ushort, string > worlds ) + : base( worlds.OrderBy( kvp => kvp.Value ).Prepend( AllWorldPair ) ) + { + CurrentSelection = AllWorldPair; + CurrentSelectionIdx = 0; + } + + protected override string ToString( KeyValuePair< ushort, string > obj ) + => obj.Value; + + public void Draw( float width ) + => Draw( "##worldCombo", CurrentSelection.Value, width, ImGui.GetTextLineHeightWithSpacing() ); + } + + private sealed class NpcCombo : FilterComboCache< (string Name, uint[] Ids) > + { + private readonly string _label; + + public NpcCombo( string label, IReadOnlyDictionary< uint, string > names ) + : base( () => names.GroupBy( kvp => kvp.Value ).Select( g => ( g.Key, g.Select( g => g.Key ).ToArray() ) ).OrderBy( g => g.Key ).ToList() ) + => _label = label; + + protected override string ToString( (string Name, uint[] Ids) obj ) + => obj.Name; + + protected override bool DrawSelectable( int globalIdx, bool selected ) + { + var (name, ids) = Items[ globalIdx ]; + var ret = ImGui.Selectable( name, selected ); + if( ImGui.IsItemHovered() ) + { + ImGui.SetTooltip( string.Join( '\n', ids.Select( i => i.ToString() ) ) ); + } + + return ret; + } + + public void Draw( float width ) + => Draw( _label, CurrentSelection.Name, width, ImGui.GetTextLineHeightWithSpacing() ); + } + + + // Input Selections. + private string _newCharacterName = string.Empty; + private IdentifierType _newType = IdentifierType.Player; + private ObjectKind _newKind = ObjectKind.BattleNpc; + + private readonly WorldCombo _worldCombo = new(Penumbra.Actors.Worlds); + private readonly NpcCombo _mountCombo = new("##mountCombo", Penumbra.Actors.Mounts); + private readonly NpcCombo _companionCombo = new("##companionCombo", Penumbra.Actors.Companions); + private readonly NpcCombo _bnpcCombo = new("##bnpcCombo", Penumbra.Actors.BNpcs); + private readonly NpcCombo _enpcCombo = new("##enpcCombo", Penumbra.Actors.ENpcs); + + private void DrawNewIdentifierOptions( float width ) + { + ImGui.SetNextItemWidth( width ); + using var combo = ImRaii.Combo( "##newType", _newType.ToString() ); + if( combo ) + { + if( ImGui.Selectable( IdentifierType.Player.ToString(), _newType == IdentifierType.Player ) ) + { + _newType = IdentifierType.Player; + } + + if( ImGui.Selectable( IdentifierType.Owned.ToString(), _newType == IdentifierType.Owned ) ) + { + _newType = IdentifierType.Owned; + } + + if( ImGui.Selectable( IdentifierType.Npc.ToString(), _newType == IdentifierType.Npc ) ) + { + _newType = IdentifierType.Npc; + } + } + } + + private void DrawNewObjectKindOptions( float width ) + { + ImGui.SetNextItemWidth( width ); + using var combo = ImRaii.Combo( "##newKind", _newKind.ToString() ); + if( combo ) + { + if( ImGui.Selectable( ObjectKind.BattleNpc.ToString(), _newKind == ObjectKind.BattleNpc ) ) + { + _newKind = ObjectKind.BattleNpc; + } + + if( ImGui.Selectable( ObjectKind.EventNpc.ToString(), _newKind == ObjectKind.EventNpc ) ) + { + _newKind = ObjectKind.EventNpc; + } + + if( ImGui.Selectable( ObjectKind.Companion.ToString(), _newKind == ObjectKind.Companion ) ) + { + _newKind = ObjectKind.Companion; + } + + if( ImGui.Selectable( ObjectKind.MountType.ToString(), _newKind == ObjectKind.MountType ) ) + { + _newKind = ObjectKind.MountType; + } + } + } + + // We do not check for valid character names. + private void DrawNewCharacterCollection() + { + const string description = "Character Collections apply specifically to individual game objects of the given name.\n" + + $"More general {GroupAssignment} or the {DefaultCollection} do not apply if an .\n" + + "Certain actors - like the ones in cutscenes or preview windows - will try to use appropriate character collections.\n"; + + var width = ( _window._inputTextWidth.X - 2 * ImGui.GetStyle().ItemSpacing.X ) / 3; + DrawNewIdentifierOptions( width ); + ImGui.SameLine(); + using( var dis = ImRaii.Disabled( _newType == IdentifierType.Npc ) ) + { + _worldCombo.Draw( width ); + } + + ImGui.SameLine(); + + using( var dis = ImRaii.Disabled( _newType == IdentifierType.Player ) ) + { + DrawNewObjectKindOptions( width ); + } + + ImGui.SetNextItemWidth( _window._inputTextWidth.X ); + using( var dis = ImRaii.Disabled( _newType == IdentifierType.Npc ) ) + { + ImGui.InputTextWithHint( "##NewCharacter", "Character Name...", ref _newCharacterName, 32 ); + } + + ImGui.SameLine(); + var disabled = _newCharacterName.Length == 0; + var tt = disabled + ? $"Please enter the name of a {ConditionalIndividual} before assigning the collection.\n\n" + description + : description; + if( ImGuiUtil.DrawDisabledButton( $"Assign {ConditionalIndividual}", new Vector2( 120 * ImGuiHelpers.GlobalScale, 0 ), tt, + disabled ) ) + { + Penumbra.CollectionManager.CreateCharacterCollection( _newCharacterName ); + _newCharacterName = string.Empty; + } + + using( var dis = ImRaii.Disabled( _newType == IdentifierType.Player ) ) + { + switch( _newKind ) + { + case ObjectKind.BattleNpc: + _bnpcCombo.Draw( _window._inputTextWidth.X ); + break; + case ObjectKind.EventNpc: + _enpcCombo.Draw( _window._inputTextWidth.X ); + break; + case ObjectKind.Companion: + _companionCombo.Draw( _window._inputTextWidth.X ); + break; + case ObjectKind.MountType: + _mountCombo.Draw( _window._inputTextWidth.X ); + break; + } + } + } + + private void DrawIndividualAssignments() + { + using var _ = ImRaii.Group(); + ImGui.TextUnformatted( $"Individual {ConditionalIndividual}s" ); + ImGui.Separator(); + foreach( var name in Penumbra.CollectionManager.Characters.Keys.OrderBy( k => k ).ToArray() ) + { + using var id = ImRaii.PushId( name ); + DrawCollectionSelector( string.Empty, _window._inputTextWidth.X, CollectionType.Character, true, name ); + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), _window._iconButtonSize, string.Empty, + false, true ) ) + { + Penumbra.CollectionManager.RemoveCharacterCollection( name ); + } + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted( name ); + } + + ImGui.Dummy( Vector2.Zero ); + DrawNewCharacterCollection(); + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.cs index bf8b0107..6025cb70 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.cs @@ -39,11 +39,9 @@ public partial class ConfigWindow } } - // Input text fields. private string _newCollectionName = string.Empty; private bool _canAddCollection = false; - private string _newCharacterName = string.Empty; // Create a new collection that is either empty or a duplicate of the current collection. // Resets the new collection name. @@ -212,28 +210,6 @@ public partial class ConfigWindow } } - // We do not check for valid character names. - private void DrawNewCharacterCollection() - { - const string description = "Character Collections apply specifically to individual game objects of the given name.\n" - + $"More general {GroupAssignment} or the {DefaultCollection} do not apply if an .\n" - + "Certain actors - like the ones in cutscenes or preview windows - will try to use appropriate character collections.\n"; - - ImGui.SetNextItemWidth( _window._inputTextWidth.X ); - ImGui.InputTextWithHint( "##NewCharacter", "Character Name...", ref _newCharacterName, 32 ); - ImGui.SameLine(); - var disabled = _newCharacterName.Length == 0; - var tt = disabled - ? $"Please enter the name of a {ConditionalIndividual} before assigning the collection.\n\n" + description - : description; - if( ImGuiUtil.DrawDisabledButton( $"Assign {ConditionalIndividual}", new Vector2( 120 * ImGuiHelpers.GlobalScale, 0 ), tt, - disabled ) ) - { - Penumbra.CollectionManager.CreateCharacterCollection( _newCharacterName ); - _newCharacterName = string.Empty; - } - } - private void DrawSpecialCollections() { foreach( var (type, name, desc) in CollectionTypeExtensions.Special ) @@ -268,31 +244,6 @@ public partial class ConfigWindow DrawNewSpecialCollection(); } - private void DrawIndividualAssignments() - { - using var _ = ImRaii.Group(); - ImGui.TextUnformatted( $"Individual {ConditionalIndividual}s" ); - ImGui.Separator(); - foreach( var name in Penumbra.CollectionManager.Characters.Keys.OrderBy( k => k ).ToArray() ) - { - using var id = ImRaii.PushId( name ); - DrawCollectionSelector( string.Empty, _window._inputTextWidth.X, CollectionType.Character, true, name ); - ImGui.SameLine(); - if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), _window._iconButtonSize, string.Empty, - false, true ) ) - { - Penumbra.CollectionManager.RemoveCharacterCollection( name ); - } - - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted( name ); - } - - ImGui.Dummy( Vector2.Zero ); - DrawNewCharacterCollection(); - } - private void DrawActiveCollectionSelectors() { ImGui.Dummy( _window._defaultSpace ); diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 0d64c1b8..5cf59e80 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Files; using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; using Penumbra.String; @@ -60,6 +61,8 @@ public partial class ConfigWindow ImGui.NewLine(); DrawDebugCharacterUtility(); ImGui.NewLine(); + DrawStainTemplates(); + ImGui.NewLine(); DrawDebugTabMetaLists(); ImGui.NewLine(); DrawDebugResidentResources(); @@ -171,7 +174,6 @@ public partial class ConfigWindow var identifier = Penumbra.Actors.FromObject( obj ); ImGuiUtil.DrawTableColumn( Penumbra.Actors.ToString( identifier ) ); ImGuiUtil.DrawTableColumn( identifier.DataId.ToString() ); - } } @@ -246,6 +248,47 @@ public partial class ConfigWindow } } + private static unsafe void DrawStainTemplates() + { + if( !ImGui.CollapsingHeader( "Staining Templates" ) ) + { + return; + } + + foreach( var (key, data) in Penumbra.StainManager.StmFile.Entries ) + { + using var tree = ImRaii.TreeNode( $"Template {key}" ); + if( !tree ) + { + continue; + } + + using var table = ImRaii.Table( "##table", 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg ); + if( !table ) + { + continue; + } + + for( var i = 0; i < StmFile.StainingTemplateEntry.NumElements; ++i ) + { + var (r, g, b) = data.DiffuseEntries[ i ]; + ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" ); + + ( r, g, b ) = data.SpecularEntries[ i ]; + ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" ); + + ( r, g, b ) = data.EmissiveEntries[ i ]; + ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" ); + + var a = data.SpecularPowerEntries[ i ]; + ImGuiUtil.DrawTableColumn( $"{a:F6}" ); + + a = data.GlossEntries[ i ]; + ImGuiUtil.DrawTableColumn( $"{a:F6}" ); + } + } + } + // Draw information about the character utility class from SE, // displaying all files, their sizes, the default files and the default sizes. public static unsafe void DrawDebugCharacterUtility()