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 TextureType = Penumbra.Api.Enums.TextureType;
using Penumbra.Interop.ResourceTree;
using Penumbra.Mods.Editor;
namespace Penumbra.Api;
@ -142,6 +143,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
_communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api);
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.Api);
}
public unsafe void Dispose()
@ -153,6 +155,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber);
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionEdited);
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
_lumina = null;
_communicator = 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)
{
var collection = _collectionResolver.PlayerCollection();
var (settings, parent) = collection[mod.Index];
if (settings != null)
if (settings is { Enabled: true })
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Name, mod.Identifier, parent != collection);
}
}

View file

@ -4,6 +4,7 @@ using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
@ -56,6 +57,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionStorage);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.CollectionStorage);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionStorage);
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.CollectionStorage);
ReadCollections(out DefaultNamed);
}
@ -65,6 +67,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_communicator.ModDiscoveryFinished.Unsubscribe(OnModDiscoveryFinished);
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionChange);
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
}
/// <summary>
@ -104,7 +107,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
if (!CanAddCollection(name, out var fixedName))
{
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;
}
@ -185,20 +189,23 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
if (!IsValidName(name))
{
// 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;
}
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;
}
var collection = ModCollection.CreateFromData(_saveService, _modStorage, name, version, Count, settings, inheritance);
var correctName = _saveService.FileNames.CollectionFile(collection);
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);
}
@ -220,7 +227,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
return _collections[^1];
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];
}
@ -273,4 +281,18 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_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 HideChangedItemFilters { get; set; } = false;
public bool ReplaceNonAsciiOnImport { get; set; } = false;
public bool HidePrioritiesInSelector { get; set; } = false;
public bool HideRedrawBar { get; set; } = false;
public int OptionGroupCollapsibleMin { get; set; } = 5;

View file

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

View file

@ -8,9 +8,13 @@ using FFXIVClientStructs.FFXIV.Client.Game.Housing;
using FFXIVClientStructs.Interop;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Services;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Penumbra.Interop.Services;
@ -111,6 +115,8 @@ public sealed unsafe partial class RedrawService : IDisposable
private readonly ITargetManager _targets;
private readonly ICondition _conditions;
private readonly IClientState _clientState;
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly List<int> _queue = new(100);
private readonly List<int> _afterGPoseQueue = new(GPoseSlots);
@ -127,19 +133,24 @@ public sealed unsafe partial class RedrawService : IDisposable
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;
_objects = objects;
_targets = targets;
_conditions = conditions;
_clientState = clientState;
_config = config;
_communicator = communicator;
_framework.Update += OnUpdateEvent;
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.RedrawService);
}
public void Dispose()
{
_framework.Update -= OnUpdateEvent;
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
}
public static DrawState* ActorDrawState(GameObject actor)
@ -419,4 +430,12 @@ public sealed unsafe partial class RedrawService : IDisposable
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.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
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; }
@ -136,6 +137,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager)
try
{
File.Delete(file.File.FullName);
communicator.ModFileChanged.Invoke(mod, file);
Penumbra.Log.Debug($"[DeleteFiles] Deleted {file.File.FullName} from {mod.Name}.");
++deletions;
}

View file

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

View file

@ -8,39 +8,32 @@ using OtterGui.Compression;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Files;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow;
public class FileEditor<T> : 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,
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
{
_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()
{
using var tab = ImRaii.TabItem(_tabName);
using var tab = ImRaii.TabItem(tabName);
if (!tab)
{
_quickImport = null;
@ -53,12 +46,26 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
ImGui.SameLine();
ResetButton();
ImGui.SameLine();
RedrawOnSaveBox();
ImGui.SameLine();
DefaultInput();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
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()
{
(_currentFile as IDisposable)?.Dispose();
@ -67,12 +74,6 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
_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 T? _currentFile;
private Exception? _currentException;
@ -85,7 +86,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private T? _defaultFile;
private Exception? _defaultException;
private readonly Combo _combo;
private readonly Combo _combo = new(config, getFiles);
private ModEditWindow.QuickImportAction? _quickImport;
@ -99,16 +100,16 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
{
_isDefaultPathUtf8Valid = Utf8GamePath.FromString(_defaultPath, out _defaultPathUtf8, true);
_quickImport = null;
_fileDialog.Reset();
fileDialog.Reset();
try
{
var file = _gameData.GetFile(_defaultPath);
var file = gameData.GetFile(_defaultPath);
if (file != null)
{
_defaultException = null;
(_defaultFile as IDisposable)?.Dispose();
_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
{
@ -126,7 +127,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Export this file.",
_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) =>
{
if (!success)
@ -134,16 +135,16 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
try
{
_compactor.WriteAllBytes(name, _defaultFile?.Write() ?? throw new Exception("File invalid."));
compactor.WriteAllBytes(name, _defaultFile?.Write() ?? throw new Exception("File invalid."));
}
catch (Exception e)
{
Penumbra.Messager.NotificationMessage(e, $"Could not export {_defaultPath}.", NotificationType.Error);
}
}, _getInitialPath(), false);
}, getInitialPath(), false);
_quickImport ??=
ModEditWindow.QuickImportAction.Prepare(_owner, _isDefaultPathUtf8Valid ? _defaultPathUtf8 : Utf8GamePath.Empty, _defaultFile);
ModEditWindow.QuickImportAction.Prepare(owner, _isDefaultPathUtf8Valid ? _defaultPathUtf8 : Utf8GamePath.Empty, _defaultFile);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
$"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()
{
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())
&& _combo.CurrentSelection != null)
UpdateCurrentFile(_combo.CurrentSelection);
@ -191,7 +192,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
var bytes = File.ReadAllBytes(_currentPath.File.FullName);
(_currentFile as IDisposable)?.Dispose();
_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)
{
@ -204,9 +205,11 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private void SaveButton()
{
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;
}
}
@ -214,7 +217,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
private void ResetButton()
{
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;
_currentPath = null;
@ -232,7 +235,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
{
if (_currentFile == null)
{
ImGui.TextUnformatted($"Could not parse selected {_fileType} file.");
ImGui.TextUnformatted($"Could not parse selected {fileType} file.");
if (_currentException != null)
{
using var tab = ImRaii.PushIndent();
@ -242,7 +245,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
else
{
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)
{
ImGui.TextUnformatted($"Could not parse provided {_fileType} game file:\n");
ImGui.TextUnformatted($"Could not parse provided {fileType} game file:\n");
if (_defaultException != null)
{
using var tab = ImRaii.PushIndent();
@ -268,7 +271,7 @@ public class FileEditor<T> : IDisposable where T : class, IWritable
else
{
using var id = ImRaii.PushId(1);
_drawEdit(_defaultFile, true);
drawEdit(_defaultFile, true);
}
}
}

View file

@ -80,7 +80,7 @@ public partial class ModEditWindow
return f.SubModUsage.Count == 0
? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1)
: 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)

View file

@ -143,7 +143,7 @@ public partial class ModEditWindow
{
if (success)
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);
ImGui.SameLine();

View file

@ -209,7 +209,7 @@ public partial class ModEditWindow
info.Restore();
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.SetNextItemWidth(400 * UiHelpers.Scale);
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());
ImGui.SameLine();
if (ImGui.Button("Write as TexTools Files"))
_metaFileManager.WriteAllTexToolsMeta(_mod!);
_metaFileManager.WriteAllTexToolsMeta(Mod!);
using var child = ImRaii.Child("##meta", -Vector2.One, true);
if (!child)

View file

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

View file

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

View file

@ -3,6 +3,7 @@ using OtterGui;
using OtterGui.Raii;
using OtterTex;
using Penumbra.Import.Textures;
using Penumbra.Mods;
using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow;
@ -45,10 +46,10 @@ public partial class ModEditWindow
using (var disabled = ImRaii.Disabled(!_center.SaveTask.IsCompleted))
{
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",
"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)
tex.Load(_textures, newPath);
@ -86,6 +87,18 @@ public partial class ModEditWindow
}
}
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()
{
ImGui.Checkbox("##mipMaps", ref _addMipMaps);
@ -103,6 +116,8 @@ public partial class ModEditWindow
if (_center.IsLoaded)
{
RedrawOnSaveBox();
ImGui.SameLine();
SaveAsCombo();
ImGui.SameLine();
MipMapInput();
@ -118,6 +133,7 @@ public partial class ModEditWindow
tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs))
{
_center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false);
}
@ -141,6 +157,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB))
{
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false);
}
@ -150,6 +167,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB))
{
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false);
}
@ -160,6 +178,7 @@ public partial class ModEditWindow
|| _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB))
{
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path);
AddReloadTask(_left.Path, false);
}
}
@ -192,6 +211,18 @@ public partial class ModEditWindow
_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)
{
var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path);
@ -201,12 +232,13 @@ public partial class ModEditWindow
if (a)
{
_center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, b);
if (b == _left.Path)
AddReloadTask(_left.Path, false);
else if (b == _right.Path)
AddReloadTask(_right.Path, true);
}
}, _mod!.ModPath.FullName, _forceTextureStartPath);
}, Mod!.ModPath.FullName, _forceTextureStartPath);
_forceTextureStartPath = false;
}

View file

@ -49,17 +49,18 @@ public partial class ModEditWindow : Window, IDisposable
private readonly IObjectTable _objects;
private readonly CharacterBaseDestructor _characterBaseDestructor;
private Mod? _mod;
private Vector2 _iconSize = Vector2.Zero;
private bool _allowReduplicate;
public Mod? Mod { get; private set; }
public void ChangeMod(Mod mod)
{
if (mod == _mod)
if (mod == Mod)
return;
_editor.LoadMod(mod, -1, 0);
_mod = mod;
Mod = mod;
SizeConstraints = new WindowSizeConstraints
{
@ -80,12 +81,12 @@ public partial class ModEditWindow : Window, IDisposable
public void UpdateModels()
{
if (_mod != null)
_editor.MdlMaterialEditor.ScanModels(_mod);
if (Mod != null)
_editor.MdlMaterialEditor.ScanModels(Mod);
}
public override bool DrawConditions()
=> _mod != null;
=> Mod != null;
public override void PreDraw()
{
@ -106,13 +107,13 @@ public partial class ModEditWindow : Window, IDisposable
});
var manipulations = 0;
var subMods = 0;
var swaps = _mod!.AllSubMods.Sum(m =>
var swaps = Mod!.AllSubMods.Sum(m =>
{
++subMods;
manipulations += m.Manipulations.Count;
return m.FileSwaps.Count;
});
sb.Append(_mod!.Name);
sb.Append(Mod!.Name);
if (subMods > 1)
sb.Append($" | {subMods} Options");
@ -271,7 +272,7 @@ public partial class ModEditWindow : Window, IDisposable
ImGui.NewLine();
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);
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))
{
_editor.ModNormalizer.Normalize(_mod!);
_editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(_mod!, _editor.GroupIdx, _editor.OptionIdx));
_editor.ModNormalizer.Normalize(Mod!);
_editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.OptionIdx));
}
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))
{
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);
ImGui.TableNextColumn();
ImGuiUtil.RightAlign(Functions.HumanReadableSize(size));
@ -384,7 +385,7 @@ public partial class ModEditWindow : Window, IDisposable
{
ImGui.TableNextColumn();
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.TableSetBgColor(ImGuiTableBgTarget.CellBg, Colors.RedTableBgTint);
ImGui.TableNextColumn();
@ -421,7 +422,7 @@ public partial class ModEditWindow : Window, IDisposable
if (!combo)
return ret;
foreach (var (option, idx) in _mod!.AllSubMods.WithIndex())
foreach (var (option, idx) in Mod!.AllSubMods.WithIndex())
{
using var id = ImRaii.PushId(idx);
if (ImGui.Selectable(option.FullName, option == _editor.Option))
@ -537,10 +538,10 @@ public partial class ModEditWindow : Window, IDisposable
if (currentFile != null)
return currentFile.Value;
if (_mod != null)
foreach (var option in _mod.Groups.OrderByDescending(g => g.Priority)
if (Mod != null)
foreach (var option in Mod.Groups.OrderByDescending(g => g.Priority)
.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))
return value;
@ -559,8 +560,8 @@ public partial class ModEditWindow : Window, IDisposable
ret.Add(path);
}
if (_mod != null)
foreach (var option in _mod.Groups.SelectMany(g => g).Append(_mod.Default))
if (Mod != null)
foreach (var option in Mod.Groups.SelectMany(g => g).Append(Mod.Default))
{
foreach (var path in option.Files.Keys)
{
@ -596,15 +597,15 @@ public partial class ModEditWindow : Window, IDisposable
_objects = objects;
_framework = framework;
_characterBaseDestructor = characterBaseDestructor;
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
_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,
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
_modelTab = new FileEditor<MdlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Models", ".mdl",
() => PopulateIsOnPlayer(_editor.Files.Mdl, ResourceType.Mdl), DrawModelPanel, () => _mod?.ModPath.FullName ?? string.Empty,
_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,
(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,
() => _mod?.ModPath.FullName ?? string.Empty,
() => Mod?.ModPath.FullName ?? string.Empty,
(bytes, _, _) => new ShpkTab(_fileDialog, bytes));
_center = new CombinedTexture(_left, _right);
_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)
{
if (type is not (ModPathChangeType.Reloaded or ModPathChangeType.Moved) || mod != _mod)
if (type is not (ModPathChangeType.Reloaded or ModPathChangeType.Moved) || mod != Mod)
return;
_mod = null;
Mod = null;
ChangeMod(mod);
}
}