mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Add even more setting changed events and add auto-player-redraw on saving files.
This commit is contained in:
parent
883580d465
commit
7b0be25f6e
17 changed files with 221 additions and 99 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
Penumbra/Communication/ModFileChanged.cs
Normal file
28
Penumbra/Communication/ModFileChanged.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
_owner = owner;
|
||||
_gameData = gameData;
|
||||
_fileDialog = fileDialog;
|
||||
_tabName = tabName;
|
||||
_fileType = fileType;
|
||||
_drawEdit = drawEdit;
|
||||
_getInitialPath = getInitialPath;
|
||||
_parseFile = parseFile;
|
||||
_compactor = compactor;
|
||||
_combo = new Combo(config, getFiles);
|
||||
}
|
||||
|
||||
: IDisposable
|
||||
where T : class, IWritable
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue