From 04baf7ef50b69cf0f203c38608034eaab924f0c5 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Sat, 17 Jun 2023 18:37:28 -0500 Subject: [PATCH] Add Asset Compiler tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Loose Texture Compiler Core Submodule changes Correct text Add custom path function. Refactor name to asset compiler Right align buttons. Store project files outside of the folder of the currently selected mod. Add Simple Mode Fix issue with simple mode not clearing itself. Update constructors Add modifier keys to filepicker clearing buttons. Submodule changes Minor cleanup Adjust event names Fix project persistence. Add a cap to how many texture sets can exist. Only save project if asset compiler tab is actually used. Submodule updates Remove incomplete class. Add Drag And Drop, add bulk name replacement. Attempt to fix some texture errors. Get penumbra to compile again. # This is a combination of 105 commits. Add support for the DalamudSubstitutionProvider for textures. # The commit message #9 will be skipped: # Attempt to fix some texture errors. # The commit message #10 will be skipped: # Get penumbra to compile again. # The commit message #11 will be skipped: # Submodule update # The commit message #12 will be skipped: # Disable UI for textures when converting. # The commit message #13 will be skipped: # Update DirectXTex/OtterTex # The commit message #14 will be skipped: # Keep the texture alive during write. # The commit message #15 will be skipped: # [CI] Updating repo.json for 0.7.3.2 # The commit message #16 will be skipped: # Explain comment. # The commit message #17 will be skipped: # Add some unnamed mounts to actor identification. # The commit message #18 will be skipped: # Material editor: improve color accuracy # The commit message #19 will be skipped: # Material editor: live-preview changes # The commit message #20 will be skipped: # Material editor 2099 # The commit message #21 will be skipped: # Material editor: better color constants # The commit message #22 will be skipped: # Update GameData # The commit message #23 will be skipped: # Material editor: Allow negatives again with R²G²B² # # There seems to be people using it. # The commit message #24 will be skipped: # Material editor: tweak colorset highlighting # # Make the frequency framerate-independent, set it to 1 Hz, and decrease the dynamic range. # # Thanks @StoiaCode for feedback! # The commit message #25 will be skipped: # Material editor: tear down previewers bound to a CharacterBase that goes away # The commit message #26 will be skipped: # Add a few texture manipulation tools. # The commit message #27 will be skipped: # Small cleanup, auto-formatting. # The commit message #28 will be skipped: # Textures: Un-merge save buttons, make ignore unselectable # The commit message #29 will be skipped: # Textures: Renumber CombineOps. # # Positive values in this enum also double as indices into the labels and tooltip arrays. # # (confirmed skill issue moment) # The commit message #30 will be skipped: # Textures: Automatic resizing # The commit message #31 will be skipped: # Textures: Add a matrix preset that drops alpha # The commit message #32 will be skipped: # Textures: PR #327 feedback # The commit message #33 will be skipped: # Textures: PR #327 feedback # The commit message #34 will be skipped: # Textures: Refactor resizing code # The commit message #35 will be skipped: # Textures: Simplify away _targetPixels # The commit message #36 will be skipped: # Slight restructuring. # The commit message #37 will be skipped: # Skin Fixer (fixes modding of skin.shpk) # The commit message #38 will be skipped: # Skin Fixer: Make resolving skin.shpk for new draw objects async # The commit message #39 will be skipped: # Skin Fixer: Fix potential ref leak + add SRH # # `SafeResourceHandle` wraps a `ResourceHandle*` with auto `IncRef` / `DecRef`, to further help prevent leaks. # The commit message #40 will be skipped: # Use better event in SkinFixer and some cleanup. # The commit message #41 will be skipped: # Remove Finalize from FileEditor. # The commit message #42 will be skipped: # Some formatting in Materials.Shpk. # The commit message #43 will be skipped: # Small cleanup in ResolveContext. # The commit message #44 will be skipped: # Auto-formatting and some cleanup. # The commit message #45 will be skipped: # Restructure Live Preview. # The commit message #46 will be skipped: # Fix slash direction in material path. # The commit message #47 will be skipped: # [CI] Updating repo.json for testing_0.7.3.3 # The commit message #48 will be skipped: # Fix newtonsoft not playing well with records with strings. # The commit message #49 will be skipped: # Cleanup # The commit message #50 will be skipped: # Check for drawObject != null before invoking draw object created event. # The commit message #51 will be skipped: # Fix variant gamepath. # The commit message #52 will be skipped: # Material editor: use a SafeHandle for texture swapping # The commit message #53 will be skipped: # Material editor: Customizable highlight color # The commit message #54 will be skipped: # Material editor: Vector field spacing # The commit message #55 will be skipped: # Fix compiler warning # The commit message #56 will be skipped: # [CI] Updating repo.json for testing_0.7.3.4 # The commit message #57 will be skipped: # Resource Tree: Improve mtrl and sklb support # The commit message #58 will be skipped: # Resource Tree: Deduplicate nodes, add skp # The commit message #59 will be skipped: # Resource Tree: Add ChangedItem-like icons, make UI prettier # The commit message #60 will be skipped: # Resource Tree: Make skp child of sklb # The commit message #61 will be skipped: # Resource Tree: Fix shared model fold state # The commit message #62 will be skipped: # Some auto-formatting and ROS iteration for lookups. # The commit message #63 will be skipped: # Resource Tree: Use `/`s for game actual paths # The commit message #64 will be skipped: # Skin Fixer: Switch to a passive approach. # # Do not load skin.shpk for ourselves as it causes a race condition. # Instead, inspect the materials' ShPk names. # The commit message #65 will be skipped: # Some formatting, use ConcurrentSet explicitly for clarity. # The commit message #66 will be skipped: # [CI] Updating repo.json for testing_0.7.3.5 # The commit message #67 will be skipped: # Add Emotes to Changed Items. # The commit message #68 will be skipped: # [CI] Updating repo.json for testing_0.7.3.6 # The commit message #69 will be skipped: # Fix changed item flags for emotes. # The commit message #70 will be skipped: # Update GameData for new parsing. # The commit message #71 will be skipped: # Fix CS update creating ambiguous reference. # The commit message #72 will be skipped: # Add Player and Interface to quick select collections and rework their tooltips and names slightly. # The commit message #73 will be skipped: # [CI] Updating repo.json for testing_0.7.3.7 # The commit message #74 will be skipped: # Allow drag & drop of multiple mods or folders with Control. # The commit message #75 will be skipped: # [CI] Updating repo.json for testing_0.7.3.8 # The commit message #76 will be skipped: # Fix click check for selectables. # The commit message #77 will be skipped: # [CI] Updating repo.json for testing_0.7.3.9 # The commit message #78 will be skipped: # Add Filesystem Compression as a toggle and button. Also some auto-formatting. # The commit message #79 will be skipped: # [CI] Updating repo.json for testing_0.7.3.10 # The commit message #80 will be skipped: # Add Compacting to API AddMod. # The commit message #81 will be skipped: # Add key checks to restoring from backup or deleting backups. # The commit message #82 will be skipped: # Material Editor: Split ColorTable apart from ColorSet # The commit message #83 will be skipped: # GameData Commit. # The commit message #84 will be skipped: # Auto Formatting. # The commit message #85 will be skipped: # Use System global usings. # The commit message #86 will be skipped: # Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces. # The commit message #87 will be skipped: # ResourceTree improvements + IPC # # - Moves ResourceType enum out of GameData as discussed on Discord ; # - Adds new color coding for local player and non-networked objects on On-Screen ; # - Adds ResourceTree-related IPC ; # - Fixes #342. # The commit message #88 will be skipped: # ResourceTree IPC: Remove mergeSameCollection. # The commit message #89 will be skipped: # ResourceTree: Avoid enumerating the whole object table in some cases # The commit message #90 will be skipped: # Move IPC Arguments around. # The commit message #91 will be skipped: # Rename ResourceType file. # The commit message #92 will be skipped: # Remove enums folder from csproj?! # The commit message #93 will be skipped: # Fix params bug. # The commit message #94 will be skipped: # Api nuget version. # The commit message #95 will be skipped: # Add load state to resource watcher. # The commit message #96 will be skipped: # [CI] Updating repo.json for testing_0.7.3.11 # The commit message #97 will be skipped: # Add CalculateHeight Hook # The commit message #98 will be skipped: # ResourceTree: Reverse-resolve in bulk # The commit message #99 will be skipped: # Remove some allocations from resource tree. # The commit message #100 will be skipped: # Update OtterGui. # The commit message #101 will be skipped: # Fix ambiguous reference for no fucking reason. # The commit message #102 will be skipped: # [CI] Updating repo.json for testing_0.7.3.12 # The commit message #103 will be skipped: # Add automatic restore from backup for sort_order and active_collections for now. # The commit message #104 will be skipped: # Material Editor: Extend live preview. # The commit message #105 will be skipped: # Optimize ResourceTree somewhat. --- .gitmodules | 3 + LooseTextureCompilerCore | 1 + Penumbra.sln | 6 + Penumbra/Api/PenumbraIpcProviders.cs | 2 - Penumbra/Import/Textures/BaseImage.cs | 3 +- Penumbra/Import/Textures/CombinedTexture.cs | 66 +- Penumbra/Import/Textures/TexFileParser.cs | 6 +- Penumbra/Import/Textures/Texture.cs | 3 +- Penumbra/Penumbra.csproj | 4 +- Penumbra/Services/ServiceManager.cs | 3 +- .../AdvancedWindow/ModEditWindow.Textures.cs | 34 + .../ModsTab/ModPanelLooseAssetCompilerTab.cs | 2484 +++++++++++++++++ Penumbra/UI/ModsTab/ModPanelTabBar.cs | 13 +- Penumbra/packages.lock.json | 44 +- 14 files changed, 2640 insertions(+), 32 deletions(-) create mode 160000 LooseTextureCompilerCore create mode 100644 Penumbra/UI/ModsTab/ModPanelLooseAssetCompilerTab.cs diff --git a/.gitmodules b/.gitmodules index ea1199ad9..d4ba0d348 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,9 @@ path = Penumbra.String url = https://github.com/Ottermandias/Penumbra.String.git branch = main +[submodule "LooseTextureCompilerCore"] + path = LooseTextureCompilerCore + url = https://github.com/Sebane1/LooseTextureCompilerCore.git [submodule "Penumbra.GameData"] path = Penumbra.GameData url = https://github.com/Ottermandias/Penumbra.GameData.git diff --git a/LooseTextureCompilerCore b/LooseTextureCompilerCore new file mode 160000 index 000000000..2f6fbb28d --- /dev/null +++ b/LooseTextureCompilerCore @@ -0,0 +1 @@ +Subproject commit 2f6fbb28dbbeddfc6f6ee23db3ff5a205247a7d4 diff --git a/Penumbra.sln b/Penumbra.sln index 5c11aaea0..ef4b31385 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LooseTextureCompilerCore", "LooseTextureCompilerCore\LooseTextureCompilerCore.csproj", "{39769DC8-E6B7-4C5C-8758-7FB36F6A7FB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,6 +46,10 @@ Global {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU + {39769DC8-E6B7-4C5C-8758-7FB36F6A7FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39769DC8-E6B7-4C5C-8758-7FB36F6A7FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39769DC8-E6B7-4C5C-8758-7FB36F6A7FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39769DC8-E6B7-4C5C-8758-7FB36F6A7FB4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index b72073fba..9b55ea4ba 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -1,12 +1,10 @@ using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; using Penumbra.GameData.Enums; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.Collections.Manager; using Penumbra.Mods.Manager; using Penumbra.Services; -using Penumbra.Util; namespace Penumbra.Api; diff --git a/Penumbra/Import/Textures/BaseImage.cs b/Penumbra/Import/Textures/BaseImage.cs index a4a0e2039..025d5f01f 100644 --- a/Penumbra/Import/Textures/BaseImage.cs +++ b/Penumbra/Import/Textures/BaseImage.cs @@ -1,5 +1,6 @@ using Lumina.Data.Files; using OtterTex; +using Penumbra.Api.Enums; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -103,7 +104,7 @@ public int MipMaps { null => 0, ScratchImage s => s.Meta.MipLevels, - TexFile t => t.Header.MipLevelsCount, + TexFile t => t.Header.MipLevels, _ => 1, }; } diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index 98b87ac3a..32323ba83 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -1,3 +1,8 @@ +using FFXIVLooseTextureCompiler.ImageProcessing; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; + namespace Penumbra.Import.Textures; public partial class CombinedTexture : IDisposable @@ -138,8 +143,67 @@ public void Update() break; } } + public void ImageToEyeMaps(string path, string textureCompilerDLC) + { + if (!IsLoaded || _current == null) + { + return; + } + + try + { + var image = Image.LoadPixelData(_current.RgbaPixels, _current.TextureWrap!.Width, + _current.TextureWrap!.Height); + image.Save(path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }); + ImageManipulation.ConvertToEyeMaps(path, textureCompilerDLC); + } + catch (Exception e) + { - private void Clean() + } + } + internal void EyeMultiToGrayscale(string path) + { + if (!IsLoaded || _current == null) + { + return; + } + + try + { + var image = Image.LoadPixelData(_current.RgbaPixels, _current.TextureWrap!.Width, + _current.TextureWrap!.Height); + image.Save(path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }); + System.Drawing.Bitmap multi = TexLoader.ResolveBitmap(path); + ImageManipulation.ExtractRed(multi).Save(ImageManipulation.AddSuffix(path, "_grayscale")); + } + catch (Exception e) + { + + } + } + + public void AtramentumLuminisDiffuseToGlowMap(string path) + { + if (!IsLoaded || _current == null) + { + return; + } + + try + { + var image = Image.LoadPixelData(_current.RgbaPixels, _current.TextureWrap!.Width, + _current.TextureWrap!.Height); + image.Save(path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }); + System.Drawing.Bitmap diffuse = TexLoader.ResolveBitmap(path); + AtramentumLuminisGlow.ExtractGlowMapFromDiffuse(diffuse).Save(path, System.Drawing.Imaging.ImageFormat.Png); + } + catch (Exception e) + { + + } + } + private void Clean() { _centerStorage.Dispose(); _current = null; diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 629cdff5f..cca47fa3b 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -79,7 +79,7 @@ public static void Write(this TexFile.TexHeader header, BinaryWriter w) w.Write(header.Width); w.Write(header.Height); w.Write(header.Depth); - w.Write(header.MipLevelsCount); + w.Write(header.MipLevels); w.Write((byte)0); // TODO Lumina Update unsafe { @@ -99,7 +99,7 @@ public static TexFile.TexHeader ToTexHeader(this ScratchImage scratch) Height = (ushort)meta.Height, Width = (ushort)meta.Width, Depth = (ushort)Math.Max(meta.Depth, 1), - MipLevelsCount = (byte)Math.Min(meta.MipLevels, 13), + MipLevels = (byte)Math.Min(meta.MipLevels, 13), Format = meta.Format.ToTexFormat(), Type = meta.Dimension switch { @@ -143,7 +143,7 @@ public static TexMeta ToTexMeta(this TexFile.TexHeader header) Height = header.Height, Width = header.Width, Depth = Math.Max(header.Depth, (ushort)1), - MipLevels = header.MipLevelsCount, + MipLevels = header.MipLevels, ArraySize = 1, Format = header.Format.ToDXGI(), Dimension = header.Type.ToDimension(), diff --git a/Penumbra/Import/Textures/Texture.cs b/Penumbra/Import/Textures/Texture.cs index c4d6dc566..fb9ad712b 100644 --- a/Penumbra/Import/Textures/Texture.cs +++ b/Penumbra/Import/Textures/Texture.cs @@ -2,7 +2,6 @@ using OtterTex; namespace Penumbra.Import.Textures; - public enum TextureType { Unknown, @@ -11,9 +10,9 @@ public enum TextureType Png, Bitmap, } - public sealed class Texture : IDisposable { + // Path to the file we tried to load. public string Path = string.Empty; diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index ec4331136..528b5dae8 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -69,15 +69,17 @@ - + + + diff --git a/Penumbra/Services/ServiceManager.cs b/Penumbra/Services/ServiceManager.cs index 84f89f6d4..8c2070d77 100644 --- a/Penumbra/Services/ServiceManager.cs +++ b/Penumbra/Services/ServiceManager.cs @@ -157,7 +157,8 @@ private static IServiceCollection AddInterface(this IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index a3b17848a..81183db43 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -140,6 +140,40 @@ private void DrawOutputChild(Vector2 size, Vector2 imageSize) _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); AddReloadTask(_left.Path, false); } + string eyeMapResources = _config.ModDirectory + @"\LooseTextureCompilerDLC\"; + if (Directory.Exists(eyeMapResources)) + { + if (ImGui.Button("Image To Eye Maps", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.ImageToEyeMaps(b, eyeMapResources); + }, _mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + if (ImGui.Button("Eye Multi To Grayscale", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.EyeMultiToGrayscale(b); + }, _mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + } + if (ImGui.Button("Seperate Glow Information From Diffuse", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.AtramentumLuminisDiffuseToGlowMap(b); + }, _mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Convert to BC3", buttonSize3, diff --git a/Penumbra/UI/ModsTab/ModPanelLooseAssetCompilerTab.cs b/Penumbra/UI/ModsTab/ModPanelLooseAssetCompilerTab.cs new file mode 100644 index 000000000..3d27afee0 --- /dev/null +++ b/Penumbra/UI/ModsTab/ModPanelLooseAssetCompilerTab.cs @@ -0,0 +1,2484 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using Dalamud.Interface.DragDrop; +using FFXIVLooseTextureCompiler; +using FFXIVLooseTextureCompiler.Export; +using FFXIVLooseTextureCompiler.PathOrganization; +using FFXIVLooseTextureCompiler.Racial; +using ImGuiNET; +using Newtonsoft.Json; +using OtterGui; +using OtterGui.Widgets; +using Penumbra.Interop.Services; +using Penumbra.Mods; +using Penumbra.Mods.Manager; +using TypingConnector; + +namespace Penumbra.UI.ModsTab; + +public class ModPanelLooseAssetCompilerTab : ITab +{ + public ReadOnlySpan Label + => "Asset Compiler"u8; + + #region Variables + private string _xNormalPath; + private Configuration _config; + private FileDialogService _fileDialog; + private TextureProcessor _textureProcessor; + private Dictionary _watchers = new Dictionary(); + private bool _lockDuplicateGeneration; + + private List _textureSets = new List(); + + private Dictionary _groupOptionTypes = new Dictionary(); + private List _textureSetNames = new List(); + private Color _originalDiffuseBoxColour; + private Color _originalNormalBoxColour; + private Color _originalMultiBoxColour; + private ModManager _manager; + private RedrawService _redrawService; + private ModFileSystemSelector _selector; + + private string[] _choiceTypes; + private string[] _bodyNames; + private string[] _bodyNamesSimplified; + private string[] _genders; + private string[] _races; + private string[] _subRaces; + private string[] _faceTypes; + private string[] _faceParts; + private string[] _faceScales; + private string[] _tails; + private string[] _simpleModeNormalChoices; + private string[] _faceExtraValues; + + + private LTCFilePicker _diffuse; + private LTCFilePicker _normal; + private LTCFilePicker _multi; + private LTCFilePicker _glow; + private LTCFilePicker _mask; + + private LTCFilePicker _skin; + private LTCFilePicker _face; + private LTCFilePicker _eyes; + + private LTCComboBox _baseBodyList; + private LTCComboBox _baseBodyListSimplified; + private LTCComboBox _genderList; + private LTCComboBox _raceList; + private LTCComboBox _subRaceList; + private LTCComboBox _faceTypeList; + private LTCComboBox _facePartList; + private LTCComboBox _faceExtraList; + private LTCComboBox _auraFaceScalesDropdown; + private LTCComboBox _tailList; + private LTCComboBox _choiceTypeList; + private LTCComboBox _simpleModeNormalComboBox; + + private LTCCheckBox _bakeNormals; + private LTCCheckBox _generateMulti; + private LTCCheckBox _asymCheckbox; + private LTCCheckBox _uniqueAuRa; + + private LTCCustomPathConfigurator _customPathConfigurator; + private LTCFindAndReplace _ltcFindAndReplace; + private LTCTemplateConfigurator _ltcTemplateConfigurator; + private LTCBulkNameReplacement _ltcBulkNameReplacement; + + + private TextureSet _skinTextureSet; + private TextureSet _faceTextureSet; + private TextureSet _eyesTextureSet; + + private int _currentTextureSet = -1; + private int _lastTextureSet = -1; + + private string _currentEditLabel; + private int _lastRaceIndex; + private bool _isSimpleMode; + private Mod? _currentMod; + private bool _editingInternalValues; + private bool _bulkReplacingValues; + private string _exportStatus = ""; + private bool _configuringTemplate; + private bool _showOmniExportPrompt; + private bool _addingCustomValues; + private bool _bulkNameReplacingValue; + private const int _textureSetLimit = 10510; + + #endregion + public ModPanelLooseAssetCompilerTab(RedrawService redrawService, ModManager manager, + ModFileSystemSelector selector, FileDialogService fileDialog, Configuration config, IDragDropManager dragDrop) + { + // This will be used for underlay textures. + // The user will need to download a mod pack with the following path until there is a better way to aquire underlay assets. + string underlayTexturePath = manager.BasePath.FullName + @"\LooseTextureCompilerDLC\"; + + // This should reference the xNormal install no matter where its been installed. + // If this path is not found xNormal reliant functions will be disabled until xNormal is installed. + _xNormalPath = @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\xNormal\3.19.3\xNormal (x64).lnk"; + + #region Initialization + _config = config; + _fileDialog = fileDialog; + _textureProcessor = new TextureProcessor(underlayTexturePath); + _textureProcessor.OnStartedProcessing += TextureProcessor_OnStartedProcessing; + _textureProcessor.OnLaunchedXnormal += TextureProcessor_OnLaunchedXnormal; + + _manager = manager; + _redrawService = redrawService; + _selector = selector; + _choiceTypes = new string[] { "Detailed", "Simple", "Dropdown", "Group Is Checkbox" }; + _bodyNames = new string[] { "Vanilla and Gen2", "BIBO+", "EVE", "Gen3 and T&F3", "SCALES+", "TBSE and HRBODY", "TAIL", "Otopop" }; + _bodyNamesSimplified = new string[] { "BIBO+ Based", "Gen3 Based", "TBSE and HRBODY", "Otopop" }; + _genders = new string[] { "Masculine", "Feminine" }; + _races = new string[] { "Midlander", "Highlander", "Elezen", "Miqo'te", "Roegadyn", "Lalafell", "Raen", "Xaela", "Hrothgar", "Viera" }; + _subRaces = new string[] { "Midlander", "Highlander", "Wildwood", "Duskwight", "Seeker", "Keeper", "Sea Wolf", "Hellsguard", + "Plainsfolk", "Dunesfolk", "Raen", "Xaela", "Helions", "The Lost", "Rava", "Veena" }; + _faceTypes = new string[] { "Face 1", "Face 2", "Face 3", "Face 4", "Face 5", "Face 6", "Face 7", "Face 8", "Face 9" }; + _faceParts = new string[] { "Face", "Eyebrows", "Eyes", "Ears", "Face Paint", "Hair", "Face B", "Etc B" }; + _faceScales = new string[] { "Vanilla Scales", "Scaleless Vanilla", "Scaleless Varied" }; + _tails = new string[8]; + _simpleModeNormalChoices = new string[] { "No Bumps On Skin", "Bumps On Skin", "Inverted Bumps On Skin" }; + _faceExtraValues = new string[999]; + + for (int i = 0; i < _tails.Length; i++) + { + _tails[i] = (i + 1) + ""; + } + for (int i = 0; i < _faceExtraValues.Length; i++) + { + _faceExtraValues[i] = (i + 1) + ""; + } + + _baseBodyList = new LTCComboBox("bodyTypeList", _bodyNames, 0, 150); + _baseBodyList.OnSelectedIndexChanged += BaseBodyList_OnSelectedIndexChanged; + _baseBodyList.SelectedIndex = 0; + + _baseBodyListSimplified = new LTCComboBox("bodyTypeListSimplified", _bodyNamesSimplified, 0, 150); + _baseBodyListSimplified.OnSelectedIndexChanged += BaseBodyListSimplified_OnSelectedIndexChanged; + _baseBodyListSimplified.SelectedIndex = 0; + + _genderList = new LTCComboBox("genderList", _genders, 0, 100); + + _raceList = new LTCComboBox("raceList", _races, 0, 100); + _raceList.OnSelectedIndexChanged += RaceList_OnSelectedIndexChanged; + + _subRaceList = new LTCComboBox("subRaceList", _subRaces, 0, 100); + _subRaceList.OnSelectedIndexChanged += SubRaceList_OnSelectedIndexChanged; + _subRaceList.SelectedIndex = 0; + + _faceTypeList = new LTCComboBox("faceTypeList", _faceTypes, 0, 70); + _faceTypeList.OnSelectedIndexChanged += FaceTypeList_OnSelectedIndexChanged; + + _facePartList = new LTCComboBox("facePartList", _faceParts, 0, 90); + _facePartList.OnSelectedIndexChanged += FacePartList_OnSelectedIndexChanged; + + _auraFaceScalesDropdown = new LTCComboBox("faceScaleList", _faceScales, 0, 135); + _auraFaceScalesDropdown.Enabled = false; + + _faceExtraList = new LTCComboBox("faceExtraList", _faceExtraValues, 0, 70); + _faceExtraList.Enabled = false; + + _tailList = new LTCComboBox("tailList", _tails, 0, 62); + _tailList.Enabled = false; + + _choiceTypeList = new LTCComboBox("choiceTypeList", _choiceTypes, 0, 135); + + _simpleModeNormalComboBox = new LTCComboBox("simpleModeNormalChoices", _simpleModeNormalChoices, 0, 230); + _simpleModeNormalComboBox.OnSelectedIndexChanged += SimpleModeNormalComboBox_OnSelectedIndexChanged; + + _currentEditLabel = "Please highlight a texture set to start importing"; + + _diffuse = new LTCFilePicker("Diffuse", _config, fileDialog, dragDrop); + _normal = new LTCFilePicker("Normal", _config, fileDialog, dragDrop); + _multi = new LTCFilePicker("Multi", _config, fileDialog, dragDrop); + _glow = new LTCFilePicker("Glow", _config, fileDialog, dragDrop); + _mask = new LTCFilePicker("Mask", _config, fileDialog, dragDrop); + + _skin = new LTCFilePicker("Skin", _config, fileDialog, dragDrop); + _face = new LTCFilePicker("Face", _config, fileDialog, dragDrop); + _eyes = new LTCFilePicker("Eyes", _config, fileDialog, dragDrop); + + _customPathConfigurator = new LTCCustomPathConfigurator(_choiceTypes); + _customPathConfigurator.OnWantsToClose += CustomPathConfiguration_OnWantsToClose; + + _ltcFindAndReplace = new LTCFindAndReplace(_textureSets, config, fileDialog, dragDrop); + _ltcFindAndReplace.OnWantsToClose += LtcFindAndReplace_OnWantsToClose; + + _ltcTemplateConfigurator = new LTCTemplateConfigurator(this, _textureSets); + _ltcTemplateConfigurator.OnWantsToClose += LtcTemplateConfigurator_OnWantsToClose; + + _ltcBulkNameReplacement = new LTCBulkNameReplacement(_textureSets); + _ltcBulkNameReplacement.OnWantsToClose += _ltcBulkNameReplacement_OnWantsToClose; + + _diffuse.OnFileSelected += TextureSelection_OnFileSelected; + _normal.OnFileSelected += TextureSelection_OnFileSelected; + _multi.OnFileSelected += TextureSelection_OnFileSelected; + _glow.OnFileSelected += TextureSelection_OnFileSelected; + _mask.OnFileSelected += TextureSelection_OnFileSelected; + + _skin.OnFileSelected += Skin_OnFileSelected; + _face.OnFileSelected += Face_OnFileSelected; + _eyes.OnFileSelected += Eyes_OnFileSelected; + + _diffuse.Enabled = false; + _normal.Enabled = false; + _multi.Enabled = false; + _mask.Enabled = false; + _glow.Enabled = false; + + _bakeNormals = new LTCCheckBox("Bake Normals", 90); + _bakeNormals.OnCheckedChanged += BakeNormals_OnCheckedChanged; + + _generateMulti = new LTCCheckBox("Bake Multi", 70); + _asymCheckbox = new LTCCheckBox("Asym", 50); + _uniqueAuRa = new LTCCheckBox("Unique Au Ra", 90); + _uniqueAuRa.Enabled = false; + + _originalDiffuseBoxColour = _diffuse.BackColor = Color.Pink; + _originalNormalBoxColour = _normal.BackColor = Color.AliceBlue; + _originalMultiBoxColour = _multi.BackColor = Color.Orange; + #endregion + } + + #region UI + public void DrawContent() + { + if (!_lockDuplicateGeneration) + { + CheckForNewMod(); + DrawCoreWindow(); + } + else + { + ProgressDisplay(); + } + } + + private void DrawCoreWindow() + { + if (!_editingInternalValues && !_bulkReplacingValues + && !_configuringTemplate && !_addingCustomValues + && !_bulkNameReplacingValue) + { + WarningsAndDisclaimers(); + if (!_isSimpleMode) + { + AdvancedMode(); + } + else + { + SimpleMode(); + } + } + else + { + ConfiguratorWindows(); + } + } + + private void SimpleMode() + { + _baseBodyListSimplified.Draw(); + ImGui.SameLine(); + _subRaceList.Draw(); + ImGui.SameLine(); + _faceTypeList.Draw(); + + _skin.Draw(); + _face.Draw(); + _eyes.Draw(); + + ImGui.SetNextItemWidth(80); + ImGui.LabelText("##skinBumpsLabel", "Skin Bumps"); + ImGui.SameLine(); + _simpleModeNormalComboBox.Draw(); + if (ImGui.Button("Preview (For quick edits)")) + { + Task.Run(() => Export(false)); + } + + ImGui.SameLine(); + + if (ImGui.Button("Finalize (To finish mod")) + { + Task.Run(() => Export(true)); + } + } + + private void AdvancedMode() + { + BodySelection(); + FaceSelection(); + TextureSetManagement(); + ExportSettings(); + } + + private void MenuBar() + { + TemplateButtons(); + } + + private void WarningsAndDisclaimers() + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - 180); + ImGui.LabelText("##disclaimer", "This tab will overwrite any previously autogenerated assets."); + ImGui.SameLine(); + if (ImGui.Button(_isSimpleMode ? "Enable Advanced Mode" : "Enable Simple Mode", new Vector2(170, 20))) + { + if (!_isSimpleMode) + { + if (_textureSets.Count == 3 || _textureSets.Count == 0) + { + SimpleModeSwitch(); + } + else + { + Penumbra.Chat.NotificationMessage("This project is too complex to switch to simple mode", "Cannot Switch Modes", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else + { + _isSimpleMode = false; + } + } + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - 410); + if (!Directory.Exists(_textureProcessor.BasePath)) + { + ImGui.LabelText("##dlcInstall", "Loose Texture Compiler DLC is not installed. Underlay support will not function."); + ImGui.SameLine(); + if (ImGui.Button("Download DLC")) + { + looseTextureDLCDownload(); + } + } + if (!File.Exists(_xNormalPath) || IsRunningUnderWine()) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - 540); + ImGui.LabelText("##xNormalInstall", IsRunningUnderWine() ? + "Universal mode is not supported on Wine. Universal options will be unavailable" : + "xNormal is not installed. Universal options will be unavailable"); + ImGui.SameLine(); + if (!IsRunningUnderWine()) + { + if (ImGui.Button("Download xNormal")) + { + xNormalDownload(); + } + } + } + } + + private void xNormalDownload() + { + try + { + Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = "https://xnormal.net/", + UseShellExecute = true, + Verb = "OPEN" + }); + } + catch + { + + } + } + + private void looseTextureDLCDownload() + { + try + { + Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = "https://drive.google.com/uc?id=1qUExzMshqVovo2cP5jkSpkkcC4pLpfHs", + UseShellExecute = true, + Verb = "OPEN" + }); + } + catch + { + + } + } + + private void ConfiguratorWindows() + { + if (_editingInternalValues || _addingCustomValues) + { + _customPathConfigurator.Draw(); + } + else if (_bulkReplacingValues) + { + _ltcFindAndReplace?.Draw(); + } + else if (_configuringTemplate) + { + _ltcTemplateConfigurator?.Draw(); + } + else if (_bulkNameReplacingValue) + { + _ltcBulkNameReplacement?.Draw(); + } + } + + private void ProgressDisplay() + { + ImGui.LabelText("##progressLabel", "This tab is unavailable until the previous operation completes."); + ImGui.LabelText("##progressStatus", _exportStatus + " " + _currentMod.Name); + ImGui.ProgressBar((float)_textureProcessor.ExportCompletion / (float)_textureProcessor.ExportMax, + new System.Numerics.Vector2(ImGui.GetContentRegionMax().X, 20)); + } + + private void CheckForNewMod() + { + if (!_lockDuplicateGeneration) + { + if (_selector.Selected != _currentMod) + { + string projectPath = ""; + if (_currentMod != null && _textureSets.Count > 0) + { + if (Directory.Exists(_currentMod.ModPath.FullName)) + { + projectPath = Path.Combine(_manager.BasePath.FullName, + (_currentMod.Name.Text.Replace("/", null).Replace(@"\", null)) + ".ffxivtp"); + SaveProject(projectPath); + } + } + if (_selector.Selected != null) + { + projectPath = Path.Combine(_manager.BasePath.FullName, + _selector.Selected.Name.Text.Replace("/", null).Replace(@"\", null) + ".ffxivtp"); + NewProject(); + ExitConfigurators(); + if (File.Exists(projectPath)) + { + OpenProject(projectPath); + } + _currentMod = _selector.Selected; + } + } + } + } + + private void ExitConfigurators() + { + _editingInternalValues = false; + _bulkReplacingValues = false; + _configuringTemplate = false; + _addingCustomValues = false; + _bulkNameReplacingValue = false; + } + + private void ExportSettings() + { + ImGui.SetNextItemWidth(80); + ImGui.LabelText("##choiceTypeLabel", "Choice Type"); + ImGui.SameLine(); + _choiceTypeList.Draw(); + ImGui.SameLine(); + + _bakeNormals.Draw(); + ImGui.SameLine(); + + _generateMulti.Draw(); + ImGui.SameLine(); + if (ImGui.Button("Preview")) + { + Task.Run(() => Export(false)); + } + ImGui.SameLine(); + if (ImGui.Button("Finalize")) + { + Task.Run(() => Export(true)); + } + } + + private void TextureSetManagement() + { + _textureSetNames.Clear(); + foreach (TextureSet textureSet in _textureSets) + { + _textureSetNames.Add(textureSet.ToString()); + } + ImGui.SetNextItemWidth(ImGui.GetWindowContentRegionMax().X - 105); + ImGui.LabelText("##textureSetsLabel", "Texture Sets"); + ImGui.SameLine(); + + if (ImGui.Button("Custom Path")) + { + OnCustomPath(); + } + ImGui.SetNextItemWidth(ImGui.GetWindowContentRegionMax().X - 10); + ImGui.ListBox("##textureSets", ref _currentTextureSet, _textureSetNames.ToArray(), _textureSetNames.Count, 6); + + if (_currentTextureSet != _lastTextureSet) + { + SelectedIndexChanged(); + } + + _lastTextureSet = _currentTextureSet; + + TextureSetContextMenu(); + TextureSetManagementButtons(); + ImGui.SameLine(); + MenuBar(); + TextureSetManagementFileEntries(); + } + + private void TextureSetManagementFileEntries() + { + ImGui.LabelText("##currentEditLabelText" + _currentEditLabel, _currentEditLabel); + + _diffuse.Draw(); + _normal.Draw(); + _multi.Draw(); + _glow.Draw(); + _mask.Draw(); + } + + private void TextureSetManagementButtons() + { + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.BeginDisabled(); + } + if (ImGui.Button("Delete")) + { + OnRemoveSelection(); + } + + ImGui.SameLine(); + + if (ImGui.Button("Clear List")) + { + OnClearList(); + } + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.EndDisabled(); + } + + ImGui.SameLine(); + + if (ImGui.Button("Move Up")) + { + OnMoveUp(); + } + + ImGui.SameLine(); + + if (ImGui.Button("Move Down")) + { + OnMoveDown(); + } + + } + + private void OnCustomPath() + { + if (_textureSets.Count < _textureSetLimit) + { + + _customPathConfigurator.TextureSet = new TextureSet(); + _addingCustomValues = true; + } + else + { + Penumbra.Chat.NotificationMessage("You have hit the cap of " + _textureSetLimit + " texture sets", "Too Many Texture Sets", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + + private void TextureSetContextMenu() + { + if (_currentTextureSet > -1) + { + if (ImGui.IsMouseClicked(ImGuiMouseButton.Right) && ImGui.IsItemHovered()) + { + ImGui.OpenPopup("##textureSetContextMenu"); + } + if (ImGui.BeginPopup("##textureSetContextMenu")) + { + if (ImGui.MenuItem("Edit Internal Values##context")) + { + OnEditInternalValues(); + } + if (File.Exists(_xNormalPath)) + { + if (ImGui.MenuItem(!_textureSets[_currentTextureSet].OmniExportMode + ? "Enable Universal Mode##context" : "Disable Universal Mode##context")) + { + ToggleUniversalModeOnCurrentTextureSet(); + } + } + if (ImGui.MenuItem("Bulk Name Replacement##context")) + { + OnBulkReplaceName(); + } + if (ImGui.MenuItem("Bulk Replace Values##context")) + { + OnBulkReplace(); + } + + if (ImGui.MenuItem("Move Up##context")) + { + OnMoveUp(); + } + + if (ImGui.MenuItem("Move Down##context")) + { + OnMoveDown(); + } + if (ImGui.MenuItem("Duplicate##context")) + { + OnDuplicateSelection(); + } + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.BeginDisabled(); + } + if (ImGui.MenuItem("Delete##context")) + { + OnRemoveSelection(); + } + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.EndDisabled(); + } + ImGui.EndPopup(); + } + } + if (_showOmniExportPrompt) + { + ImGui.OpenPopup("UniversalModeNotice"); + _showOmniExportPrompt = false; + } + UniversalModeDialog(); + } + + private void OnDuplicateSelection() + { + TextureSet newTextureSet = new TextureSet(); + TextureSet textureSet = _textureSets[_currentTextureSet]; + newTextureSet.Diffuse = textureSet.Diffuse; + newTextureSet.Normal = textureSet.Normal; + newTextureSet.Multi = textureSet.Multi; + newTextureSet.Glow = newTextureSet.Glow; + newTextureSet.NormalCorrection = newTextureSet.NormalCorrection; + newTextureSet.InternalDiffusePath = textureSet.InternalDiffusePath; + newTextureSet.InternalNormalPath = textureSet.InternalNormalPath; + newTextureSet.InternalMultiPath = textureSet.InternalMultiPath; + newTextureSet.BackupTexturePaths = textureSet.BackupTexturePaths; + newTextureSet.ChildSets = textureSet.ChildSets; + newTextureSet.GroupName = textureSet.GroupName; + newTextureSet.TextureSetName = textureSet.GroupName; + newTextureSet.InvertNormalGeneration = textureSet.InvertNormalGeneration; + newTextureSet.IgnoreNormalGeneration = textureSet.IgnoreNormalGeneration; + newTextureSet.IgnoreMultiGeneration = textureSet.IgnoreMultiGeneration; + newTextureSet.OmniExportMode = textureSet.OmniExportMode; + _textureSets.Add(newTextureSet); + _currentTextureSet = _textureSets.Count - 1; + } + + private void OnBulkReplaceName() + { + _bulkNameReplacingValue = true; + } + + private void TemplateButtons() + { + ImGui.Dummy(new System.Numerics.Vector2(ImGui.GetContentRegionMax().X - 510, 5)); + ImGui.SameLine(); + if (ImGui.Button("Templates")) + { + ImGui.OpenPopup("##templateContextMenu"); + } + if (ImGui.BeginPopup("##templateContextMenu")) + { + if (ImGui.MenuItem("Import Template##context")) + { + _fileDialog.OpenFilePicker("Import Template", + "Template Project {.ffxivtp}", (s, f) => + { + if (!s) + { + return; + } + OpenTemplate(f[0]); + }, 1, "", false); + } + if (Directory.Exists(_textureProcessor.BasePath)) + { + foreach (string item in Directory.EnumerateFiles(_textureProcessor.BasePath + @"\res\templates")) + { + if (item.EndsWith(".ffxivtp")) + { + if (ImGui.MenuItem(Path.GetFileNameWithoutExtension(item) + "##context")) + { + OpenTemplate(item); + } + } + } + } + ImGui.EndPopup(); + } + ImGui.SameLine(); + if (ImGui.Button("Export Template")) + { + _fileDialog.OpenSavePicker("Save Template", "Template Project{.ffxivtp}", + "New template.ffxivtp", "{.ffxivtp}", (s, f) => + { + if (!s) + { + return; + } + SaveProject(f.Replace(".ffxivtp", "") + ".ffxivtp"); + }, "", false); + } + } + + private void OnEditInternalValues() + { + _editingInternalValues = true; + _customPathConfigurator.TextureSet = _textureSets[_currentTextureSet]; + _customPathConfigurator.GroupingType.SelectedIndex = ( + _groupOptionTypes.ContainsKey(_customPathConfigurator.TextureSet.GroupName) ? + _groupOptionTypes[_customPathConfigurator.TextureSet.GroupName] : 0); + } + + private void OnBulkReplace() + { + if (_currentTextureSet > -1) + { + TextureSet sourceTextureSet = _textureSets[_currentTextureSet]; + Tokenizer tokenizer = new Tokenizer(sourceTextureSet.TextureSetName); + _ltcFindAndReplace.TextureSetSearchString = tokenizer.GetToken(); + _ltcFindAndReplace.GroupSearchString = sourceTextureSet.GroupName + != sourceTextureSet.TextureSetName ? sourceTextureSet.GroupName : ""; + _ltcFindAndReplace.Diffuse.CurrentPath = _diffuse.CurrentPath; + _ltcFindAndReplace.Normal.CurrentPath = _normal.CurrentPath; + _ltcFindAndReplace.Multi.CurrentPath = _multi.CurrentPath; + _ltcFindAndReplace.Mask.CurrentPath = _mask.CurrentPath; + _ltcFindAndReplace.Glow.CurrentPath = _glow.CurrentPath; + _ltcFindAndReplace.IsForEyes = sourceTextureSet.InternalMultiPath.ToLower().Contains("catchlight"); + _bulkReplacingValues = true; + } + } + + private void OnMoveDown() + { + if (_currentTextureSet + 1 < _textureSets.Count && _currentTextureSet != -1) + { + TextureSet object1 = _textureSets[_currentTextureSet + 1]; + TextureSet object2 = _textureSets[_currentTextureSet]; + + _textureSets[_currentTextureSet] = object1; + _textureSets[_currentTextureSet + 1] = object2; + _currentTextureSet += 1; + } + } + + private void OnMoveUp() + { + if (_currentTextureSet > 0) + { + TextureSet object1 = _textureSets[_currentTextureSet - 1]; + TextureSet object2 = _textureSets[_currentTextureSet]; + + _textureSets[_currentTextureSet] = object1; + _textureSets[_currentTextureSet - 1] = object2; + _currentTextureSet -= 1; + } + } + + private void OnClearList() + { + _textureSets.Clear(); + _diffuse.CurrentPath = ""; + _normal.CurrentPath = ""; + _multi.CurrentPath = ""; + _mask.CurrentPath = ""; + _glow.CurrentPath = ""; + + _diffuse.Enabled = false; + _normal.Enabled = false; + _multi.Enabled = false; + _mask.Enabled = false; + _glow.Enabled = false; + + _currentEditLabel = "Please highlight a texture set to start importing"; + + _currentTextureSet = -1; + } + + private void OnRemoveSelection() + { + if (_currentTextureSet > -1) + { + _textureSets.RemoveAt(_currentTextureSet); + _diffuse.CurrentPath = ""; + _normal.CurrentPath = ""; + _multi.CurrentPath = ""; + _glow.CurrentPath = ""; + _currentEditLabel = "Please highlight a texture set to start importing"; + + _currentTextureSet = -1; + } + } + + private void FaceSelection() + { + #region Face Selection + if (_subRaceList.Enabled) + { + _subRaceList.Draw(); + ImGui.SameLine(); + } + + if (_faceTypeList.Enabled) + { + _faceTypeList.Draw(); + ImGui.SameLine(); + } + + _facePartList.Draw(); + ImGui.SameLine(); + + _faceExtraList.Draw(); + ImGui.SameLine(); + + _auraFaceScalesDropdown.Draw(); + ImGui.SameLine(); + + _asymCheckbox.Draw(); + ImGui.SameLine(); + ImGui.Dummy(new Vector2(ImGui.GetWindowContentRegionMax().X - + _subRaceList.Width - _faceTypeList.Width - + _facePartList.Width - _faceExtraList.Width - + _auraFaceScalesDropdown.Width - _asymCheckbox.Width - 100, 1)); + ImGui.SameLine(); + if (ImGui.Button("Add Face")) + { + AddFace(); + } + #endregion + } + + private void BodySelection() + { + #region Body Selection + _baseBodyList.Draw(); + ImGui.SameLine(); + + _genderList.Draw(); + ImGui.SameLine(); + + _raceList.Draw(); + ImGui.SameLine(); + + _tailList.Draw(); + ImGui.SameLine(); + + _uniqueAuRa.Draw(); + ImGui.SameLine(); + ImGui.Dummy(new Vector2(ImGui.GetWindowContentRegionMax().X - + _baseBodyList.Width - _genderList.Width - + _raceList.Width - _tailList.Width - + _uniqueAuRa.Width - 110, 1)); + ImGui.SameLine(); + if (ImGui.Button("Add Body")) + { + AddBody(); + } + #endregion + } + + private void SelectedIndexChanged() + { + if (_currentTextureSet == -1) + { + _currentEditLabel = "Please highlight a texture set to start importing"; + SetControlsEnabled(false); + } + else + { + TextureSet textureSet = _textureSets[_currentTextureSet]; + _currentEditLabel = "Editing: " + textureSet.TextureSetName; + SetControlsEnabled(true, textureSet); + SetControlsPaths(textureSet); + SetControlsColors(textureSet); + } + } + + private void SetControlsEnabled(bool enabled, TextureSet textureSet = null) + { + if (textureSet == null) + { + enabled = false; + } + _diffuse.Enabled = enabled && !string.IsNullOrEmpty(textureSet.InternalDiffusePath); + _normal.Enabled = enabled && !string.IsNullOrEmpty(textureSet.InternalNormalPath); + _multi.Enabled = enabled && !string.IsNullOrEmpty(textureSet.InternalMultiPath); + _mask.Enabled = enabled && _bakeNormals.Checked; + _glow.Enabled = enabled && !textureSet.TextureSetName.ToLower().Contains("face paint") + && !textureSet.TextureSetName.ToLower().Contains("hair") && _diffuse.Enabled; + } + + private void SetControlsPaths(TextureSet textureSet) + { + _diffuse.CurrentPath = textureSet.Diffuse; + _normal.CurrentPath = textureSet.Normal; + _multi.CurrentPath = textureSet.Multi; + _mask.CurrentPath = textureSet.NormalMask; + _glow.CurrentPath = textureSet.Glow; + } + + private void SetControlsColors(TextureSet textureSet) + { + if (textureSet.InternalMultiPath != null && textureSet.InternalMultiPath.ToLower().Contains("catchlight")) + { + _diffuse.LabelName = "Normal"; + _normal.LabelName = "Multi"; + _multi.LabelName = "Catchlight"; + _diffuse.BackColor = _originalNormalBoxColour; + _normal.BackColor = Color.Lavender; + _multi.BackColor = Color.LightGray; + } + else + { + _diffuse.LabelName = "Diffuse"; + _normal.LabelName = "Normal"; + _multi.LabelName = "Multi"; + _diffuse.BackColor = _originalDiffuseBoxColour; + _normal.BackColor = _originalNormalBoxColour; + _multi.BackColor = _originalMultiBoxColour; + } + } + #endregion + #region Event Callbacks + private void SimpleModeNormalComboBox_OnSelectedIndexChanged(object? sender, EventArgs e) + { + switch (_simpleModeNormalComboBox.SelectedIndex) + { + case 0: + _bakeNormals.Checked = false; + _skinTextureSet.InvertNormalGeneration = false; + _faceTextureSet.InvertNormalGeneration = false; + break; + case 1: + _bakeNormals.Checked = true; + _skinTextureSet.InvertNormalGeneration = false; + _faceTextureSet.InvertNormalGeneration = false; + break; + case 2: + _bakeNormals.Checked = true; + _skinTextureSet.InvertNormalGeneration = true; + _faceTextureSet.InvertNormalGeneration = true; + break; + } + SaveState(); + } + + private void Eyes_OnFileSelected(object? sender, EventArgs e) + { + _eyesTextureSet.Normal = _eyes.FilePath; + AddWatcher(_eyesTextureSet.Diffuse); + SaveState(); + } + + private void Face_OnFileSelected(object? sender, EventArgs e) + { + _faceTextureSet.Diffuse = _face.FilePath; + AddWatcher(_faceTextureSet.Diffuse); + SaveState(); + } + + private void Skin_OnFileSelected(object? sender, EventArgs e) + { + _skinTextureSet.Diffuse = _skin.FilePath; + AddWatcher(_skinTextureSet.Diffuse); + SaveState(); + } + + private void FaceTypeList_OnSelectedIndexChanged(object? sender, EventArgs e) + { + if (_isSimpleMode) + { + _facePartList.SelectedIndex = 0; + AddFacePaths(_faceTextureSet); + _facePartList.SelectedIndex = 2; + AddEyePaths(_eyesTextureSet); + SaveState(); + } + } + + private void BaseBodyListSimplified_OnSelectedIndexChanged(object? sender, EventArgs e) + { + switch (_baseBodyListSimplified.SelectedIndex) + { + case 0: + _baseBodyList.SelectedIndex = 1; + break; + case 1: + _baseBodyList.SelectedIndex = 3; + break; + case 2: + _baseBodyList.SelectedIndex = 5; + break; + case 3: + _baseBodyList.SelectedIndex = 7; + break; + } + AddBodyPaths(_skinTextureSet); + SaveState(); + } + private void _ltcBulkNameReplacement_OnWantsToClose(object? sender, EventArgs e) + { + _bulkNameReplacingValue = false; + } + private void LtcTemplateConfigurator_OnWantsToClose(object? sender, EventArgs e) + { + _configuringTemplate = false; + } + + private void TextureProcessor_OnLaunchedXnormal(object? sender, EventArgs e) + { + _exportStatus = "Waiting For XNormal To Generate Assets For"; + } + + private void TextureProcessor_OnStartedProcessing(object? sender, EventArgs e) + { + _exportStatus = "Compiling Assets For"; + } + + private void LtcFindAndReplace_OnWantsToClose(object? sender, EventArgs e) + { + if (_ltcFindAndReplace.WasValidated) + { + foreach (TextureSet textureSet in _textureSets) + { + AddWatcher(textureSet.Diffuse); + AddWatcher(textureSet.Normal); + AddWatcher(textureSet.Multi); + AddWatcher(textureSet.NormalMask); + AddWatcher(textureSet.Glow); + } + _currentTextureSet = -1; + _ltcFindAndReplace.WasValidated = false; + } + _bulkReplacingValues = false; + } + + private void CustomPathConfiguration_OnWantsToClose(object? sender, EventArgs e) + { + if (_customPathConfigurator.WasValidated) + { + _groupOptionTypes[_customPathConfigurator.TextureSet.GroupName] = _customPathConfigurator.GroupingType.SelectedIndex; + _customPathConfigurator.WasValidated = false; + } + if (_editingInternalValues) + { + _editingInternalValues = false; + } + if (_addingCustomValues) + { + _textureSets.Add(_customPathConfigurator.TextureSet); + _addingCustomValues = false; + SaveState(); + } + } + + private void TextureSelection_OnFileSelected(object? sender, EventArgs e) + { + SetPaths(); + SaveState(); + } + + private void BakeNormals_OnCheckedChanged(object? sender, EventArgs e) + { + _mask.Enabled = _bakeNormals.Checked && _currentTextureSet > -1; + } + + private void FacePartList_OnSelectedIndexChanged(object? sender, EventArgs e) + { + if (_facePartList.SelectedIndex == 4) + { + _auraFaceScalesDropdown.Enabled = _asymCheckbox.Enabled = _faceTypeList.Enabled = _subRaceList.Enabled = false; + _faceExtraList.Enabled = true; + } + else if (_facePartList.SelectedIndex == 5) + { + _auraFaceScalesDropdown.Enabled = false; + _asymCheckbox.Enabled = _faceTypeList.Enabled; + _faceExtraList.Enabled = true; + } + else + { + _asymCheckbox.Enabled = _faceTypeList.Enabled = _subRaceList.Enabled = true; + if (_subRaceList.SelectedIndex == 10 || _subRaceList.SelectedIndex == 11) + { + _auraFaceScalesDropdown.Enabled = true; + } + _faceExtraList.Enabled = false; + } + } + + private void SubRaceList_OnSelectedIndexChanged(object? sender, EventArgs e) + { + if (_subRaceList.SelectedIndex == 10 || _subRaceList.SelectedIndex == 11) + { + _auraFaceScalesDropdown.Enabled = true; + } + else + { + _auraFaceScalesDropdown.Enabled = false; + } + _raceList.SelectedIndex = RaceInfo.SubRaceToMainRace(_subRaceList.SelectedIndex); + } + + private void RaceList_OnSelectedIndexChanged(object? sender, EventArgs e) + { + if (_baseBodyList.SelectedIndex == 6) + { + if (_raceList.SelectedIndex != 3 && _raceList.SelectedIndex != 6 && _raceList.SelectedIndex != 7) + { + _raceList.SelectedIndex = 3; + Penumbra.Chat.NotificationMessage("Tail is only compatible with Miqo'te Xaela, and Raen", "Bad Combination", Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else if (_baseBodyList.SelectedIndex == 4) + { + if (_raceList.SelectedIndex != 6 && _raceList.SelectedIndex != 7) + { + _raceList.SelectedIndex = 6; + Penumbra.Chat.NotificationMessage("Scales+ is only compatible with Xaela, and Raen", "Bad Combination", Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else if (_baseBodyList.SelectedIndex > 0 && _baseBodyList.SelectedIndex < 7) + { + if (_raceList.SelectedIndex == 5) + { + _raceList.SelectedIndex = _lastRaceIndex; + Penumbra.Chat.NotificationMessage("Lalafells are not compatible with the selected body", "Bad Combination", Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else if (_baseBodyList.SelectedIndex > 6) + { + if (_raceList.SelectedIndex != 5) + { + _raceList.SelectedIndex = 5; + Penumbra.Chat.NotificationMessage("Only Lalafells are compatible with the selected body", "Bad Combination", Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + _lastRaceIndex = _raceList.SelectedIndex; + } + + private void BaseBodyList_OnSelectedIndexChanged(object? sender, EventArgs e) + { + switch (_baseBodyList.SelectedIndex) + { + case 0: + _genderList.Enabled = true; + _tailList.Enabled = false; + _uniqueAuRa.Enabled = false; + break; + case 1: + case 2: + case 3: + _genderList.SelectedIndex = 1; + _genderList.Enabled = false; + _tailList.Enabled = false; + if (_raceList.SelectedIndex == 5) + { + _raceList.SelectedIndex = 0; + } + _uniqueAuRa.Enabled = false; + break; + case 4: + _raceList.SelectedIndex = 6; + _genderList.SelectedIndex = 1; + _genderList.Enabled = false; + _tailList.Enabled = false; + _uniqueAuRa.Enabled = false; + break; + case 5: + _genderList.SelectedIndex = 0; + _genderList.Enabled = false; + _tailList.Enabled = false; + if (_raceList.SelectedIndex == 5) + { + _raceList.SelectedIndex = 0; + } + _uniqueAuRa.Enabled = true; + break; + case 6: + _raceList.SelectedIndex = 6; + _genderList.Enabled = true; + _tailList.Enabled = true; + _uniqueAuRa.Enabled = false; + break; + case 7: + _genderList.Enabled = false; + _raceList.SelectedIndex = 5; + _tailList.Enabled = false; + break; + } + } + #endregion + #region File Persistence + private void NewProject() + { + _lockDuplicateGeneration = true; + _textureSets.Clear(); + _diffuse.CurrentPath = ""; + _normal.CurrentPath = ""; + _multi.CurrentPath = ""; + _glow.CurrentPath = ""; + + _diffuse.Enabled = false; + _normal.Enabled = false; + _multi.Enabled = false; + _mask.Enabled = false; + _glow.Enabled = false; + + _currentTextureSet = -1; + + foreach (FileSystemWatcher watcher in _watchers.Values) + { + watcher.Dispose(); + } + _watchers.Clear(); + _currentEditLabel = "Please highlight a texture set to start importing"; + _lockDuplicateGeneration = false; + _isSimpleMode = false; + } + private void OpenProject(string path) + { + using (StreamReader file = File.OpenText(path)) + { + JsonSerializer serializer = new JsonSerializer(); + ProjectFile projectFile = (ProjectFile)serializer.Deserialize(file, typeof(ProjectFile)); + _textureSets.Clear(); + _choiceTypeList.SelectedIndex = projectFile.ExportType; + _bakeNormals.Checked = projectFile.BakeMissingNormals; + _generateMulti.Checked = projectFile.GenerateMulti; + if (projectFile.GroupOptionTypes != null) + { + _groupOptionTypes = projectFile.GroupOptionTypes; + } + _textureSets.AddRange(projectFile.TextureSets?.ToArray()); + if (projectFile.SimpleMode) + { + SimpleModeSwitch(); + } + else + { + _isSimpleMode = false; + } + _baseBodyList.SelectedIndex = projectFile.SimpleBodyType; + _faceTypeList.SelectedIndex = projectFile.SimpleFaceType; + _subRaceList.SelectedIndex = projectFile.SimpleSubRaceType; + _simpleModeNormalComboBox.SelectedIndex = projectFile.SimpleNormalGeneration; + foreach (TextureSet textureSet in projectFile.TextureSets) + { + AddWatcher(textureSet.Diffuse); + AddWatcher(textureSet.Normal); + AddWatcher(textureSet.Multi); + AddWatcher(textureSet.NormalMask); + AddWatcher(textureSet.Glow); + BackupTexturePaths.AddBackupPaths(_genderList.SelectedIndex, _raceList.SelectedIndex, textureSet); + } + } + } + + private void OpenTemplate(string path) + { + _configuringTemplate = true; + _ltcTemplateConfigurator.OpenTemplate(path, _genderList.SelectedIndex, _raceList.SelectedIndex); + SaveState(); + } + private void SaveProject(string path) + { + using (StreamWriter writer = new StreamWriter(path)) + { + JsonSerializer serializer = new JsonSerializer(); + ProjectFile projectFile = new ProjectFile(); + projectFile.Name = _currentMod.Name; + projectFile.Author = _currentMod.Author; + projectFile.Version = _currentMod.Description; + projectFile.Description = _currentMod.Description; + projectFile.Website = _currentMod.Website; + projectFile.GroupOptionTypes = _groupOptionTypes; + projectFile.TextureSets = new List(); + projectFile.ExportType = _choiceTypeList.SelectedIndex; + projectFile.BakeMissingNormals = _bakeNormals.Checked; + projectFile.GenerateMulti = _generateMulti.Checked; + projectFile.SimpleMode = _isSimpleMode; + + projectFile.SimpleBodyType = _baseBodyList.SelectedIndex; + projectFile.SimpleFaceType = _faceTypeList.SelectedIndex; + projectFile.SimpleSubRaceType = _subRaceList.SelectedIndex; + projectFile.SimpleNormalGeneration = _simpleModeNormalComboBox.SelectedIndex; + foreach (TextureSet textureSet in _textureSets) + { + projectFile.TextureSets.Add(textureSet); + } + serializer.Serialize(writer, projectFile); + } + //Penumbra.Chat.NotificationMessage("Save successfull", "Changes Saved"); + } + private void SaveState() + { + string projectPath = Path.Combine(_manager.BasePath.FullName, _currentMod.Name + ".ffxivtp"); + SaveProject(projectPath); + } + #endregion + #region Texture Set Management + public void SimpleModeSwitch() + { + _isSimpleMode = true; + _baseBodyList.SelectedIndex = 1; + _simpleModeNormalComboBox.SelectedIndex = 0; + if (_textureSets.Count == 0) + { + _skinTextureSet = new TextureSet(); + _faceTextureSet = new TextureSet(); + _eyesTextureSet = new TextureSet(); + _textureSets.Add(_skinTextureSet); + _textureSets.Add(_faceTextureSet); + _textureSets.Add(_eyesTextureSet); + } + else if (_textureSets.Count == 3) + { + _skinTextureSet = _textureSets[0]; + _faceTextureSet = _textureSets[1]; + _eyesTextureSet = _textureSets[2]; + } + else + { + return; + } + + // Enable universal export if XNormal is installed and not running under wine + if (!IsRunningUnderWine() && File.Exists(_xNormalPath)) + { + _skinTextureSet.OmniExportMode = true; + } + + _skinTextureSet.MaterialSetName = "Skin"; + _faceTextureSet.MaterialSetName = "Face"; + _eyesTextureSet.MaterialSetName = "Eyes"; + + _skinTextureSet.MaterialGroupName = "Character Customization"; + _faceTextureSet.MaterialGroupName = "Character Customization"; + _eyesTextureSet.MaterialGroupName = "Character Customization"; + + _skin.FilePath = _skinTextureSet.Diffuse; + _face.FilePath = _faceTextureSet.Diffuse; + _eyes.FilePath = _eyesTextureSet.Normal; + + AddBodyPaths(_skinTextureSet); + _facePartList.SelectedIndex = 0; + AddFacePaths(_faceTextureSet); + _facePartList.SelectedIndex = 2; + AddEyePaths(_eyesTextureSet); + _choiceTypeList.SelectedIndex = 1; + } + private void ToggleUniversalModeOnCurrentTextureSet() + { + TextureSet textureSet = _textureSets[_currentTextureSet]; + if (textureSet != null) + { + if (!textureSet.OmniExportMode) + { + UniversalTextureSetCreator.ConfigureOmniConfiguration(textureSet); + _showOmniExportPrompt = true; + } + else + { + textureSet.OmniExportMode = false; + textureSet.ChildSets.Clear(); + _showOmniExportPrompt = false; + } + } + } + + public void UniversalModeDialog() + => ImGuiUtil.HelpPopup("UniversalModeNotice", new Vector2(500 * UiHelpers.Scale, 10 * ImGui.GetTextLineHeightWithSpacing()), () => + { + ImGui.NewLine(); + ImGui.TextWrapped( + "Enabling universal compatibility mode allows your currently selected body or face textures to be compatible with other body/face configurations on a best effort basis." + + "\r\n\r\nWarning: this slows down the generation process, so you will need to click the finalize button to update changes on bodies that arent this one."); + }); + private void SetPaths() + { + if (_currentTextureSet != -1) + { + TextureSet textureSet = _textureSets[_currentTextureSet]; + DisposeWatcher(textureSet.Diffuse, _diffuse); + DisposeWatcher(textureSet.Normal, _normal); + DisposeWatcher(textureSet.Multi, _multi); + DisposeWatcher(textureSet.NormalMask, _mask); + DisposeWatcher(textureSet.Glow, _glow); + + if (!string.IsNullOrWhiteSpace(textureSet.Glow)) + { + _generateMulti.Checked = true; + } + + textureSet.Diffuse = _diffuse.CurrentPath; + textureSet.Normal = _normal.CurrentPath; + textureSet.Multi = _multi.CurrentPath; + textureSet.NormalMask = _mask.CurrentPath; + textureSet.Glow = _glow.CurrentPath; + + AddWatcher(textureSet.Diffuse); + AddWatcher(textureSet.Normal); + AddWatcher(textureSet.Multi); + AddWatcher(textureSet.NormalMask); + AddWatcher(textureSet.Glow); + } + } + private void AddFace() + { + if (_textureSets.Count < _textureSetLimit) + { + TextureSet textureSet = new TextureSet(); + textureSet.TextureSetName = _facePartList.Text + (_facePartList.SelectedIndex == 4 ? " " + + (_faceExtraList.SelectedIndex + 1) : "") + ", " + (_facePartList.SelectedIndex != 4 ? _genderList.Text : "Unisex") + + ", " + (_facePartList.SelectedIndex != 4 ? _subRaceList.Text : "Multi Race") + ", " + + (_facePartList.SelectedIndex != 4 ? _faceTypeList.Text : "Multi Face"); + switch (_facePartList.SelectedIndex) + { + default: + AddFacePaths(textureSet); + break; + case 2: + AddEyePaths(textureSet); + break; + case 4: + AddDecalPath(textureSet); + break; + case 5: + AddHairPaths(textureSet); + break; + } + textureSet.IgnoreMultiGeneration = true; + textureSet.BackupTexturePaths = null; + _textureSets.Add(textureSet); + _currentTextureSet = _textureSets.Count - 1; + SaveState(); + } + else + { + Penumbra.Chat.NotificationMessage("You have hit the cap of " + _textureSetLimit + " texture sets", "Too Many Texture Sets", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + + private void AddBody() + { + if (_textureSets.Count < _textureSetLimit) + { + TextureSet textureSet = new TextureSet(); + textureSet.TextureSetName = _baseBodyList.Text + (_baseBodyList.Text.ToLower().Contains("tail") ? " " + + (_tailList.SelectedIndex + 1) : "") + ", " + (_raceList.SelectedIndex == 5 ? "Unisex" : _genderList.Text) + + ", " + _raceList.Text; + AddBodyPaths(textureSet); + _textureSets.Add(textureSet); + _currentTextureSet = _textureSets.Count - 1; + SaveState(); + } + else + { + Penumbra.Chat.NotificationMessage("You have hit the cap of " + _textureSetLimit + " texture sets", "Too Many Texture Sets", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + + private void AddBodyPaths(TextureSet textureSet) + { + if (_raceList.SelectedIndex != 3 || _baseBodyList.SelectedIndex != 6) + { + textureSet.InternalDiffusePath = RacePaths.GetBodyTexturePath(0, _genderList.SelectedIndex, + _baseBodyList.SelectedIndex, _raceList.SelectedIndex, _tailList.SelectedIndex, _uniqueAuRa.Checked); + } + textureSet.InternalNormalPath = RacePaths.GetBodyTexturePath(1, _genderList.SelectedIndex, + _baseBodyList.SelectedIndex, _raceList.SelectedIndex, _tailList.SelectedIndex, _uniqueAuRa.Checked); + + textureSet.InternalMultiPath = RacePaths.GetBodyTexturePath(2, _genderList.SelectedIndex, + _baseBodyList.SelectedIndex, _raceList.SelectedIndex, _tailList.SelectedIndex, _uniqueAuRa.Checked); + BackupTexturePaths.AddBackupPaths(_genderList.SelectedIndex, _raceList.SelectedIndex, textureSet); + } + + private void AddDecalPath(TextureSet textureSet) + { + textureSet.InternalDiffusePath = RacePaths.GetFaceTexturePath(_faceExtraList.SelectedIndex); + } + + private void AddHairPaths(TextureSet textureSet) + { + textureSet.TextureSetName = _faceParts[_facePartList.SelectedIndex] + " " + (_faceExtraList.SelectedIndex + 1) + + ", " + _genderList.Text + ", " + _races[_raceList.SelectedIndex]; + + textureSet.InternalNormalPath = RacePaths.GetHairTexturePath(1, _faceExtraList.SelectedIndex, + _genderList.SelectedIndex, _raceList.SelectedIndex, _subRaceList.SelectedIndex); + + textureSet.InternalMultiPath = RacePaths.GetHairTexturePath(2, _faceExtraList.SelectedIndex, + _genderList.SelectedIndex, _raceList.SelectedIndex, _subRaceList.SelectedIndex); + } + + private void AddEyePaths(TextureSet textureSet) + { + textureSet.InternalDiffusePath = RacePaths.GetFaceTexturePath(1, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + 2, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + + textureSet.InternalNormalPath = RacePaths.GetFaceTexturePath(2, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + 2, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + + textureSet.InternalMultiPath = RacePaths.GetFaceTexturePath(3, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + 2, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + } + + private void AddFacePaths(TextureSet textureSet) + { + if (_facePartList.SelectedIndex != 1) + { + textureSet.InternalDiffusePath = RacePaths.GetFaceTexturePath(0, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + _facePartList.SelectedIndex, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + } + + textureSet.InternalNormalPath = RacePaths.GetFaceTexturePath(1, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + _facePartList.SelectedIndex, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + + textureSet.InternalMultiPath = RacePaths.GetFaceTexturePath(2, _genderList.SelectedIndex, _subRaceList.SelectedIndex, + _facePartList.SelectedIndex, _faceTypeList.SelectedIndex, _auraFaceScalesDropdown.SelectedIndex, _asymCheckbox.Checked); + + if (_facePartList.SelectedIndex == 0) + { + if (_subRaceList.SelectedIndex == 10 || _subRaceList.SelectedIndex == 11) + { + if (_auraFaceScalesDropdown.SelectedIndex > 0) + { + if (_faceTypeList.SelectedIndex < 4) + { + if (_asymCheckbox.Checked) + { + textureSet.NormalCorrection = Path.Combine(_textureProcessor.BasePath, + @"res\textures\s" + (_genderList.SelectedIndex == 0 ? "m" : "f") + _faceTypeList.SelectedIndex + "a.png"); + } + else + { + textureSet.NormalCorrection = Path.Combine(_textureProcessor.BasePath, + @"res\textures\s" + (_genderList.SelectedIndex == 0 ? "m" : "f") + _faceTypeList.SelectedIndex + ".png"); + } + } + } + } + } + } + + private bool IsRunningUnderWine() + { + return Dalamud.Utility.Util.IsWine(); + } + + + /// + /// Allows file changes to be autmatically pulled in game while the mod tab is open. Function is ignored while running on Wine. + /// + /// + private void AddWatcher(string path) + { + // Check to see if we are running under wine before attempting to watch for files changes. + if (!IsRunningUnderWine()) + { + string directory = Path.GetDirectoryName(path); + if (Directory.Exists(directory) && !string.IsNullOrWhiteSpace(path)) + { + FileSystemWatcher fileSystemWatcher = _watchers.ContainsKey(path) ? _watchers[path] : new FileSystemWatcher(); + fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite; + fileSystemWatcher.Changed += delegate (object sender, FileSystemEventArgs e) + { + if (e.Name.Contains(Path.GetFileName(path))) + { + Task.Run(() => Export(false)); + } + return; + }; + fileSystemWatcher.Path = directory; + fileSystemWatcher.EnableRaisingEvents = !string.IsNullOrEmpty(path); + _watchers[path] = fileSystemWatcher; + } + } + } + + /// + /// Untracks files from being automatically pulled in for changes. Function is ignored while running on Wine. + /// + private void DisposeWatcher(string path, LTCFilePicker filePicker) + { + // Check to see if we are running under wine. + if (!IsRunningUnderWine()) + { + if (!string.IsNullOrWhiteSpace(path)) + { + if (_watchers.ContainsKey(path)) + { + if (path != filePicker.CurrentPath) + { + _watchers[path].Dispose(); + _watchers.Remove(path); + } + } + } + } + } + #endregion + #region Export + public async Task Export(bool finalize) + { + if (!_lockDuplicateGeneration) + { + _exportStatus = "Initializing"; + _lockDuplicateGeneration = true; + List textureSets = new List(); + foreach (TextureSet item in _textureSets) + { + if (item.OmniExportMode) + { + UniversalTextureSetCreator.ConfigureOmniConfiguration(item); + } + textureSets.Add(item); + } + _textureProcessor.CleanGeneratedAssets(_selector.Selected.ModPath.FullName); + await _textureProcessor.Export( + textureSets, + _groupOptionTypes, + _selector.Selected.ModPath.FullName, + _choiceTypeList.SelectedIndex, + _bakeNormals.Checked, + _generateMulti.Checked, + File.Exists(_xNormalPath) && finalize, + _xNormalPath); + + _manager.ReloadMod(_currentMod); + _redrawService.RedrawObject(0, Api.Enums.RedrawType.Redraw); + _lockDuplicateGeneration = false; + } + return true; + } + #endregion + #region Classes + internal class LTCCheckBox + { + string _label = ""; + bool _isChecked = false; + bool _lastValue = false; + bool _enabled = true; + int _width = 100; + public LTCCheckBox(string _label, int width = 100) + { + this._label = _label; + this._width = width; + } + + public bool Enabled { get => _enabled; set => _enabled = value; } + public bool Checked { get => _isChecked; set => _isChecked = value; } + public int Width { get => (_enabled ? (45 + _width) : 0); set => _width = value; } + + public void Draw() + { + if (_enabled) + { + ImGui.Checkbox("##" + _label, ref _isChecked); + ImGui.SameLine(); + ImGui.SetNextItemWidth(_width); + ImGui.LabelText("##labelText" + _label, _label); + } + if (_isChecked != _lastValue) + { + if (OnCheckedChanged != null) + { + OnCheckedChanged.Invoke(this, EventArgs.Empty); + } + } + _lastValue = _isChecked; + } + + public event EventHandler OnCheckedChanged; + } + internal class LTCComboBox + { + string _label; + int _width; + int index = -1; + int _lastIndex; + bool _enabled = true; + string[] _contents; + public event EventHandler OnSelectedIndexChanged; + public string Text { get { return index > -1 ? _contents[index] : ""; } } + public LTCComboBox(string _label, string[] contents, int index, int width = 100) + { + this._label = _label; + this._width = width; + this.index = index; + this._contents = contents; + } + + public string[] Contents { get => _contents; set => _contents = value; } + public int SelectedIndex { get => index; set => index = value; } + public int Width { get => (_enabled ? _width : 0); set => _width = value; } + public string Label { get => _label; set => _label = value; } + public bool Enabled { get => _enabled; set => _enabled = value; } + + public void Draw() + { + if (_enabled) + { + ImGui.SetNextItemWidth(_width); + ImGui.Combo("##" + _label, ref index, _contents, _contents.Length); + } + if (index != _lastIndex) + { + if (OnSelectedIndexChanged != null) + { + OnSelectedIndexChanged.Invoke(this, EventArgs.Empty); + } + } + _lastIndex = index; + } + } + internal class LTCFilePicker + { + string _label = ""; + private Configuration _config; + string _filePath = ""; + private string _currentPath; + bool _enabled = true; + private string _lastText; + private FileDialogService _fileDialog; + private IDragDropManager _dragDrop; + + public LTCFilePicker(string label, Configuration config, FileDialogService fileDialog, IDragDropManager dragDrop) + { + _label = label; + _config = config; + _fileDialog = fileDialog; + _dragDrop = dragDrop; + OnTextChanged += LTCFilePicker_OnTextChanged; + } + + private void LTCFilePicker_OnTextChanged(object? sender, EventArgs e) + { + if (_filePath.ToLower().Contains("basetexbaked")) + { + _currentPath = ""; + _filePath = ""; + Penumbra.Chat.NotificationMessage("Please remove the prefix 'baseTexBaked' from the file name! " + + "\r\n\r\nAlternatively, please use the source image that was used to generate this texture.", + "Invalid File", Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + else if (_filePath.Contains(".")) + { + if (CheckExtentions(_filePath)) + { + if (!_filePath.Contains("_generated") && !_filePath.Contains("-generated")) + { + _currentPath = _filePath; + if (OnFileSelected != null) + { + OnFileSelected.Invoke(this, EventArgs.Empty); + } + } + else + { + _currentPath = ""; + _filePath = ""; + Penumbra.Chat.NotificationMessage("Do not use autogenerated files here.", "Invalid File", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else + { + Penumbra.Chat.NotificationMessage("This is not a file this tool supports.", "Invalid File", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + else if (string.IsNullOrEmpty(_filePath)) + { + _currentPath = ""; + _filePath = ""; + if (OnFileSelected != null) + { + OnFileSelected.Invoke(this, EventArgs.Empty); + } + } + } + + public string CurrentPath + { + get => _currentPath; set + { + string newValue = value != null ? value : ""; + _currentPath = newValue; + _filePath = newValue; + } + } + + public Color BackColor { get; internal set; } + public string LabelName { get => _label; set => _label = value; } + public bool Enabled { get => _enabled; set => _enabled = value; } + public string FilePath { get => _filePath; set => _filePath = value; } + public static bool FileDialogInUse { get; private set; } + + public event EventHandler OnFileSelected; + public event EventHandler OnTextChanged; + public void Draw() + { + if (_enabled) + { + _dragDrop.CreateImGuiSource("TextureDragDrop", m => m.Extensions.Any(e => ValidTextureExtensions.Contains(e.ToLowerInvariant())), m => + { + ImGui.TextUnformatted($"Dragging texture for import:\n\t{string.Join("\n\t", m.Files.Select(Path.GetFileName))}"); + return true; + }); + ImGui.SetNextItemWidth(80); + ImGui.LabelText("##labelText" + _label, _label); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetWindowContentRegionMax().X - 180); + ImGui.InputText("##" + _label, ref _filePath, 256); + if (_dragDrop.CreateImGuiTarget("TextureDragDrop", out var files, out _)) + { + if (ValidTextureExtensions.Contains(Path.GetExtension(files[0]))) + { + _filePath = files[0]; + } + } + ImGui.SameLine(); + + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.BeginDisabled(); + } + if (ImGui.Button("X" + "##" + _label)) + { + _filePath = ""; + } + if (!_config.DeleteModModifier.IsActive()) + { + ImGui.EndDisabled(); + } + ImGui.SameLine(); + if (_filePath != _currentPath) + { + OnTextChanged?.Invoke(this, EventArgs.Empty); + } + + if (ImGui.Button("Select" + "##" + LabelName)) + { + if (_fileDialog != null) + { + _fileDialog.OpenFilePicker("Import Texture", + "Textures{.png,.dds,.bmp,.tex}", (s, f) => + { + if (!s) + { + return; + } + string value = f[0]; + _filePath = value != null ? value : ""; + OnFileSelected?.Invoke(this, EventArgs.Empty); + }, 1, "", false); + } + } + _lastText = _filePath; + } + } + public static bool CheckExtentions(string file) + { + string[] extentions = new string[] { ".png", ".dds", ".bmp", ".tex" }; + foreach (string extention in extentions) + { + if (file.EndsWith(extention)) + { + return true; + } + } + return false; + } + private static readonly string[] ValidTextureExtensions = new[] + { + ".png", + ".dds", + ".bmp", + ".tex", + }; + + } + + + internal class LTCCustomPathConfigurator + { + TextureSet textureSet = new TextureSet(); + string _group = ""; + string _textureSetName = ""; + string _internalDiffusePath = ""; + string _internalNormalPath = ""; + string _internalMultiPath = ""; + LTCCheckBox _ignoreNormals; + LTCCheckBox _ignoreMulti; + LTCCheckBox _invertNormals; + string _normalCorrection = ""; + private LTCComboBox _groupChoiceType = null; + private string _diffuseLabel; + private string _normalLabel; + private string _multiLabel; + private string _errorText = ""; + + private bool _wasValidated; + public event EventHandler OnWantsToClose; + public LTCCustomPathConfigurator(string[] newGroupChoices) + { + List groupChoices = new List(); + groupChoices.Add("Use Global Setting"); + groupChoices.AddRange(newGroupChoices); + + _groupChoiceType = new LTCComboBox("Group Choice Type", groupChoices.ToArray(), 0, 150); + _ignoreNormals = new LTCCheckBox("Ignore Normals", 120); + _ignoreMulti = new LTCCheckBox("Ignore Multi", 120); + _invertNormals = new LTCCheckBox("Invert Normals", 120); + } + + public LTCComboBox GroupingType + { + get { return _groupChoiceType; } + } + public TextureSet TextureSet + { + get => textureSet; + set + { + textureSet = value; + if (textureSet != null) + { + _group = textureSet.GroupName; + _textureSetName = textureSet.TextureSetName; + + _internalDiffusePath = textureSet.InternalDiffusePath != null + ? textureSet.InternalDiffusePath : ""; + + _internalNormalPath = textureSet.InternalNormalPath != null + ? textureSet.InternalNormalPath : ""; + + _internalMultiPath = textureSet.InternalMultiPath != null + ? textureSet.InternalMultiPath : ""; + + _ignoreNormals.Checked = textureSet.IgnoreNormalGeneration; + _ignoreMulti.Checked = textureSet.IgnoreMultiGeneration; + _invertNormals.Checked = textureSet.InvertNormalGeneration; + + _normalCorrection = textureSet.NormalCorrection != null ? textureSet.NormalCorrection : ""; + } + _wasValidated = false; + } + } + + public bool WasValidated { get => _wasValidated; set => _wasValidated = value; } + + public void Draw() + { + int labelWidth = 120; + RefreshLabels(); + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##groupLabelText" + _group, "Group"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##group", ref _group, 256); + + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##groupChoiceType" + _group, "Group Choice Type"); + ImGui.SameLine(); + _groupChoiceType.Draw(); + + ImGui.Dummy(new System.Numerics.Vector2(0, 20)); + + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##textureSetNameLabel" + _textureSetName, "Texture Set Name"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##textureSet", ref _textureSetName, 256); + + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##diffuseLabel" + _diffuseLabel, _diffuseLabel); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##internalDiffuse", ref _internalDiffusePath, 256); + + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##normalLabel" + _normalLabel, _normalLabel); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##internalNormal", ref _internalNormalPath, 256); + + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##multiLabel" + _multiLabel, _multiLabel); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##internalMulti", ref _internalMultiPath, 256); + + if (!textureSet.InternalMultiPath.ToLower().Contains("catchlight")) + { + ImGui.SetNextItemWidth(labelWidth); + ImGui.LabelText("##normalCorrection", "Normal Correction"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X - labelWidth); + ImGui.InputText("##normalCorrection", ref _normalCorrection, 256); + _invertNormals.Draw(); + _ignoreNormals.Draw(); + _ignoreMulti.Draw(); + } + if (ImGui.Button("Confirm Changes")) + { + AcceptChanges(); + } + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + Cancel(); + } + + ImGui.LabelText("##errorText", _errorText); + } + + private void Cancel() + { + _errorText = ""; + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + + public void AcceptChanges() + { + if (!string.IsNullOrWhiteSpace(_textureSetName)) + { + textureSet.GroupName = _group; + textureSet.TextureSetName = _textureSetName; + int validationCount = 0; + if (IsValidGamePathFormat(_internalDiffusePath)) + { + textureSet.InternalDiffusePath = _internalDiffusePath; + validationCount++; + } + else + { + _errorText = "Internal diffuse path is invalid. Make sure an in game path format is being used and that it points to a .tex file!"; + } + if (IsValidGamePathFormat(_internalNormalPath)) + { + textureSet.InternalNormalPath = _internalNormalPath; + validationCount++; + } + else + { + _errorText = "Internal normal path is invalid. Make sure an in game path format is being used and that it points to a .tex file!"; + } + if (IsValidGamePathFormat(_internalMultiPath)) + { + textureSet.InternalMultiPath = _internalMultiPath; + validationCount++; + } + else + { + _errorText = "Internal multi path is invalid. Make sure an in game path format is being used and that it points to a .tex file!"; + } + if (File.Exists(_normalCorrection) || string.IsNullOrEmpty(_normalCorrection)) + { + textureSet.NormalCorrection = _normalCorrection; + validationCount++; + } + else + { + _errorText = "Normal correction path is invalid."; + } + if (validationCount == 4) + { + textureSet.IgnoreNormalGeneration = _ignoreNormals.Checked; + textureSet.IgnoreMultiGeneration = _ignoreMulti.Checked; + textureSet.InvertNormalGeneration = _invertNormals.Checked; + _wasValidated = true; + _errorText = ""; + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + } + else + { + _errorText = "Please enter a name for your texture set!"; + } + } + public void RefreshLabels() + { + if (textureSet.InternalMultiPath != null + && textureSet.InternalMultiPath.ToLower().Contains("catchlight")) + { + _diffuseLabel = "Internal Normal"; + _normalLabel = "Internal Multi"; + _multiLabel = "Internal Catchlight"; + } + else + { + _diffuseLabel = "Internal Diffuse"; + _normalLabel = "Internal Normal"; + _multiLabel = "Internal Multi"; + } + } + public bool IsValidGamePathFormat(string input) + { + if ((input.Contains(@"\") || !input.Contains(".tex")) && !string.IsNullOrWhiteSpace(input)) + { + return false; + } + else + { + return true; + } + } + } + internal class LTCBulkNameReplacement + { + public event EventHandler OnWantsToClose; + private List _textureSets = new List(); + private LTCComboBox _replacementTypeComboBox; + + private string _findBoxText = ""; + private string _replaceBoxText = ""; + private string _errorText = ""; + + private bool _wasValidated = false; + + private string[] _replacementOptions = new string[] + { + "Search And Replace Inside Name", + "Search And Replace Inside Group", + "Find In Name Then Change Whole Group", + "Find In Group Then Change Whole Name", + "Find In Name Then Replace Whole Name ", + "Find In Group Then Change Whole Group" + }; + + public bool WasValidated { get => _wasValidated; set => _wasValidated = value; } + + public LTCBulkNameReplacement(List textureSets) + { + _textureSets = textureSets; + _replacementTypeComboBox = new LTCComboBox("replacementTypeCheckbox", _replacementOptions, 0, 300); + } + public void Draw() + { + ImGui.LabelText("##label1", "Find this"); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X); + ImGui.InputText("##textureSetSearchString", ref _findBoxText, 256); + ImGui.LabelText("##label2", "Replace With this"); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X); + ImGui.InputText("##groupSearchString", ref _replaceBoxText, 256); + _replacementTypeComboBox.Draw(); + ImGui.LabelText("##label3", _errorText); + if (ImGui.Button("Replace All")) + { + ReplaceNames(); + } + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + } + private void ReplaceNames() + { + if (!string.IsNullOrEmpty(_findBoxText)) + { + switch (_replacementTypeComboBox.SelectedIndex) + { + case 0: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.TextureSetName.Contains(_findBoxText)) + { + textureSet.TextureSetName = textureSet.TextureSetName.Replace(_findBoxText, _replaceBoxText); + } + } + break; + case 1: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.GroupName.Contains(_findBoxText)) + { + textureSet.GroupName = textureSet.GroupName.Replace(_findBoxText, _replaceBoxText); + } + } + break; + case 2: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.TextureSetName.Contains(_findBoxText)) + { + textureSet.GroupName = _replaceBoxText; + } + } + break; + case 3: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.GroupName.Contains(_findBoxText)) + { + textureSet.TextureSetName = textureSet.TextureSetName = _replaceBoxText; + } + } + break; + case 4: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.TextureSetName.Contains(_findBoxText)) + { + textureSet.TextureSetName = textureSet.TextureSetName = _replaceBoxText; + } + } + break; + case 5: + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.GroupName.Contains(_findBoxText)) + { + textureSet.GroupName = _replaceBoxText; + } + } + break; + } + _wasValidated = true; + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + else + { + _errorText = "Find text cannot be empty!"; + } + } + } + + internal class LTCFindAndReplace + { + List _textureSets = new List(); + string _textureSetSearchString = ""; + string _groupSearchString = ""; + + LTCFilePicker _diffuse; + LTCFilePicker _normal; + LTCFilePicker _multi; + LTCFilePicker _glow; + LTCFilePicker _mask; + + private bool _wasValidated; + private string _errorText = ""; + + public event EventHandler OnWantsToClose; + + public LTCFindAndReplace(List textureSets, Configuration config, FileDialogService fileDialog, IDragDropManager dragDrop) + { + _diffuse = new LTCFilePicker("Diffuse", config, fileDialog, dragDrop); + _normal = new LTCFilePicker("Normal", config, fileDialog, dragDrop); + _multi = new LTCFilePicker("Multi", config, fileDialog, dragDrop); + _glow = new LTCFilePicker("Glow", config, fileDialog, dragDrop); + _mask = new LTCFilePicker("Mask", config, fileDialog, dragDrop); + _textureSets = textureSets; + } + + public bool IsForEyes { get; internal set; } + public List TextureSets + { + get => _textureSets; + set + { + _textureSets = value; + } + } + + public bool WasValidated { get => _wasValidated; set => _wasValidated = value; } + public string TextureSetSearchString { get => _textureSetSearchString; set => _textureSetSearchString = value; } + public string GroupSearchString { get => _groupSearchString; set => _groupSearchString = value; } + public LTCFilePicker Diffuse { get => _diffuse; set => _diffuse = value; } + public LTCFilePicker Normal { get => _normal; set => _normal = value; } + public LTCFilePicker Multi { get => _multi; set => _multi = value; } + public LTCFilePicker Glow { get => _glow; set => _glow = value; } + public LTCFilePicker Mask { get => _mask; set => _mask = value; } + + public void Draw() + { + RefreshLabels(); + + ImGui.LabelText("##label1", "Find Texture Sets Containing This Name"); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X); + ImGui.InputText("##textureSetSearchString", ref _textureSetSearchString, 256); + ImGui.LabelText("##label2", "And In This Group"); + ImGui.SetNextItemWidth(ImGui.GetContentRegionMax().X); + ImGui.InputText("##groupSearchString", ref _groupSearchString, 256); + ImGui.LabelText("##label3", "Then Replace Files With These"); + + _diffuse.Draw(); + _normal.Draw(); + _multi.Draw(); + _glow.Draw(); + _mask.Draw(); + + if (ImGui.Button("Confirm Replacement")) + { + AcceptChanges(); + } + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + + ImGui.LabelText("##errorText", _errorText); + } + public void AcceptChanges() + { + if (!string.IsNullOrEmpty(_textureSetSearchString)) + { + foreach (TextureSet textureSet in _textureSets) + { + if (textureSet.TextureSetName.ToLower().Contains(_textureSetSearchString.ToLower()) + && textureSet.GroupName.ToLower().Contains(_groupSearchString.ToLower())) + { + if (!string.IsNullOrEmpty(_diffuse.FilePath)) + { + textureSet.Diffuse = _diffuse.FilePath; + } + if (!string.IsNullOrEmpty(_normal.FilePath)) + { + textureSet.Normal = _normal.FilePath; + } + if (!string.IsNullOrEmpty(_multi.FilePath)) + { + textureSet.Multi = _multi.FilePath; + } + if (!string.IsNullOrEmpty(_mask.FilePath)) + { + textureSet.NormalMask = _mask.FilePath; + } + if (!string.IsNullOrEmpty(_glow.FilePath)) + { + textureSet.Glow = _glow.FilePath; + } + } + } + _wasValidated = true; + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + else + { + _errorText = "Search name value cannot be empty!"; + } + } + public void RefreshLabels() + { + if (IsForEyes) + { + _diffuse.LabelName = "Normal"; + _normal.LabelName = "Multi"; + _multi.LabelName = "Catchlight"; + } + else + { + _diffuse.LabelName = "Diffuse"; + _normal.LabelName = "Normal"; + _multi.LabelName = "Multi"; + } + } + } + internal class LTCTemplateConfigurator + { + ModPanelLooseAssetCompilerTab _compilerTab; + private bool _wasValidated; + string _groupName = "Default"; + private List _textureSets; + string _currentPath = ""; + private int _currentGenderIndex; + private int _currentRaceIndex; + + public LTCTemplateConfigurator(ModPanelLooseAssetCompilerTab compilerTab, List textureSets) + { + _compilerTab = compilerTab; + _textureSets = textureSets; + } + + public string GroupName { get => _groupName; set => _groupName = value; } + public bool WasValidated { get => _wasValidated; set => _wasValidated = value; } + + public event EventHandler OnWantsToClose; + public void OpenTemplate(string path, int genderIndex, int raceIndex) + { + _groupName = "Default"; + _currentPath = path; + _currentGenderIndex = genderIndex; + _currentRaceIndex = raceIndex; + } + public void Draw() + { + ImGui.LabelText("##groupNameLabel", "Enter Group Name For Template"); + ImGui.InputText("##groupName", ref _groupName, 256); + if (ImGui.Button("Confirm")) + { + using (StreamReader file = File.OpenText(_currentPath)) + { + JsonSerializer serializer = new JsonSerializer(); + ProjectFile projectFile = (ProjectFile)serializer.Deserialize(file, typeof(ProjectFile)); + if (_textureSets.Count + projectFile.TextureSets.Count < 10510) + { + foreach (TextureSet textureSet in projectFile.TextureSets) + { + if (!_groupName.Contains("Default")) + { + textureSet.GroupName = _groupName; + } + _compilerTab.AddWatcher(textureSet.Diffuse); + _compilerTab.AddWatcher(textureSet.Normal); + _compilerTab.AddWatcher(textureSet.Multi); + _compilerTab.AddWatcher(textureSet.NormalMask); + _compilerTab.AddWatcher(textureSet.Glow); + BackupTexturePaths.AddBackupPaths(_currentGenderIndex, _currentRaceIndex, textureSet); + } + _textureSets.AddRange(projectFile.TextureSets?.ToArray()); + } + else + { + Penumbra.Chat.NotificationMessage("Importing this template will go above the limit of 10510 texture sets", "Failed to import", + Dalamud.Interface.Internal.Notifications.NotificationType.Error); + } + } + _wasValidated = true; + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + OnWantsToClose?.Invoke(this, EventArgs.Empty); + } + } + } + #endregion +} diff --git a/Penumbra/UI/ModsTab/ModPanelTabBar.cs b/Penumbra/UI/ModsTab/ModPanelTabBar.cs index 02ec9a326..97e9e9ef6 100644 --- a/Penumbra/UI/ModsTab/ModPanelTabBar.cs +++ b/Penumbra/UI/ModsTab/ModPanelTabBar.cs @@ -20,13 +20,15 @@ private enum ModPanelTabType Conflicts, Collections, Edit, + AssetCompiler }; public readonly ModPanelSettingsTab Settings; public readonly ModPanelDescriptionTab Description; public readonly ModPanelCollectionsTab Collections; - public readonly ModPanelConflictsTab Conflicts; + public readonly ModPanelConflictsTab Conflicts; public readonly ModPanelChangedItemsTab ChangedItems; + public readonly ModPanelLooseAssetCompilerTab AssetCompiler; public readonly ModPanelEditTab Edit; private readonly ModEditWindow _modEditWindow; private readonly ModManager _modManager; @@ -37,7 +39,7 @@ private enum ModPanelTabType private Mod? _lastMod = null; public ModPanelTabBar(ModEditWindow modEditWindow, ModPanelSettingsTab settings, ModPanelDescriptionTab description, - ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelEditTab edit, ModManager modManager, + ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelLooseAssetCompilerTab assetCompiler, ModPanelEditTab edit, ModManager modManager, TutorialService tutorial, ModPanelCollectionsTab collections) { _modEditWindow = modEditWindow; @@ -49,6 +51,7 @@ public ModPanelTabBar(ModEditWindow modEditWindow, ModPanelSettingsTab settings, _modManager = modManager; _tutorial = tutorial; Collections = collections; + AssetCompiler = assetCompiler; Tabs = new ITab[] { @@ -58,6 +61,7 @@ public ModPanelTabBar(ModEditWindow modEditWindow, ModPanelSettingsTab settings, ChangedItems, Collections, Edit, + AssetCompiler, }; } @@ -88,6 +92,7 @@ private ReadOnlySpan ToLabel(ModPanelTabType type) ModPanelTabType.Conflicts => Conflicts.Label, ModPanelTabType.Collections => Collections.Label, ModPanelTabType.Edit => Edit.Label, + ModPanelTabType.AssetCompiler => AssetCompiler.Label, _ => ReadOnlySpan.Empty, }; @@ -103,8 +108,8 @@ private ModPanelTabType ToType(ReadOnlySpan label) return ModPanelTabType.Conflicts; if (label == Collections.Label) return ModPanelTabType.Collections; - if (label == Edit.Label) - return ModPanelTabType.Edit; + if (label == AssetCompiler.Label) + return ModPanelTabType.AssetCompiler; return 0; } diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index eed5d7c88..42f86e92f 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -28,35 +28,36 @@ }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[2.1.2, )", - "resolved": "2.1.2", - "contentHash": "In0pC521LqJXJXZgFVHegvSzES10KkKRN31McxqA1+fKtKsNe+EShWavBFQnKRlXCdeAmfx/wDjLILbvCaq+8Q==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "5.0.0", - "System.Text.Encoding.CodePages": "5.0.0" - } + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "o0v/J6SJwp3RFrzR29beGx0cK7xcMRgOyIuw8ZNLQyNnBhiyL/vIQKn7cfycthcWUPG3XezUjFwBWzkcUUDFbg==" + }, + "Lumina": { + "type": "Transitive", + "resolved": "3.10.2", + "contentHash": "2bm0s8yh9JsBrK93Pxaq66RkaQdQI5RGVz2c4IFJaRBH3OhueP50O9k0FA4+Z3m7Z+uHMM3Tz6b8qysK9LWvVw==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "7.0.0", "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" }, - "Microsoft.NETCore.Platforms": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" }, - "System.Runtime.CompilerServices.Unsafe": { + "Newtonsoft.Json": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==" + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Text.Encoding.CodePages": { + "System.Drawing.Common": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", "dependencies": { - "Microsoft.NETCore.Platforms": "5.0.0" + "Microsoft.Win32.SystemEvents": "7.0.0" } }, "System.ValueTuple": { @@ -72,6 +73,15 @@ "System.ValueTuple": "4.5.0" } }, + "loosetexturecompilercore": { + "type": "Project", + "dependencies": { + "Lumina": "[3.10.2, )", + "Newtonsoft.Json": "[13.0.3, )", + "SixLabors.ImageSharp": "[3.0.1, )", + "System.Drawing.Common": "[7.0.0, )" + } + }, "ottergui": { "type": "Project" },