mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Adding support for mod groups/options. Removed SwapFiles.
This commit is contained in:
parent
c472fdd8cf
commit
01215b5697
9 changed files with 284 additions and 80 deletions
|
|
@ -204,11 +204,6 @@ namespace Penumbra.Importer
|
||||||
);
|
);
|
||||||
newModFolder.Create();
|
newModFolder.Create();
|
||||||
|
|
||||||
File.WriteAllText(
|
|
||||||
Path.Combine( newModFolder.FullName, "meta.json" ),
|
|
||||||
JsonConvert.SerializeObject( modMeta )
|
|
||||||
);
|
|
||||||
|
|
||||||
if( modList.SimpleModsList != null )
|
if( modList.SimpleModsList != null )
|
||||||
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
|
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
|
||||||
|
|
||||||
|
|
@ -216,15 +211,34 @@ namespace Penumbra.Importer
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Iterate through all pages
|
// 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
|
foreach( var option in from modPackPage in modList.ModPackPages
|
||||||
from modGroup in modPackPage.ModGroups
|
from modGroup in modPackPage.ModGroups
|
||||||
from option in modGroup.OptionList
|
from option in modGroup.OptionList
|
||||||
where option.IsChecked
|
|
||||||
select option )
|
select option )
|
||||||
{
|
{
|
||||||
ExtractSimpleModList( newModFolder, option.ModsJsons, modData );
|
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() )
|
||||||
|
{
|
||||||
|
foreach( var file in dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||||
|
{
|
||||||
|
meta.Groups.AddFileToOtherGroups(optionName
|
||||||
|
, file.FullName.Substring(baseFolderLength).TrimStart('\\')
|
||||||
|
, file.FullName.Substring(optionFolderLength).TrimStart('\\').Replace('\\', '/'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
157
Penumbra/Models/GroupInformation.cs
Normal file
157
Penumbra/Models/GroupInformation.cs
Normal file
|
|
@ -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<string> AddGroupTypes(ulong flags, ulong bound, List<string> groupType)
|
||||||
|
{
|
||||||
|
List<string> 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<string> ownTypes, List<string> 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<string> TopTypes { get; set; } = null;
|
||||||
|
public List<string> BottomTypes { get; set; } = null;
|
||||||
|
public List<string> GroupExclusions { get; set; } = null;
|
||||||
|
|
||||||
|
// Customize (De)-Serialization to ignore nulls.
|
||||||
|
public GroupDescription(SerializationInfo info, StreamingContext context)
|
||||||
|
{
|
||||||
|
List<string> readListOrNull(string name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = (List<string>) info.GetValue(name, typeof(List<string>));
|
||||||
|
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<string> TopTypes { get; set; } = new();
|
||||||
|
public List<string> BottomTypes { get; set; } = new();
|
||||||
|
public List<string> 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<string, (string, uint, uint, ulong)> FileToGameAndGroup { get; set; } = new();
|
||||||
|
|
||||||
|
public GroupInformation(){ }
|
||||||
|
|
||||||
|
public GroupInformation(SerializationInfo info, StreamingContext context)
|
||||||
|
{
|
||||||
|
try { TopTypes = (List<string>) info.GetValue( "TopTypes", TopTypes.GetType() ); } catch(Exception){ }
|
||||||
|
try { BottomTypes = (List<string>) info.GetValue( "BottomTypes", BottomTypes.GetType() ); } catch(Exception){ }
|
||||||
|
try { OtherGroups = (List<string>) info.GetValue( "Groups", OtherGroups.GetType() ); } catch(Exception){ }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dictionary<string, GroupDescription> dict = new();
|
||||||
|
dict = (Dictionary<string, GroupDescription>) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,11 @@ namespace Penumbra.Models
|
||||||
public class ModInfo
|
public class ModInfo
|
||||||
{
|
{
|
||||||
public string FolderName { get; set; }
|
public string FolderName { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public int Priority { 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]
|
[JsonIgnore]
|
||||||
public ResourceMod Mod { get; set; }
|
public ResourceMod Mod { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ namespace Penumbra.Models
|
||||||
|
|
||||||
public string Website { get; set; }
|
public string Website { get; set; }
|
||||||
|
|
||||||
public Dictionary< string, string > FileSwaps { get; } = new();
|
|
||||||
|
|
||||||
public List<string> ChangedItems { get; set; } = new();
|
public List<string> ChangedItems { get; set; } = new();
|
||||||
|
|
||||||
|
public GroupInformation Groups { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +156,9 @@ namespace Penumbra.Mods
|
||||||
var entry = new ModInfo
|
var entry = new ModInfo
|
||||||
{
|
{
|
||||||
Priority = ModSettings.Count,
|
Priority = ModSettings.Count,
|
||||||
|
CurrentGroup = 0,
|
||||||
|
CurrentTop = 0,
|
||||||
|
CurrentBottom = 0,
|
||||||
FolderName = mod.ModBasePath.Name,
|
FolderName = mod.ModBasePath.Name,
|
||||||
Enabled = true,
|
Enabled = true,
|
||||||
Mod = mod
|
Mod = mod
|
||||||
|
|
@ -181,12 +184,23 @@ namespace Penumbra.Mods
|
||||||
return AddModSettings( mod );
|
return AddModSettings( mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable< ResourceMod > GetOrderedAndEnabledModList()
|
public IEnumerable<ModInfo> GetOrderedAndEnabledModSettings()
|
||||||
{
|
{
|
||||||
return ModSettings
|
return ModSettings
|
||||||
.Where( x => x.Enabled )
|
.Where( x => x.Enabled )
|
||||||
.OrderBy( x => x.Priority )
|
.OrderBy( x => x.Priority );
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ResourceMod> GetOrderedAndEnabledModList()
|
||||||
|
{
|
||||||
|
return GetOrderedAndEnabledModSettings()
|
||||||
.Select( x => x.Mod );
|
.Select( x => x.Mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(ResourceMod, ModInfo)> GetOrderedAndEnabledModListWithSettings()
|
||||||
|
{
|
||||||
|
return GetOrderedAndEnabledModSettings()
|
||||||
|
.Select( x => (x.Mod, x) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Windows.Forms.VisualStyles;
|
||||||
using Penumbra.Models;
|
using Penumbra.Models;
|
||||||
|
using Swan.Logging;
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Mods
|
||||||
{
|
{
|
||||||
|
|
@ -9,7 +11,6 @@ namespace Penumbra.Mods
|
||||||
{
|
{
|
||||||
private readonly Plugin _plugin;
|
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; }
|
public ModCollection Mods { get; set; }
|
||||||
|
|
||||||
|
|
@ -106,11 +107,10 @@ namespace Penumbra.Mods
|
||||||
public void CalculateEffectiveFileList()
|
public void CalculateEffectiveFileList()
|
||||||
{
|
{
|
||||||
ResolvedFiles.Clear();
|
ResolvedFiles.Clear();
|
||||||
SwappedFiles.Clear();
|
|
||||||
|
|
||||||
var registeredFiles = new Dictionary< string, string >();
|
var registeredFiles = new Dictionary< string, string >();
|
||||||
|
|
||||||
foreach( var mod in Mods.GetOrderedAndEnabledModList() )
|
foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings() )
|
||||||
{
|
{
|
||||||
mod.FileConflicts?.Clear();
|
mod.FileConflicts?.Clear();
|
||||||
|
|
||||||
|
|
@ -119,31 +119,34 @@ namespace Penumbra.Mods
|
||||||
|
|
||||||
foreach( var file in mod.ModFiles )
|
foreach( var file in mod.ModFiles )
|
||||||
{
|
{
|
||||||
var gamePath = file.FullName.Substring( baseDir.Length )
|
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
|
||||||
.TrimStart( '\\' ).Replace( '\\', '/' );
|
|
||||||
|
|
||||||
if( !ResolvedFiles.ContainsKey( gamePath ) )
|
string gamePath;
|
||||||
|
bool addFile = true;
|
||||||
|
(string, uint, uint, ulong) tuple;
|
||||||
|
if (mod.Meta.Groups.FileToGameAndGroup.TryGetValue(relativeFilePath, out tuple))
|
||||||
{
|
{
|
||||||
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
|
gamePath = tuple.Item1;
|
||||||
registeredFiles[ gamePath ] = mod.Meta.Name;
|
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 if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
else
|
||||||
{
|
gamePath = relativeFilePath.Replace( '\\', '/' );
|
||||||
mod.AddConflict( modName, gamePath );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var swap in mod.Meta.FileSwaps )
|
if ( addFile )
|
||||||
{
|
|
||||||
// just assume people put not fucked paths in here lol
|
|
||||||
if( !SwappedFiles.ContainsKey( swap.Value ) )
|
|
||||||
{
|
{
|
||||||
SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value;
|
if( !ResolvedFiles.ContainsKey( gamePath ) )
|
||||||
registeredFiles[ swap.Key ] = mod.Meta.Name;
|
{
|
||||||
}
|
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
|
||||||
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
|
registeredFiles[ gamePath ] = mod.Meta.Name;
|
||||||
{
|
}
|
||||||
mod.AddConflict( modName, swap.Key );
|
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
||||||
|
{
|
||||||
|
mod.AddConflict( modName, gamePath );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +164,6 @@ namespace Penumbra.Mods
|
||||||
DiscoverMods();
|
DiscoverMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public FileInfo GetCandidateForGameFile( string gameResourcePath )
|
public FileInfo GetCandidateForGameFile( string gameResourcePath )
|
||||||
{
|
{
|
||||||
var val = ResolvedFiles.TryGetValue( gameResourcePath, out var candidate );
|
var val = ResolvedFiles.TryGetValue( gameResourcePath, out var candidate );
|
||||||
|
|
@ -178,16 +180,11 @@ namespace Penumbra.Mods
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSwappedFilePath( string gameResourcePath )
|
public string ResolveReplacementFilePath( string gameResourcePath )
|
||||||
{
|
|
||||||
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
|
|
||||||
{
|
{
|
||||||
gameResourcePath = gameResourcePath.ToLowerInvariant();
|
gameResourcePath = gameResourcePath.ToLowerInvariant();
|
||||||
|
|
||||||
return GetCandidateForGameFile( gameResourcePath )?.FullName ?? GetSwappedFilePath( gameResourcePath );
|
return GetCandidateForGameFile( gameResourcePath )?.FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ namespace Penumbra.Mods
|
||||||
ModFiles.Add( file );
|
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 )
|
public void AddConflict( string modName, string path )
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -129,7 +129,7 @@ namespace Penumbra
|
||||||
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
|
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 :(
|
// path must be < 260 because statically defined array length :(
|
||||||
if( replacementPath == null || replacementPath.Length >= 260 )
|
if( replacementPath == null || replacementPath.Length >= 260 )
|
||||||
|
|
|
||||||
|
|
@ -558,6 +558,39 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<string> 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()
|
void DrawInstalledMods()
|
||||||
{
|
{
|
||||||
|
|
@ -621,6 +654,7 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
DrawEditButtons();
|
DrawEditButtons();
|
||||||
|
|
||||||
|
DrawGroupSelectors();
|
||||||
|
|
||||||
ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" );
|
ImGui.TextWrapped( _selectedMod.Mod.Meta.Description ?? "" );
|
||||||
|
|
||||||
|
|
@ -649,25 +683,6 @@ namespace Penumbra.UI
|
||||||
ImGui.EndTabItem();
|
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( _selectedMod.Mod.FileConflicts.Any() )
|
||||||
{
|
{
|
||||||
if( ImGui.BeginTabItem( "File Conflicts" ) )
|
if( ImGui.BeginTabItem( "File Conflicts" ) )
|
||||||
|
|
@ -742,7 +757,7 @@ namespace Penumbra.UI
|
||||||
// todo: virtualise this
|
// todo: virtualise this
|
||||||
foreach( var file in _plugin.ModManager.ResolvedFiles )
|
foreach( var file in _plugin.ModManager.ResolvedFiles )
|
||||||
{
|
{
|
||||||
ImGui.Selectable( file.Value.FullName );
|
ImGui.Selectable( file.Value.FullName + " -> " + file.Key );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue