From 8e87e2d1cf0aab45ac9620ef15311d055a943d49 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Jan 2021 10:26:09 +0100 Subject: [PATCH 1/5] Use version and website information and add edit options. --- Penumbra/UI/SettingsInterface.cs | 95 +++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 88b4242f9..62a4235e3 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -6,8 +6,9 @@ using System.Threading.Tasks; using System.Windows.Forms; using Dalamud.Interface; -using Dalamud.Plugin; -using ImGuiNET; +using Dalamud.Plugin; +using ImGuiNET; +using Newtonsoft.Json; using Penumbra.Importer; using Penumbra.Models; @@ -500,7 +501,63 @@ void DrawDeleteModal() } ImGui.EndPopup(); + } + + // Website button with On-Hover address if valid http(s), otherwise text. + private void DrawWebsiteText() + { + if ((_selectedMod.Mod.Meta.Website?.Length ?? 0) > 0) + { + var validUrl = Uri.TryCreate(_selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttps ||uriResult.Scheme == Uri.UriSchemeHttp); + ImGui.SameLine(); + if (validUrl) + { + if (ImGui.SmallButton("Open Website")) + { + Process.Start( _selectedMod.Mod.Meta.Website ); + } + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text( _selectedMod.Mod.Meta.Website ); + ImGui.EndTooltip(); + } + } + else + { + ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" ); + ImGui.SameLine(); + ImGui.Text(_selectedMod.Mod.Meta.Website); + } + } } + + // Create Mod-Handling buttons. + private void DrawEditButtons() + { + ImGui.SameLine(); + if( ImGui.Button( "Open Mod Folder" ) ) + { + Process.Start( _selectedMod.Mod.ModBasePath.FullName ); + } + + ImGui.SameLine(); + if( ImGui.Button( "Edit JSON" ) ) + { + var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json"); + File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) ); + Process.Start( metaPath ); + } + + ImGui.SameLine(); + if( ImGui.Button( "Reload JSON" ) ) + { + ReloadMods(); + _selectedMod = _plugin.ModManager.Mods.ModSettings[ _selectedModIndex ]; + } + } + void DrawInstalledMods() { @@ -532,13 +589,25 @@ void DrawInstalledMods() { ImGui.BeginChild( "selectedModInfo", AutoFillSize, true ); - ImGui.Text( _selectedMod.Mod.Meta.Name ); + ImGui.Text( _selectedMod.Mod.Meta.Name ); + + // (Version ...) or nothing. + if ((_selectedMod.Mod.Meta.Version?.Length ?? 0) > 0) + { + ImGui.SameLine(); + ImGui.Text($"(Version {_selectedMod.Mod.Meta.Version})" ); + } + + // by Author or Unknown. ImGui.SameLine(); ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "by" ); - ImGui.SameLine(); - ImGui.Text( _selectedMod.Mod.Meta.Author ); - - ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); + ImGui.SameLine(); + if ((_selectedMod.Mod.Meta.Author?.Length ?? 0) > 0 ) + ImGui.Text( _selectedMod.Mod.Meta.Author ); + else + ImGui.Text( "Unknown" ); + + DrawWebsiteText(); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 10 ); @@ -548,12 +617,12 @@ void DrawInstalledMods() _selectedMod.Enabled = enabled; _plugin.ModManager.Mods.Save(); _plugin.ModManager.CalculateEffectiveFileList(); - } - - if( ImGui.Button( "Open Mod Folder" ) ) - { - Process.Start( _selectedMod.Mod.ModBasePath.FullName ); - } + } + + DrawEditButtons(); + + + ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); ImGui.BeginTabBar( "PenumbraPluginDetails" ); if( ImGui.BeginTabItem( "Files" ) ) From c472fdd8cf180de05b8f073974933c43759d8946 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Jan 2021 10:41:42 +0100 Subject: [PATCH 2/5] Added customizable (user-input) list of replaced items. --- Penumbra/Models/ModMeta.cs | 3 +++ Penumbra/UI/SettingsInterface.cs | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Penumbra/Models/ModMeta.cs b/Penumbra/Models/ModMeta.cs index 08b007507..4be464616 100644 --- a/Penumbra/Models/ModMeta.cs +++ b/Penumbra/Models/ModMeta.cs @@ -13,5 +13,8 @@ public class ModMeta public string Website { get; set; } public Dictionary< string, string > FileSwaps { get; } = new(); + + public List ChangedItems { get; set; } = new(); + } } \ No newline at end of file diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 62a4235e3..a5017d8ec 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -623,18 +623,27 @@ void DrawInstalledMods() ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); + + ImGui.BeginTabBar( "PenumbraPluginDetails" ); + if ( (_selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0) + { + if( ImGui.BeginTabItem( "Changed Items" ) ) + { + ImGui.SetNextItemWidth( -1 ); + if( ImGui.ListBoxHeader( "###", AutoFillSize ) ) + foreach(var item in _selectedMod.Mod.Meta.ChangedItems) + ImGui.Selectable( item ); + ImGui.ListBoxFooter(); + ImGui.EndTabItem(); + } + } - ImGui.BeginTabBar( "PenumbraPluginDetails" ); if( ImGui.BeginTabItem( "Files" ) ) { ImGui.SetNextItemWidth( -1 ); if( ImGui.ListBoxHeader( "##", AutoFillSize ) ) - { foreach( var file in _selectedMod.Mod.ModFiles ) - { ImGui.Selectable( file.FullName ); - } - } ImGui.ListBoxFooter(); ImGui.EndTabItem(); From 01215b56974870861a7d3c4deb29aa067af0f060 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Jan 2021 14:48:46 +0100 Subject: [PATCH 3/5] Adding support for mod groups/options. Removed SwapFiles. --- Penumbra/Importer/TexToolsImport.cs | 36 +++++-- Penumbra/Models/GroupInformation.cs | 157 ++++++++++++++++++++++++++++ Penumbra/Models/ModInfo.cs | 9 +- Penumbra/Models/ModMeta.cs | 5 +- Penumbra/Mods/ModCollection.cs | 18 +++- Penumbra/Mods/ModManager.cs | 75 +++++++------ Penumbra/Mods/ResourceMod.cs | 5 + Penumbra/ResourceLoader.cs | 4 +- Penumbra/UI/SettingsInterface.cs | 55 ++++++---- 9 files changed, 284 insertions(+), 80 deletions(-) create mode 100644 Penumbra/Models/GroupInformation.cs diff --git a/Penumbra/Importer/TexToolsImport.cs b/Penumbra/Importer/TexToolsImport.cs index de183fab8..0173bcec1 100644 --- a/Penumbra/Importer/TexToolsImport.cs +++ b/Penumbra/Importer/TexToolsImport.cs @@ -204,11 +204,6 @@ private void ImportExtendedV2ModPack( ZipFile extractedModPack ) ); newModFolder.Create(); - File.WriteAllText( - Path.Combine( newModFolder.FullName, "meta.json" ), - JsonConvert.SerializeObject( modMeta ) - ); - if( modList.SimpleModsList != null ) ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); @@ -216,17 +211,36 @@ private void ImportExtendedV2ModPack( ZipFile extractedModPack ) return; // Iterate through all pages - // For now, we are just going to import the default selections - // TODO: implement such a system in resrep? foreach( var option in from modPackPage in modList.ModPackPages from modGroup in modPackPage.ModGroups from option in modGroup.OptionList - where option.IsChecked select option ) + { + var OptionFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, option.Name)); + ExtractSimpleModList(OptionFolder, option.ModsJsons, modData ); + AddMeta(OptionFolder, newModFolder, modMeta, option.Name); + } + + File.WriteAllText( + Path.Combine( newModFolder.FullName, "meta.json" ), + JsonConvert.SerializeObject( modMeta, Formatting.Indented ) + ); + } + + void AddMeta(DirectoryInfo optionFolder, DirectoryInfo baseFolder, ModMeta meta, string optionName) + { + var optionFolderLength = optionFolder.FullName.Length; + var baseFolderLength = baseFolder.FullName.Length; + foreach( var dir in optionFolder.EnumerateDirectories() ) { - ExtractSimpleModList( newModFolder, option.ModsJsons, modData ); - } - } + foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) + { + meta.Groups.AddFileToOtherGroups(optionName + , file.FullName.Substring(baseFolderLength).TrimStart('\\') + , file.FullName.Substring(optionFolderLength).TrimStart('\\').Replace('\\', '/')); + } + } + } private void ImportMetaModPack( FileInfo file ) { diff --git a/Penumbra/Models/GroupInformation.cs b/Penumbra/Models/GroupInformation.cs new file mode 100644 index 000000000..50b6cad45 --- /dev/null +++ b/Penumbra/Models/GroupInformation.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Linq; + +namespace Penumbra.Models +{ + [Serializable] + public class GroupInformation : ISerializable + { + + // This class is just used as a temp class while (de)-serializing. + // It converts the flags into lists and back. + [Serializable] + private class GroupDescription : ISerializable + { + public GroupDescription(GroupInformation info, (string, uint, uint, ulong) vars) + { + GamePath = vars.Item1; + + static List AddGroupTypes(ulong flags, ulong bound, List groupType) + { + List ret = null; + if (flags != uint.MaxValue) + { + ret = new(); + for (var i = 0; i < groupType.Count; ++i) + { + var flag = 1u << i; + if ((flags & flag) == flag) + ret.Add(groupType[i]); + } + } + return ret; + } + + // Tops and Bottoms are uint. + TopTypes = AddGroupTypes(vars.Item2, uint.MaxValue, info.TopTypes); + BottomTypes = AddGroupTypes(vars.Item3, uint.MaxValue, info.BottomTypes); + // Exclusions are the other way around and ulong. + GroupExclusions = AddGroupTypes(~vars.Item4, 0, info.OtherGroups); + } + + public (string, uint, uint, ulong) ToTuple(GroupInformation info) + { + static ulong TypesToFlags(List ownTypes, List globalTypes) + { + if (ownTypes == null) + return ulong.MaxValue; + + ulong flags = 0; + foreach (var x in ownTypes) + { + var index = globalTypes.IndexOf(x); + if (index >= 0) + flags |= (1u << index); + } + return flags; + } + var tops = (uint) TypesToFlags(TopTypes, info.TopTypes); + var bottoms = (uint) TypesToFlags(BottomTypes, info.BottomTypes); + // Exclusions are the other way around. + var groupEx = (GroupExclusions == null) ? ulong.MaxValue : ~TypesToFlags(GroupExclusions, info.OtherGroups); + return (GamePath, tops, bottoms, groupEx); + } + + public string GamePath { get; set; } + public List TopTypes { get; set; } = null; + public List BottomTypes { get; set; } = null; + public List GroupExclusions { get; set; } = null; + + // Customize (De)-Serialization to ignore nulls. + public GroupDescription(SerializationInfo info, StreamingContext context) + { + List readListOrNull(string name) + { + try + { + var ret = (List) info.GetValue(name, typeof(List)); + if (ret == null || ret.Count == 0) + return null; + return ret; + } + catch (Exception) { return null; } + } + GamePath = info.GetString("GamePath"); + TopTypes = readListOrNull("TopTypes"); + BottomTypes = readListOrNull("BottomTypes"); + GroupExclusions = readListOrNull("GroupExclusions"); + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue( "GamePath", GamePath ); + if (TopTypes != null) info.AddValue("TopTypes", TopTypes); + if (BottomTypes != null) info.AddValue("BottomTypes", BottomTypes); + if (GroupExclusions != null) info.AddValue("GroupExclusions", GroupExclusions); + } + } + + public List TopTypes { get; set; } = new(); + public List BottomTypes { get; set; } = new(); + public List OtherGroups { get; set; } = new(); + + public void AddFileToOtherGroups(string optionName, string fileName, string gamePath) + { + var idx = OtherGroups.IndexOf(optionName); + if (idx < 0) + { + idx = OtherGroups.Count; + OtherGroups.Add(optionName); + } + + (string, uint, uint, ulong) tuple = (gamePath, uint.MaxValue, uint.MaxValue, (1ul << idx)); + + if (!FileToGameAndGroup.TryGetValue(fileName, out var tuple2)) + { + FileToGameAndGroup.Add(fileName, tuple); + } + else + { + tuple2.Item1 = tuple.Item1; + tuple2.Item4 |= tuple.Item4; + } + } + + public Dictionary FileToGameAndGroup { get; set; } = new(); + + public GroupInformation(){ } + + public GroupInformation(SerializationInfo info, StreamingContext context) + { + try { TopTypes = (List) info.GetValue( "TopTypes", TopTypes.GetType() ); } catch(Exception){ } + try { BottomTypes = (List) info.GetValue( "BottomTypes", BottomTypes.GetType() ); } catch(Exception){ } + try { OtherGroups = (List) info.GetValue( "Groups", OtherGroups.GetType() ); } catch(Exception){ } + try + { + Dictionary dict = new(); + dict = (Dictionary) info.GetValue( "FileToGameAndGroups", dict.GetType()); + foreach (var pair in dict) + FileToGameAndGroup.Add(pair.Key, pair.Value.ToTuple(this)); + } catch (Exception){ } + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if ((TopTypes?.Count ?? 0) > 0) info.AddValue("TopTypes", TopTypes); + if ((BottomTypes?.Count ?? 0) > 0) info.AddValue("BottomTypes", BottomTypes); + if ((OtherGroups?.Count ?? 0) > 0) info.AddValue("Groups", OtherGroups); + if ((FileToGameAndGroup?.Count ?? 0) > 0) + { + var dict = FileToGameAndGroup.ToDictionary( pair => pair.Key, pair => new GroupDescription( this, pair.Value ) ); + info.AddValue("FileToGameAndGroups", dict); + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Models/ModInfo.cs b/Penumbra/Models/ModInfo.cs index cbf864822..8eb01437d 100644 --- a/Penumbra/Models/ModInfo.cs +++ b/Penumbra/Models/ModInfo.cs @@ -6,9 +6,12 @@ namespace Penumbra.Models public class ModInfo { public string FolderName { get; set; } - public bool Enabled { get; set; } - public int Priority { get; set; } - + public bool Enabled { get; set; } + public int Priority { get; set; } + public int CurrentTop { get; set; } = 0; + public int CurrentBottom { get; set; } = 0; + public int CurrentGroup { get; set; } = 0; + [JsonIgnore] public ResourceMod Mod { get; set; } } diff --git a/Penumbra/Models/ModMeta.cs b/Penumbra/Models/ModMeta.cs index 4be464616..9d1b869d3 100644 --- a/Penumbra/Models/ModMeta.cs +++ b/Penumbra/Models/ModMeta.cs @@ -10,11 +10,10 @@ public class ModMeta public string Version { get; set; } - public string Website { get; set; } - - public Dictionary< string, string > FileSwaps { get; } = new(); + public string Website { get; set; } public List ChangedItems { get; set; } = new(); + public GroupInformation Groups { get; set; } = new(); } } \ No newline at end of file diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index cfbfd7ddd..e0acad544 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -156,6 +156,9 @@ public ModInfo AddModSettings( ResourceMod mod ) var entry = new ModInfo { Priority = ModSettings.Count, + CurrentGroup = 0, + CurrentTop = 0, + CurrentBottom = 0, FolderName = mod.ModBasePath.Name, Enabled = true, Mod = mod @@ -181,12 +184,23 @@ public ModInfo FindOrCreateModSettings( ResourceMod mod ) return AddModSettings( mod ); } - public IEnumerable< ResourceMod > GetOrderedAndEnabledModList() + public IEnumerable GetOrderedAndEnabledModSettings() { return ModSettings .Where( x => x.Enabled ) - .OrderBy( x => x.Priority ) + .OrderBy( x => x.Priority ); + } + + public IEnumerable GetOrderedAndEnabledModList() + { + return GetOrderedAndEnabledModSettings() .Select( x => x.Mod ); + } + + public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings() + { + return GetOrderedAndEnabledModSettings() + .Select( x => (x.Mod, x) ); } } } \ No newline at end of file diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 14a2c0d12..3ce172423 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Windows.Forms.VisualStyles; using Penumbra.Models; - +using Swan.Logging; + namespace Penumbra.Mods { public class ModManager : IDisposable { private readonly Plugin _plugin; public readonly Dictionary< string, FileInfo > ResolvedFiles = new(); - public readonly Dictionary< string, string > SwappedFiles = new(); public ModCollection Mods { get; set; } @@ -106,44 +107,46 @@ public void DiscoverMods( DirectoryInfo basePath ) public void CalculateEffectiveFileList() { ResolvedFiles.Clear(); - SwappedFiles.Clear(); var registeredFiles = new Dictionary< string, string >(); - foreach( var mod in Mods.GetOrderedAndEnabledModList() ) - { + foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings() ) + { mod.FileConflicts?.Clear(); // fixup path var baseDir = mod.ModBasePath.FullName; - foreach( var file in mod.ModFiles ) - { - var gamePath = file.FullName.Substring( baseDir.Length ) - .TrimStart( '\\' ).Replace( '\\', '/' ); - - if( !ResolvedFiles.ContainsKey( gamePath ) ) - { - ResolvedFiles[ gamePath.ToLowerInvariant() ] = file; - registeredFiles[ gamePath ] = mod.Meta.Name; - } - else if( registeredFiles.TryGetValue( gamePath, out var modName ) ) + foreach( var file in mod.ModFiles ) + { + var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); + + string gamePath; + bool addFile = true; + (string, uint, uint, ulong) tuple; + if (mod.Meta.Groups.FileToGameAndGroup.TryGetValue(relativeFilePath, out tuple)) + { + gamePath = tuple.Item1; + var (_, tops, bottoms, excludes) = tuple; + var validTop = ((1u << settings.CurrentTop) & tops ) != 0; + var validBottom = ((1u << settings.CurrentBottom) & bottoms ) != 0; + var validGroup = ((1ul << settings.CurrentGroup) & excludes) != 0; + addFile = validTop && validBottom && validGroup; + } + else + gamePath = relativeFilePath.Replace( '\\', '/' ); + + if ( addFile ) { - mod.AddConflict( modName, gamePath ); - } - } - - foreach( var swap in mod.Meta.FileSwaps ) - { - // just assume people put not fucked paths in here lol - if( !SwappedFiles.ContainsKey( swap.Value ) ) - { - SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value; - registeredFiles[ swap.Key ] = mod.Meta.Name; - } - else if( registeredFiles.TryGetValue( swap.Key, out var modName ) ) - { - mod.AddConflict( modName, swap.Key ); + if( !ResolvedFiles.ContainsKey( gamePath ) ) + { + ResolvedFiles[ gamePath.ToLowerInvariant() ] = file; + registeredFiles[ gamePath ] = mod.Meta.Name; + } + else if( registeredFiles.TryGetValue( gamePath, out var modName ) ) + { + mod.AddConflict( modName, gamePath ); + } } } } @@ -161,7 +164,6 @@ public void DeleteMod( ResourceMod mod ) DiscoverMods(); } - public FileInfo GetCandidateForGameFile( string gameResourcePath ) { var val = ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ); @@ -178,16 +180,11 @@ public FileInfo GetCandidateForGameFile( string gameResourcePath ) return candidate; } - public string GetSwappedFilePath( string gameResourcePath ) - { - return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null; - } - - public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) + public string ResolveReplacementFilePath( string gameResourcePath ) { gameResourcePath = gameResourcePath.ToLowerInvariant(); - return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath ); + return GetCandidateForGameFile( gameResourcePath )?.FullName; } public void Dispose() diff --git a/Penumbra/Mods/ResourceMod.cs b/Penumbra/Mods/ResourceMod.cs index a150928e6..4800841d3 100644 --- a/Penumbra/Mods/ResourceMod.cs +++ b/Penumbra/Mods/ResourceMod.cs @@ -31,6 +31,11 @@ public void RefreshModFiles() ModFiles.Add( file ); } } + + // Only add if not in a sub-folder, otherwise it was already added. + foreach( var pair in Meta.Groups.FileToGameAndGroup ) + if (pair.Key.IndexOfAny(new char[]{'/', '\\'}) < 0) + ModFiles.Add( new FileInfo(Path.Combine(ModBasePath.FullName, pair.Key)) ); } public void AddConflict( string modName, string path ) diff --git a/Penumbra/ResourceLoader.cs b/Penumbra/ResourceLoader.cs index 86cf2b444..37b3a3234 100644 --- a/Penumbra/ResourceLoader.cs +++ b/Penumbra/ResourceLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -129,7 +129,7 @@ bool isUnknown PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath ); } - var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath ); + var replacementPath = Plugin.ModManager.ResolveReplacementFilePath( gameFsPath ); // path must be < 260 because statically defined array length :( if( replacementPath == null || replacementPath.Length >= 260 ) diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index a5017d8ec..ba0e5c1d7 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -558,6 +558,39 @@ private void DrawEditButtons() } } + private void DrawGroupSelectors() + { + var hasTopTypes = (_selectedMod.Mod.Meta.Groups.TopTypes?.Count ?? 0) > 1; + var hasBottomTypes = (_selectedMod.Mod.Meta.Groups.BottomTypes?.Count ?? 0) > 1; + var hasGroups = (_selectedMod.Mod.Meta.Groups.OtherGroups?.Count ?? 0) > 1; + var numSelectors = (hasTopTypes ? 1 : 0) + (hasBottomTypes ? 1 : 0) + (hasGroups ? 1 : 0); + var selectorWidth = (ImGui.GetWindowWidth() + - (hasTopTypes ? ImGui.CalcTextSize("Top ").X : 0) + - (hasBottomTypes ? ImGui.CalcTextSize("Bottom ").X : 0) + - (hasGroups ? ImGui.CalcTextSize("Group ").X : 0)) / numSelectors; + + void DrawSelector(string label, string propertyName, System.Collections.Generic.List list, bool sameLine) + { + var current = (int) _selectedMod.GetType().GetProperty(propertyName).GetValue(_selectedMod); + ImGui.SetNextItemWidth( selectorWidth ); + if (sameLine) ImGui.SameLine(); + if ( ImGui.Combo(label, ref current, list.ToArray(), list.Count()) ) + { + _selectedMod.GetType().GetProperty(propertyName).SetValue(_selectedMod, current); + _plugin.ModManager.Mods.Save(); + _plugin.ModManager.CalculateEffectiveFileList(); + } + } + + if ( hasTopTypes ) + DrawSelector("Top", "CurrentTop", _selectedMod.Mod.Meta.Groups.TopTypes, false); + + if ( hasBottomTypes ) + DrawSelector("Bottom", "CurrentBottom", _selectedMod.Mod.Meta.Groups.BottomTypes, hasTopTypes); + + if ( hasGroups ) + DrawSelector("Group", "CurrentGroup", _selectedMod.Mod.Meta.Groups.OtherGroups, numSelectors > 1); + } void DrawInstalledMods() { @@ -621,6 +654,7 @@ void DrawInstalledMods() DrawEditButtons(); + DrawGroupSelectors(); ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" ); @@ -649,25 +683,6 @@ void DrawInstalledMods() ImGui.EndTabItem(); } - if( _selectedMod.Mod.Meta.FileSwaps.Any() ) - { - if( ImGui.BeginTabItem( "File Swaps" ) ) - { - ImGui.SetNextItemWidth( -1 ); - if( ImGui.ListBoxHeader( "##", AutoFillSize ) ) - { - foreach( var file in _selectedMod.Mod.Meta.FileSwaps ) - { - // todo: fucking gross alloc every frame * items - ImGui.Selectable( $"{file.Key} -> {file.Value}" ); - } - } - - ImGui.ListBoxFooter(); - ImGui.EndTabItem(); - } - } - if( _selectedMod.Mod.FileConflicts.Any() ) { if( ImGui.BeginTabItem( "File Conflicts" ) ) @@ -742,7 +757,7 @@ void DrawEffectiveFileList() // todo: virtualise this foreach( var file in _plugin.ModManager.ResolvedFiles ) { - ImGui.Selectable( file.Value.FullName ); + ImGui.Selectable( file.Value.FullName + " -> " + file.Key ); } } From d24fed6eb96be57e6269b20f17c6671504a17713 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 15 Jan 2021 10:12:42 +0100 Subject: [PATCH 4/5] Reverted Removed SwapFiles. --- Penumbra/Models/ModMeta.cs | 2 ++ Penumbra/Mods/ModManager.cs | 38 ++++++++++++++++++++++++-------- Penumbra/ResourceLoader.cs | 4 ++-- Penumbra/UI/SettingsInterface.cs | 18 +++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/Penumbra/Models/ModMeta.cs b/Penumbra/Models/ModMeta.cs index 9d1b869d3..b5bb31136 100644 --- a/Penumbra/Models/ModMeta.cs +++ b/Penumbra/Models/ModMeta.cs @@ -13,6 +13,8 @@ public class ModMeta public string Website { get; set; } public List ChangedItems { get; set; } = new(); + + public Dictionary< string, string > FileSwaps { get; } = new(); public GroupInformation Groups { get; set; } = new(); } diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 3ce172423..4ab046609 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Windows.Forms.VisualStyles; -using Penumbra.Models; -using Swan.Logging; +using System.IO; +using Penumbra.Models; namespace Penumbra.Mods { public class ModManager : IDisposable { private readonly Plugin _plugin; - public readonly Dictionary< string, FileInfo > ResolvedFiles = new(); + public readonly Dictionary< string, FileInfo > ResolvedFiles = new(); + public readonly Dictionary< string, string > SwappedFiles = new(); public ModCollection Mods { get; set; } @@ -106,7 +105,8 @@ public void DiscoverMods( DirectoryInfo basePath ) public void CalculateEffectiveFileList() { - ResolvedFiles.Clear(); + ResolvedFiles.Clear(); + SwappedFiles.Clear(); var registeredFiles = new Dictionary< string, string >(); @@ -148,6 +148,20 @@ public void CalculateEffectiveFileList() mod.AddConflict( modName, gamePath ); } } + } + + foreach( var swap in mod.Meta.FileSwaps ) + { + // just assume people put not fucked paths in here lol + if( !SwappedFiles.ContainsKey( swap.Value ) ) + { + SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value; + registeredFiles[ swap.Key ] = mod.Meta.Name; + } + else if( registeredFiles.TryGetValue( swap.Key, out var modName ) ) + { + mod.AddConflict( modName, swap.Key ); + } } } } @@ -180,12 +194,18 @@ public FileInfo GetCandidateForGameFile( string gameResourcePath ) return candidate; } - public string ResolveReplacementFilePath( string gameResourcePath ) + public string GetSwappedFilePath( string gameResourcePath ) { - gameResourcePath = gameResourcePath.ToLowerInvariant(); + return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null; + } - return GetCandidateForGameFile( gameResourcePath )?.FullName; + public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) + { + gameResourcePath = gameResourcePath.ToLowerInvariant(); + + return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath ); } + public void Dispose() { diff --git a/Penumbra/ResourceLoader.cs b/Penumbra/ResourceLoader.cs index 37b3a3234..86cf2b444 100644 --- a/Penumbra/ResourceLoader.cs +++ b/Penumbra/ResourceLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -129,7 +129,7 @@ bool isUnknown PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath ); } - var replacementPath = Plugin.ModManager.ResolveReplacementFilePath( gameFsPath ); + var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath ); // path must be < 260 because statically defined array length :( if( replacementPath == null || replacementPath.Length >= 260 ) diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index ba0e5c1d7..0aec8150c 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -683,6 +683,24 @@ void DrawInstalledMods() ImGui.EndTabItem(); } + if( _selectedMod.Mod.Meta.FileSwaps.Any() ) + { + if( ImGui.BeginTabItem( "File Swaps" ) ) + { + ImGui.SetNextItemWidth( -1 ); + if( ImGui.ListBoxHeader( "##", AutoFillSize ) ) + { + foreach( var file in _selectedMod.Mod.Meta.FileSwaps ) + { + // todo: fucking gross alloc every frame * items + ImGui.Selectable( $"{file.Key} -> {file.Value}" ); + } + } + + ImGui.ListBoxFooter(); + ImGui.EndTabItem(); + } + } if( _selectedMod.Mod.FileConflicts.Any() ) { if( ImGui.BeginTabItem( "File Conflicts" ) ) From 3df367550090a408aef57bad04a1c4ff8a87dcc7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 15 Jan 2021 12:06:30 +0100 Subject: [PATCH 5/5] review: _selectedModIndex fix, redundant initialization removed. --- Penumbra/Mods/ModCollection.cs | 3 --- Penumbra/UI/SettingsInterface.cs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index e0acad544..7a9a853cc 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -156,9 +156,6 @@ public ModInfo AddModSettings( ResourceMod mod ) var entry = new ModInfo { Priority = ModSettings.Count, - CurrentGroup = 0, - CurrentTop = 0, - CurrentBottom = 0, FolderName = mod.ModBasePath.Name, Enabled = true, Mod = mod diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 0aec8150c..7824e2b12 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -267,7 +267,9 @@ void DrawSettingsTab() if( ImGui.Button( "Rediscover Mods" ) ) { - ReloadMods(); + ReloadMods(); + _selectedModIndex = 0; + _selectedMod = null; } ImGui.SameLine(); @@ -554,7 +556,17 @@ private void DrawEditButtons() if( ImGui.Button( "Reload JSON" ) ) { ReloadMods(); - _selectedMod = _plugin.ModManager.Mods.ModSettings[ _selectedModIndex ]; + + // May select a different mod than before if mods were added or deleted, but will not crash. + if (_selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count) + { + _selectedMod = _plugin.ModManager.Mods.ModSettings[_selectedModIndex]; + } + else + { + _selectedModIndex = 0; + _selectedMod = null; + } } }