mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Start ModManager dissemination....
This commit is contained in:
parent
174e640c45
commit
c8415e3079
34 changed files with 1305 additions and 1542 deletions
|
|
@ -38,7 +38,7 @@ public class IpcTester : IDisposable
|
|||
private readonly ModSettings _modSettings;
|
||||
private readonly Temporary _temporary;
|
||||
|
||||
public IpcTester( DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders )
|
||||
public IpcTester(DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders, Mod.Manager modManager)
|
||||
{
|
||||
_ipcProviders = ipcProviders;
|
||||
_pluginState = new PluginState(pi);
|
||||
|
|
@ -51,7 +51,7 @@ public class IpcTester : IDisposable
|
|||
_meta = new Meta(pi);
|
||||
_mods = new Mods(pi);
|
||||
_modSettings = new ModSettings(pi);
|
||||
_temporary = new Temporary( pi );
|
||||
_temporary = new Temporary(pi, modManager);
|
||||
UnsubscribeEvents();
|
||||
}
|
||||
|
||||
|
|
@ -183,15 +183,11 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Plugin State");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void DrawList(string label, string text, List<DateTimeOffset> list)
|
||||
{
|
||||
|
|
@ -204,12 +200,10 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
ImGui.TextUnformatted(list[^1].LocalDateTime.ToString(CultureInfo.CurrentCulture));
|
||||
if (list.Count > 1 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(string.Join("\n",
|
||||
list.SkipLast(1).Select(t => t.LocalDateTime.ToString(CultureInfo.CurrentCulture))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawList(Ipc.Initialized.Label, "Last Initialized", _initializedList);
|
||||
DrawList(Ipc.Disposed.Label, "Last Disposed", _disposedList);
|
||||
|
|
@ -252,15 +246,11 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Configuration");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetModDirectory.Label, "Current Mod Directory");
|
||||
ImGui.TextUnformatted(Ipc.GetModDirectory.Subscriber(_pi).Invoke());
|
||||
|
|
@ -290,11 +280,9 @@ public class IpcTester : IDisposable
|
|||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateModDirectoryChanged(string path, bool valid)
|
||||
=> (_lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime) = (path, valid, DateTimeOffset.Now);
|
||||
|
|
@ -331,30 +319,22 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("UI");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var combo = ImRaii.Combo("Tab to Open at", _selectTab.ToString()))
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var val in Enum.GetValues<TabType>())
|
||||
{
|
||||
if (ImGui.Selectable(val.ToString(), _selectTab == val))
|
||||
{
|
||||
_selectTab = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##openMod", "Mod to Open at...", ref _modName, 256);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.PostSettingsDraw.Label, "Last Drawn Mod");
|
||||
ImGui.TextUnformatted(_lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None");
|
||||
|
|
@ -363,14 +343,10 @@ public class IpcTester : IDisposable
|
|||
if (ImGui.Checkbox("##tooltip", ref _subscribedToTooltip))
|
||||
{
|
||||
if (_subscribedToTooltip)
|
||||
{
|
||||
Tooltip.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Tooltip.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastHovered);
|
||||
|
|
@ -379,32 +355,24 @@ public class IpcTester : IDisposable
|
|||
if (ImGui.Checkbox("##click", ref _subscribedToClick))
|
||||
{
|
||||
if (_subscribedToClick)
|
||||
{
|
||||
Click.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Click.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastClicked);
|
||||
DrawIntro(Ipc.OpenMainWindow.Label, "Open Mod Window");
|
||||
if (ImGui.Button("Open##window"))
|
||||
{
|
||||
_ec = Ipc.OpenMainWindow.Subscriber(_pi).Invoke(_selectTab, _modName, _modName);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_ec.ToString());
|
||||
|
||||
DrawIntro(Ipc.CloseMainWindow.Label, "Close Mod Window");
|
||||
if (ImGui.Button("Close##window"))
|
||||
{
|
||||
Ipc.CloseMainWindow.Subscriber(_pi).Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLastDrawnMod(string name)
|
||||
=> (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
|
||||
|
|
@ -440,50 +408,36 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Redrawing");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RedrawObjectByName.Label, "Redraw by Name");
|
||||
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
|
||||
ImGui.InputTextWithHint("##redrawName", "Name...", ref _redrawName, 32);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Redraw##Name"))
|
||||
{
|
||||
Ipc.RedrawObjectByName.Subscriber(_pi).Invoke(_redrawName, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RedrawObject.Label, "Redraw Player Character");
|
||||
if (ImGui.Button("Redraw##pc") && DalamudServices.SClientState.LocalPlayer != null)
|
||||
{
|
||||
Ipc.RedrawObject.Subscriber(_pi).Invoke(DalamudServices.SClientState.LocalPlayer, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RedrawObjectByIndex.Label, "Redraw by Index");
|
||||
var tmp = _redrawIndex;
|
||||
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
|
||||
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, DalamudServices.SObjects.Length))
|
||||
{
|
||||
_redrawIndex = Math.Clamp(tmp, 0, DalamudServices.SObjects.Length);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Redraw##Index"))
|
||||
{
|
||||
Ipc.RedrawObjectByIndex.Subscriber(_pi).Invoke(_redrawIndex, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RedrawAll.Label, "Redraw All");
|
||||
if (ImGui.Button("Redraw##All"))
|
||||
{
|
||||
Ipc.RedrawAll.Subscriber(_pi).Invoke(RedrawType.Redraw);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GameObjectRedrawn.Label, "Last Redrawn Object:");
|
||||
ImGui.TextUnformatted(_lastRedrawnString);
|
||||
|
|
@ -491,10 +445,11 @@ public class IpcTester : IDisposable
|
|||
|
||||
private void SetLastRedrawn(IntPtr address, int index)
|
||||
{
|
||||
if( index < 0 || index > DalamudServices.SObjects.Length || address == IntPtr.Zero || DalamudServices.SObjects[ index ]?.Address != address )
|
||||
{
|
||||
if (index < 0
|
||||
|| index > DalamudServices.SObjects.Length
|
||||
|| address == IntPtr.Zero
|
||||
|| DalamudServices.SObjects[index]?.Address != address)
|
||||
_lastRedrawnString = "Invalid";
|
||||
}
|
||||
|
||||
_lastRedrawnString = $"{DalamudServices.SObjects[index]!.Name} (0x{address:X}, {index})";
|
||||
}
|
||||
|
|
@ -531,24 +486,19 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Game State");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.InputTextWithHint("##drawObject", "Draw Object Address..", ref _currentDrawObjectString, 16,
|
||||
ImGuiInputTextFlags.CharsHexadecimal))
|
||||
{
|
||||
_currentDrawObject = IntPtr.TryParse( _currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var tmp )
|
||||
_currentDrawObject = IntPtr.TryParse(_currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
||||
out var tmp)
|
||||
? tmp
|
||||
: IntPtr.Zero;
|
||||
}
|
||||
|
||||
ImGui.InputInt("Cutscene Actor", ref _currentCutsceneActor, 0);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetDrawObjectInfo.Label, "Draw Object Info");
|
||||
if (_currentDrawObject == IntPtr.Zero)
|
||||
|
|
@ -566,19 +516,15 @@ public class IpcTester : IDisposable
|
|||
|
||||
DrawIntro(Ipc.CreatingCharacterBase.Label, "Last Drawobject created");
|
||||
if (_lastCreatedGameObjectTime < DateTimeOffset.Now)
|
||||
{
|
||||
ImGui.TextUnformatted(_lastCreatedDrawObject != IntPtr.Zero
|
||||
? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
|
||||
: $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}");
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GameObjectResourcePathResolved.Label, "Last GamePath resolved");
|
||||
if (_lastResolvedGamePathTime < DateTimeOffset.Now)
|
||||
{
|
||||
ImGui.TextUnformatted(
|
||||
$"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLastCreated(IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4)
|
||||
{
|
||||
|
|
@ -626,9 +572,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Resolving");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##resolvePath", "Resolve this game path...", ref _currentResolvePath, Utf8GamePath.MaxGamePathLength);
|
||||
ImGui.InputTextWithHint("##resolveCharacter", "Character Name (leave blank for default)...", ref _currentResolveCharacter, 32);
|
||||
|
|
@ -637,39 +581,27 @@ public class IpcTester : IDisposable
|
|||
ImGui.InputInt("##resolveIdx", ref _currentReverseIdx, 0, 0);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolveDefaultPath.Label, "Default Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
{
|
||||
ImGui.TextUnformatted(Ipc.ResolveDefaultPath.Subscriber(_pi).Invoke(_currentResolvePath));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolveInterfacePath.Label, "Interface Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
{
|
||||
ImGui.TextUnformatted(Ipc.ResolveInterfacePath.Subscriber(_pi).Invoke(_currentResolvePath));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolvePlayerPath.Label, "Player Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
{
|
||||
ImGui.TextUnformatted(Ipc.ResolvePlayerPath.Subscriber(_pi).Invoke(_currentResolvePath));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolveCharacterPath.Label, "Character Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0 && _currentResolveCharacter.Length != 0)
|
||||
{
|
||||
ImGui.TextUnformatted(Ipc.ResolveCharacterPath.Subscriber(_pi).Invoke(_currentResolvePath, _currentResolveCharacter));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolveGameObjectPath.Label, "Game Object Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
{
|
||||
ImGui.TextUnformatted(Ipc.ResolveGameObjectPath.Subscriber(_pi).Invoke(_currentResolvePath, _currentReverseIdx));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ReverseResolvePath.Label, "Reversed Game Paths");
|
||||
if (_currentReversePath.Length > 0)
|
||||
|
|
@ -679,11 +611,9 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
ImGui.TextUnformatted(list[0]);
|
||||
if (list.Length > 1 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ReverseResolvePlayerPath.Label, "Reversed Game Paths (Player)");
|
||||
if (_currentReversePath.Length > 0)
|
||||
|
|
@ -693,11 +623,9 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
ImGui.TextUnformatted(list[0]);
|
||||
if (list.Length > 1 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)");
|
||||
if (_currentReversePath.Length > 0)
|
||||
|
|
@ -707,30 +635,34 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
ImGui.TextUnformatted(list[0]);
|
||||
if (list.Length > 1 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ResolvePlayerPaths.Label, "Resolved Paths (Player)");
|
||||
if (_currentResolvePath.Length > 0 || _currentReversePath.Length > 0)
|
||||
{
|
||||
var forwardArray = _currentResolvePath.Length > 0 ? new[] { _currentResolvePath } : Array.Empty< string >();
|
||||
var reverseArray = _currentReversePath.Length > 0 ? new[] { _currentReversePath } : Array.Empty< string >();
|
||||
var forwardArray = _currentResolvePath.Length > 0
|
||||
? new[]
|
||||
{
|
||||
_currentResolvePath,
|
||||
}
|
||||
: Array.Empty<string>();
|
||||
var reverseArray = _currentReversePath.Length > 0
|
||||
? new[]
|
||||
{
|
||||
_currentReversePath,
|
||||
}
|
||||
: Array.Empty<string>();
|
||||
var ret = Ipc.ResolvePlayerPaths.Subscriber(_pi).Invoke(forwardArray, reverseArray);
|
||||
var text = string.Empty;
|
||||
if (ret.Item1.Length > 0)
|
||||
{
|
||||
if (ret.Item2.Length > 0)
|
||||
{
|
||||
text = $"Forward: {ret.Item1[0]} | Reverse: {string.Join("; ", ret.Item2[0])}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = $"Forward: {ret.Item1[0]}.";
|
||||
}
|
||||
}
|
||||
else if (ret.Item2.Length > 0)
|
||||
{
|
||||
text = $"Reverse: {string.Join("; ", ret.Item2[0])}.";
|
||||
|
|
@ -765,9 +697,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Collections");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiUtil.GenericEnumCombo("Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName());
|
||||
ImGui.InputInt("Object Index##Collections", ref _objectIdx, 0, 0);
|
||||
|
|
@ -778,15 +708,11 @@ public class IpcTester : IDisposable
|
|||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro("Last Return Code", _returnCode.ToString());
|
||||
if (_oldCollection != null)
|
||||
{
|
||||
ImGui.TextUnformatted(_oldCollection.Length == 0 ? "Created" : _oldCollection);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetCurrentCollectionName.Label, "Current Collection");
|
||||
ImGui.TextUnformatted(Ipc.GetCurrentCollectionName.Subscriber(_pi).Invoke());
|
||||
|
|
@ -813,9 +739,8 @@ public class IpcTester : IDisposable
|
|||
ImGui.TextUnformatted(name.Length == 0 ? "Unassigned" : name);
|
||||
DrawIntro(Ipc.SetCollectionForType.Label, "Set Special Collection");
|
||||
if (ImGui.Button("Set##TypeCollection"))
|
||||
{
|
||||
( _returnCode, _oldCollection ) = Ipc.SetCollectionForType.Subscriber( _pi ).Invoke( _type, _collectionName, _allowCreation, _allowDeletion );
|
||||
}
|
||||
(_returnCode, _oldCollection) =
|
||||
Ipc.SetCollectionForType.Subscriber(_pi).Invoke(_type, _collectionName, _allowCreation, _allowDeletion);
|
||||
|
||||
DrawIntro(Ipc.GetCollectionForObject.Label, "Get Object Collection");
|
||||
(var valid, var individual, name) = Ipc.GetCollectionForObject.Subscriber(_pi).Invoke(_objectIdx);
|
||||
|
|
@ -823,14 +748,11 @@ public class IpcTester : IDisposable
|
|||
$"{(valid ? "Valid" : "Invalid")} Object, {(name.Length == 0 ? "Unassigned" : name)}{(individual ? " (Individual Assignment)" : string.Empty)}");
|
||||
DrawIntro(Ipc.SetCollectionForObject.Label, "Set Object Collection");
|
||||
if (ImGui.Button("Set##ObjectCollection"))
|
||||
{
|
||||
( _returnCode, _oldCollection ) = Ipc.SetCollectionForObject.Subscriber( _pi ).Invoke( _objectIdx, _collectionName, _allowCreation, _allowDeletion );
|
||||
}
|
||||
(_returnCode, _oldCollection) = Ipc.SetCollectionForObject.Subscriber(_pi)
|
||||
.Invoke(_objectIdx, _collectionName, _allowCreation, _allowDeletion);
|
||||
|
||||
if (_returnCode == PenumbraApiEc.NothingChanged && _oldCollection.IsNullOrEmpty())
|
||||
{
|
||||
_oldCollection = null;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetChangedItems.Label, "Changed Item List");
|
||||
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
|
||||
|
|
@ -851,41 +773,29 @@ public class IpcTester : IDisposable
|
|||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Changed Item List");
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in _changedItems)
|
||||
{
|
||||
ImGui.TextUnformatted(item.Key);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCollectionPopup()
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Collections");
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var collection in _collections)
|
||||
{
|
||||
ImGui.TextUnformatted(collection);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Meta
|
||||
{
|
||||
|
|
@ -901,17 +811,13 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Meta");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##characterName", "Character Name...", ref _characterName, 64);
|
||||
ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetMetaManipulations.Label, "Meta Manipulations");
|
||||
if (ImGui.Button("Copy to Clipboard"))
|
||||
|
|
@ -986,18 +892,14 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Mods");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100);
|
||||
ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100);
|
||||
ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetMods.Label, "Mods");
|
||||
if (ImGui.Button("Get##Mods"))
|
||||
|
|
@ -1008,27 +910,21 @@ public class IpcTester : IDisposable
|
|||
|
||||
DrawIntro(Ipc.ReloadMod.Label, "Reload Mod");
|
||||
if (ImGui.Button("Reload"))
|
||||
{
|
||||
_lastReloadEc = Ipc.ReloadMod.Subscriber(_pi).Invoke(_modDirectory, _modName);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastReloadEc.ToString());
|
||||
|
||||
DrawIntro(Ipc.AddMod.Label, "Add Mod");
|
||||
if (ImGui.Button("Add"))
|
||||
{
|
||||
_lastAddEc = Ipc.AddMod.Subscriber(_pi).Invoke(_modDirectory);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastAddEc.ToString());
|
||||
|
||||
DrawIntro(Ipc.DeleteMod.Label, "Delete Mod");
|
||||
if (ImGui.Button("Delete"))
|
||||
{
|
||||
_lastDeleteEc = Ipc.DeleteMod.Subscriber(_pi).Invoke(_modDirectory, _modName);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastDeleteEc.ToString());
|
||||
|
|
@ -1039,30 +935,22 @@ public class IpcTester : IDisposable
|
|||
|
||||
DrawIntro(Ipc.SetModPath.Label, "Set Path");
|
||||
if (ImGui.Button("Set"))
|
||||
{
|
||||
_lastSetPathEc = Ipc.SetModPath.Subscriber(_pi).Invoke(_modDirectory, _modName, _pathInput);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastSetPathEc.ToString());
|
||||
|
||||
DrawIntro(Ipc.ModDeleted.Label, "Last Mod Deleted");
|
||||
if (_lastDeletedModTime > DateTimeOffset.UnixEpoch)
|
||||
{
|
||||
ImGui.TextUnformatted($"{_lastDeletedMod} at {_lastDeletedModTime}");
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ModAdded.Label, "Last Mod Added");
|
||||
if (_lastAddedModTime > DateTimeOffset.UnixEpoch)
|
||||
{
|
||||
ImGui.TextUnformatted($"{_lastAddedMod} at {_lastAddedModTime}");
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.ModMoved.Label, "Last Mod Moved");
|
||||
if (_lastMovedModTime > DateTimeOffset.UnixEpoch)
|
||||
{
|
||||
ImGui.TextUnformatted($"{_lastMovedModFrom} -> {_lastMovedModTo} at {_lastMovedModTime}");
|
||||
}
|
||||
|
||||
DrawModsPopup();
|
||||
}
|
||||
|
|
@ -1072,21 +960,15 @@ public class IpcTester : IDisposable
|
|||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Mods");
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (modDir, modName) in _mods)
|
||||
{
|
||||
ImGui.TextUnformatted($"{modDir}: {modName}");
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ModSettings
|
||||
{
|
||||
|
|
@ -1120,9 +1002,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Mod Settings");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100);
|
||||
ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100);
|
||||
|
|
@ -1131,9 +1011,7 @@ public class IpcTester : IDisposable
|
|||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro("Last Error", _lastSettingsError.ToString());
|
||||
DrawIntro(Ipc.ModSettingChanged.Label, "Last Mod Setting Changed");
|
||||
|
|
@ -1151,7 +1029,8 @@ public class IpcTester : IDisposable
|
|||
DrawIntro(Ipc.GetCurrentModSettings.Label, "Get Current Settings");
|
||||
if (ImGui.Button("Get##Current"))
|
||||
{
|
||||
var ret = Ipc.GetCurrentModSettings.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName, _settingsAllowInheritance );
|
||||
var ret = Ipc.GetCurrentModSettings.Subscriber(_pi)
|
||||
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsAllowInheritance);
|
||||
_lastSettingsError = ret.Item1;
|
||||
if (ret.Item1 == PenumbraApiEc.Success)
|
||||
{
|
||||
|
|
@ -1170,40 +1049,33 @@ public class IpcTester : IDisposable
|
|||
ImGui.Checkbox("##inherit", ref _settingsInherit);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Inherit"))
|
||||
{
|
||||
_lastSettingsError = Ipc.TryInheritMod.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName, _settingsInherit );
|
||||
}
|
||||
_lastSettingsError = Ipc.TryInheritMod.Subscriber(_pi)
|
||||
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsInherit);
|
||||
|
||||
DrawIntro(Ipc.TrySetMod.Label, "Set Enabled");
|
||||
ImGui.Checkbox("##enabled", ref _settingsEnabled);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Enabled"))
|
||||
{
|
||||
_lastSettingsError = Ipc.TrySetMod.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName, _settingsEnabled );
|
||||
}
|
||||
_lastSettingsError = Ipc.TrySetMod.Subscriber(_pi)
|
||||
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsEnabled);
|
||||
|
||||
DrawIntro(Ipc.TrySetModPriority.Label, "Set Priority");
|
||||
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
|
||||
ImGui.DragInt("##Priority", ref _settingsPriority);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Priority"))
|
||||
{
|
||||
_lastSettingsError = Ipc.TrySetModPriority.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName, _settingsPriority );
|
||||
}
|
||||
_lastSettingsError = Ipc.TrySetModPriority.Subscriber(_pi)
|
||||
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsPriority);
|
||||
|
||||
DrawIntro(Ipc.CopyModSettings.Label, "Copy Mod Settings");
|
||||
if (ImGui.Button("Copy Settings"))
|
||||
{
|
||||
_lastSettingsError = Ipc.CopyModSettings.Subscriber(_pi).Invoke(_settingsCollection, _settingsModDirectory, _settingsModName);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip("Copy settings from Mod Directory Name to Mod Name (as directory) in collection.");
|
||||
|
||||
DrawIntro(Ipc.TrySetModSetting.Label, "Set Setting(s)");
|
||||
if (_availableSettings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (group, (list, type)) in _availableSettings)
|
||||
{
|
||||
|
|
@ -1218,48 +1090,36 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
current = new List<string>();
|
||||
if (_currentSettings != null)
|
||||
{
|
||||
_currentSettings[group] = current;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
|
||||
using (var c = ImRaii.Combo("##group", preview))
|
||||
{
|
||||
if (c)
|
||||
{
|
||||
foreach (var s in list)
|
||||
{
|
||||
var contained = current.Contains(s);
|
||||
if (ImGui.Checkbox(s, ref contained))
|
||||
{
|
||||
if (contained)
|
||||
{
|
||||
current.Add(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##setting"))
|
||||
{
|
||||
if (type == GroupType.Single)
|
||||
{
|
||||
_lastSettingsError = Ipc.TrySetModSetting.Subscriber(_pi).Invoke(_settingsCollection,
|
||||
_settingsModDirectory, _settingsModName, group, current.Count > 0 ? current[0] : string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastSettingsError = Ipc.TrySetModSettings.Subscriber(_pi).Invoke(_settingsCollection,
|
||||
_settingsModDirectory, _settingsModName, group, current.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(group);
|
||||
|
|
@ -1278,10 +1138,14 @@ public class IpcTester : IDisposable
|
|||
|
||||
private class Temporary
|
||||
{
|
||||
public readonly DalamudPluginInterface _pi;
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly Mod.Manager _modManager;
|
||||
|
||||
public Temporary( DalamudPluginInterface pi )
|
||||
=> _pi = pi;
|
||||
public Temporary(DalamudPluginInterface pi, Mod.Manager modManager)
|
||||
{
|
||||
_pi = pi;
|
||||
_modManager = modManager;
|
||||
}
|
||||
|
||||
public string LastCreatedCollectionName = string.Empty;
|
||||
|
||||
|
|
@ -1299,9 +1163,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var _ = ImRaii.TreeNode("Temporary");
|
||||
if (!_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128);
|
||||
ImGui.InputTextWithHint("##tempCollectionChar", "Collection Character...", ref _tempCharacterName, 32);
|
||||
|
|
@ -1315,53 +1177,41 @@ public class IpcTester : IDisposable
|
|||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIntro("Last Error", _lastTempError.ToString());
|
||||
DrawIntro("Last Created Collection", LastCreatedCollectionName);
|
||||
DrawIntro(Ipc.CreateTemporaryCollection.Label, "Create Temporary Collection");
|
||||
#pragma warning disable 0612
|
||||
if (ImGui.Button("Create##Collection"))
|
||||
{
|
||||
( _lastTempError, LastCreatedCollectionName ) = Ipc.CreateTemporaryCollection.Subscriber( _pi ).Invoke( _tempCollectionName, _tempCharacterName, _forceOverwrite );
|
||||
}
|
||||
(_lastTempError, LastCreatedCollectionName) = Ipc.CreateTemporaryCollection.Subscriber(_pi)
|
||||
.Invoke(_tempCollectionName, _tempCharacterName, _forceOverwrite);
|
||||
|
||||
DrawIntro(Ipc.CreateNamedTemporaryCollection.Label, "Create Named Temporary Collection");
|
||||
if (ImGui.Button("Create##NamedCollection"))
|
||||
{
|
||||
_lastTempError = Ipc.CreateNamedTemporaryCollection.Subscriber(_pi).Invoke(_tempCollectionName);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RemoveTemporaryCollection.Label, "Remove Temporary Collection from Character");
|
||||
if (ImGui.Button("Delete##Collection"))
|
||||
{
|
||||
_lastTempError = Ipc.RemoveTemporaryCollection.Subscriber(_pi).Invoke(_tempCharacterName);
|
||||
}
|
||||
#pragma warning restore 0612
|
||||
DrawIntro(Ipc.RemoveTemporaryCollectionByName.Label, "Remove Temporary Collection");
|
||||
if (ImGui.Button("Delete##NamedCollection"))
|
||||
{
|
||||
_lastTempError = Ipc.RemoveTemporaryCollectionByName.Subscriber(_pi).Invoke(_tempCollectionName);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.AssignTemporaryCollection.Label, "Assign Temporary Collection");
|
||||
if (ImGui.Button("Assign##NamedCollection"))
|
||||
{
|
||||
_lastTempError = Ipc.AssignTemporaryCollection.Subscriber(_pi).Invoke(_tempCollectionName, _tempActorIndex, _forceOverwrite);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.AddTemporaryMod.Label, "Add Temporary Mod to specific Collection");
|
||||
if (ImGui.Button("Add##Mod"))
|
||||
{
|
||||
_lastTempError = Ipc.AddTemporaryMod.Subscriber(_pi).Invoke(_tempModName, _tempCollectionName,
|
||||
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
|
||||
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.CreateTemporaryCollection.Label, "Copy Existing Collection");
|
||||
if( ImGuiUtil.DrawDisabledButton( "Copy##Collection", Vector2.Zero, "Copies the effective list from the collection named in Temporary Mod Name...",
|
||||
if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
|
||||
"Copies the effective list from the collection named in Temporary Mod Name...",
|
||||
!Penumbra.CollectionManager.ByName(_tempModName, out var copyCollection))
|
||||
&& copyCollection is { HasCache: true })
|
||||
{
|
||||
|
|
@ -1373,51 +1223,43 @@ public class IpcTester : IDisposable
|
|||
|
||||
DrawIntro(Ipc.AddTemporaryModAll.Label, "Add Temporary Mod to all Collections");
|
||||
if (ImGui.Button("Add##All"))
|
||||
{
|
||||
_lastTempError = Ipc.AddTemporaryModAll.Subscriber( _pi ).Invoke( _tempModName, new Dictionary< string, string > { { _tempGamePath, _tempFilePath } },
|
||||
_lastTempError = Ipc.AddTemporaryModAll.Subscriber(_pi).Invoke(_tempModName,
|
||||
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
|
||||
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RemoveTemporaryMod.Label, "Remove Temporary Mod from specific Collection");
|
||||
if (ImGui.Button("Remove##Mod"))
|
||||
{
|
||||
_lastTempError = Ipc.RemoveTemporaryMod.Subscriber(_pi).Invoke(_tempModName, _tempCollectionName, int.MaxValue);
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
|
||||
if (ImGui.Button("Remove##ModAll"))
|
||||
{
|
||||
_lastTempError = Ipc.RemoveTemporaryModAll.Subscriber(_pi).Invoke(_tempModName, int.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawCollections()
|
||||
{
|
||||
using var collTree = ImRaii.TreeNode("Collections##TempCollections");
|
||||
if (!collTree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table("##collTree", 5);
|
||||
if (!table)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var collection in Penumbra.TempCollections.Values)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var character = Penumbra.TempCollections.Collections.Where( p => p.Collection == collection ).Select( p => p.DisplayName ).FirstOrDefault() ?? "Unknown";
|
||||
var character = Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
|
||||
.FirstOrDefault()
|
||||
?? "Unknown";
|
||||
if (ImGui.Button($"Save##{collection.Name}"))
|
||||
{
|
||||
Mod.TemporaryMod.SaveTempCollection( collection, character );
|
||||
}
|
||||
Mod.TemporaryMod.SaveTempCollection(_modManager, collection, character);
|
||||
|
||||
ImGuiUtil.DrawTableColumn(collection.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
|
||||
ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
|
||||
ImGuiUtil.DrawTableColumn( string.Join( ", ", Penumbra.TempCollections.Collections.Where( p => p.Collection == collection ).Select( c => c.DisplayName ) ) );
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ",
|
||||
Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1425,9 +1267,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var modTree = ImRaii.TreeNode("Mods##TempMods");
|
||||
if (!modTree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table("##modTree", 5);
|
||||
|
||||
|
|
@ -1447,10 +1287,8 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
foreach (var (path, file) in mod.Default.Files)
|
||||
{
|
||||
ImGui.TextUnformatted($"{path} -> {file}");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(mod.TotalManipulations.ToString());
|
||||
|
|
@ -1458,21 +1296,17 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
foreach (var manip in mod.Default.Manipulations)
|
||||
{
|
||||
ImGui.TextUnformatted(manip.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (table)
|
||||
{
|
||||
PrintList("All", Penumbra.TempMods.ModsForAllCollections);
|
||||
foreach (var (collection, list) in Penumbra.TempMods.Mods)
|
||||
{
|
||||
PrintList(collection.Name, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ public class PenumbraIpcProviders : IDisposable
|
|||
internal readonly FuncProvider< string, int, PenumbraApiEc > RemoveTemporaryModAll;
|
||||
internal readonly FuncProvider< string, string, int, PenumbraApiEc > RemoveTemporaryMod;
|
||||
|
||||
public PenumbraIpcProviders( DalamudPluginInterface pi, IPenumbraApi api )
|
||||
public PenumbraIpcProviders( DalamudPluginInterface pi, IPenumbraApi api, Mod.Manager modManager )
|
||||
{
|
||||
Api = api;
|
||||
|
||||
|
|
@ -219,7 +220,7 @@ public class PenumbraIpcProviders : IDisposable
|
|||
RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider( pi, Api.RemoveTemporaryModAll );
|
||||
RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider( pi, Api.RemoveTemporaryMod );
|
||||
|
||||
Tester = new IpcTester( pi, this );
|
||||
Tester = new IpcTester( pi, this, modManager );
|
||||
|
||||
Initialized.Invoke();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Penumbra.Collections;
|
|||
|
||||
public partial class ModCollection
|
||||
{
|
||||
public sealed partial class Manager : ISaveable
|
||||
public sealed partial class Manager : ISavable
|
||||
{
|
||||
public const int Version = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Penumbra.Util;
|
|||
namespace Penumbra.Collections;
|
||||
|
||||
// File operations like saving, loading and deleting for a collection.
|
||||
public partial class ModCollection : ISaveable
|
||||
public partial class ModCollection : ISavable
|
||||
{
|
||||
// Since inheritances depend on other collections existing,
|
||||
// we return them as a list to be applied after reading all collections.
|
||||
|
|
|
|||
|
|
@ -14,18 +14,16 @@ using Penumbra.Mods;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
public class Configuration : IPluginConfiguration, ISavable
|
||||
{
|
||||
[JsonIgnore]
|
||||
private readonly string _fileName;
|
||||
|
||||
[JsonIgnore]
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public int Version { get; set; } = Constants.CurrentVersion;
|
||||
|
||||
|
|
@ -101,14 +99,13 @@ public class Configuration : IPluginConfiguration
|
|||
/// Load the current configuration.
|
||||
/// Includes adding new colors and migrating from old versions.
|
||||
/// </summary>
|
||||
public Configuration(FilenameService fileNames, ConfigMigrationService migrator, FrameworkManager framework)
|
||||
public Configuration(FilenameService fileNames, ConfigMigrationService migrator, SaveService saveService)
|
||||
{
|
||||
_fileName = fileNames.ConfigFile;
|
||||
_framework = framework;
|
||||
Load(migrator);
|
||||
_saveService = saveService;
|
||||
Load(fileNames, migrator);
|
||||
}
|
||||
|
||||
public void Load(ConfigMigrationService migrator)
|
||||
public void Load(FilenameService fileNames, ConfigMigrationService migrator)
|
||||
{
|
||||
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
|
||||
{
|
||||
|
|
@ -117,9 +114,9 @@ public class Configuration : IPluginConfiguration
|
|||
errorArgs.ErrorContext.Handled = true;
|
||||
}
|
||||
|
||||
if (File.Exists(_fileName))
|
||||
if (File.Exists(fileNames.ConfigFile))
|
||||
{
|
||||
var text = File.ReadAllText(_fileName);
|
||||
var text = File.ReadAllText(fileNames.ConfigFile);
|
||||
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
|
||||
{
|
||||
Error = HandleDeserializationError,
|
||||
|
|
@ -130,21 +127,8 @@ public class Configuration : IPluginConfiguration
|
|||
}
|
||||
|
||||
/// <summary> Save the current configuration. </summary>
|
||||
private void SaveConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
File.WriteAllText(_fileName, text);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not save plugin configuration:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
=> _framework.RegisterDelayed(nameof(SaveConfiguration), SaveConfiguration);
|
||||
=> _saveService.QueueSave(this);
|
||||
|
||||
/// <summary> Contains some default values or boundaries for config values. </summary>
|
||||
public static class Constants
|
||||
|
|
@ -192,4 +176,14 @@ public class Configuration : IPluginConfiguration
|
|||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.ConfigFile;
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
serializer.Serialize(jWriter, this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Mods;
|
||||
using FileMode = System.IO.FileMode;
|
||||
|
|
@ -33,22 +34,19 @@ public partial class TexToolsImporter : IDisposable
|
|||
public ImporterState State { get; private set; }
|
||||
public readonly List< (FileInfo File, DirectoryInfo? Mod, Exception? Error) > ExtractedMods;
|
||||
|
||||
public TexToolsImporter( DirectoryInfo baseDirectory, ICollection< FileInfo > files,
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor)
|
||||
: this( baseDirectory, files.Count, files, handler, config, editor)
|
||||
{ }
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly ModEditor _editor;
|
||||
private readonly Mod.Manager _modManager;
|
||||
|
||||
public TexToolsImporter( DirectoryInfo baseDirectory, int count, IEnumerable< FileInfo > modPackFiles,
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor)
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor, Mod.Manager modManager)
|
||||
{
|
||||
_baseDirectory = baseDirectory;
|
||||
_tmpFile = Path.Combine( _baseDirectory.FullName, TempFileName );
|
||||
_modPackFiles = modPackFiles;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_modManager = modManager;
|
||||
_modPackCount = count;
|
||||
ExtractedMods = new List< (FileInfo, DirectoryInfo?, Exception?) >( count );
|
||||
_token = _cancellation.Token;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public partial class TexToolsImporter
|
|||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
|
||||
// Create a new ModMeta from the TTMP mod list info
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
||||
|
||||
// Open the mod data file from the mod pack as a SqPackStream
|
||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||
|
|
@ -90,7 +90,7 @@ public partial class TexToolsImporter
|
|||
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
|
||||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
||||
? "Mod imported from TexTools mod pack"
|
||||
: modList.Description, modList.Version, modList.Url );
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ public partial class TexToolsImporter
|
|||
_currentModName = modList.Name;
|
||||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
||||
|
||||
if( _currentNumOptions == 0 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ public class DuplicateManager
|
|||
try
|
||||
{
|
||||
var mod = new Mod(modDirectory);
|
||||
mod.Reload(true, out _);
|
||||
mod.Reload(_modManager, true, out _);
|
||||
|
||||
Finished = false;
|
||||
_files.UpdateAll(mod, mod.Default);
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public class ModFileEditor
|
|||
if (deletions <= 0)
|
||||
return;
|
||||
|
||||
mod.Reload(false, out _);
|
||||
mod.Reload(_modManager, false, out _);
|
||||
_files.UpdateAll(mod, option);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ public partial class Mod
|
|||
return;
|
||||
}
|
||||
|
||||
MoveDataFile( oldDirectory, dir );
|
||||
DataEditor.MoveDataFile(oldDirectory, dir);
|
||||
new ModBackup(this, mod).Move(null, dir.Name);
|
||||
|
||||
dir.Refresh();
|
||||
mod.ModPath = dir;
|
||||
if( !mod.Reload( false, out var metaChange ) )
|
||||
if (!mod.Reload(this, false, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Error($"Error reloading moved mod {mod.Name}.");
|
||||
return;
|
||||
|
|
@ -71,20 +71,20 @@ public partial class Mod
|
|||
|
||||
ModPathChanged.Invoke(ModPathChangeType.Moved, mod, oldDirectory, dir);
|
||||
if (metaChange != ModDataChangeType.None)
|
||||
{
|
||||
ModDataChanged?.Invoke( metaChange, mod, oldName );
|
||||
}
|
||||
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
|
||||
}
|
||||
|
||||
// Reload a mod without changing its base directory.
|
||||
// If the base directory does not exist anymore, the mod will be deleted.
|
||||
/// <summary>
|
||||
/// Reload a mod without changing its base directory.
|
||||
/// If the base directory does not exist anymore, the mod will be deleted.
|
||||
/// </summary>
|
||||
public void ReloadMod(int idx)
|
||||
{
|
||||
var mod = this[idx];
|
||||
var oldName = mod.Name;
|
||||
|
||||
ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
||||
if( !mod.Reload( true, out var metaChange ) )
|
||||
if (!mod.Reload(this, true, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead."
|
||||
|
|
@ -96,19 +96,18 @@ public partial class Mod
|
|||
|
||||
ModPathChanged.Invoke(ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath);
|
||||
if (metaChange != ModDataChangeType.None)
|
||||
{
|
||||
ModDataChanged?.Invoke( metaChange, mod, oldName );
|
||||
}
|
||||
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
|
||||
}
|
||||
|
||||
// Delete a mod by its index. The event is invoked before the mod is removed from the list.
|
||||
// Deletes from filesystem as well as from internal data.
|
||||
// Updates indices of later mods.
|
||||
/// <summary>
|
||||
/// Delete a mod by its index. The event is invoked before the mod is removed from the list.
|
||||
/// Deletes from filesystem as well as from internal data.
|
||||
/// Updates indices of later mods.
|
||||
/// </summary>
|
||||
public void DeleteMod(int idx)
|
||||
{
|
||||
var mod = this[idx];
|
||||
if (Directory.Exists(mod.ModPath.FullName))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(mod.ModPath.FullName, true);
|
||||
|
|
@ -118,32 +117,25 @@ public partial class Mod
|
|||
{
|
||||
Penumbra.Log.Error($"Could not delete the mod {mod.ModPath.Name}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke(ModPathChangeType.Deleted, mod, mod.ModPath, null);
|
||||
_mods.RemoveAt(idx);
|
||||
foreach (var remainingMod in _mods.Skip(idx))
|
||||
{
|
||||
--remainingMod.Index;
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug($"Deleted mod {mod.Name}.");
|
||||
}
|
||||
|
||||
// Load a new mod and add it to the manager if successful.
|
||||
/// <summary> Load a new mod and add it to the manager if successful. </summary>
|
||||
public void AddMod(DirectoryInfo modFolder)
|
||||
{
|
||||
if (_mods.Any(m => m.ModPath.Name == modFolder.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Creator.SplitMultiGroups(modFolder);
|
||||
var mod = LoadMod( modFolder, true );
|
||||
var mod = LoadMod(this, modFolder, true);
|
||||
if (mod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mod.Index = _mods.Count;
|
||||
_mods.Add(mod);
|
||||
|
|
@ -162,47 +154,35 @@ public partial class Mod
|
|||
Empty,
|
||||
}
|
||||
|
||||
// Return the state of the new potential name of a directory.
|
||||
/// <summary> Return the state of the new potential name of a directory. </summary>
|
||||
public NewDirectoryState NewDirectoryValid(string oldName, string newName, out DirectoryInfo? directory)
|
||||
{
|
||||
directory = null;
|
||||
if (newName.Length == 0)
|
||||
{
|
||||
return NewDirectoryState.Empty;
|
||||
}
|
||||
|
||||
if (oldName == newName)
|
||||
{
|
||||
return NewDirectoryState.Identical;
|
||||
}
|
||||
|
||||
var fixedNewName = Creator.ReplaceBadXivSymbols(newName);
|
||||
if (fixedNewName != newName)
|
||||
{
|
||||
return NewDirectoryState.ContainsInvalidSymbols;
|
||||
}
|
||||
|
||||
directory = new DirectoryInfo(Path.Combine(BasePath.FullName, fixedNewName));
|
||||
if (File.Exists(directory.FullName))
|
||||
{
|
||||
return NewDirectoryState.ExistsAsFile;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(directory.FullName))
|
||||
{
|
||||
return NewDirectoryState.NonExisting;
|
||||
}
|
||||
|
||||
if (directory.EnumerateFileSystemInfos().Any())
|
||||
{
|
||||
return NewDirectoryState.ExistsNonEmpty;
|
||||
}
|
||||
|
||||
return NewDirectoryState.ExistsEmpty;
|
||||
}
|
||||
|
||||
|
||||
// Add new mods to NewMods and remove deleted mods from NewMods.
|
||||
/// <summary> Add new mods to NewMods and remove deleted mods from NewMods. </summary>
|
||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory)
|
||||
{
|
||||
|
|
@ -216,9 +196,7 @@ public partial class Mod
|
|||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
if (oldDirectory != null && newDirectory != null)
|
||||
{
|
||||
MoveDataFile( oldDirectory, newDirectory );
|
||||
}
|
||||
DataEditor.MoveDataFile(oldDirectory, newDirectory);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,67 +7,6 @@ public sealed partial class Mod
|
|||
{
|
||||
public partial class Manager
|
||||
{
|
||||
public void ChangeModFavorite( Index idx, bool state )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Favorite != state )
|
||||
{
|
||||
mod.Favorite = state;
|
||||
mod.SaveLocalData();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Favorite, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModNote( Index idx, string newNote )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Note != newNote )
|
||||
{
|
||||
mod.Note = newNote;
|
||||
mod.SaveLocalData();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Favorite, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ChangeTag( Index idx, int tagIdx, string newTag, bool local )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
var which = local ? mod.LocalTags : mod.ModTags;
|
||||
if( tagIdx < 0 || tagIdx > which.Count )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModDataChangeType flags = 0;
|
||||
if( tagIdx == which.Count )
|
||||
{
|
||||
flags = mod.UpdateTags( local ? null : which.Append( newTag ), local ? which.Append( newTag ) : null );
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = which.ToArray();
|
||||
tmp[ tagIdx ] = newTag;
|
||||
flags = mod.UpdateTags( local ? null : tmp, local ? tmp : null );
|
||||
}
|
||||
|
||||
if( flags.HasFlag( ModDataChangeType.ModTags ) )
|
||||
{
|
||||
mod.SaveMeta();
|
||||
}
|
||||
|
||||
if( flags.HasFlag( ModDataChangeType.LocalTags ) )
|
||||
{
|
||||
mod.SaveLocalData();
|
||||
}
|
||||
|
||||
if( flags != 0 )
|
||||
{
|
||||
ModDataChanged?.Invoke( flags, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeLocalTag( Index idx, int tagIdx, string newTag )
|
||||
=> ChangeTag( idx, tagIdx, newTag, true );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
public partial class Manager
|
||||
{
|
||||
public delegate void ModDataChangeDelegate( ModDataChangeType type, Mod mod, string? oldName );
|
||||
public event ModDataChangeDelegate? ModDataChanged;
|
||||
|
||||
public void ChangeModName( Index idx, string newName )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Name.Text != newName )
|
||||
{
|
||||
var oldName = mod.Name;
|
||||
mod.Name = newName;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Name, mod, oldName.Text );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModAuthor( Index idx, string newAuthor )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Author != newAuthor )
|
||||
{
|
||||
mod.Author = newAuthor;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Author, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModDescription( Index idx, string newDescription )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Description != newDescription )
|
||||
{
|
||||
mod.Description = newDescription;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Description, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModVersion( Index idx, string newVersion )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Version != newVersion )
|
||||
{
|
||||
mod.Version = newVersion;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Version, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModWebsite( Index idx, string newWebsite )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Website != newWebsite )
|
||||
{
|
||||
mod.Website = newWebsite;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Website, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModTag( Index idx, int tagIdx, string newTag )
|
||||
=> ChangeTag( idx, tagIdx, newTag, false );
|
||||
}
|
||||
}
|
||||
|
|
@ -305,18 +305,18 @@ public sealed partial class Mod
|
|||
public bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
||||
{
|
||||
var path = newName.RemoveInvalidPathSymbols();
|
||||
if (path.Length == 0
|
||||
|| mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
if (path.Length != 0
|
||||
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
|
||||
if (message)
|
||||
_chat.NotificationMessage($"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
"Warning", NotificationType.Warning);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public sealed partial class Mod
|
|||
var queue = new ConcurrentQueue< Mod >();
|
||||
Parallel.ForEach( BasePath.EnumerateDirectories(), options, dir =>
|
||||
{
|
||||
var mod = LoadMod( dir, false );
|
||||
var mod = LoadMod( this, dir, false );
|
||||
if( mod != null )
|
||||
{
|
||||
queue.Enqueue( mod );
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
@ -37,13 +38,15 @@ public sealed partial class Mod
|
|||
=> GetEnumerator();
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly ChatService _chat;
|
||||
private readonly CommunicatorService _communicator;
|
||||
public readonly ModDataEditor DataEditor;
|
||||
|
||||
public Manager(StartTracker time, Configuration config, ChatService chat)
|
||||
public Manager(StartTracker time, Configuration config, CommunicatorService communicator, ModDataEditor dataEditor)
|
||||
{
|
||||
using var timer = time.Measure(StartTimeType.Mods);
|
||||
_config = config;
|
||||
_chat = chat;
|
||||
_communicator = communicator;
|
||||
DataEditor = dataEditor;
|
||||
ModDirectoryChanged += OnModDirectoryChange;
|
||||
SetBaseDirectory(config.ModDirectory, true);
|
||||
UpdateExportDirectory(_config.ExportDirectory, false);
|
||||
|
|
|
|||
21
Penumbra/Mods/Manager/ModDataChangeType.cs
Normal file
21
Penumbra/Mods/Manager/ModDataChangeType.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ModDataChangeType : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
Name = 0x0001,
|
||||
Author = 0x0002,
|
||||
Description = 0x0004,
|
||||
Version = 0x0008,
|
||||
Website = 0x0010,
|
||||
Deletion = 0x0020,
|
||||
Migration = 0x0040,
|
||||
ModTags = 0x0080,
|
||||
ImportDate = 0x0100,
|
||||
Favorite = 0x0200,
|
||||
LocalTags = 0x0400,
|
||||
Note = 0x0800,
|
||||
}
|
||||
362
Penumbra/Mods/Manager/ModDataEditor.cs
Normal file
362
Penumbra/Mods/Manager/ModDataEditor.cs
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public class ModDataEditor
|
||||
{
|
||||
private readonly FilenameService _filenameService;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly CommunicatorService _communicatorService;
|
||||
|
||||
public ModDataEditor(FilenameService filenameService, SaveService saveService, CommunicatorService communicatorService)
|
||||
{
|
||||
_filenameService = filenameService;
|
||||
_saveService = saveService;
|
||||
_communicatorService = communicatorService;
|
||||
}
|
||||
|
||||
public string MetaFile(Mod mod)
|
||||
=> _filenameService.ModMetaPath(mod);
|
||||
|
||||
public string DataFile(Mod mod)
|
||||
=> _filenameService.LocalDataFile(mod);
|
||||
|
||||
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
||||
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||
string? website)
|
||||
{
|
||||
var mod = new Mod(directory);
|
||||
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString(name!);
|
||||
mod.Author = author != null ? new LowerString(author) : mod.Author;
|
||||
mod.Description = description ?? mod.Description;
|
||||
mod.Version = version ?? mod.Version;
|
||||
mod.Website = website ?? mod.Website;
|
||||
_saveService.ImmediateSave(new ModMeta(mod));
|
||||
}
|
||||
|
||||
public ModDataChangeType LoadLocalData(Mod mod)
|
||||
{
|
||||
var dataFile = _filenameService.LocalDataFile(mod);
|
||||
|
||||
var importDate = 0L;
|
||||
var localTags = Enumerable.Empty<string>();
|
||||
var favorite = false;
|
||||
var note = string.Empty;
|
||||
|
||||
var save = true;
|
||||
if (File.Exists(dataFile))
|
||||
{
|
||||
save = false;
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(dataFile);
|
||||
var json = JObject.Parse(text);
|
||||
|
||||
importDate = json[nameof(Mod.ImportDate)]?.Value<long>() ?? importDate;
|
||||
favorite = json[nameof(Mod.Favorite)]?.Value<bool>() ?? favorite;
|
||||
note = json[nameof(Mod.Note)]?.Value<string>() ?? note;
|
||||
localTags = json[nameof(Mod.LocalTags)]?.Values<string>().OfType<string>() ?? localTags;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not load local mod data:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
if (importDate == 0)
|
||||
importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.ImportDate != importDate)
|
||||
{
|
||||
mod.ImportDate = importDate;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= mod.UpdateTags(null, localTags);
|
||||
|
||||
if (mod.Favorite != favorite)
|
||||
{
|
||||
mod.Favorite = favorite;
|
||||
changes |= ModDataChangeType.Favorite;
|
||||
}
|
||||
|
||||
if (mod.Note != note)
|
||||
{
|
||||
mod.Note = note;
|
||||
changes |= ModDataChangeType.Note;
|
||||
}
|
||||
|
||||
if (save)
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public ModDataChangeType LoadMeta(Mod mod)
|
||||
{
|
||||
var metaFile = _filenameService.ModMetaPath(mod);
|
||||
if (!File.Exists(metaFile))
|
||||
{
|
||||
Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(metaFile);
|
||||
var json = JObject.Parse(text);
|
||||
|
||||
var newName = json[nameof(Mod.Name)]?.Value<string>() ?? string.Empty;
|
||||
var newAuthor = json[nameof(Mod.Author)]?.Value<string>() ?? string.Empty;
|
||||
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
|
||||
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
|
||||
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
|
||||
var newFileVersion = json[nameof(Mod.FileVersion)]?.Value<uint>() ?? 0;
|
||||
var importDate = json[nameof(Mod.ImportDate)]?.Value<long>();
|
||||
var modTags = json[nameof(Mod.ModTags)]?.Values<string>().OfType<string>();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.Name != newName)
|
||||
{
|
||||
changes |= ModDataChangeType.Name;
|
||||
mod.Name = newName;
|
||||
}
|
||||
|
||||
if (mod.Author != newAuthor)
|
||||
{
|
||||
changes |= ModDataChangeType.Author;
|
||||
mod.Author = newAuthor;
|
||||
}
|
||||
|
||||
if (mod.Description != newDescription)
|
||||
{
|
||||
changes |= ModDataChangeType.Description;
|
||||
mod.Description = newDescription;
|
||||
}
|
||||
|
||||
if (mod.Version != newVersion)
|
||||
{
|
||||
changes |= ModDataChangeType.Version;
|
||||
mod.Version = newVersion;
|
||||
}
|
||||
|
||||
if (mod.Website != newWebsite)
|
||||
{
|
||||
changes |= ModDataChangeType.Website;
|
||||
mod.Website = newWebsite;
|
||||
}
|
||||
|
||||
if (mod.FileVersion != newFileVersion)
|
||||
{
|
||||
mod.FileVersion = newFileVersion;
|
||||
if (Mod.Migration.Migrate(mod, json))
|
||||
{
|
||||
changes |= ModDataChangeType.Migration;
|
||||
_saveService.ImmediateSave(new ModMeta(mod));
|
||||
}
|
||||
}
|
||||
|
||||
if (importDate != null && mod.ImportDate != importDate.Value)
|
||||
{
|
||||
mod.ImportDate = importDate.Value;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= mod.UpdateTags(modTags, null);
|
||||
|
||||
return changes;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not load mod meta:\n{e}");
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModName(Mod mod, string newName)
|
||||
{
|
||||
if (mod.Name.Text == newName)
|
||||
return;
|
||||
|
||||
var oldName = mod.Name;
|
||||
mod.Name = newName;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Name, mod, oldName.Text);
|
||||
}
|
||||
|
||||
public void ChangeModAuthor(Mod mod, string newAuthor)
|
||||
{
|
||||
if (mod.Author == newAuthor)
|
||||
return;
|
||||
|
||||
mod.Author = newAuthor;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Author, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModDescription(Mod mod, string newDescription)
|
||||
{
|
||||
if (mod.Description == newDescription)
|
||||
return;
|
||||
|
||||
mod.Description = newDescription;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Description, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModVersion(Mod mod, string newVersion)
|
||||
{
|
||||
if (mod.Version == newVersion)
|
||||
return;
|
||||
|
||||
mod.Version = newVersion;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Version, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModWebsite(Mod mod, string newWebsite)
|
||||
{
|
||||
if (mod.Website == newWebsite)
|
||||
return;
|
||||
|
||||
mod.Website = newWebsite;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModTag(Mod mod, int tagIdx, string newTag)
|
||||
=> ChangeTag(mod, tagIdx, newTag, false);
|
||||
|
||||
public void ChangeLocalTag(Mod mod, int tagIdx, string newTag)
|
||||
=> ChangeTag(mod, tagIdx, newTag, true);
|
||||
|
||||
public void ChangeModFavorite(Mod mod, bool state)
|
||||
{
|
||||
if (mod.Favorite == state)
|
||||
return;
|
||||
|
||||
mod.Favorite = state;
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
;
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModNote(Mod mod, string newNote)
|
||||
{
|
||||
if (mod.Note == newNote)
|
||||
return;
|
||||
|
||||
mod.Note = newNote;
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
;
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||
}
|
||||
|
||||
|
||||
private void ChangeTag(Mod mod, int tagIdx, string newTag, bool local)
|
||||
{
|
||||
var which = local ? mod.LocalTags : mod.ModTags;
|
||||
if (tagIdx < 0 || tagIdx > which.Count)
|
||||
return;
|
||||
|
||||
ModDataChangeType flags = 0;
|
||||
if (tagIdx == which.Count)
|
||||
{
|
||||
flags = mod.UpdateTags(local ? null : which.Append(newTag), local ? which.Append(newTag) : null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = which.ToArray();
|
||||
tmp[tagIdx] = newTag;
|
||||
flags = mod.UpdateTags(local ? null : tmp, local ? tmp : null);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(ModDataChangeType.ModTags))
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
|
||||
if (flags.HasFlag(ModDataChangeType.LocalTags))
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
|
||||
if (flags != 0)
|
||||
_communicatorService.ModDataChanged.Invoke(flags, mod, null);
|
||||
}
|
||||
|
||||
public void MoveDataFile(DirectoryInfo oldMod, DirectoryInfo newMod)
|
||||
{
|
||||
var oldFile = _filenameService.LocalDataFile(oldMod.Name);
|
||||
var newFile = _filenameService.LocalDataFile(newMod.Name);
|
||||
if (!File.Exists(oldFile))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(oldFile, newFile, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not move local data file {oldFile} to {newFile}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly struct ModMeta : ISavable
|
||||
{
|
||||
private readonly Mod _mod;
|
||||
|
||||
public ModMeta(Mod mod)
|
||||
=> _mod = mod;
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.ModMetaPath(_mod);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) },
|
||||
{ nameof(Mod.Name), JToken.FromObject(_mod.Name) },
|
||||
{ nameof(Mod.Author), JToken.FromObject(_mod.Author) },
|
||||
{ nameof(Mod.Description), JToken.FromObject(_mod.Description) },
|
||||
{ nameof(Mod.Version), JToken.FromObject(_mod.Version) },
|
||||
{ nameof(Mod.Website), JToken.FromObject(_mod.Website) },
|
||||
{ nameof(Mod.ModTags), JToken.FromObject(_mod.ModTags) },
|
||||
};
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
jObject.WriteTo(jWriter);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct ModData : ISavable
|
||||
{
|
||||
private readonly Mod _mod;
|
||||
|
||||
public ModData(Mod mod)
|
||||
=> _mod = mod;
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.LocalDataFile(_mod);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) },
|
||||
{ nameof(Mod.ImportDate), JToken.FromObject(_mod.ImportDate) },
|
||||
{ nameof(Mod.LocalTags), JToken.FromObject(_mod.LocalTags) },
|
||||
{ nameof(Mod.Note), JToken.FromObject(_mod.Note) },
|
||||
{ nameof(Mod.Favorite), JToken.FromObject(_mod.Favorite) },
|
||||
};
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
jObject.WriteTo(jWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ public enum ModPathChangeType
|
|||
public partial class Mod
|
||||
{
|
||||
public DirectoryInfo ModPath { get; private set; }
|
||||
public string Identifier
|
||||
=> Index >= 0 ? ModPath.Name : Name;
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
public bool IsTemporary
|
||||
|
|
@ -31,7 +33,7 @@ public partial class Mod
|
|||
_default = new SubMod( this );
|
||||
}
|
||||
|
||||
private static Mod? LoadMod( DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
private static Mod? LoadMod( Manager modManager, DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
{
|
||||
modPath.Refresh();
|
||||
if( !modPath.Exists )
|
||||
|
|
@ -41,17 +43,16 @@ public partial class Mod
|
|||
}
|
||||
|
||||
var mod = new Mod(modPath);
|
||||
if( !mod.Reload( incorporateMetaChanges, out _ ) )
|
||||
{
|
||||
if (mod.Reload(modManager, incorporateMetaChanges, out _))
|
||||
return mod;
|
||||
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Warning( $"Mod at {modPath} without name is not supported." );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
internal bool Reload( bool incorporateMetaChanges, out ModDataChangeType modDataChange )
|
||||
internal bool Reload(Manager modManager, bool incorporateMetaChanges, out ModDataChangeType modDataChange )
|
||||
{
|
||||
modDataChange = ModDataChangeType.Deletion;
|
||||
ModPath.Refresh();
|
||||
|
|
@ -60,13 +61,13 @@ public partial class Mod
|
|||
return false;
|
||||
}
|
||||
|
||||
modDataChange = LoadMeta();
|
||||
modDataChange = modManager.DataEditor.LoadMeta(this);
|
||||
if( modDataChange.HasFlag( ModDataChangeType.Deletion ) || Name.Length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LoadLocalData();
|
||||
modManager.DataEditor.LoadLocalData(this);
|
||||
|
||||
LoadDefaultOption();
|
||||
LoadAllGroups();
|
||||
|
|
|
|||
|
|
@ -64,19 +64,6 @@ public partial class Mod
|
|||
return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder );
|
||||
}
|
||||
|
||||
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
||||
public static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||
string? website )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
|
||||
mod.Author = author != null ? new LowerString( author ) : mod.Author;
|
||||
mod.Description = description ?? mod.Description;
|
||||
mod.Version = version ?? mod.Version;
|
||||
mod.Website = website ?? mod.Website;
|
||||
mod.SaveMetaFile(); // Not delayed.
|
||||
}
|
||||
|
||||
/// <summary> Create a file for an option group from given data. </summary>
|
||||
public static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name,
|
||||
int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods )
|
||||
|
|
@ -147,14 +134,12 @@ public partial class Mod
|
|||
internal static void CreateDefaultFiles( DirectoryInfo directory )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Reload( false, out _ );
|
||||
mod.Reload( Penumbra.ModManager, false, out _ );
|
||||
foreach( var file in mod.FindUnusedFiles() )
|
||||
{
|
||||
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
|
||||
{
|
||||
mod._default.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges( directory, true );
|
||||
mod.SaveDefaultMod();
|
||||
|
|
|
|||
|
|
@ -3,139 +3,24 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
public static DirectoryInfo LocalDataDirectory(DalamudPluginInterface pi)
|
||||
=> new(Path.Combine( pi.ConfigDirectory.FullName, "mod_data" ));
|
||||
|
||||
public long ImportDate { get; private set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||
|
||||
public IReadOnlyList<string> LocalTags { get; private set; } = Array.Empty<string>();
|
||||
|
||||
public string AllTagsLower { get; private set; } = string.Empty;
|
||||
public string Note { get; private set; } = string.Empty;
|
||||
public bool Favorite { get; private set; } = false;
|
||||
public string Note { get; internal set; } = string.Empty;
|
||||
public bool Favorite { get; internal set; } = false;
|
||||
|
||||
private FileInfo LocalDataFile
|
||||
=> new(Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{ModPath.Name}.json" ));
|
||||
|
||||
private ModDataChangeType LoadLocalData()
|
||||
{
|
||||
var dataFile = LocalDataFile;
|
||||
|
||||
var importDate = 0L;
|
||||
var localTags = Enumerable.Empty< string >();
|
||||
var favorite = false;
|
||||
var note = string.Empty;
|
||||
|
||||
var save = true;
|
||||
if( File.Exists( dataFile.FullName ) )
|
||||
{
|
||||
save = false;
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( dataFile.FullName );
|
||||
var json = JObject.Parse( text );
|
||||
|
||||
importDate = json[ nameof( ImportDate ) ]?.Value< long >() ?? importDate;
|
||||
favorite = json[ nameof( Favorite ) ]?.Value< bool >() ?? favorite;
|
||||
note = json[ nameof( Note ) ]?.Value< string >() ?? note;
|
||||
localTags = json[ nameof( LocalTags ) ]?.Values< string >().OfType< string >() ?? localTags;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not load local mod data:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
if( importDate == 0 )
|
||||
{
|
||||
importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if( ImportDate != importDate )
|
||||
{
|
||||
ImportDate = importDate;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= UpdateTags( null, localTags );
|
||||
|
||||
if( Favorite != favorite )
|
||||
{
|
||||
Favorite = favorite;
|
||||
changes |= ModDataChangeType.Favorite;
|
||||
}
|
||||
|
||||
if( Note != note )
|
||||
{
|
||||
Note = note;
|
||||
changes |= ModDataChangeType.Note;
|
||||
}
|
||||
|
||||
if( save )
|
||||
{
|
||||
SaveLocalDataFile();
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private void SaveLocalData()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveLocalData ) + ModPath.Name, SaveLocalDataFile );
|
||||
|
||||
private void SaveLocalDataFile()
|
||||
{
|
||||
var dataFile = LocalDataFile;
|
||||
try
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof( FileVersion ), JToken.FromObject( FileVersion ) },
|
||||
{ nameof( ImportDate ), JToken.FromObject( ImportDate ) },
|
||||
{ nameof( LocalTags ), JToken.FromObject( LocalTags ) },
|
||||
{ nameof( Note ), JToken.FromObject( Note ) },
|
||||
{ nameof( Favorite ), JToken.FromObject( Favorite ) },
|
||||
};
|
||||
dataFile.Directory!.Create();
|
||||
File.WriteAllText( dataFile.FullName, jObject.ToString( Formatting.Indented ) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not write local data file for mod {Name} to {dataFile.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
private static void MoveDataFile( DirectoryInfo oldMod, DirectoryInfo newMod )
|
||||
{
|
||||
var oldFile = Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{oldMod.Name}.json" );
|
||||
var newFile = Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{newMod.Name}.json" );
|
||||
if( File.Exists( oldFile ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move( oldFile, newFile, true );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not move local data file {oldFile} to {newFile}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModDataChangeType UpdateTags( IEnumerable< string >? newModTags, IEnumerable< string >? newLocalTags )
|
||||
internal ModDataChangeType UpdateTags(IEnumerable<string>? newModTags, IEnumerable<string>? newLocalTags)
|
||||
{
|
||||
if (newModTags == null && newLocalTags == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ModDataChangeType type = 0;
|
||||
if (newModTags != null)
|
||||
|
|
@ -160,9 +45,7 @@ public sealed partial class Mod
|
|||
}
|
||||
|
||||
if (type != 0)
|
||||
{
|
||||
AllTagsLower = string.Join('\0', ModTags.Concat(LocalTags).Select(s => s.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,59 +13,43 @@ namespace Penumbra.Mods;
|
|||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
private static class Migration
|
||||
public static partial class Migration
|
||||
{
|
||||
public static bool Migrate(Mod mod, JObject json)
|
||||
{
|
||||
var ret = MigrateV0ToV1( mod, json ) || MigrateV1ToV2( mod ) || MigrateV2ToV3( mod );
|
||||
if( ret )
|
||||
{
|
||||
// Immediately save on migration.
|
||||
mod.SaveMetaFile();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
=> MigrateV0ToV1(mod, json) || MigrateV1ToV2(mod) || MigrateV2ToV3(mod);
|
||||
|
||||
private static bool MigrateV2ToV3(Mod mod)
|
||||
{
|
||||
if (mod.FileVersion > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove import time.
|
||||
mod.FileVersion = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"group_\d{3}_", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)]
|
||||
private static partial Regex GroupRegex();
|
||||
|
||||
private static readonly Regex GroupRegex = new( @"group_\d{3}_", RegexOptions.Compiled );
|
||||
private static bool MigrateV1ToV2(Mod mod)
|
||||
{
|
||||
if (mod.FileVersion > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mod.GroupFiles.All( g => GroupRegex.IsMatch( g.Name )))
|
||||
{
|
||||
if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name)))
|
||||
foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray())
|
||||
{
|
||||
var newName = Regex.Replace(group.Name, "^group_", $"group_{index + 1:D3}_", RegexOptions.Compiled);
|
||||
try
|
||||
{
|
||||
if (newName != group.Name)
|
||||
{
|
||||
group.MoveTo(Path.Combine(group.DirectoryName ?? string.Empty, newName), false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not rename group file {group.Name} to {newName} during migration:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod.FileVersion = 2;
|
||||
|
||||
|
|
@ -75,9 +59,7 @@ public sealed partial class Mod
|
|||
private static bool MigrateV0ToV1(Mod mod, JObject json)
|
||||
{
|
||||
if (mod.FileVersion > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
||||
?? new Dictionary<Utf8GamePath, FullPath>();
|
||||
|
|
@ -85,31 +67,23 @@ public sealed partial class Mod
|
|||
var priority = 1;
|
||||
var seenMetaFiles = new HashSet<FullPath>();
|
||||
foreach (var group in groups.Values)
|
||||
{
|
||||
ConvertGroup(mod, group, ref priority, seenMetaFiles);
|
||||
}
|
||||
|
||||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||
{
|
||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||
&& !mod._default.FileData.TryAdd(gamePath, unusedFile))
|
||||
{
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}.");
|
||||
}
|
||||
}
|
||||
|
||||
mod._default.FileSwapData.Clear();
|
||||
mod._default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||
foreach (var (gamePath, swapPath) in swaps)
|
||||
{
|
||||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||
foreach (var (group, index) in mod.Groups.WithIndex())
|
||||
{
|
||||
IModGroup.Save(group, mod.ModPath, index);
|
||||
}
|
||||
|
||||
// Delete meta files.
|
||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||
|
|
@ -127,7 +101,6 @@ public sealed partial class Mod
|
|||
// Delete old meta files.
|
||||
var oldMetaFile = Path.Combine(mod.ModPath.FullName, "metadata_manipulations.json");
|
||||
if (File.Exists(oldMetaFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(oldMetaFile);
|
||||
|
|
@ -136,11 +109,9 @@ public sealed partial class Mod
|
|||
{
|
||||
Penumbra.Log.Warning($"Could not delete old meta file {oldMetaFile} during migration:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
mod.FileVersion = 1;
|
||||
mod.SaveDefaultMod();
|
||||
mod.SaveMetaFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -148,9 +119,7 @@ public sealed partial class Mod
|
|||
private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
if (group.Options.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (group.SelectionType)
|
||||
{
|
||||
|
|
@ -165,9 +134,7 @@ public sealed partial class Mod
|
|||
};
|
||||
mod._groups.Add(newMultiGroup);
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++));
|
||||
}
|
||||
|
||||
break;
|
||||
case GroupType.Single:
|
||||
|
|
@ -185,9 +152,7 @@ public sealed partial class Mod
|
|||
};
|
||||
mod._groups.Add(newSingleGroup);
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -199,16 +164,12 @@ public sealed partial class Mod
|
|||
{
|
||||
var fullPath = new FullPath(basePath, relPath);
|
||||
foreach (var gamePath in gamePaths)
|
||||
{
|
||||
mod.FileData.TryAdd(gamePath, fullPath);
|
||||
}
|
||||
|
||||
if (fullPath.Extension is ".meta" or ".rgsp")
|
||||
{
|
||||
seenMetaFiles.Add(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
|
|
@ -254,9 +215,7 @@ public sealed partial class Mod
|
|||
var token = JToken.Load(reader);
|
||||
|
||||
if (token.Type == JTokenType.Array)
|
||||
{
|
||||
return token.ToObject<HashSet<T>>() ?? new HashSet<T>();
|
||||
}
|
||||
|
||||
var tmp = token.ToObject<T>();
|
||||
return tmp != null
|
||||
|
|
@ -274,10 +233,8 @@ public sealed partial class Mod
|
|||
{
|
||||
var v = (HashSet<T>)value;
|
||||
foreach (var val in v)
|
||||
{
|
||||
serializer.Serialize(writer, val?.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ModDataChangeType : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
Name = 0x0001,
|
||||
Author = 0x0002,
|
||||
Description = 0x0004,
|
||||
Version = 0x0008,
|
||||
Website = 0x0010,
|
||||
Deletion = 0x0020,
|
||||
Migration = 0x0040,
|
||||
ModTags = 0x0080,
|
||||
ImportDate = 0x0100,
|
||||
Favorite = 0x0200,
|
||||
LocalTags = 0x0400,
|
||||
Note = 0x0800,
|
||||
}
|
||||
|
||||
public sealed partial class Mod : IMod
|
||||
{
|
||||
public static readonly TemporaryMod ForcedFiles = new()
|
||||
|
|
@ -36,122 +14,13 @@ public sealed partial class Mod : IMod
|
|||
};
|
||||
|
||||
public const uint CurrentFileVersion = 3;
|
||||
public uint FileVersion { get; private set; } = CurrentFileVersion;
|
||||
public LowerString Name { get; private set; } = "New Mod";
|
||||
public LowerString Author { get; private set; } = LowerString.Empty;
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
public string Version { get; private set; } = string.Empty;
|
||||
public string Website { get; private set; } = string.Empty;
|
||||
public IReadOnlyList< string > ModTags { get; private set; } = Array.Empty< string >();
|
||||
|
||||
internal FileInfo MetaFile
|
||||
=> new(Path.Combine( ModPath.FullName, "meta.json" ));
|
||||
|
||||
private ModDataChangeType LoadMeta()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
if( !File.Exists( metaFile.FullName ) )
|
||||
{
|
||||
Penumbra.Log.Debug( $"No mod meta found for {ModPath.Name}." );
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( metaFile.FullName );
|
||||
var json = JObject.Parse( text );
|
||||
|
||||
var newName = json[ nameof( Name ) ]?.Value< string >() ?? string.Empty;
|
||||
var newAuthor = json[ nameof( Author ) ]?.Value< string >() ?? string.Empty;
|
||||
var newDescription = json[ nameof( Description ) ]?.Value< string >() ?? string.Empty;
|
||||
var newVersion = json[ nameof( Version ) ]?.Value< string >() ?? string.Empty;
|
||||
var newWebsite = json[ nameof( Website ) ]?.Value< string >() ?? string.Empty;
|
||||
var newFileVersion = json[ nameof( FileVersion ) ]?.Value< uint >() ?? 0;
|
||||
var importDate = json[ nameof( ImportDate ) ]?.Value< long >();
|
||||
var modTags = json[ nameof( ModTags ) ]?.Values< string >().OfType< string >();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if( Name != newName )
|
||||
{
|
||||
changes |= ModDataChangeType.Name;
|
||||
Name = newName;
|
||||
}
|
||||
|
||||
if( Author != newAuthor )
|
||||
{
|
||||
changes |= ModDataChangeType.Author;
|
||||
Author = newAuthor;
|
||||
}
|
||||
|
||||
if( Description != newDescription )
|
||||
{
|
||||
changes |= ModDataChangeType.Description;
|
||||
Description = newDescription;
|
||||
}
|
||||
|
||||
if( Version != newVersion )
|
||||
{
|
||||
changes |= ModDataChangeType.Version;
|
||||
Version = newVersion;
|
||||
}
|
||||
|
||||
if( Website != newWebsite )
|
||||
{
|
||||
changes |= ModDataChangeType.Website;
|
||||
Website = newWebsite;
|
||||
}
|
||||
|
||||
if( FileVersion != newFileVersion )
|
||||
{
|
||||
FileVersion = newFileVersion;
|
||||
if( Migration.Migrate( this, json ) )
|
||||
{
|
||||
changes |= ModDataChangeType.Migration;
|
||||
}
|
||||
}
|
||||
|
||||
if( importDate != null && ImportDate != importDate.Value )
|
||||
{
|
||||
ImportDate = importDate.Value;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= UpdateTags( modTags, null );
|
||||
|
||||
return changes;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not load mod meta:\n{e}" );
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveMeta()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveMetaFile ) + ModPath.Name, SaveMetaFile );
|
||||
|
||||
private void SaveMetaFile()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
try
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof( FileVersion ), JToken.FromObject( FileVersion ) },
|
||||
{ nameof( Name ), JToken.FromObject( Name ) },
|
||||
{ nameof( Author ), JToken.FromObject( Author ) },
|
||||
{ nameof( Description ), JToken.FromObject( Description ) },
|
||||
{ nameof( Version ), JToken.FromObject( Version ) },
|
||||
{ nameof( Website ), JToken.FromObject( Website ) },
|
||||
{ nameof( ModTags ), JToken.FromObject( ModTags ) },
|
||||
};
|
||||
File.WriteAllText( metaFile.FullName, jObject.ToString( Formatting.Indented ) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not write meta file for mod {Name} to {metaFile.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
public uint FileVersion { get; internal set; } = CurrentFileVersion;
|
||||
public LowerString Name { get; internal set; } = "New Mod";
|
||||
public LowerString Author { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string Version { get; internal set; } = string.Empty;
|
||||
public string Website { get; internal set; } = string.Empty;
|
||||
public IReadOnlyList< string > ModTags { get; internal set; } = Array.Empty< string >();
|
||||
|
||||
public override string ToString()
|
||||
=> Name.Text;
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ public sealed partial class Mod
|
|||
_default.ManipulationData = manips;
|
||||
}
|
||||
|
||||
public static void SaveTempCollection( ModCollection collection, string? character = null )
|
||||
public static void SaveTempCollection( Mod.Manager modManager, ModCollection collection, string? character = null )
|
||||
{
|
||||
DirectoryInfo? dir = null;
|
||||
try
|
||||
{
|
||||
dir = Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
|
||||
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
|
||||
Creator.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
||||
var mod = new Mod( dir );
|
||||
var defaultMod = mod._default;
|
||||
|
|
@ -88,7 +88,7 @@ public sealed partial class Mod
|
|||
}
|
||||
|
||||
mod.SaveDefaultMod();
|
||||
Penumbra.ModManager.AddMod( dir );
|
||||
modManager.AddMod( dir );
|
||||
Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
|
|||
|
|
@ -10,20 +10,22 @@ using Penumbra.Util;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISaveable
|
||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
||||
{
|
||||
private readonly Mod.Manager _modManager;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly FilenameService _files;
|
||||
|
||||
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||
public ModFileSystem(Mod.Manager modManager, FilenameService files)
|
||||
public ModFileSystem(Mod.Manager modManager, CommunicatorService communicator, FilenameService files)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_communicator = communicator;
|
||||
_files = files;
|
||||
Reload();
|
||||
Changed += OnChange;
|
||||
_modManager.ModDiscoveryFinished += Reload;
|
||||
_modManager.ModDataChanged += OnDataChange;
|
||||
_communicator.ModDataChanged.Event += OnDataChange;
|
||||
_modManager.ModPathChanged += OnModPathChange;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISaveable
|
|||
{
|
||||
_modManager.ModPathChanged -= OnModPathChange;
|
||||
_modManager.ModDiscoveryFinished -= Reload;
|
||||
_modManager.ModDataChanged -= OnDataChange;
|
||||
_communicator.ModDataChanged.Event -= OnDataChange;
|
||||
}
|
||||
|
||||
public struct ImportDate : ISortMode<Mod>
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public class PenumbraNew
|
|||
|
||||
// Add Mod Services
|
||||
services.AddSingleton<TempModManager>()
|
||||
.AddSingleton<ModDataEditor>()
|
||||
.AddSingleton<Mod.Manager>()
|
||||
.AddSingleton<ModFileSystem>();
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@ public class CommunicatorService : IDisposable
|
|||
/// </list> </summary>
|
||||
public readonly EventWrapper<nint, string, nint> CreatedCharacterBase = new(nameof(CreatedCharacterBase));
|
||||
|
||||
/// <summary><list type="number">
|
||||
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
|
||||
/// <item>Parameter is the changed mod. </item>
|
||||
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
||||
/// </list> </summary>
|
||||
public readonly EventWrapper<ModDataChangeType, Mod, string?> ModDataChanged = new(nameof(ModDataChanged));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CollectionChange.Dispose();
|
||||
|
|
@ -52,5 +59,6 @@ public class CommunicatorService : IDisposable
|
|||
ModMetaChange.Dispose();
|
||||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.Dispose();
|
||||
ModDataChanged.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class FilenameService
|
|||
|
||||
/// <summary> Obtain the path of the local data file given a mod directory. Returns an empty string if the mod is temporary. </summary>
|
||||
public string LocalDataFile(Mod mod)
|
||||
=> mod.IsTemporary ? string.Empty : LocalDataFile(mod.ModPath.FullName);
|
||||
=> LocalDataFile(mod.ModPath.FullName);
|
||||
|
||||
/// <summary> Obtain the path of the local data file given a mod directory. </summary>
|
||||
public string LocalDataFile(string modDirectory)
|
||||
|
|
@ -66,7 +66,7 @@ public class FilenameService
|
|||
|
||||
/// <summary> Obtain the path of the meta file for a given mod. Returns an empty string if the mod is temporary. </summary>
|
||||
public string ModMetaPath(Mod mod)
|
||||
=> mod.IsTemporary ? string.Empty : ModMetaPath(mod.ModPath.FullName);
|
||||
=> ModMetaPath(mod.ModPath.FullName);
|
||||
|
||||
/// <summary> Obtain the path of the meta file given a mod directory. </summary>
|
||||
public string ModMetaPath(string modDirectory)
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
private void CreateMod()
|
||||
{
|
||||
var newDir = Mod.Creator.CreateModFolder(_modManager.BasePath, _newModName);
|
||||
Mod.Creator.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||
Mod.Creator.CreateDefaultFiles(newDir);
|
||||
_modManager.AddMod(newDir);
|
||||
if (!_swapData.WriteMod(_modManager.Last(),
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_itemSwapTab.DrawContent();
|
||||
}
|
||||
|
||||
// A row of three buttonSizes and a help marker that can be used for material suffix changing.
|
||||
/// <summary> A row of three buttonSizes and a help marker that can be used for material suffix changing. </summary>
|
||||
private static class MaterialSuffix
|
||||
{
|
||||
private static string _materialSuffixFrom = string.Empty;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
_communicator.CollectionChange.Event += OnCollectionChange;
|
||||
_collectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||
_collectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
||||
_modManager.ModDataChanged += OnModDataChange;
|
||||
_communicator.ModDataChanged.Event += OnModDataChange;
|
||||
_modManager.ModDiscoveryStarted += StoreCurrentSelection;
|
||||
_modManager.ModDiscoveryFinished += RestoreLastSelection;
|
||||
OnCollectionChange(CollectionType.Current, null, _collectionManager.Current, "");
|
||||
|
|
@ -89,7 +89,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
base.Dispose();
|
||||
_modManager.ModDiscoveryStarted -= StoreCurrentSelection;
|
||||
_modManager.ModDiscoveryFinished -= RestoreLastSelection;
|
||||
_modManager.ModDataChanged -= OnModDataChange;
|
||||
_communicator.ModDataChanged.Event -= OnModDataChange;
|
||||
_collectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||
_collectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
||||
_communicator.CollectionChange.Event -= OnCollectionChange;
|
||||
|
|
@ -127,7 +127,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
try
|
||||
{
|
||||
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
||||
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||
Mod.Creator.CreateDefaultFiles(newDir);
|
||||
Penumbra.ModManager.AddMod(newDir);
|
||||
_newModName = string.Empty;
|
||||
|
|
@ -187,30 +187,30 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
private void ToggleLeafFavorite(FileSystem<Mod>.Leaf mod)
|
||||
{
|
||||
if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite"))
|
||||
_modManager.ChangeModFavorite(mod.Value.Index, !mod.Value.Favorite);
|
||||
_modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite);
|
||||
}
|
||||
|
||||
private void SetDefaultImportFolder(ModFileSystem.Folder folder)
|
||||
{
|
||||
if (ImGui.MenuItem("Set As Default Import Folder"))
|
||||
{
|
||||
if (!ImGui.MenuItem("Set As Default Import Folder"))
|
||||
return;
|
||||
|
||||
var newName = folder.FullName();
|
||||
if (newName != _config.DefaultImportFolder)
|
||||
{
|
||||
if (newName == _config.DefaultImportFolder)
|
||||
return;
|
||||
|
||||
_config.DefaultImportFolder = newName;
|
||||
_config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearDefaultImportFolder()
|
||||
{
|
||||
if (ImGui.MenuItem("Clear Default Import Folder") && _config.DefaultImportFolder.Length > 0)
|
||||
{
|
||||
if (!ImGui.MenuItem("Clear Default Import Folder") || _config.DefaultImportFolder.Length <= 0)
|
||||
return;
|
||||
|
||||
_config.DefaultImportFolder = string.Empty;
|
||||
_config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private string _newModName = string.Empty;
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
return;
|
||||
|
||||
_import = new TexToolsImporter(_modManager.BasePath, f.Count, f.Select(file => new FileInfo(file)),
|
||||
AddNewMod, _config, _modEditor);
|
||||
AddNewMod, _config, _modEditor, _modManager);
|
||||
ImGui.OpenPopup("Import Status");
|
||||
}, 0, modPath, _config.AlwaysOpenDefaultImport);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class ModPanelDescriptionTab : ITab
|
|||
out var editedTag);
|
||||
_tutorial.OpenTutorial(BasicTutorialSteps.Tags);
|
||||
if (tagIdx >= 0)
|
||||
_modManager.ChangeLocalTag(_selector.Selected!.Index, tagIdx, editedTag);
|
||||
_modManager.DataEditor.ChangeLocalTag(_selector.Selected!, tagIdx, editedTag);
|
||||
|
||||
if (_selector.Selected!.ModTags.Count > 0)
|
||||
_modTags.Draw("Mod Tags: ", "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod.",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ public class ModPanelEditTab : ITab
|
|||
var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags,
|
||||
out var editedTag);
|
||||
if (tagIdx >= 0)
|
||||
_modManager.ChangeModTag(_mod.Index, tagIdx, editedTag);
|
||||
_modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
|
||||
|
||||
UiHelpers.DefaultLineSpace();
|
||||
AddOptionGroup.Draw(_modManager, _mod);
|
||||
|
|
@ -172,18 +172,18 @@ public class ModPanelEditTab : ITab
|
|||
private void EditRegularMeta()
|
||||
{
|
||||
if (Input.Text("Name", Input.Name, Input.None, _mod.Name, out var newName, 256, UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModName(_mod.Index, newName);
|
||||
_modManager.DataEditor.ChangeModName(_mod, newName);
|
||||
|
||||
if (Input.Text("Author", Input.Author, Input.None, _mod.Author, out var newAuthor, 256, UiHelpers.InputTextWidth.X))
|
||||
Penumbra.ModManager.ChangeModAuthor(_mod.Index, newAuthor);
|
||||
_modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
|
||||
|
||||
if (Input.Text("Version", Input.Version, Input.None, _mod.Version, out var newVersion, 32,
|
||||
UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModVersion(_mod.Index, newVersion);
|
||||
_modManager.DataEditor.ChangeModVersion(_mod, newVersion);
|
||||
|
||||
if (Input.Text("Website", Input.Website, Input.None, _mod.Website, out var newWebsite, 256,
|
||||
UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModWebsite(_mod.Index, newWebsite);
|
||||
_modManager.DataEditor.ChangeModWebsite(_mod, newWebsite);
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3));
|
||||
|
||||
|
|
@ -192,13 +192,13 @@ public class ModPanelEditTab : ITab
|
|||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, Input.Description));
|
||||
|
||||
ImGui.SameLine();
|
||||
var fileExists = File.Exists(_mod.MetaFile.FullName);
|
||||
var fileExists = File.Exists(_modManager.DataEditor.MetaFile(_mod));
|
||||
var tt = fileExists
|
||||
? "Open the metadata json file in the text editor of your choice."
|
||||
: "The metadata json file does not exist.";
|
||||
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##metaFile", UiHelpers.IconButtonSize, tt,
|
||||
!fileExists, true))
|
||||
Process.Start(new ProcessStartInfo(_mod.MetaFile.FullName) { UseShellExecute = true });
|
||||
Process.Start(new ProcessStartInfo(_modManager.DataEditor.MetaFile(_mod)) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
/// <summary> Do some edits outside of iterations. </summary>
|
||||
|
|
@ -349,7 +349,7 @@ public class ModPanelEditTab : ITab
|
|||
switch (_newDescriptionIdx)
|
||||
{
|
||||
case Input.Description:
|
||||
modManager.ChangeModDescription(_mod.Index, _newDescription);
|
||||
modManager.DataEditor.ChangeModDescription(_mod, _newDescription);
|
||||
break;
|
||||
case >= 0:
|
||||
if (_newDescriptionOptionIdx < 0)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ public class ModPanelTabBar
|
|||
|
||||
ImGui.SetCursorPos(newPos);
|
||||
if (ImGui.Button(FontAwesomeIcon.Star.ToIconString()))
|
||||
_modManager.ChangeModFavorite(mod.Index, !mod.Favorite);
|
||||
_modManager.DataEditor.ChangeModFavorite(mod, !mod.Favorite);
|
||||
}
|
||||
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
|
@ -12,7 +10,7 @@ namespace Penumbra.Util;
|
|||
/// <summary>
|
||||
/// Any file type that we want to save via SaveService.
|
||||
/// </summary>
|
||||
public interface ISaveable
|
||||
public interface ISavable
|
||||
{
|
||||
/// <summary> The full file name of a given object. </summary>
|
||||
public string ToFilename(FilenameService fileNames);
|
||||
|
|
@ -42,7 +40,7 @@ public class SaveService
|
|||
}
|
||||
|
||||
/// <summary> Queue a save for the next framework tick. </summary>
|
||||
public void QueueSave(ISaveable value)
|
||||
public void QueueSave(ISavable value)
|
||||
{
|
||||
var file = value.ToFilename(_fileNames);
|
||||
_framework.RegisterDelayed(value.GetType().Name + file, () =>
|
||||
|
|
@ -52,7 +50,7 @@ public class SaveService
|
|||
}
|
||||
|
||||
/// <summary> Immediately trigger a save. </summary>
|
||||
public void ImmediateSave(ISaveable value)
|
||||
public void ImmediateSave(ISavable value)
|
||||
{
|
||||
var name = value.ToFilename(_fileNames);
|
||||
try
|
||||
|
|
@ -75,7 +73,7 @@ public class SaveService
|
|||
}
|
||||
}
|
||||
|
||||
public void ImmediateDelete(ISaveable value)
|
||||
public void ImmediateDelete(ISavable value)
|
||||
{
|
||||
var name = value.ToFilename(_fileNames);
|
||||
try
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue