Add even more setting changed events and add auto-player-redraw on saving files.

This commit is contained in:
Ottermandias 2024-02-24 14:04:39 +01:00
parent 883580d465
commit 7b0be25f6e
17 changed files with 221 additions and 99 deletions

View file

@ -24,6 +24,7 @@ using Penumbra.Interop.Services;
using Penumbra.UI; using Penumbra.UI;
using TextureType = Penumbra.Api.Enums.TextureType; using TextureType = Penumbra.Api.Enums.TextureType;
using Penumbra.Interop.ResourceTree; using Penumbra.Interop.ResourceTree;
using Penumbra.Mods.Editor;
namespace Penumbra.Api; namespace Penumbra.Api;
@ -142,6 +143,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api); _communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api); _communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
_communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api); _communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api);
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.Api);
} }
public unsafe void Dispose() public unsafe void Dispose()
@ -153,6 +155,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber); _communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber);
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange); _communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase); _communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionEdited);
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
_lumina = null; _lumina = null;
_communicator = null!; _communicator = null!;
_modManager = null!; _modManager = null!;
@ -1277,11 +1281,19 @@ public class PenumbraApi : IDisposable, IPenumbraApi
} }
} }
private void OnModFileChanged(Mod mod, FileRegistry file)
{
if (file.CurrentUsage == 0)
return;
TriggerSettingEdited(mod);
}
private void TriggerSettingEdited(Mod mod) private void TriggerSettingEdited(Mod mod)
{ {
var collection = _collectionResolver.PlayerCollection(); var collection = _collectionResolver.PlayerCollection();
var (settings, parent) = collection[mod.Index]; var (settings, parent) = collection[mod.Index];
if (settings != null) if (settings is { Enabled: true })
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Name, mod.Identifier, parent != collection); ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Name, mod.Identifier, parent != collection);
} }
} }

View file

@ -4,6 +4,7 @@ using OtterGui.Classes;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.Communication; using Penumbra.Communication;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.Services; using Penumbra.Services;
@ -56,6 +57,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionStorage); _communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionStorage);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.CollectionStorage); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.CollectionStorage);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionStorage); _communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionStorage);
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.CollectionStorage);
ReadCollections(out DefaultNamed); ReadCollections(out DefaultNamed);
} }
@ -65,6 +67,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_communicator.ModDiscoveryFinished.Unsubscribe(OnModDiscoveryFinished); _communicator.ModDiscoveryFinished.Unsubscribe(OnModDiscoveryFinished);
_communicator.ModPathChanged.Unsubscribe(OnModPathChange); _communicator.ModPathChanged.Unsubscribe(OnModPathChange);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionChange); _communicator.ModOptionChanged.Unsubscribe(OnModOptionChange);
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
} }
/// <summary> /// <summary>
@ -104,7 +107,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
if (!CanAddCollection(name, out var fixedName)) if (!CanAddCollection(name, out var fixedName))
{ {
Penumbra.Messager.NotificationMessage( Penumbra.Messager.NotificationMessage(
$"The new collection {name} would lead to the same path {fixedName} as one that already exists.", NotificationType.Warning, false); $"The new collection {name} would lead to the same path {fixedName} as one that already exists.", NotificationType.Warning,
false);
return false; return false;
} }
@ -185,20 +189,23 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
if (!IsValidName(name)) if (!IsValidName(name))
{ {
// TODO: handle better. // TODO: handle better.
Penumbra.Messager.NotificationMessage($"Collection of unsupported name found: {name} is not a valid collection name.", NotificationType.Warning); Penumbra.Messager.NotificationMessage($"Collection of unsupported name found: {name} is not a valid collection name.",
NotificationType.Warning);
continue; continue;
} }
if (ByName(name, out _)) if (ByName(name, out _))
{ {
Penumbra.Messager.NotificationMessage($"Duplicate collection found: {name} already exists. Import skipped.", NotificationType.Warning); Penumbra.Messager.NotificationMessage($"Duplicate collection found: {name} already exists. Import skipped.",
NotificationType.Warning);
continue; continue;
} }
var collection = ModCollection.CreateFromData(_saveService, _modStorage, name, version, Count, settings, inheritance); var collection = ModCollection.CreateFromData(_saveService, _modStorage, name, version, Count, settings, inheritance);
var correctName = _saveService.FileNames.CollectionFile(collection); var correctName = _saveService.FileNames.CollectionFile(collection);
if (file.FullName != correctName) if (file.FullName != correctName)
Penumbra.Messager.NotificationMessage($"Collection {file.Name} does not correspond to {collection.Name}.", NotificationType.Warning); Penumbra.Messager.NotificationMessage($"Collection {file.Name} does not correspond to {collection.Name}.",
NotificationType.Warning);
_collections.Add(collection); _collections.Add(collection);
} }
@ -220,7 +227,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
return _collections[^1]; return _collections[^1];
Penumbra.Messager.NotificationMessage( Penumbra.Messager.NotificationMessage(
$"Unknown problem creating a collection with the name {ModCollection.DefaultCollectionName}, which is required to exist.", NotificationType.Error); $"Unknown problem creating a collection with the name {ModCollection.DefaultCollectionName}, which is required to exist.",
NotificationType.Error);
return Count > 1 ? _collections[1] : _collections[0]; return Count > 1 ? _collections[1] : _collections[0];
} }
@ -273,4 +281,18 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); _saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
} }
} }
/// <summary> Update change counters when changing files. </summary>
private void OnModFileChanged(Mod mod, FileRegistry file)
{
if (file.CurrentUsage == 0)
return;
foreach (var collection in this)
{
var (settings, _) = collection[mod.Index];
if (settings is { Enabled: true })
collection.IncrementCounter();
}
}
} }

View file

@ -0,0 +1,28 @@
using OtterGui.Classes;
using Penumbra.Api;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever an existing file in a mod is overwritten by Penumbra.
/// <list type="number">
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter file registry of the changed file. </item>
/// </list> </summary>
public sealed class ModFileChanged()
: EventWrapper<Mod, FileRegistry, ModFileChanged.Priority>(nameof(ModFileChanged))
{
public enum Priority
{
/// <seealso cref="PenumbraApi.OnModFileChanged"/>
Api = int.MinValue,
/// <seealso cref="Interop.Services.RedrawService.OnModFileChanged"/>
RedrawService = -50,
/// <seealso cref="Collections.Manager.CollectionStorage.OnModFileChanged"/>
CollectionStorage = 0,
}
}

View file

@ -47,7 +47,6 @@ public class Configuration : IPluginConfiguration, ISavable
public bool UseNoModsInInspect { get; set; } = false; public bool UseNoModsInInspect { get; set; } = false;
public bool HideChangedItemFilters { get; set; } = false; public bool HideChangedItemFilters { get; set; } = false;
public bool ReplaceNonAsciiOnImport { get; set; } = false; public bool ReplaceNonAsciiOnImport { get; set; } = false;
public bool HidePrioritiesInSelector { get; set; } = false; public bool HidePrioritiesInSelector { get; set; } = false;
public bool HideRedrawBar { get; set; } = false; public bool HideRedrawBar { get; set; } = false;
public int OptionGroupCollapsibleMin { get; set; } = 5; public int OptionGroupCollapsibleMin { get; set; } = 5;

View file

@ -39,6 +39,7 @@ public class EphemeralConfig : ISavable, IDisposable
public bool FixMainWindow { get; set; } = false; public bool FixMainWindow { get; set; } = false;
public string LastModPath { get; set; } = string.Empty; public string LastModPath { get; set; } = string.Empty;
public bool AdvancedEditingOpen { get; set; } = false; public bool AdvancedEditingOpen { get; set; } = false;
public bool ForceRedrawOnFileChange { get; set; } = false;
/// <summary> /// <summary>
/// Load the current configuration. /// Load the current configuration.

View file

@ -8,9 +8,13 @@ using FFXIVClientStructs.FFXIV.Client.Game.Housing;
using FFXIVClientStructs.Interop; using FFXIVClientStructs.Interop;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Services;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Penumbra.Interop.Services; namespace Penumbra.Interop.Services;
@ -106,11 +110,13 @@ public sealed unsafe partial class RedrawService : IDisposable
{ {
private const int FurnitureIdx = 1337; private const int FurnitureIdx = 1337;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly IObjectTable _objects; private readonly IObjectTable _objects;
private readonly ITargetManager _targets; private readonly ITargetManager _targets;
private readonly ICondition _conditions; private readonly ICondition _conditions;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly List<int> _queue = new(100); private readonly List<int> _queue = new(100);
private readonly List<int> _afterGPoseQueue = new(GPoseSlots); private readonly List<int> _afterGPoseQueue = new(GPoseSlots);
@ -127,19 +133,24 @@ public sealed unsafe partial class RedrawService : IDisposable
public event GameObjectRedrawnDelegate? GameObjectRedrawn; public event GameObjectRedrawnDelegate? GameObjectRedrawn;
public RedrawService(IFramework framework, IObjectTable objects, ITargetManager targets, ICondition conditions, IClientState clientState) public RedrawService(IFramework framework, IObjectTable objects, ITargetManager targets, ICondition conditions, IClientState clientState,
Configuration config, CommunicatorService communicator)
{ {
_framework = framework; _framework = framework;
_objects = objects; _objects = objects;
_targets = targets; _targets = targets;
_conditions = conditions; _conditions = conditions;
_clientState = clientState; _clientState = clientState;
_config = config;
_communicator = communicator;
_framework.Update += OnUpdateEvent; _framework.Update += OnUpdateEvent;
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.RedrawService);
} }
public void Dispose() public void Dispose()
{ {
_framework.Update -= OnUpdateEvent; _framework.Update -= OnUpdateEvent;
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
} }
public static DrawState* ActorDrawState(GameObject actor) public static DrawState* ActorDrawState(GameObject actor)
@ -419,4 +430,12 @@ public sealed unsafe partial class RedrawService : IDisposable
gameObject->DisableDraw(); gameObject->DisableDraw();
} }
} }
private void OnModFileChanged(Mod _1, FileRegistry _2)
{
if (!_config.ForceRedrawOnFileChange)
return;
RedrawObject(0, RedrawType.Redraw);
}
} }

View file

@ -1,10 +1,11 @@
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes; using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor; namespace Penumbra.Mods.Editor;
public class ModFileEditor(ModFileCollection files, ModManager modManager) public class ModFileEditor(ModFileCollection files, ModManager modManager, CommunicatorService communicator)
{ {
public bool Changes { get; private set; } public bool Changes { get; private set; }
@ -136,6 +137,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager)
try try
{ {
File.Delete(file.File.FullName); File.Delete(file.File.FullName);
communicator.ModFileChanged.Invoke(mod, file);
Penumbra.Log.Debug($"[DeleteFiles] Deleted {file.File.FullName} from {mod.Name}."); Penumbra.Log.Debug($"[DeleteFiles] Deleted {file.File.FullName} from {mod.Name}.");
++deletions; ++deletions;
} }

View file

@ -42,6 +42,9 @@ public class CommunicatorService : IDisposable, IService
/// <inheritdoc cref="Communication.ModDirectoryChanged"/> /// <inheritdoc cref="Communication.ModDirectoryChanged"/>
public readonly ModDirectoryChanged ModDirectoryChanged = new(); public readonly ModDirectoryChanged ModDirectoryChanged = new();
/// <inheritdoc cref="Communication.ModFileChanged"/>
public readonly ModFileChanged ModFileChanged = new();
/// <inheritdoc cref="Communication.ModPathChanged"/> /// <inheritdoc cref="Communication.ModPathChanged"/>
public readonly ModPathChanged ModPathChanged = new(); public readonly ModPathChanged ModPathChanged = new();

View file

@ -8,39 +8,32 @@ using OtterGui.Compression;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Mods;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.Services;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.AdvancedWindow;
public class FileEditor<T> : IDisposable where T : class, IWritable public class FileEditor<T>(
ModEditWindow owner,
CommunicatorService communicator,
IDataManager gameData,
Configuration config,
FileCompactor compactor,
FileDialogService fileDialog,
string tabName,
string fileType,
Func<IReadOnlyList<FileRegistry>> getFiles,
Func<T, bool, bool> drawEdit,
Func<string> getInitialPath,
Func<byte[], string, bool, T?> parseFile)
: IDisposable
where T : class, IWritable
{ {
private readonly FileDialogService _fileDialog;
private readonly IDataManager _gameData;
private readonly ModEditWindow _owner;
private readonly FileCompactor _compactor;
public FileEditor(ModEditWindow owner, IDataManager gameData, Configuration config, FileCompactor compactor, FileDialogService fileDialog,
string tabName, string fileType, Func<IReadOnlyList<FileRegistry>> getFiles, Func<T, bool, bool> drawEdit, Func<string> getInitialPath,
Func<byte[], string, bool, T?> parseFile)
{
_owner = owner;
_gameData = gameData;
_fileDialog = fileDialog;
_tabName = tabName;
_fileType = fileType;
_drawEdit = drawEdit;
_getInitialPath = getInitialPath;
_parseFile = parseFile;
_compactor = compactor;
_combo = new Combo(config, getFiles);
}
public void Draw() public void Draw()
{ {
using var tab = ImRaii.TabItem(_tabName); using var tab = ImRaii.TabItem(tabName);
if (!tab) if (!tab)
{ {
_quickImport = null; _quickImport = null;
@ -53,12 +46,26 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
ImGui.SameLine(); ImGui.SameLine();
ResetButton(); ResetButton();
ImGui.SameLine(); ImGui.SameLine();
RedrawOnSaveBox();
ImGui.SameLine();
DefaultInput(); DefaultInput();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawFilePanel(); DrawFilePanel();
} }
private void RedrawOnSaveBox()
{
var redraw = config.Ephemeral.ForceRedrawOnFileChange;
if (ImGui.Checkbox("Redraw on Save", ref redraw))
{
config.Ephemeral.ForceRedrawOnFileChange = redraw;
config.Ephemeral.Save();
}
ImGuiUtil.HoverTooltip("Force a redraw of your player character whenever you save a file here.");
}
public void Dispose() public void Dispose()
{ {
(_currentFile as IDisposable)?.Dispose(); (_currentFile as IDisposable)?.Dispose();
@ -67,12 +74,6 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
_defaultFile = null; _defaultFile = null;
} }
private readonly string _tabName;
private readonly string _fileType;
private readonly Func<T, bool, bool> _drawEdit;
private readonly Func<string> _getInitialPath;
private readonly Func<byte[], string, bool, T?> _parseFile;
private FileRegistry? _currentPath; private FileRegistry? _currentPath;
private T? _currentFile; private T? _currentFile;
private Exception? _currentException; private Exception? _currentException;
@ -85,7 +86,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private T? _defaultFile; private T? _defaultFile;
private Exception? _defaultException; private Exception? _defaultException;
private readonly Combo _combo; private readonly Combo _combo = new(config, getFiles);
private ModEditWindow.QuickImportAction? _quickImport; private ModEditWindow.QuickImportAction? _quickImport;
@ -99,16 +100,16 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
{ {
_isDefaultPathUtf8Valid = Utf8GamePath.FromString(_defaultPath, out _defaultPathUtf8, true); _isDefaultPathUtf8Valid = Utf8GamePath.FromString(_defaultPath, out _defaultPathUtf8, true);
_quickImport = null; _quickImport = null;
_fileDialog.Reset(); fileDialog.Reset();
try try
{ {
var file = _gameData.GetFile(_defaultPath); var file = gameData.GetFile(_defaultPath);
if (file != null) if (file != null)
{ {
_defaultException = null; _defaultException = null;
(_defaultFile as IDisposable)?.Dispose(); (_defaultFile as IDisposable)?.Dispose();
_defaultFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file. _defaultFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file.
_defaultFile = _parseFile(file.Data, _defaultPath, false); _defaultFile = parseFile(file.Data, _defaultPath, false);
} }
else else
{ {
@ -126,7 +127,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Export this file.", if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Export this file.",
_defaultFile == null, true)) _defaultFile == null, true))
_fileDialog.OpenSavePicker($"Export {_defaultPath} to...", _fileType, Path.GetFileNameWithoutExtension(_defaultPath), _fileType, fileDialog.OpenSavePicker($"Export {_defaultPath} to...", fileType, Path.GetFileNameWithoutExtension(_defaultPath), fileType,
(success, name) => (success, name) =>
{ {
if (!success) if (!success)
@ -134,16 +135,16 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
try try
{ {
_compactor.WriteAllBytes(name, _defaultFile?.Write() ?? throw new Exception("File invalid.")); compactor.WriteAllBytes(name, _defaultFile?.Write() ?? throw new Exception("File invalid."));
} }
catch (Exception e) catch (Exception e)
{ {
Penumbra.Messager.NotificationMessage(e, $"Could not export {_defaultPath}.", NotificationType.Error); Penumbra.Messager.NotificationMessage(e, $"Could not export {_defaultPath}.", NotificationType.Error);
} }
}, _getInitialPath(), false); }, getInitialPath(), false);
_quickImport ??= _quickImport ??=
ModEditWindow.QuickImportAction.Prepare(_owner, _isDefaultPathUtf8Valid ? _defaultPathUtf8 : Utf8GamePath.Empty, _defaultFile); ModEditWindow.QuickImportAction.Prepare(owner, _isDefaultPathUtf8Valid ? _defaultPathUtf8 : Utf8GamePath.Empty, _defaultFile);
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), new Vector2(ImGui.GetFrameHeight()), if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
$"Add a copy of this file to {_quickImport.OptionName}.", !_quickImport.CanExecute, true)) $"Add a copy of this file to {_quickImport.OptionName}.", !_quickImport.CanExecute, true))
@ -172,7 +173,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private void DrawFileSelectCombo() private void DrawFileSelectCombo()
{ {
if (_combo.Draw("##fileSelect", _currentPath?.RelPath.ToString() ?? $"Select {_fileType} File...", string.Empty, if (_combo.Draw("##fileSelect", _currentPath?.RelPath.ToString() ?? $"Select {fileType} File...", string.Empty,
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()) ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())
&& _combo.CurrentSelection != null) && _combo.CurrentSelection != null)
UpdateCurrentFile(_combo.CurrentSelection); UpdateCurrentFile(_combo.CurrentSelection);
@ -191,7 +192,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
var bytes = File.ReadAllBytes(_currentPath.File.FullName); var bytes = File.ReadAllBytes(_currentPath.File.FullName);
(_currentFile as IDisposable)?.Dispose(); (_currentFile as IDisposable)?.Dispose();
_currentFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file. _currentFile = null; // Avoid double disposal if an exception occurs during the parsing of the new file.
_currentFile = _parseFile(bytes, _currentPath.File.FullName, true); _currentFile = parseFile(bytes, _currentPath.File.FullName, true);
} }
catch (Exception e) catch (Exception e)
{ {
@ -204,9 +205,11 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private void SaveButton() private void SaveButton()
{ {
if (ImGuiUtil.DrawDisabledButton("Save to File", Vector2.Zero, if (ImGuiUtil.DrawDisabledButton("Save to File", Vector2.Zero,
$"Save the selected {_fileType} file with all changes applied. This is not revertible.", !_changed)) $"Save the selected {fileType} file with all changes applied. This is not revertible.", !_changed))
{ {
_compactor.WriteAllBytes(_currentPath!.File.FullName, _currentFile!.Write()); compactor.WriteAllBytes(_currentPath!.File.FullName, _currentFile!.Write());
if (owner.Mod != null)
communicator.ModFileChanged.Invoke(owner.Mod, _currentPath);
_changed = false; _changed = false;
} }
} }
@ -214,7 +217,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private void ResetButton() private void ResetButton()
{ {
if (ImGuiUtil.DrawDisabledButton("Reset Changes", Vector2.Zero, if (ImGuiUtil.DrawDisabledButton("Reset Changes", Vector2.Zero,
$"Reset all changes made to the {_fileType} file.", !_changed)) $"Reset all changes made to the {fileType} file.", !_changed))
{ {
var tmp = _currentPath; var tmp = _currentPath;
_currentPath = null; _currentPath = null;
@ -232,7 +235,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
{ {
if (_currentFile == null) if (_currentFile == null)
{ {
ImGui.TextUnformatted($"Could not parse selected {_fileType} file."); ImGui.TextUnformatted($"Could not parse selected {fileType} file.");
if (_currentException != null) if (_currentException != null)
{ {
using var tab = ImRaii.PushIndent(); using var tab = ImRaii.PushIndent();
@ -242,7 +245,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
else else
{ {
using var id = ImRaii.PushId(0); using var id = ImRaii.PushId(0);
_changed |= _drawEdit(_currentFile, false); _changed |= drawEdit(_currentFile, false);
} }
} }
@ -258,7 +261,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
if (_defaultFile == null) if (_defaultFile == null)
{ {
ImGui.TextUnformatted($"Could not parse provided {_fileType} game file:\n"); ImGui.TextUnformatted($"Could not parse provided {fileType} game file:\n");
if (_defaultException != null) if (_defaultException != null)
{ {
using var tab = ImRaii.PushIndent(); using var tab = ImRaii.PushIndent();
@ -268,7 +271,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
else else
{ {
using var id = ImRaii.PushId(1); using var id = ImRaii.PushId(1);
_drawEdit(_defaultFile, true); drawEdit(_defaultFile, true);
} }
} }
} }
@ -283,7 +286,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
protected override bool DrawSelectable(int globalIdx, bool selected) protected override bool DrawSelectable(int globalIdx, bool selected)
{ {
var file = Items[globalIdx]; var file = Items[globalIdx];
bool ret; bool ret;
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), file.IsOnPlayer)) using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), file.IsOnPlayer))
{ {

View file

@ -80,7 +80,7 @@ public partial class ModEditWindow
return f.SubModUsage.Count == 0 return f.SubModUsage.Count == 0
? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1) ? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1)
: f.SubModUsage.Select(s => (file, s.Item2.ToString(), s.Item1.FullName, : f.SubModUsage.Select(s => (file, s.Item2.ToString(), s.Item1.FullName,
_editor.Option! == s.Item1 && _mod!.HasOptions ? 0x40008000u : 0u)); _editor.Option! == s.Item1 && Mod!.HasOptions ? 0x40008000u : 0u));
}); });
void DrawLine((string, string, string, uint) data) void DrawLine((string, string, string, uint) data)

View file

@ -143,7 +143,7 @@ public partial class ModEditWindow
{ {
if (success) if (success)
tab.LoadShpk(new FullPath(name[0])); tab.LoadShpk(new FullPath(name[0]));
}, 1, _mod!.ModPath.FullName, false); }, 1, Mod!.ModPath.FullName, false);
var moddedPath = tab.FindAssociatedShpk(out var defaultPath, out var gamePath); var moddedPath = tab.FindAssociatedShpk(out var defaultPath, out var gamePath);
ImGui.SameLine(); ImGui.SameLine();

View file

@ -209,7 +209,7 @@ public partial class ModEditWindow
info.Restore(); info.Restore();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(info.Path.FullName[(_mod!.ModPath.FullName.Length + 1)..]); ImGui.TextUnformatted(info.Path.FullName[(Mod!.ModPath.FullName.Length + 1)..]);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.SetNextItemWidth(400 * UiHelpers.Scale); ImGui.SetNextItemWidth(400 * UiHelpers.Scale);
var tmp = info.CurrentMaterials[0]; var tmp = info.CurrentMaterials[0];

View file

@ -60,7 +60,7 @@ public partial class ModEditWindow
CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor.Recombine()); CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor.Recombine());
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Write as TexTools Files")) if (ImGui.Button("Write as TexTools Files"))
_metaFileManager.WriteAllTexToolsMeta(_mod!); _metaFileManager.WriteAllTexToolsMeta(Mod!);
using var child = ImRaii.Child("##meta", -Vector2.One, true); using var child = ImRaii.Child("##meta", -Vector2.One, true);
if (!child) if (!child)

View file

@ -94,7 +94,7 @@ public partial class ModEditWindow
{ {
if (success && paths.Count > 0) if (success && paths.Count > 0)
tab.Import(paths[0]); tab.Import(paths[0]);
}, 1, _mod!.ModPath.FullName, false); }, 1, Mod!.ModPath.FullName, false);
ImGui.SameLine(); ImGui.SameLine();
DrawDocumentationLink(MdlImportDocumentation); DrawDocumentationLink(MdlImportDocumentation);
@ -142,7 +142,7 @@ public partial class ModEditWindow
tab.Export(path, gamePath); tab.Export(path, gamePath);
}, },
_mod!.ModPath.FullName, Mod!.ModPath.FullName,
false false
); );

View file

@ -195,7 +195,7 @@ public partial class ModEditWindow
if (subMod.Files.ContainsKey(gamePath) || subMod.FileSwaps.ContainsKey(gamePath)) if (subMod.Files.ContainsKey(gamePath) || subMod.FileSwaps.ContainsKey(gamePath))
return new QuickImportAction(editor, optionName, gamePath); return new QuickImportAction(editor, optionName, gamePath);
var mod = owner._mod; var mod = owner.Mod;
if (mod == null) if (mod == null)
return new QuickImportAction(editor, optionName, gamePath); return new QuickImportAction(editor, optionName, gamePath);

View file

@ -3,6 +3,7 @@ using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterTex; using OtterTex;
using Penumbra.Import.Textures; using Penumbra.Import.Textures;
using Penumbra.Mods;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.AdvancedWindow;
@ -45,10 +46,10 @@ public partial class ModEditWindow
using (var disabled = ImRaii.Disabled(!_center.SaveTask.IsCompleted)) using (var disabled = ImRaii.Disabled(!_center.SaveTask.IsCompleted))
{ {
TextureDrawer.PathInputBox(_textures, tex, ref tex.TmpPath, "##input", "Import Image...", TextureDrawer.PathInputBox(_textures, tex, ref tex.TmpPath, "##input", "Import Image...",
"Can import game paths as well as your own files.", _mod!.ModPath.FullName, _fileDialog, _config.DefaultModImportPath); "Can import game paths as well as your own files.", Mod!.ModPath.FullName, _fileDialog, _config.DefaultModImportPath);
if (_textureSelectCombo.Draw("##combo", if (_textureSelectCombo.Draw("##combo",
"Select the textures included in this mod on your drive or the ones they replace from the game files.", tex.Path, "Select the textures included in this mod on your drive or the ones they replace from the game files.", tex.Path,
_mod.ModPath.FullName.Length + 1, out var newPath) Mod.ModPath.FullName.Length + 1, out var newPath)
&& newPath != tex.Path) && newPath != tex.Path)
tex.Load(_textures, newPath); tex.Load(_textures, newPath);
@ -84,6 +85,18 @@ public partial class ModEditWindow
ImGuiUtil.SelectableHelpMarker(newDesc); ImGuiUtil.SelectableHelpMarker(newDesc);
} }
}
private void RedrawOnSaveBox()
{
var redraw = _config.Ephemeral.ForceRedrawOnFileChange;
if (ImGui.Checkbox("Redraw on Save", ref redraw))
{
_config.Ephemeral.ForceRedrawOnFileChange = redraw;
_config.Ephemeral.Save();
}
ImGuiUtil.HoverTooltip("Force a redraw of your player character whenever you save a file here.");
} }
private void MipMapInput() private void MipMapInput()
@ -103,6 +116,8 @@ public partial class ModEditWindow
if (_center.IsLoaded) if (_center.IsLoaded)
{ {
RedrawOnSaveBox();
ImGui.SameLine();
SaveAsCombo(); SaveAsCombo();
ImGui.SameLine(); ImGui.SameLine();
MipMapInput(); MipMapInput();
@ -118,6 +133,7 @@ public partial class ModEditWindow
tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs))
{ {
_center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -141,6 +157,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -150,6 +167,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -160,6 +178,7 @@ public partial class ModEditWindow
|| _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
} }
@ -192,6 +211,18 @@ public partial class ModEditWindow
_center.Draw(_textures, imageSize); _center.Draw(_textures, imageSize);
} }
private void InvokeChange(Mod? mod, string path)
{
if (mod == null)
return;
if (!_editor.Files.Tex.FindFirst(r => string.Equals(r.File.FullName, path, StringComparison.OrdinalIgnoreCase),
out var registry))
return;
_communicator.ModFileChanged.Invoke(mod, registry);
}
private void OpenSaveAsDialog(string defaultExtension) private void OpenSaveAsDialog(string defaultExtension)
{ {
var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path);
@ -201,12 +232,13 @@ public partial class ModEditWindow
if (a) if (a)
{ {
_center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, b);
if (b == _left.Path) if (b == _left.Path)
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
else if (b == _right.Path) else if (b == _right.Path)
AddReloadTask(_right.Path, true); AddReloadTask(_right.Path, true);
} }
}, _mod!.ModPath.FullName, _forceTextureStartPath); }, Mod!.ModPath.FullName, _forceTextureStartPath);
_forceTextureStartPath = false; _forceTextureStartPath = false;
} }

View file

@ -49,17 +49,18 @@ public partial class ModEditWindow : Window, IDisposable
private readonly IObjectTable _objects; private readonly IObjectTable _objects;
private readonly CharacterBaseDestructor _characterBaseDestructor; private readonly CharacterBaseDestructor _characterBaseDestructor;
private Mod? _mod;
private Vector2 _iconSize = Vector2.Zero; private Vector2 _iconSize = Vector2.Zero;
private bool _allowReduplicate; private bool _allowReduplicate;
public Mod? Mod { get; private set; }
public void ChangeMod(Mod mod) public void ChangeMod(Mod mod)
{ {
if (mod == _mod) if (mod == Mod)
return; return;
_editor.LoadMod(mod, -1, 0); _editor.LoadMod(mod, -1, 0);
_mod = mod; Mod = mod;
SizeConstraints = new WindowSizeConstraints SizeConstraints = new WindowSizeConstraints
{ {
@ -80,12 +81,12 @@ public partial class ModEditWindow : Window, IDisposable
public void UpdateModels() public void UpdateModels()
{ {
if (_mod != null) if (Mod != null)
_editor.MdlMaterialEditor.ScanModels(_mod); _editor.MdlMaterialEditor.ScanModels(Mod);
} }
public override bool DrawConditions() public override bool DrawConditions()
=> _mod != null; => Mod != null;
public override void PreDraw() public override void PreDraw()
{ {
@ -106,13 +107,13 @@ public partial class ModEditWindow : Window, IDisposable
}); });
var manipulations = 0; var manipulations = 0;
var subMods = 0; var subMods = 0;
var swaps = _mod!.AllSubMods.Sum(m => var swaps = Mod!.AllSubMods.Sum(m =>
{ {
++subMods; ++subMods;
manipulations += m.Manipulations.Count; manipulations += m.Manipulations.Count;
return m.FileSwaps.Count; return m.FileSwaps.Count;
}); });
sb.Append(_mod!.Name); sb.Append(Mod!.Name);
if (subMods > 1) if (subMods > 1)
sb.Append($" | {subMods} Options"); sb.Append($" | {subMods} Options");
@ -271,7 +272,7 @@ public partial class ModEditWindow : Window, IDisposable
ImGui.NewLine(); ImGui.NewLine();
if (ImGui.Button("Remove Missing Files from Mod")) if (ImGui.Button("Remove Missing Files from Mod"))
_editor.FileEditor.RemoveMissingPaths(_mod!, _editor.Option!); _editor.FileEditor.RemoveMissingPaths(Mod!, _editor.Option!);
using var child = ImRaii.Child("##unusedFiles", -Vector2.One, true); using var child = ImRaii.Child("##unusedFiles", -Vector2.One, true);
if (!child) if (!child)
@ -324,8 +325,8 @@ public partial class ModEditWindow : Window, IDisposable
} }
else if (ImGuiUtil.DrawDisabledButton("Re-Duplicate and Normalize Mod", Vector2.Zero, tt, !_allowReduplicate && !modifier)) else if (ImGuiUtil.DrawDisabledButton("Re-Duplicate and Normalize Mod", Vector2.Zero, tt, !_allowReduplicate && !modifier))
{ {
_editor.ModNormalizer.Normalize(_mod!); _editor.ModNormalizer.Normalize(Mod!);
_editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(_mod!, _editor.GroupIdx, _editor.OptionIdx)); _editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.OptionIdx));
} }
if (!_editor.Duplicates.Worker.IsCompleted) if (!_editor.Duplicates.Worker.IsCompleted)
@ -363,7 +364,7 @@ public partial class ModEditWindow : Window, IDisposable
foreach (var (set, size, hash) in _editor.Duplicates.Duplicates.Where(s => s.Paths.Length > 1)) foreach (var (set, size, hash) in _editor.Duplicates.Duplicates.Where(s => s.Paths.Length > 1))
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
using var tree = ImRaii.TreeNode(set[0].FullName[(_mod!.ModPath.FullName.Length + 1)..], using var tree = ImRaii.TreeNode(set[0].FullName[(Mod!.ModPath.FullName.Length + 1)..],
ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGuiTreeNodeFlags.NoTreePushOnOpen);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.RightAlign(Functions.HumanReadableSize(size)); ImGuiUtil.RightAlign(Functions.HumanReadableSize(size));
@ -384,7 +385,7 @@ public partial class ModEditWindow : Window, IDisposable
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, Colors.RedTableBgTint); ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, Colors.RedTableBgTint);
using var node = ImRaii.TreeNode(duplicate.FullName[(_mod!.ModPath.FullName.Length + 1)..], ImGuiTreeNodeFlags.Leaf); using var node = ImRaii.TreeNode(duplicate.FullName[(Mod!.ModPath.FullName.Length + 1)..], ImGuiTreeNodeFlags.Leaf);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, Colors.RedTableBgTint); ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, Colors.RedTableBgTint);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -421,7 +422,7 @@ public partial class ModEditWindow : Window, IDisposable
if (!combo) if (!combo)
return ret; return ret;
foreach (var (option, idx) in _mod!.AllSubMods.WithIndex()) foreach (var (option, idx) in Mod!.AllSubMods.WithIndex())
{ {
using var id = ImRaii.PushId(idx); using var id = ImRaii.PushId(idx);
if (ImGui.Selectable(option.FullName, option == _editor.Option)) if (ImGui.Selectable(option.FullName, option == _editor.Option))
@ -537,10 +538,10 @@ public partial class ModEditWindow : Window, IDisposable
if (currentFile != null) if (currentFile != null)
return currentFile.Value; return currentFile.Value;
if (_mod != null) if (Mod != null)
foreach (var option in _mod.Groups.OrderByDescending(g => g.Priority) foreach (var option in Mod.Groups.OrderByDescending(g => g.Priority)
.SelectMany(g => g.WithIndex().OrderByDescending(o => g.OptionPriority(o.Index)).Select(g => g.Value)) .SelectMany(g => g.WithIndex().OrderByDescending(o => g.OptionPriority(o.Index)).Select(g => g.Value))
.Append(_mod.Default)) .Append(Mod.Default))
{ {
if (option.Files.TryGetValue(path, out var value) || option.FileSwaps.TryGetValue(path, out value)) if (option.Files.TryGetValue(path, out var value) || option.FileSwaps.TryGetValue(path, out value))
return value; return value;
@ -559,8 +560,8 @@ public partial class ModEditWindow : Window, IDisposable
ret.Add(path); ret.Add(path);
} }
if (_mod != null) if (Mod != null)
foreach (var option in _mod.Groups.SelectMany(g => g).Append(_mod.Default)) foreach (var option in Mod.Groups.SelectMany(g => g).Append(Mod.Default))
{ {
foreach (var path in option.Files.Keys) foreach (var path in option.Files.Keys)
{ {
@ -596,15 +597,15 @@ public partial class ModEditWindow : Window, IDisposable
_objects = objects; _objects = objects;
_framework = framework; _framework = framework;
_characterBaseDestructor = characterBaseDestructor; _characterBaseDestructor = characterBaseDestructor;
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl", _materialTab = new FileEditor<MtrlTab>(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty, () => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => Mod?.ModPath.FullName ?? string.Empty,
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable)); (bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
_modelTab = new FileEditor<MdlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Models", ".mdl", _modelTab = new FileEditor<MdlTab>(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Models", ".mdl",
() => PopulateIsOnPlayer(_editor.Files.Mdl, ResourceType.Mdl), DrawModelPanel, () => _mod?.ModPath.FullName ?? string.Empty, () => PopulateIsOnPlayer(_editor.Files.Mdl, ResourceType.Mdl), DrawModelPanel, () => Mod?.ModPath.FullName ?? string.Empty,
(bytes, path, _) => new MdlTab(this, bytes, path)); (bytes, path, _) => new MdlTab(this, bytes, path));
_shaderPackageTab = new FileEditor<ShpkTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Shaders", ".shpk", _shaderPackageTab = new FileEditor<ShpkTab>(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Shaders", ".shpk",
() => PopulateIsOnPlayer(_editor.Files.Shpk, ResourceType.Shpk), DrawShaderPackagePanel, () => PopulateIsOnPlayer(_editor.Files.Shpk, ResourceType.Shpk), DrawShaderPackagePanel,
() => _mod?.ModPath.FullName ?? string.Empty, () => Mod?.ModPath.FullName ?? string.Empty,
(bytes, _, _) => new ShpkTab(_fileDialog, bytes)); (bytes, _, _) => new ShpkTab(_fileDialog, bytes));
_center = new CombinedTexture(_left, _right); _center = new CombinedTexture(_left, _right);
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex)); _textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex));
@ -629,10 +630,10 @@ public partial class ModEditWindow : Window, IDisposable
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2) private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
{ {
if (type is not (ModPathChangeType.Reloaded or ModPathChangeType.Moved) || mod != _mod) if (type is not (ModPathChangeType.Reloaded or ModPathChangeType.Moved) || mod != Mod)
return; return;
_mod = null; Mod = null;
ChangeMod(mod); ChangeMod(mod);
} }
} }