From fba5bc68209d7c2614ca48a13e3cc9dcf36d6ad8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 18 Apr 2023 18:44:53 +0200 Subject: [PATCH] Fix some bugs and start work on new collections tab. --- Penumbra.GameData/Enums/Race.cs | 10 + .../Cache/CollectionCacheManager.cs | 2 +- .../Collections/Manager/ActiveCollections.cs | 123 ++- .../Collections/Manager/CollectionStorage.cs | 4 +- .../Collections/Manager/CollectionType.cs | 566 ++++++----- Penumbra/Collections/ModCollection.cs | 6 +- Penumbra/Interop/Services/FontReloader.cs | 37 +- Penumbra/Meta/Files/ImcFile.cs | 4 +- Penumbra/Meta/MetaFileManager.cs | 4 +- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 10 +- Penumbra/Mods/Manager/ModDataEditor.cs | 6 - Penumbra/Mods/ModCreator.cs | 2 +- Penumbra/Penumbra.cs | 4 - Penumbra/PenumbraNew.cs | 2 + Penumbra/Services/ConfigMigrationService.cs | 6 +- Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 6 +- Penumbra/UI/Classes/Colors.cs | 2 + ...llectionSelector.cs => CollectionCombo.cs} | 4 +- ...lectionUi.cs => IndividualCollectionUi.cs} | 4 +- ...ions.InheritanceUi.cs => InheritanceUi.cs} | 0 .../{Collections.NpcCombo.cs => NpcCombo.cs} | 0 ...ctions.SpecialCombo.cs => SpecialCombo.cs} | 1 - ...ollections.WorldCombo.cs => WorldCombo.cs} | 0 Penumbra/UI/Tabs/CollectionsTab.cs | 891 ++++++++++++------ Penumbra/UI/Tabs/CollectionsTabOld.cs | 299 ++++++ Penumbra/UI/Tabs/ModsTab.cs | 70 +- 26 files changed, 1346 insertions(+), 717 deletions(-) rename Penumbra/UI/CollectionTab/{Collections.CollectionSelector.cs => CollectionCombo.cs} (85%) rename Penumbra/UI/CollectionTab/{Collections.IndividualCollectionUi.cs => IndividualCollectionUi.cs} (96%) rename Penumbra/UI/CollectionTab/{Collections.InheritanceUi.cs => InheritanceUi.cs} (100%) rename Penumbra/UI/CollectionTab/{Collections.NpcCombo.cs => NpcCombo.cs} (100%) rename Penumbra/UI/CollectionTab/{Collections.SpecialCombo.cs => SpecialCombo.cs} (95%) rename Penumbra/UI/CollectionTab/{Collections.WorldCombo.cs => WorldCombo.cs} (100%) create mode 100644 Penumbra/UI/Tabs/CollectionsTabOld.cs diff --git a/Penumbra.GameData/Enums/Race.cs b/Penumbra.GameData/Enums/Race.cs index 7f86cb6c..d1d859b7 100644 --- a/Penumbra.GameData/Enums/Race.cs +++ b/Penumbra.GameData/Enums/Race.cs @@ -221,6 +221,16 @@ public static class RaceEnumExtensions }; } + public static string ToShortName(this SubRace subRace) + { + return subRace switch + { + SubRace.SeekerOfTheSun => "Sunseeker", + SubRace.KeeperOfTheMoon => "Moonkeeper", + _ => subRace.ToName(), + }; + } + public static bool FitsRace(this SubRace subRace, Race race) => subRace.ToRace() == race; diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 4e3bb0cf..a14facb4 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -43,7 +43,7 @@ public class CollectionCacheManager : IDisposable MetaFileManager = metaFileManager; _active = active; - _communicator.CollectionChange.Subscribe(OnCollectionChange); + _communicator.CollectionChange.Subscribe(OnCollectionChange, -100); _communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100); _communicator.ModPathChanged.Subscribe(OnModChangeRemoval, 100); _communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange); diff --git a/Penumbra/Collections/Manager/ActiveCollections.cs b/Penumbra/Collections/Manager/ActiveCollections.cs index 1e083d90..616640cf 100644 --- a/Penumbra/Collections/Manager/ActiveCollections.cs +++ b/Penumbra/Collections/Manager/ActiveCollections.cs @@ -13,24 +13,35 @@ using Penumbra.Util; namespace Penumbra.Collections.Manager; +public class ActiveCollectionData +{ + public ModCollection Current { get; internal set; } = ModCollection.Empty; + public ModCollection Default { get; internal set; } = ModCollection.Empty; + public ModCollection Interface { get; internal set; } = ModCollection.Empty; + + public readonly ModCollection?[] SpecialCollections = new ModCollection?[Enum.GetValues().Length - 3]; +} + public class ActiveCollections : ISavable, IDisposable { public const int Version = 1; - private readonly CollectionStorage _storage; - private readonly CommunicatorService _communicator; - private readonly SaveService _saveService; + private readonly CollectionStorage _storage; + private readonly CommunicatorService _communicator; + private readonly SaveService _saveService; + private readonly ActiveCollectionData _data; - public ActiveCollections(Configuration config, CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService) + public ActiveCollections(Configuration config, CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService, ActiveCollectionData data) { _storage = storage; _communicator = communicator; _saveService = saveService; + _data = data; Current = storage.DefaultNamed; Default = storage.DefaultNamed; Interface = storage.DefaultNamed; Individuals = new IndividualCollections(actors.AwaitedService, config); - _communicator.CollectionChange.Subscribe(OnCollectionChange); + _communicator.CollectionChange.Subscribe(OnCollectionChange, -100); LoadCollections(); UpdateCurrentCollectionInUse(); } @@ -39,16 +50,28 @@ public class ActiveCollections : ISavable, IDisposable => _communicator.CollectionChange.Unsubscribe(OnCollectionChange); /// The collection currently selected for changing settings. - public ModCollection Current { get; private set; } + public ModCollection Current + { + get => _data.Current; + private set => _data.Current = value; + } /// Whether the currently selected collection is used either directly via assignment or via inheritance. public bool CurrentCollectionInUse { get; private set; } /// The collection used for general file redirections and all characters not specifically named. - public ModCollection Default { get; private set; } + public ModCollection Default + { + get => _data.Default; + private set => _data.Default = value; + } /// The collection used for all files categorized as UI files. - public ModCollection Interface { get; private set; } + public ModCollection Interface + { + get => _data.Interface; + private set => _data.Interface = value; + } /// The list of individual assignments. public readonly IndividualCollections Individuals; @@ -58,16 +81,17 @@ public class ActiveCollections : ISavable, IDisposable => Individuals.TryGetCollection(identifier, out var c) ? c : Default; /// The list of group assignments. - private readonly ModCollection?[] _specialCollections = new ModCollection?[Enum.GetValues().Length - 3]; + private ModCollection?[] SpecialCollections + => _data.SpecialCollections; /// Return all actually assigned group assignments. public IEnumerable> SpecialAssignments { get { - for (var i = 0; i < _specialCollections.Length; ++i) + for (var i = 0; i < SpecialCollections.Length; ++i) { - var collection = _specialCollections[i]; + var collection = SpecialCollections[i]; if (collection != null) yield return new KeyValuePair((CollectionType)i, collection); } @@ -82,7 +106,7 @@ public class ActiveCollections : ISavable, IDisposable public ModCollection? ByType(CollectionType type, ActorIdentifier identifier) { if (type.IsSpecial()) - return _specialCollections[(int)type]; + return SpecialCollections[(int)type]; return type switch { @@ -94,13 +118,13 @@ public class ActiveCollections : ISavable, IDisposable }; } - /// Create a special collection if it does not exist and set it to Empty. + /// Create a special collection if it does not exist and set it to the current default. public bool CreateSpecialCollection(CollectionType collectionType) { - if (!collectionType.IsSpecial() || _specialCollections[(int)collectionType] != null) + if (!collectionType.IsSpecial() || SpecialCollections[(int)collectionType] != null) return false; - _specialCollections[(int)collectionType] = Default; + SpecialCollections[(int)collectionType] = Default; _communicator.CollectionChange.Invoke(collectionType, null, Default, string.Empty); return true; } @@ -111,11 +135,11 @@ public class ActiveCollections : ISavable, IDisposable if (!collectionType.IsSpecial()) return; - var old = _specialCollections[(int)collectionType]; + var old = SpecialCollections[(int)collectionType]; if (old == null) return; - _specialCollections[(int)collectionType] = null; + SpecialCollections[(int)collectionType] = null; _communicator.CollectionChange.Invoke(collectionType, old, null, string.Empty); } @@ -144,7 +168,38 @@ public class ActiveCollections : ISavable, IDisposable _saveService.QueueSave(this); } - /// Set a active collection, can be used to set Default, Current, Interface, Special, or Individual collections. + /// Set and create an active collection, can be used to set Default, Current, Interface, Special, or Individual collections. + public void SetCollection(ModCollection? collection, CollectionType collectionType, ActorIdentifier[] identifiers) + { + if (collectionType is CollectionType.Individual && identifiers.Length > 0 && identifiers[0].IsValid) + { + var idx = Individuals.Index(identifiers[0]); + if (idx >= 0) + { + if (collection == null) + RemoveIndividualCollection(idx); + else + SetCollection(collection, collectionType, idx); + } + else if (collection != null) + { + CreateIndividualCollection(identifiers); + SetCollection(collection, CollectionType.Individual, Individuals.Count - 1); + } + } + else + { + if (collection == null) + RemoveSpecialCollection(collectionType); + else + { + CreateSpecialCollection(collectionType); + SetCollection(collection, collectionType); + } + } + } + + /// Set an active collection, can be used to set Default, Current, Interface, Special, or Individual collections. public void SetCollection(ModCollection collection, CollectionType collectionType, int individualIndex = -1) { var oldCollection = collectionType switch @@ -154,7 +209,7 @@ public class ActiveCollections : ISavable, IDisposable CollectionType.Current => Current, CollectionType.Individual when individualIndex >= 0 && individualIndex < Individuals.Count => Individuals[individualIndex].Collection, CollectionType.Individual => null, - _ when collectionType.IsSpecial() => _specialCollections[(int)collectionType] ?? Default, + _ when collectionType.IsSpecial() => SpecialCollections[(int)collectionType] ?? Default, _ => null, }; @@ -178,7 +233,7 @@ public class ActiveCollections : ISavable, IDisposable break; default: - _specialCollections[(int)collectionType] = collection; + SpecialCollections[(int)collectionType] = collection; break; } @@ -205,7 +260,7 @@ public class ActiveCollections : ISavable, IDisposable { nameof(Interface), Interface.Name }, { nameof(Current), Current.Name }, }; - foreach (var (type, collection) in _specialCollections.WithIndex().Where(p => p.Value != null) + foreach (var (type, collection) in SpecialCollections.WithIndex().Where(p => p.Value != null) .Select(p => ((CollectionType)p.Index, p.Value!))) jObj.Add(type.ToString(), collection.Name); @@ -215,7 +270,7 @@ public class ActiveCollections : ISavable, IDisposable } private void UpdateCurrentCollectionInUse() - => CurrentCollectionInUse = _specialCollections + => CurrentCollectionInUse = SpecialCollections .OfType() .Prepend(Interface) .Prepend(Default) @@ -240,9 +295,9 @@ public class ActiveCollections : ISavable, IDisposable if (oldCollection == Current) SetCollection(Default.Index > ModCollection.Empty.Index ? Default : _storage.DefaultNamed, CollectionType.Current); - for (var i = 0; i < _specialCollections.Length; ++i) + for (var i = 0; i < SpecialCollections.Length; ++i) { - if (oldCollection == _specialCollections[i]) + if (oldCollection == SpecialCollections[i]) SetCollection(ModCollection.Empty, (CollectionType)i); } @@ -329,7 +384,7 @@ public class ActiveCollections : ISavable, IDisposable } else { - _specialCollections[(int)type] = typeCollection; + SpecialCollections[(int)type] = typeCollection; } } } @@ -398,24 +453,6 @@ public class ActiveCollections : ISavable, IDisposable : string.Empty; } - break; - // The group of all Characters is redundant if they are all equal to Default or unassigned. - case CollectionType.MalePlayerCharacter: - case CollectionType.MaleNonPlayerCharacter: - case CollectionType.FemalePlayerCharacter: - case CollectionType.FemaleNonPlayerCharacter: - var first = ByType(CollectionType.MalePlayerCharacter) ?? Default; - var second = ByType(CollectionType.MaleNonPlayerCharacter) ?? Default; - var third = ByType(CollectionType.FemalePlayerCharacter) ?? Default; - var fourth = ByType(CollectionType.FemaleNonPlayerCharacter) ?? Default; - if (first.Index == second.Index - && first.Index == third.Index - && first.Index == fourth.Index - && first.Index == Default.Index) - return - "Assignment is currently redundant due to the group [Male, Female, Player, NPC] Characters being unassigned or identical to each other and Default.\n" - + "You can keep just the Default Assignment."; - break; // Children and Elderly are redundant if they are identical to both Male NPCs and Female NPCs, or if they are unassigned to Default. case CollectionType.NonPlayerChild: diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index be8db099..bf273e1c 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -113,7 +113,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable return false; } - var newCollection = duplicate?.Duplicate(name, _collections.Count) ?? ModCollection.CreateEmpty(name, _collections.Count); + var newCollection = duplicate?.Duplicate(name, _collections.Count) ?? ModCollection.CreateEmpty(name, _collections.Count, _modStorage.Count); _collections.Add(newCollection); _saveService.ImmediateSave(new ModCollectionSave(_modStorage, newCollection)); @@ -200,7 +200,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable /// Does not check for uniqueness. /// private static bool IsValidName(string name) - => name.Length > 0 && name.All(c => !c.IsInvalidAscii() && c is not '|' && !c.IsInvalidInPath()); + => name.Length is > 0 and < 32 && name.All(c => !c.IsInvalidAscii() && c is not '|' && !c.IsInvalidInPath()); /// /// Read all collection files in the Collection Directory. diff --git a/Penumbra/Collections/Manager/CollectionType.cs b/Penumbra/Collections/Manager/CollectionType.cs index 52b48d9b..f08cf380 100644 --- a/Penumbra/Collections/Manager/CollectionType.cs +++ b/Penumbra/Collections/Manager/CollectionType.cs @@ -10,96 +10,96 @@ public enum CollectionType : byte // Special Collections Yourself = Api.Enums.ApiCollectionType.Yourself, - MalePlayerCharacter = Api.Enums.ApiCollectionType.MalePlayerCharacter, - FemalePlayerCharacter = Api.Enums.ApiCollectionType.FemalePlayerCharacter, - MaleNonPlayerCharacter = Api.Enums.ApiCollectionType.MaleNonPlayerCharacter, + MalePlayerCharacter = Api.Enums.ApiCollectionType.MalePlayerCharacter, + FemalePlayerCharacter = Api.Enums.ApiCollectionType.FemalePlayerCharacter, + MaleNonPlayerCharacter = Api.Enums.ApiCollectionType.MaleNonPlayerCharacter, FemaleNonPlayerCharacter = Api.Enums.ApiCollectionType.FemaleNonPlayerCharacter, - NonPlayerChild = Api.Enums.ApiCollectionType.NonPlayerChild, - NonPlayerElderly = Api.Enums.ApiCollectionType.NonPlayerElderly, + NonPlayerChild = Api.Enums.ApiCollectionType.NonPlayerChild, + NonPlayerElderly = Api.Enums.ApiCollectionType.NonPlayerElderly, - MaleMidlander = Api.Enums.ApiCollectionType.MaleMidlander, - FemaleMidlander = Api.Enums.ApiCollectionType.FemaleMidlander, - MaleHighlander = Api.Enums.ApiCollectionType.MaleHighlander, + MaleMidlander = Api.Enums.ApiCollectionType.MaleMidlander, + FemaleMidlander = Api.Enums.ApiCollectionType.FemaleMidlander, + MaleHighlander = Api.Enums.ApiCollectionType.MaleHighlander, FemaleHighlander = Api.Enums.ApiCollectionType.FemaleHighlander, - MaleWildwood = Api.Enums.ApiCollectionType.MaleWildwood, - FemaleWildwood = Api.Enums.ApiCollectionType.FemaleWildwood, - MaleDuskwight = Api.Enums.ApiCollectionType.MaleDuskwight, + MaleWildwood = Api.Enums.ApiCollectionType.MaleWildwood, + FemaleWildwood = Api.Enums.ApiCollectionType.FemaleWildwood, + MaleDuskwight = Api.Enums.ApiCollectionType.MaleDuskwight, FemaleDuskwight = Api.Enums.ApiCollectionType.FemaleDuskwight, - MalePlainsfolk = Api.Enums.ApiCollectionType.MalePlainsfolk, + MalePlainsfolk = Api.Enums.ApiCollectionType.MalePlainsfolk, FemalePlainsfolk = Api.Enums.ApiCollectionType.FemalePlainsfolk, - MaleDunesfolk = Api.Enums.ApiCollectionType.MaleDunesfolk, - FemaleDunesfolk = Api.Enums.ApiCollectionType.FemaleDunesfolk, + MaleDunesfolk = Api.Enums.ApiCollectionType.MaleDunesfolk, + FemaleDunesfolk = Api.Enums.ApiCollectionType.FemaleDunesfolk, - MaleSeekerOfTheSun = Api.Enums.ApiCollectionType.MaleSeekerOfTheSun, - FemaleSeekerOfTheSun = Api.Enums.ApiCollectionType.FemaleSeekerOfTheSun, - MaleKeeperOfTheMoon = Api.Enums.ApiCollectionType.MaleKeeperOfTheMoon, + MaleSeekerOfTheSun = Api.Enums.ApiCollectionType.MaleSeekerOfTheSun, + FemaleSeekerOfTheSun = Api.Enums.ApiCollectionType.FemaleSeekerOfTheSun, + MaleKeeperOfTheMoon = Api.Enums.ApiCollectionType.MaleKeeperOfTheMoon, FemaleKeeperOfTheMoon = Api.Enums.ApiCollectionType.FemaleKeeperOfTheMoon, - MaleSeawolf = Api.Enums.ApiCollectionType.MaleSeawolf, - FemaleSeawolf = Api.Enums.ApiCollectionType.FemaleSeawolf, - MaleHellsguard = Api.Enums.ApiCollectionType.MaleHellsguard, + MaleSeawolf = Api.Enums.ApiCollectionType.MaleSeawolf, + FemaleSeawolf = Api.Enums.ApiCollectionType.FemaleSeawolf, + MaleHellsguard = Api.Enums.ApiCollectionType.MaleHellsguard, FemaleHellsguard = Api.Enums.ApiCollectionType.FemaleHellsguard, - MaleRaen = Api.Enums.ApiCollectionType.MaleRaen, - FemaleRaen = Api.Enums.ApiCollectionType.FemaleRaen, - MaleXaela = Api.Enums.ApiCollectionType.MaleXaela, + MaleRaen = Api.Enums.ApiCollectionType.MaleRaen, + FemaleRaen = Api.Enums.ApiCollectionType.FemaleRaen, + MaleXaela = Api.Enums.ApiCollectionType.MaleXaela, FemaleXaela = Api.Enums.ApiCollectionType.FemaleXaela, - MaleHelion = Api.Enums.ApiCollectionType.MaleHelion, + MaleHelion = Api.Enums.ApiCollectionType.MaleHelion, FemaleHelion = Api.Enums.ApiCollectionType.FemaleHelion, - MaleLost = Api.Enums.ApiCollectionType.MaleLost, - FemaleLost = Api.Enums.ApiCollectionType.FemaleLost, + MaleLost = Api.Enums.ApiCollectionType.MaleLost, + FemaleLost = Api.Enums.ApiCollectionType.FemaleLost, - MaleRava = Api.Enums.ApiCollectionType.MaleRava, - FemaleRava = Api.Enums.ApiCollectionType.FemaleRava, - MaleVeena = Api.Enums.ApiCollectionType.MaleVeena, + MaleRava = Api.Enums.ApiCollectionType.MaleRava, + FemaleRava = Api.Enums.ApiCollectionType.FemaleRava, + MaleVeena = Api.Enums.ApiCollectionType.MaleVeena, FemaleVeena = Api.Enums.ApiCollectionType.FemaleVeena, - MaleMidlanderNpc = Api.Enums.ApiCollectionType.MaleMidlanderNpc, - FemaleMidlanderNpc = Api.Enums.ApiCollectionType.FemaleMidlanderNpc, - MaleHighlanderNpc = Api.Enums.ApiCollectionType.MaleHighlanderNpc, + MaleMidlanderNpc = Api.Enums.ApiCollectionType.MaleMidlanderNpc, + FemaleMidlanderNpc = Api.Enums.ApiCollectionType.FemaleMidlanderNpc, + MaleHighlanderNpc = Api.Enums.ApiCollectionType.MaleHighlanderNpc, FemaleHighlanderNpc = Api.Enums.ApiCollectionType.FemaleHighlanderNpc, - MaleWildwoodNpc = Api.Enums.ApiCollectionType.MaleWildwoodNpc, - FemaleWildwoodNpc = Api.Enums.ApiCollectionType.FemaleWildwoodNpc, - MaleDuskwightNpc = Api.Enums.ApiCollectionType.MaleDuskwightNpc, + MaleWildwoodNpc = Api.Enums.ApiCollectionType.MaleWildwoodNpc, + FemaleWildwoodNpc = Api.Enums.ApiCollectionType.FemaleWildwoodNpc, + MaleDuskwightNpc = Api.Enums.ApiCollectionType.MaleDuskwightNpc, FemaleDuskwightNpc = Api.Enums.ApiCollectionType.FemaleDuskwightNpc, - MalePlainsfolkNpc = Api.Enums.ApiCollectionType.MalePlainsfolkNpc, + MalePlainsfolkNpc = Api.Enums.ApiCollectionType.MalePlainsfolkNpc, FemalePlainsfolkNpc = Api.Enums.ApiCollectionType.FemalePlainsfolkNpc, - MaleDunesfolkNpc = Api.Enums.ApiCollectionType.MaleDunesfolkNpc, - FemaleDunesfolkNpc = Api.Enums.ApiCollectionType.FemaleDunesfolkNpc, + MaleDunesfolkNpc = Api.Enums.ApiCollectionType.MaleDunesfolkNpc, + FemaleDunesfolkNpc = Api.Enums.ApiCollectionType.FemaleDunesfolkNpc, - MaleSeekerOfTheSunNpc = Api.Enums.ApiCollectionType.MaleSeekerOfTheSunNpc, - FemaleSeekerOfTheSunNpc = Api.Enums.ApiCollectionType.FemaleSeekerOfTheSunNpc, - MaleKeeperOfTheMoonNpc = Api.Enums.ApiCollectionType.MaleKeeperOfTheMoonNpc, + MaleSeekerOfTheSunNpc = Api.Enums.ApiCollectionType.MaleSeekerOfTheSunNpc, + FemaleSeekerOfTheSunNpc = Api.Enums.ApiCollectionType.FemaleSeekerOfTheSunNpc, + MaleKeeperOfTheMoonNpc = Api.Enums.ApiCollectionType.MaleKeeperOfTheMoonNpc, FemaleKeeperOfTheMoonNpc = Api.Enums.ApiCollectionType.FemaleKeeperOfTheMoonNpc, - MaleSeawolfNpc = Api.Enums.ApiCollectionType.MaleSeawolfNpc, - FemaleSeawolfNpc = Api.Enums.ApiCollectionType.FemaleSeawolfNpc, - MaleHellsguardNpc = Api.Enums.ApiCollectionType.MaleHellsguardNpc, + MaleSeawolfNpc = Api.Enums.ApiCollectionType.MaleSeawolfNpc, + FemaleSeawolfNpc = Api.Enums.ApiCollectionType.FemaleSeawolfNpc, + MaleHellsguardNpc = Api.Enums.ApiCollectionType.MaleHellsguardNpc, FemaleHellsguardNpc = Api.Enums.ApiCollectionType.FemaleHellsguardNpc, - MaleRaenNpc = Api.Enums.ApiCollectionType.MaleRaenNpc, - FemaleRaenNpc = Api.Enums.ApiCollectionType.FemaleRaenNpc, - MaleXaelaNpc = Api.Enums.ApiCollectionType.MaleXaelaNpc, + MaleRaenNpc = Api.Enums.ApiCollectionType.MaleRaenNpc, + FemaleRaenNpc = Api.Enums.ApiCollectionType.FemaleRaenNpc, + MaleXaelaNpc = Api.Enums.ApiCollectionType.MaleXaelaNpc, FemaleXaelaNpc = Api.Enums.ApiCollectionType.FemaleXaelaNpc, - MaleHelionNpc = Api.Enums.ApiCollectionType.MaleHelionNpc, + MaleHelionNpc = Api.Enums.ApiCollectionType.MaleHelionNpc, FemaleHelionNpc = Api.Enums.ApiCollectionType.FemaleHelionNpc, - MaleLostNpc = Api.Enums.ApiCollectionType.MaleLostNpc, - FemaleLostNpc = Api.Enums.ApiCollectionType.FemaleLostNpc, + MaleLostNpc = Api.Enums.ApiCollectionType.MaleLostNpc, + FemaleLostNpc = Api.Enums.ApiCollectionType.FemaleLostNpc, - MaleRavaNpc = Api.Enums.ApiCollectionType.MaleRavaNpc, - FemaleRavaNpc = Api.Enums.ApiCollectionType.FemaleRavaNpc, - MaleVeenaNpc = Api.Enums.ApiCollectionType.MaleVeenaNpc, + MaleRavaNpc = Api.Enums.ApiCollectionType.MaleRavaNpc, + FemaleRavaNpc = Api.Enums.ApiCollectionType.FemaleRavaNpc, + MaleVeenaNpc = Api.Enums.ApiCollectionType.MaleVeenaNpc, FemaleVeenaNpc = Api.Enums.ApiCollectionType.FemaleVeenaNpc, - Default = Api.Enums.ApiCollectionType.Default, // The default collection was changed + Default = Api.Enums.ApiCollectionType.Default, // The default collection was changed Interface = Api.Enums.ApiCollectionType.Interface, // The ui collection was changed - Current = Api.Enums.ApiCollectionType.Current, // The current collection was changed + Current = Api.Enums.ApiCollectionType.Current, // The current collection was changed Individual, // An individual collection was changed Inactive, // A collection was added or removed Temporary, // A temporary collections was set or deleted via IPC @@ -111,202 +111,200 @@ public static class CollectionTypeExtensions => collectionType < CollectionType.Default; public static readonly (CollectionType, string, string)[] Special = Enum.GetValues() - .Where(IsSpecial) - .Select(s => (s, s.ToName(), s.ToDescription())) - .ToArray(); + .Where(IsSpecial) + .Select(s => (s, s.ToName(), s.ToDescription())) + .ToArray(); public static CollectionType FromParts(Gender gender, bool npc) { gender = gender switch { - Gender.MaleNpc => Gender.Male, + Gender.MaleNpc => Gender.Male, Gender.FemaleNpc => Gender.Female, - _ => gender, + _ => gender, }; return (gender, npc) switch { - (Gender.Male, false) => CollectionType.MalePlayerCharacter, + (Gender.Male, false) => CollectionType.MalePlayerCharacter, (Gender.Female, false) => CollectionType.FemalePlayerCharacter, - (Gender.Male, true) => CollectionType.MaleNonPlayerCharacter, - (Gender.Female, true) => CollectionType.FemaleNonPlayerCharacter, - _ => CollectionType.Inactive, + (Gender.Male, true) => CollectionType.MaleNonPlayerCharacter, + (Gender.Female, true) => CollectionType.FemaleNonPlayerCharacter, + _ => CollectionType.Inactive, }; } - - // @formatter:off - private static readonly IReadOnlyList DefaultList = new[] { CollectionType.Default }; - private static readonly IReadOnlyList MalePlayerList = new[] { CollectionType.MalePlayerCharacter, CollectionType.Default }; - private static readonly IReadOnlyList FemalePlayerList = new[] { CollectionType.FemalePlayerCharacter, CollectionType.Default }; - private static readonly IReadOnlyList MaleNpcList = new[] { CollectionType.MaleNonPlayerCharacter, CollectionType.Default }; - private static readonly IReadOnlyList FemaleNpcList = new[] { CollectionType.FemaleNonPlayerCharacter, CollectionType.Default }; + + // @formatter:off + private static readonly IReadOnlyList DefaultList = new[] { CollectionType.Default }; + private static readonly IReadOnlyList MalePlayerList = new[] { CollectionType.MalePlayerCharacter, CollectionType.Default }; + private static readonly IReadOnlyList FemalePlayerList = new[] { CollectionType.FemalePlayerCharacter, CollectionType.Default }; + private static readonly IReadOnlyList MaleNpcList = new[] { CollectionType.MaleNonPlayerCharacter, CollectionType.Default }; + private static readonly IReadOnlyList FemaleNpcList = new[] { CollectionType.FemaleNonPlayerCharacter, CollectionType.Default }; // @formatter:on - - /// A list of definite redundancy possibilities. + + /// A list of definite redundancy possibilities. public static IReadOnlyList InheritanceOrder(this CollectionType collectionType) => collectionType switch { - CollectionType.Yourself => DefaultList, - CollectionType.MalePlayerCharacter => DefaultList, - CollectionType.FemalePlayerCharacter => DefaultList, - CollectionType.MaleNonPlayerCharacter => DefaultList, + CollectionType.Yourself => DefaultList, + CollectionType.MalePlayerCharacter => DefaultList, + CollectionType.FemalePlayerCharacter => DefaultList, + CollectionType.MaleNonPlayerCharacter => DefaultList, CollectionType.FemaleNonPlayerCharacter => DefaultList, - CollectionType.MaleMidlander => MalePlayerList, - CollectionType.FemaleMidlander => FemalePlayerList, - CollectionType.MaleHighlander => MalePlayerList, - CollectionType.FemaleHighlander => FemalePlayerList, - CollectionType.MaleWildwood => MalePlayerList, - CollectionType.FemaleWildwood => FemalePlayerList, - CollectionType.MaleDuskwight => MalePlayerList, - CollectionType.FemaleDuskwight => FemalePlayerList, - CollectionType.MalePlainsfolk => MalePlayerList, - CollectionType.FemalePlainsfolk => FemalePlayerList, - CollectionType.MaleDunesfolk => MalePlayerList, - CollectionType.FemaleDunesfolk => FemalePlayerList, - CollectionType.MaleSeekerOfTheSun => MalePlayerList, - CollectionType.FemaleSeekerOfTheSun => FemalePlayerList, - CollectionType.MaleKeeperOfTheMoon => MalePlayerList, - CollectionType.FemaleKeeperOfTheMoon => FemalePlayerList, - CollectionType.MaleSeawolf => MalePlayerList, - CollectionType.FemaleSeawolf => FemalePlayerList, - CollectionType.MaleHellsguard => MalePlayerList, - CollectionType.FemaleHellsguard => FemalePlayerList, - CollectionType.MaleRaen => MalePlayerList, - CollectionType.FemaleRaen => FemalePlayerList, - CollectionType.MaleXaela => MalePlayerList, - CollectionType.FemaleXaela => FemalePlayerList, - CollectionType.MaleHelion => MalePlayerList, - CollectionType.FemaleHelion => FemalePlayerList, - CollectionType.MaleLost => MalePlayerList, - CollectionType.FemaleLost => FemalePlayerList, - CollectionType.MaleRava => MalePlayerList, - CollectionType.FemaleRava => FemalePlayerList, - CollectionType.MaleVeena => MalePlayerList, - CollectionType.FemaleVeena => FemalePlayerList, - CollectionType.MaleMidlanderNpc => MaleNpcList, - CollectionType.FemaleMidlanderNpc => FemaleNpcList, - CollectionType.MaleHighlanderNpc => MaleNpcList, - CollectionType.FemaleHighlanderNpc => FemaleNpcList, - CollectionType.MaleWildwoodNpc => MaleNpcList, - CollectionType.FemaleWildwoodNpc => FemaleNpcList, - CollectionType.MaleDuskwightNpc => MaleNpcList, - CollectionType.FemaleDuskwightNpc => FemaleNpcList, - CollectionType.MalePlainsfolkNpc => MaleNpcList, - CollectionType.FemalePlainsfolkNpc => FemaleNpcList, - CollectionType.MaleDunesfolkNpc => MaleNpcList, - CollectionType.FemaleDunesfolkNpc => FemaleNpcList, - CollectionType.MaleSeekerOfTheSunNpc => MaleNpcList, - CollectionType.FemaleSeekerOfTheSunNpc => FemaleNpcList, - CollectionType.MaleKeeperOfTheMoonNpc => MaleNpcList, + CollectionType.MaleMidlander => MalePlayerList, + CollectionType.FemaleMidlander => FemalePlayerList, + CollectionType.MaleHighlander => MalePlayerList, + CollectionType.FemaleHighlander => FemalePlayerList, + CollectionType.MaleWildwood => MalePlayerList, + CollectionType.FemaleWildwood => FemalePlayerList, + CollectionType.MaleDuskwight => MalePlayerList, + CollectionType.FemaleDuskwight => FemalePlayerList, + CollectionType.MalePlainsfolk => MalePlayerList, + CollectionType.FemalePlainsfolk => FemalePlayerList, + CollectionType.MaleDunesfolk => MalePlayerList, + CollectionType.FemaleDunesfolk => FemalePlayerList, + CollectionType.MaleSeekerOfTheSun => MalePlayerList, + CollectionType.FemaleSeekerOfTheSun => FemalePlayerList, + CollectionType.MaleKeeperOfTheMoon => MalePlayerList, + CollectionType.FemaleKeeperOfTheMoon => FemalePlayerList, + CollectionType.MaleSeawolf => MalePlayerList, + CollectionType.FemaleSeawolf => FemalePlayerList, + CollectionType.MaleHellsguard => MalePlayerList, + CollectionType.FemaleHellsguard => FemalePlayerList, + CollectionType.MaleRaen => MalePlayerList, + CollectionType.FemaleRaen => FemalePlayerList, + CollectionType.MaleXaela => MalePlayerList, + CollectionType.FemaleXaela => FemalePlayerList, + CollectionType.MaleHelion => MalePlayerList, + CollectionType.FemaleHelion => FemalePlayerList, + CollectionType.MaleLost => MalePlayerList, + CollectionType.FemaleLost => FemalePlayerList, + CollectionType.MaleRava => MalePlayerList, + CollectionType.FemaleRava => FemalePlayerList, + CollectionType.MaleVeena => MalePlayerList, + CollectionType.FemaleVeena => FemalePlayerList, + CollectionType.MaleMidlanderNpc => MaleNpcList, + CollectionType.FemaleMidlanderNpc => FemaleNpcList, + CollectionType.MaleHighlanderNpc => MaleNpcList, + CollectionType.FemaleHighlanderNpc => FemaleNpcList, + CollectionType.MaleWildwoodNpc => MaleNpcList, + CollectionType.FemaleWildwoodNpc => FemaleNpcList, + CollectionType.MaleDuskwightNpc => MaleNpcList, + CollectionType.FemaleDuskwightNpc => FemaleNpcList, + CollectionType.MalePlainsfolkNpc => MaleNpcList, + CollectionType.FemalePlainsfolkNpc => FemaleNpcList, + CollectionType.MaleDunesfolkNpc => MaleNpcList, + CollectionType.FemaleDunesfolkNpc => FemaleNpcList, + CollectionType.MaleSeekerOfTheSunNpc => MaleNpcList, + CollectionType.FemaleSeekerOfTheSunNpc => FemaleNpcList, + CollectionType.MaleKeeperOfTheMoonNpc => MaleNpcList, CollectionType.FemaleKeeperOfTheMoonNpc => FemaleNpcList, - CollectionType.MaleSeawolfNpc => MaleNpcList, - CollectionType.FemaleSeawolfNpc => FemaleNpcList, - CollectionType.MaleHellsguardNpc => MaleNpcList, - CollectionType.FemaleHellsguardNpc => FemaleNpcList, - CollectionType.MaleRaenNpc => MaleNpcList, - CollectionType.FemaleRaenNpc => FemaleNpcList, - CollectionType.MaleXaelaNpc => MaleNpcList, - CollectionType.FemaleXaelaNpc => FemaleNpcList, - CollectionType.MaleHelionNpc => MaleNpcList, - CollectionType.FemaleHelionNpc => FemaleNpcList, - CollectionType.MaleLostNpc => MaleNpcList, - CollectionType.FemaleLostNpc => FemaleNpcList, - CollectionType.MaleRavaNpc => MaleNpcList, - CollectionType.FemaleRavaNpc => FemaleNpcList, - CollectionType.MaleVeenaNpc => MaleNpcList, - CollectionType.FemaleVeenaNpc => FemaleNpcList, - CollectionType.Individual => DefaultList, - _ => Array.Empty(), + CollectionType.MaleSeawolfNpc => MaleNpcList, + CollectionType.FemaleSeawolfNpc => FemaleNpcList, + CollectionType.MaleHellsguardNpc => MaleNpcList, + CollectionType.FemaleHellsguardNpc => FemaleNpcList, + CollectionType.MaleRaenNpc => MaleNpcList, + CollectionType.FemaleRaenNpc => FemaleNpcList, + CollectionType.MaleXaelaNpc => MaleNpcList, + CollectionType.FemaleXaelaNpc => FemaleNpcList, + CollectionType.MaleHelionNpc => MaleNpcList, + CollectionType.FemaleHelionNpc => FemaleNpcList, + CollectionType.MaleLostNpc => MaleNpcList, + CollectionType.FemaleLostNpc => FemaleNpcList, + CollectionType.MaleRavaNpc => MaleNpcList, + CollectionType.FemaleRavaNpc => FemaleNpcList, + CollectionType.MaleVeenaNpc => MaleNpcList, + CollectionType.FemaleVeenaNpc => FemaleNpcList, + CollectionType.Individual => DefaultList, + _ => Array.Empty(), }; public static CollectionType FromParts(SubRace race, Gender gender, bool npc) { gender = gender switch { - Gender.MaleNpc => Gender.Male, + Gender.MaleNpc => Gender.Male, Gender.FemaleNpc => Gender.Female, - _ => gender, + _ => gender, }; return (race, gender, npc) switch { - (SubRace.Midlander, Gender.Male, false) => CollectionType.MaleMidlander, - (SubRace.Highlander, Gender.Male, false) => CollectionType.MaleHighlander, - (SubRace.Wildwood, Gender.Male, false) => CollectionType.MaleWildwood, - (SubRace.Duskwight, Gender.Male, false) => CollectionType.MaleDuskwight, - (SubRace.Plainsfolk, Gender.Male, false) => CollectionType.MalePlainsfolk, - (SubRace.Dunesfolk, Gender.Male, false) => CollectionType.MaleDunesfolk, - (SubRace.SeekerOfTheSun, Gender.Male, false) => CollectionType.MaleSeekerOfTheSun, + (SubRace.Midlander, Gender.Male, false) => CollectionType.MaleMidlander, + (SubRace.Highlander, Gender.Male, false) => CollectionType.MaleHighlander, + (SubRace.Wildwood, Gender.Male, false) => CollectionType.MaleWildwood, + (SubRace.Duskwight, Gender.Male, false) => CollectionType.MaleDuskwight, + (SubRace.Plainsfolk, Gender.Male, false) => CollectionType.MalePlainsfolk, + (SubRace.Dunesfolk, Gender.Male, false) => CollectionType.MaleDunesfolk, + (SubRace.SeekerOfTheSun, Gender.Male, false) => CollectionType.MaleSeekerOfTheSun, (SubRace.KeeperOfTheMoon, Gender.Male, false) => CollectionType.MaleKeeperOfTheMoon, - (SubRace.Seawolf, Gender.Male, false) => CollectionType.MaleSeawolf, - (SubRace.Hellsguard, Gender.Male, false) => CollectionType.MaleHellsguard, - (SubRace.Raen, Gender.Male, false) => CollectionType.MaleRaen, - (SubRace.Xaela, Gender.Male, false) => CollectionType.MaleXaela, - (SubRace.Helion, Gender.Male, false) => CollectionType.MaleHelion, - (SubRace.Lost, Gender.Male, false) => CollectionType.MaleLost, - (SubRace.Rava, Gender.Male, false) => CollectionType.MaleRava, - (SubRace.Veena, Gender.Male, false) => CollectionType.MaleVeena, + (SubRace.Seawolf, Gender.Male, false) => CollectionType.MaleSeawolf, + (SubRace.Hellsguard, Gender.Male, false) => CollectionType.MaleHellsguard, + (SubRace.Raen, Gender.Male, false) => CollectionType.MaleRaen, + (SubRace.Xaela, Gender.Male, false) => CollectionType.MaleXaela, + (SubRace.Helion, Gender.Male, false) => CollectionType.MaleHelion, + (SubRace.Lost, Gender.Male, false) => CollectionType.MaleLost, + (SubRace.Rava, Gender.Male, false) => CollectionType.MaleRava, + (SubRace.Veena, Gender.Male, false) => CollectionType.MaleVeena, - (SubRace.Midlander, Gender.Female, false) => CollectionType.FemaleMidlander, - (SubRace.Highlander, Gender.Female, false) => CollectionType.FemaleHighlander, - (SubRace.Wildwood, Gender.Female, false) => CollectionType.FemaleWildwood, - (SubRace.Duskwight, Gender.Female, false) => CollectionType.FemaleDuskwight, - (SubRace.Plainsfolk, Gender.Female, false) => CollectionType.FemalePlainsfolk, - (SubRace.Dunesfolk, Gender.Female, false) => CollectionType.FemaleDunesfolk, - (SubRace.SeekerOfTheSun, Gender.Female, false) => CollectionType.FemaleSeekerOfTheSun, + (SubRace.Midlander, Gender.Female, false) => CollectionType.FemaleMidlander, + (SubRace.Highlander, Gender.Female, false) => CollectionType.FemaleHighlander, + (SubRace.Wildwood, Gender.Female, false) => CollectionType.FemaleWildwood, + (SubRace.Duskwight, Gender.Female, false) => CollectionType.FemaleDuskwight, + (SubRace.Plainsfolk, Gender.Female, false) => CollectionType.FemalePlainsfolk, + (SubRace.Dunesfolk, Gender.Female, false) => CollectionType.FemaleDunesfolk, + (SubRace.SeekerOfTheSun, Gender.Female, false) => CollectionType.FemaleSeekerOfTheSun, (SubRace.KeeperOfTheMoon, Gender.Female, false) => CollectionType.FemaleKeeperOfTheMoon, - (SubRace.Seawolf, Gender.Female, false) => CollectionType.FemaleSeawolf, - (SubRace.Hellsguard, Gender.Female, false) => CollectionType.FemaleHellsguard, - (SubRace.Raen, Gender.Female, false) => CollectionType.FemaleRaen, - (SubRace.Xaela, Gender.Female, false) => CollectionType.FemaleXaela, - (SubRace.Helion, Gender.Female, false) => CollectionType.FemaleHelion, - (SubRace.Lost, Gender.Female, false) => CollectionType.FemaleLost, - (SubRace.Rava, Gender.Female, false) => CollectionType.FemaleRava, - (SubRace.Veena, Gender.Female, false) => CollectionType.FemaleVeena, + (SubRace.Seawolf, Gender.Female, false) => CollectionType.FemaleSeawolf, + (SubRace.Hellsguard, Gender.Female, false) => CollectionType.FemaleHellsguard, + (SubRace.Raen, Gender.Female, false) => CollectionType.FemaleRaen, + (SubRace.Xaela, Gender.Female, false) => CollectionType.FemaleXaela, + (SubRace.Helion, Gender.Female, false) => CollectionType.FemaleHelion, + (SubRace.Lost, Gender.Female, false) => CollectionType.FemaleLost, + (SubRace.Rava, Gender.Female, false) => CollectionType.FemaleRava, + (SubRace.Veena, Gender.Female, false) => CollectionType.FemaleVeena, - (SubRace.Midlander, Gender.Male, true) => CollectionType.MaleMidlanderNpc, - (SubRace.Highlander, Gender.Male, true) => CollectionType.MaleHighlanderNpc, - (SubRace.Wildwood, Gender.Male, true) => CollectionType.MaleWildwoodNpc, - (SubRace.Duskwight, Gender.Male, true) => CollectionType.MaleDuskwightNpc, - (SubRace.Plainsfolk, Gender.Male, true) => CollectionType.MalePlainsfolkNpc, - (SubRace.Dunesfolk, Gender.Male, true) => CollectionType.MaleDunesfolkNpc, - (SubRace.SeekerOfTheSun, Gender.Male, true) => CollectionType.MaleSeekerOfTheSunNpc, + (SubRace.Midlander, Gender.Male, true) => CollectionType.MaleMidlanderNpc, + (SubRace.Highlander, Gender.Male, true) => CollectionType.MaleHighlanderNpc, + (SubRace.Wildwood, Gender.Male, true) => CollectionType.MaleWildwoodNpc, + (SubRace.Duskwight, Gender.Male, true) => CollectionType.MaleDuskwightNpc, + (SubRace.Plainsfolk, Gender.Male, true) => CollectionType.MalePlainsfolkNpc, + (SubRace.Dunesfolk, Gender.Male, true) => CollectionType.MaleDunesfolkNpc, + (SubRace.SeekerOfTheSun, Gender.Male, true) => CollectionType.MaleSeekerOfTheSunNpc, (SubRace.KeeperOfTheMoon, Gender.Male, true) => CollectionType.MaleKeeperOfTheMoonNpc, - (SubRace.Seawolf, Gender.Male, true) => CollectionType.MaleSeawolfNpc, - (SubRace.Hellsguard, Gender.Male, true) => CollectionType.MaleHellsguardNpc, - (SubRace.Raen, Gender.Male, true) => CollectionType.MaleRaenNpc, - (SubRace.Xaela, Gender.Male, true) => CollectionType.MaleXaelaNpc, - (SubRace.Helion, Gender.Male, true) => CollectionType.MaleHelionNpc, - (SubRace.Lost, Gender.Male, true) => CollectionType.MaleLostNpc, - (SubRace.Rava, Gender.Male, true) => CollectionType.MaleRavaNpc, - (SubRace.Veena, Gender.Male, true) => CollectionType.MaleVeenaNpc, + (SubRace.Seawolf, Gender.Male, true) => CollectionType.MaleSeawolfNpc, + (SubRace.Hellsguard, Gender.Male, true) => CollectionType.MaleHellsguardNpc, + (SubRace.Raen, Gender.Male, true) => CollectionType.MaleRaenNpc, + (SubRace.Xaela, Gender.Male, true) => CollectionType.MaleXaelaNpc, + (SubRace.Helion, Gender.Male, true) => CollectionType.MaleHelionNpc, + (SubRace.Lost, Gender.Male, true) => CollectionType.MaleLostNpc, + (SubRace.Rava, Gender.Male, true) => CollectionType.MaleRavaNpc, + (SubRace.Veena, Gender.Male, true) => CollectionType.MaleVeenaNpc, - (SubRace.Midlander, Gender.Female, true) => CollectionType.FemaleMidlanderNpc, - (SubRace.Highlander, Gender.Female, true) => CollectionType.FemaleHighlanderNpc, - (SubRace.Wildwood, Gender.Female, true) => CollectionType.FemaleWildwoodNpc, - (SubRace.Duskwight, Gender.Female, true) => CollectionType.FemaleDuskwightNpc, - (SubRace.Plainsfolk, Gender.Female, true) => CollectionType.FemalePlainsfolkNpc, - (SubRace.Dunesfolk, Gender.Female, true) => CollectionType.FemaleDunesfolkNpc, - (SubRace.SeekerOfTheSun, Gender.Female, true) => CollectionType.FemaleSeekerOfTheSunNpc, + (SubRace.Midlander, Gender.Female, true) => CollectionType.FemaleMidlanderNpc, + (SubRace.Highlander, Gender.Female, true) => CollectionType.FemaleHighlanderNpc, + (SubRace.Wildwood, Gender.Female, true) => CollectionType.FemaleWildwoodNpc, + (SubRace.Duskwight, Gender.Female, true) => CollectionType.FemaleDuskwightNpc, + (SubRace.Plainsfolk, Gender.Female, true) => CollectionType.FemalePlainsfolkNpc, + (SubRace.Dunesfolk, Gender.Female, true) => CollectionType.FemaleDunesfolkNpc, + (SubRace.SeekerOfTheSun, Gender.Female, true) => CollectionType.FemaleSeekerOfTheSunNpc, (SubRace.KeeperOfTheMoon, Gender.Female, true) => CollectionType.FemaleKeeperOfTheMoonNpc, - (SubRace.Seawolf, Gender.Female, true) => CollectionType.FemaleSeawolfNpc, - (SubRace.Hellsguard, Gender.Female, true) => CollectionType.FemaleHellsguardNpc, - (SubRace.Raen, Gender.Female, true) => CollectionType.FemaleRaenNpc, - (SubRace.Xaela, Gender.Female, true) => CollectionType.FemaleXaelaNpc, - (SubRace.Helion, Gender.Female, true) => CollectionType.FemaleHelionNpc, - (SubRace.Lost, Gender.Female, true) => CollectionType.FemaleLostNpc, - (SubRace.Rava, Gender.Female, true) => CollectionType.FemaleRavaNpc, - (SubRace.Veena, Gender.Female, true) => CollectionType.FemaleVeenaNpc, - _ => CollectionType.Inactive, + (SubRace.Seawolf, Gender.Female, true) => CollectionType.FemaleSeawolfNpc, + (SubRace.Hellsguard, Gender.Female, true) => CollectionType.FemaleHellsguardNpc, + (SubRace.Raen, Gender.Female, true) => CollectionType.FemaleRaenNpc, + (SubRace.Xaela, Gender.Female, true) => CollectionType.FemaleXaelaNpc, + (SubRace.Helion, Gender.Female, true) => CollectionType.FemaleHelionNpc, + (SubRace.Lost, Gender.Female, true) => CollectionType.FemaleLostNpc, + (SubRace.Rava, Gender.Female, true) => CollectionType.FemaleRavaNpc, + (SubRace.Veena, Gender.Female, true) => CollectionType.FemaleVeenaNpc, + _ => CollectionType.Inactive, }; } public static bool TryParse(string text, out CollectionType type) { if (Enum.TryParse(text, true, out type)) - { return type is not CollectionType.Inactive and not CollectionType.Temporary; - } if (string.Equals(text, "character", StringComparison.OrdinalIgnoreCase)) { @@ -335,9 +333,7 @@ public static class CollectionTypeExtensions foreach (var t in Enum.GetValues()) { if (t is CollectionType.Inactive or CollectionType.Temporary) - { continue; - } if (string.Equals(text, t.ToName(), StringComparison.OrdinalIgnoreCase)) { @@ -352,83 +348,83 @@ public static class CollectionTypeExtensions public static string ToName(this CollectionType collectionType) => collectionType switch { - CollectionType.Yourself => "Your Character", - CollectionType.NonPlayerChild => "Non-Player Children", - CollectionType.NonPlayerElderly => "Non-Player Elderly", - CollectionType.MalePlayerCharacter => "Male Player Characters", - CollectionType.MaleNonPlayerCharacter => "Male Non-Player Characters", - CollectionType.MaleMidlander => $"Male {SubRace.Midlander.ToName()}", - CollectionType.MaleHighlander => $"Male {SubRace.Highlander.ToName()}", - CollectionType.MaleWildwood => $"Male {SubRace.Wildwood.ToName()}", - CollectionType.MaleDuskwight => $"Male {SubRace.Duskwight.ToName()}", - CollectionType.MalePlainsfolk => $"Male {SubRace.Plainsfolk.ToName()}", - CollectionType.MaleDunesfolk => $"Male {SubRace.Dunesfolk.ToName()}", - CollectionType.MaleSeekerOfTheSun => $"Male {SubRace.SeekerOfTheSun.ToName()}", - CollectionType.MaleKeeperOfTheMoon => $"Male {SubRace.KeeperOfTheMoon.ToName()}", - CollectionType.MaleSeawolf => $"Male {SubRace.Seawolf.ToName()}", - CollectionType.MaleHellsguard => $"Male {SubRace.Hellsguard.ToName()}", - CollectionType.MaleRaen => $"Male {SubRace.Raen.ToName()}", - CollectionType.MaleXaela => $"Male {SubRace.Xaela.ToName()}", - CollectionType.MaleHelion => $"Male {SubRace.Helion.ToName()}", - CollectionType.MaleLost => $"Male {SubRace.Lost.ToName()}", - CollectionType.MaleRava => $"Male {SubRace.Rava.ToName()}", - CollectionType.MaleVeena => $"Male {SubRace.Veena.ToName()}", - CollectionType.MaleMidlanderNpc => $"Male {SubRace.Midlander.ToName()} (NPC)", - CollectionType.MaleHighlanderNpc => $"Male {SubRace.Highlander.ToName()} (NPC)", - CollectionType.MaleWildwoodNpc => $"Male {SubRace.Wildwood.ToName()} (NPC)", - CollectionType.MaleDuskwightNpc => $"Male {SubRace.Duskwight.ToName()} (NPC)", - CollectionType.MalePlainsfolkNpc => $"Male {SubRace.Plainsfolk.ToName()} (NPC)", - CollectionType.MaleDunesfolkNpc => $"Male {SubRace.Dunesfolk.ToName()} (NPC)", - CollectionType.MaleSeekerOfTheSunNpc => $"Male {SubRace.SeekerOfTheSun.ToName()} (NPC)", - CollectionType.MaleKeeperOfTheMoonNpc => $"Male {SubRace.KeeperOfTheMoon.ToName()} (NPC)", - CollectionType.MaleSeawolfNpc => $"Male {SubRace.Seawolf.ToName()} (NPC)", - CollectionType.MaleHellsguardNpc => $"Male {SubRace.Hellsguard.ToName()} (NPC)", - CollectionType.MaleRaenNpc => $"Male {SubRace.Raen.ToName()} (NPC)", - CollectionType.MaleXaelaNpc => $"Male {SubRace.Xaela.ToName()} (NPC)", - CollectionType.MaleHelionNpc => $"Male {SubRace.Helion.ToName()} (NPC)", - CollectionType.MaleLostNpc => $"Male {SubRace.Lost.ToName()} (NPC)", - CollectionType.MaleRavaNpc => $"Male {SubRace.Rava.ToName()} (NPC)", - CollectionType.MaleVeenaNpc => $"Male {SubRace.Veena.ToName()} (NPC)", - CollectionType.FemalePlayerCharacter => "Female Player Characters", + CollectionType.Yourself => "Your Character", + CollectionType.NonPlayerChild => "Non-Player Children", + CollectionType.NonPlayerElderly => "Non-Player Elderly", + CollectionType.MalePlayerCharacter => "Male Player Characters", + CollectionType.MaleNonPlayerCharacter => "Male Non-Player Characters", + CollectionType.MaleMidlander => $"Male {SubRace.Midlander.ToName()}", + CollectionType.MaleHighlander => $"Male {SubRace.Highlander.ToName()}", + CollectionType.MaleWildwood => $"Male {SubRace.Wildwood.ToName()}", + CollectionType.MaleDuskwight => $"Male {SubRace.Duskwight.ToName()}", + CollectionType.MalePlainsfolk => $"Male {SubRace.Plainsfolk.ToName()}", + CollectionType.MaleDunesfolk => $"Male {SubRace.Dunesfolk.ToName()}", + CollectionType.MaleSeekerOfTheSun => $"Male {SubRace.SeekerOfTheSun.ToName()}", + CollectionType.MaleKeeperOfTheMoon => $"Male {SubRace.KeeperOfTheMoon.ToName()}", + CollectionType.MaleSeawolf => $"Male {SubRace.Seawolf.ToName()}", + CollectionType.MaleHellsguard => $"Male {SubRace.Hellsguard.ToName()}", + CollectionType.MaleRaen => $"Male {SubRace.Raen.ToName()}", + CollectionType.MaleXaela => $"Male {SubRace.Xaela.ToName()}", + CollectionType.MaleHelion => $"Male {SubRace.Helion.ToName()}", + CollectionType.MaleLost => $"Male {SubRace.Lost.ToName()}", + CollectionType.MaleRava => $"Male {SubRace.Rava.ToName()}", + CollectionType.MaleVeena => $"Male {SubRace.Veena.ToName()}", + CollectionType.MaleMidlanderNpc => $"Male {SubRace.Midlander.ToName()} (NPC)", + CollectionType.MaleHighlanderNpc => $"Male {SubRace.Highlander.ToName()} (NPC)", + CollectionType.MaleWildwoodNpc => $"Male {SubRace.Wildwood.ToName()} (NPC)", + CollectionType.MaleDuskwightNpc => $"Male {SubRace.Duskwight.ToName()} (NPC)", + CollectionType.MalePlainsfolkNpc => $"Male {SubRace.Plainsfolk.ToName()} (NPC)", + CollectionType.MaleDunesfolkNpc => $"Male {SubRace.Dunesfolk.ToName()} (NPC)", + CollectionType.MaleSeekerOfTheSunNpc => $"Male {SubRace.SeekerOfTheSun.ToName()} (NPC)", + CollectionType.MaleKeeperOfTheMoonNpc => $"Male {SubRace.KeeperOfTheMoon.ToName()} (NPC)", + CollectionType.MaleSeawolfNpc => $"Male {SubRace.Seawolf.ToName()} (NPC)", + CollectionType.MaleHellsguardNpc => $"Male {SubRace.Hellsguard.ToName()} (NPC)", + CollectionType.MaleRaenNpc => $"Male {SubRace.Raen.ToName()} (NPC)", + CollectionType.MaleXaelaNpc => $"Male {SubRace.Xaela.ToName()} (NPC)", + CollectionType.MaleHelionNpc => $"Male {SubRace.Helion.ToName()} (NPC)", + CollectionType.MaleLostNpc => $"Male {SubRace.Lost.ToName()} (NPC)", + CollectionType.MaleRavaNpc => $"Male {SubRace.Rava.ToName()} (NPC)", + CollectionType.MaleVeenaNpc => $"Male {SubRace.Veena.ToName()} (NPC)", + CollectionType.FemalePlayerCharacter => "Female Player Characters", CollectionType.FemaleNonPlayerCharacter => "Female Non-Player Characters", - CollectionType.FemaleMidlander => $"Female {SubRace.Midlander.ToName()}", - CollectionType.FemaleHighlander => $"Female {SubRace.Highlander.ToName()}", - CollectionType.FemaleWildwood => $"Female {SubRace.Wildwood.ToName()}", - CollectionType.FemaleDuskwight => $"Female {SubRace.Duskwight.ToName()}", - CollectionType.FemalePlainsfolk => $"Female {SubRace.Plainsfolk.ToName()}", - CollectionType.FemaleDunesfolk => $"Female {SubRace.Dunesfolk.ToName()}", - CollectionType.FemaleSeekerOfTheSun => $"Female {SubRace.SeekerOfTheSun.ToName()}", - CollectionType.FemaleKeeperOfTheMoon => $"Female {SubRace.KeeperOfTheMoon.ToName()}", - CollectionType.FemaleSeawolf => $"Female {SubRace.Seawolf.ToName()}", - CollectionType.FemaleHellsguard => $"Female {SubRace.Hellsguard.ToName()}", - CollectionType.FemaleRaen => $"Female {SubRace.Raen.ToName()}", - CollectionType.FemaleXaela => $"Female {SubRace.Xaela.ToName()}", - CollectionType.FemaleHelion => $"Female {SubRace.Helion.ToName()}", - CollectionType.FemaleLost => $"Female {SubRace.Lost.ToName()}", - CollectionType.FemaleRava => $"Female {SubRace.Rava.ToName()}", - CollectionType.FemaleVeena => $"Female {SubRace.Veena.ToName()}", - CollectionType.FemaleMidlanderNpc => $"Female {SubRace.Midlander.ToName()} (NPC)", - CollectionType.FemaleHighlanderNpc => $"Female {SubRace.Highlander.ToName()} (NPC)", - CollectionType.FemaleWildwoodNpc => $"Female {SubRace.Wildwood.ToName()} (NPC)", - CollectionType.FemaleDuskwightNpc => $"Female {SubRace.Duskwight.ToName()} (NPC)", - CollectionType.FemalePlainsfolkNpc => $"Female {SubRace.Plainsfolk.ToName()} (NPC)", - CollectionType.FemaleDunesfolkNpc => $"Female {SubRace.Dunesfolk.ToName()} (NPC)", - CollectionType.FemaleSeekerOfTheSunNpc => $"Female {SubRace.SeekerOfTheSun.ToName()} (NPC)", + CollectionType.FemaleMidlander => $"Female {SubRace.Midlander.ToName()}", + CollectionType.FemaleHighlander => $"Female {SubRace.Highlander.ToName()}", + CollectionType.FemaleWildwood => $"Female {SubRace.Wildwood.ToName()}", + CollectionType.FemaleDuskwight => $"Female {SubRace.Duskwight.ToName()}", + CollectionType.FemalePlainsfolk => $"Female {SubRace.Plainsfolk.ToName()}", + CollectionType.FemaleDunesfolk => $"Female {SubRace.Dunesfolk.ToName()}", + CollectionType.FemaleSeekerOfTheSun => $"Female {SubRace.SeekerOfTheSun.ToName()}", + CollectionType.FemaleKeeperOfTheMoon => $"Female {SubRace.KeeperOfTheMoon.ToName()}", + CollectionType.FemaleSeawolf => $"Female {SubRace.Seawolf.ToName()}", + CollectionType.FemaleHellsguard => $"Female {SubRace.Hellsguard.ToName()}", + CollectionType.FemaleRaen => $"Female {SubRace.Raen.ToName()}", + CollectionType.FemaleXaela => $"Female {SubRace.Xaela.ToName()}", + CollectionType.FemaleHelion => $"Female {SubRace.Helion.ToName()}", + CollectionType.FemaleLost => $"Female {SubRace.Lost.ToName()}", + CollectionType.FemaleRava => $"Female {SubRace.Rava.ToName()}", + CollectionType.FemaleVeena => $"Female {SubRace.Veena.ToName()}", + CollectionType.FemaleMidlanderNpc => $"Female {SubRace.Midlander.ToName()} (NPC)", + CollectionType.FemaleHighlanderNpc => $"Female {SubRace.Highlander.ToName()} (NPC)", + CollectionType.FemaleWildwoodNpc => $"Female {SubRace.Wildwood.ToName()} (NPC)", + CollectionType.FemaleDuskwightNpc => $"Female {SubRace.Duskwight.ToName()} (NPC)", + CollectionType.FemalePlainsfolkNpc => $"Female {SubRace.Plainsfolk.ToName()} (NPC)", + CollectionType.FemaleDunesfolkNpc => $"Female {SubRace.Dunesfolk.ToName()} (NPC)", + CollectionType.FemaleSeekerOfTheSunNpc => $"Female {SubRace.SeekerOfTheSun.ToName()} (NPC)", CollectionType.FemaleKeeperOfTheMoonNpc => $"Female {SubRace.KeeperOfTheMoon.ToName()} (NPC)", - CollectionType.FemaleSeawolfNpc => $"Female {SubRace.Seawolf.ToName()} (NPC)", - CollectionType.FemaleHellsguardNpc => $"Female {SubRace.Hellsguard.ToName()} (NPC)", - CollectionType.FemaleRaenNpc => $"Female {SubRace.Raen.ToName()} (NPC)", - CollectionType.FemaleXaelaNpc => $"Female {SubRace.Xaela.ToName()} (NPC)", - CollectionType.FemaleHelionNpc => $"Female {SubRace.Helion.ToName()} (NPC)", - CollectionType.FemaleLostNpc => $"Female {SubRace.Lost.ToName()} (NPC)", - CollectionType.FemaleRavaNpc => $"Female {SubRace.Rava.ToName()} (NPC)", - CollectionType.FemaleVeenaNpc => $"Female {SubRace.Veena.ToName()} (NPC)", - CollectionType.Inactive => "Collection", - CollectionType.Default => "Default", - CollectionType.Interface => "Interface", - CollectionType.Individual => "Individual", - CollectionType.Current => "Current", - _ => string.Empty, + CollectionType.FemaleSeawolfNpc => $"Female {SubRace.Seawolf.ToName()} (NPC)", + CollectionType.FemaleHellsguardNpc => $"Female {SubRace.Hellsguard.ToName()} (NPC)", + CollectionType.FemaleRaenNpc => $"Female {SubRace.Raen.ToName()} (NPC)", + CollectionType.FemaleXaelaNpc => $"Female {SubRace.Xaela.ToName()} (NPC)", + CollectionType.FemaleHelionNpc => $"Female {SubRace.Helion.ToName()} (NPC)", + CollectionType.FemaleLostNpc => $"Female {SubRace.Lost.ToName()} (NPC)", + CollectionType.FemaleRavaNpc => $"Female {SubRace.Rava.ToName()} (NPC)", + CollectionType.FemaleVeenaNpc => $"Female {SubRace.Veena.ToName()} (NPC)", + CollectionType.Inactive => "Collection", + CollectionType.Default => "Base", + CollectionType.Interface => "Interface", + CollectionType.Individual => "Individual", + CollectionType.Current => "Current", + _ => string.Empty, }; public static string ToDescription(this CollectionType collectionType) @@ -580,4 +576,4 @@ public static class CollectionTypeExtensions "This collection applies to all female non-player character Veena Viera that do not have a more specific character collection associated.", _ => string.Empty, }; -} \ No newline at end of file +} diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 84e47897..a2b201cd 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -28,7 +28,7 @@ public partial class ModCollection /// Create the always available Empty Collection that will always sit at index 0, /// can not be deleted and does never create a cache. /// - public static readonly ModCollection Empty = CreateEmpty(EmptyCollectionName, 0); + public static readonly ModCollection Empty = CreateEmpty(EmptyCollectionName, 0, 0); /// The name of a collection can not contain characters invalid in a path. public string Name { get; internal init; } @@ -133,10 +133,10 @@ public partial class ModCollection } /// Constructor for empty collections. - public static ModCollection CreateEmpty(string name, int index) + public static ModCollection CreateEmpty(string name, int index, int modCount) { Debug.Assert(index >= 0, "Empty collection created with negative index."); - return new ModCollection(name, index, 0, CurrentVersion, new List(), new List(), + return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?) null, modCount).ToList(), new List(), new Dictionary()); } diff --git a/Penumbra/Interop/Services/FontReloader.cs b/Penumbra/Interop/Services/FontReloader.cs index 51f8abc2..76a205dc 100644 --- a/Penumbra/Interop/Services/FontReloader.cs +++ b/Penumbra/Interop/Services/FontReloader.cs @@ -1,12 +1,12 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Component.GUI; using Penumbra.GameData; - + namespace Penumbra.Interop.Services; -/// +/// /// Handle font reloading via game functions. -/// May cause a interface flicker while reloading. +/// May cause a interface flicker while reloading. /// public unsafe class FontReloader { @@ -21,24 +21,27 @@ public unsafe class FontReloader Penumbra.Log.Error("Could not reload fonts, function could not be found."); } - private readonly AtkModule* _atkModule = null!; - private readonly delegate* unmanaged _reloadFontsFunc = null!; + private AtkModule* _atkModule = null!; + private delegate* unmanaged _reloadFontsFunc = null!; - public FontReloader() + public FontReloader(Dalamud.Game.Framework dFramework) { - var framework = Framework.Instance(); - if (framework == null) - return; + dFramework.RunOnFrameworkThread(() => + { + var framework = Framework.Instance(); + if (framework == null) + return; - var uiModule = framework->GetUiModule(); - if (uiModule == null) - return; + var uiModule = framework->GetUiModule(); + if (uiModule == null) + return; - var atkModule = uiModule->GetRaptureAtkModule(); - if (atkModule == null) - return; + var atkModule = uiModule->GetRaptureAtkModule(); + if (atkModule == null) + return; - _atkModule = &atkModule->AtkModule; - _reloadFontsFunc = ((delegate* unmanaged*)_atkModule->vtbl)[Offsets.ReloadFontsVfunc]; + _atkModule = &atkModule->AtkModule; + _reloadFontsFunc = ((delegate* unmanaged*)_atkModule->vtbl)[Offsets.ReloadFontsVfunc]; + }); } } diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 2dc60120..79313a6f 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -178,7 +178,7 @@ public unsafe class ImcFile : MetaBaseFile public void Replace(ResourceHandle* resource) { var (data, length) = resource->GetData(); - var newData = Penumbra.MetaFileManager.AllocateDefaultMemory(ActualLength, 8); + var newData = Manager.AllocateDefaultMemory(ActualLength, 8); if (newData == null) { Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed."); @@ -187,7 +187,7 @@ public unsafe class ImcFile : MetaBaseFile MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength); - Penumbra.MetaFileManager.Free(data, length); + Manager.Free(data, length); resource->SetData((IntPtr)newData, ActualLength); } } diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 72ebac34..53b35559 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -20,12 +20,12 @@ public unsafe class MetaFileManager internal readonly CharacterUtility CharacterUtility; internal readonly ResidentResourceManager ResidentResources; internal readonly DataManager GameData; - internal readonly ActiveCollections ActiveCollections; + internal readonly ActiveCollectionData ActiveCollections; internal readonly ValidityChecker ValidityChecker; internal readonly IdentifierService Identifier; public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, DataManager gameData, - ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker, IdentifierService identifier) + ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, IdentifierService identifier) { CharacterUtility = characterUtility; ResidentResources = residentResources; diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 09283dc7..c793c68f 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -8,16 +8,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Penumbra.GameData; using Penumbra.Meta; using Penumbra.Mods.Manager; +using Penumbra.Services; namespace Penumbra.Mods.ItemSwap; public class ItemSwapContainer { private readonly MetaFileManager _manager; - private readonly IObjectIdentifier _identifier; + private readonly IdentifierService _identifier; private Dictionary< Utf8GamePath, FullPath > _modRedirections = new(); private HashSet< MetaManipulation > _modManipulations = new(); @@ -114,7 +114,7 @@ public class ItemSwapContainer } } - public ItemSwapContainer(MetaFileManager manager, IObjectIdentifier identifier) + public ItemSwapContainer(MetaFileManager manager, IdentifierService identifier) { _manager = manager; _identifier = identifier; @@ -136,7 +136,7 @@ public class ItemSwapContainer { Swaps.Clear(); Loaded = false; - var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing ); + var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing ); Loaded = true; return ret; } @@ -145,7 +145,7 @@ public class ItemSwapContainer { Swaps.Clear(); Loaded = false; - var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to ); + var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to ); Loaded = true; return ret; } diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index 15fc5e92..d8116998 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -38,12 +38,6 @@ public class ModDataEditor _communicatorService = communicatorService; } - public string MetaFile(Mod mod) - => _saveService.FileNames.ModMetaPath(mod); - - public string DataFile(Mod mod) - => _saveService.FileNames.LocalDataFile(mod); - /// Create the file containing the meta information about a mod from scratch. public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version, string? website) diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index b0add38f..806d1a1b 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -220,7 +220,7 @@ public partial class ModCreator if (!file.Exists) continue; - var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName), + var rgsp = TexToolsMeta.FromRgspFile(_metaFileManager, file.FullName, File.ReadAllBytes(file.FullName), _config.KeepDefaultMetaChanges); Penumbra.Log.Verbose( $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 2ff848d8..8a936998 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -41,7 +41,6 @@ public class Penumbra : IDalamudPlugin public static Configuration Config { get; private set; } = null!; public static CharacterUtility CharacterUtility { get; private set; } = null!; - public static MetaFileManager MetaFileManager { get; private set; } = null!; public static ModManager ModManager { get; private set; } = null!; public static ModCacheManager ModCaches { get; private set; } = null!; public static CollectionManager CollectionManager { get; private set; } = null!; @@ -72,7 +71,6 @@ public class Penumbra : IDalamudPlugin _tmp.Services.GetRequiredService(); Config = _tmp.Services.GetRequiredService(); CharacterUtility = _tmp.Services.GetRequiredService(); - MetaFileManager = _tmp.Services.GetRequiredService(); Actors = _tmp.Services.GetRequiredService().AwaitedService; _tempMods = _tmp.Services.GetRequiredService(); _residentResources = _tmp.Services.GetRequiredService(); @@ -114,8 +112,6 @@ public class Penumbra : IDalamudPlugin var api = _tmp.Services.GetRequiredService(); HttpApi = _tmp.Services.GetRequiredService(); _tmp.Services.GetRequiredService(); - if (Config.EnableHttpApi) - HttpApi.CreateWebServer(); api.ChangedItemTooltip += it => { if (it is Item) diff --git a/Penumbra/PenumbraNew.cs b/Penumbra/PenumbraNew.cs index d32bed09..5cd501af 100644 --- a/Penumbra/PenumbraNew.cs +++ b/Penumbra/PenumbraNew.cs @@ -84,6 +84,7 @@ public class PenumbraNew // Add Collection Services services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -139,6 +140,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Penumbra/Services/ConfigMigrationService.cs b/Penumbra/Services/ConfigMigrationService.cs index fde8ea62..2d525c00 100644 --- a/Penumbra/Services/ConfigMigrationService.cs +++ b/Penumbra/Services/ConfigMigrationService.cs @@ -252,11 +252,11 @@ public class ConfigMigrationService using var j = new JsonTextWriter(writer); j.Formatting = Formatting.Indented; j.WriteStartObject(); - j.WritePropertyName(nameof(ActiveCollections.Default)); + j.WritePropertyName(nameof(ActiveCollectionData.Default)); j.WriteValue(def); - j.WritePropertyName(nameof(ActiveCollections.Interface)); + j.WritePropertyName(nameof(ActiveCollectionData.Interface)); j.WriteValue(ui); - j.WritePropertyName(nameof(ActiveCollections.Current)); + j.WritePropertyName(nameof(ActiveCollectionData.Current)); j.WriteValue(current); foreach (var (type, collection) in special) { diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index d602ed3c..22bca756 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -30,19 +30,17 @@ public class ItemSwapTab : IDisposable, ITab private readonly ItemService _itemService; private readonly CollectionManager _collectionManager; private readonly ModManager _modManager; - private readonly Configuration _config; private readonly MetaFileManager _metaFileManager; public ItemSwapTab(CommunicatorService communicator, ItemService itemService, CollectionManager collectionManager, - ModManager modManager, Configuration config, IdentifierService identifier, MetaFileManager metaFileManager) + ModManager modManager, IdentifierService identifier, MetaFileManager metaFileManager) { _communicator = communicator; _itemService = itemService; _collectionManager = collectionManager; _modManager = modManager; - _config = config; _metaFileManager = metaFileManager; - _swapData = new ItemSwapContainer(metaFileManager, identifier.AwaitedService); + _swapData = new ItemSwapContainer(metaFileManager, identifier); _selectors = new Dictionary { diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index 3edc0e9c..b2c65c45 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -27,6 +27,8 @@ public static class Colors public const uint MetaInfoText = 0xAAFFFFFF; public const uint RedTableBgTint = 0x40000080; public const uint DiscordColor = 0xFFDA8972; + public const uint SelectedColor = 0x6069C056; + public const uint RedundantColor = 0x6050D0D0; public const uint FilterActive = 0x807070FF; public const uint TutorialMarker = 0xFF20FFFF; public const uint TutorialBorder = 0xD00000FF; diff --git a/Penumbra/UI/CollectionTab/Collections.CollectionSelector.cs b/Penumbra/UI/CollectionTab/CollectionCombo.cs similarity index 85% rename from Penumbra/UI/CollectionTab/Collections.CollectionSelector.cs rename to Penumbra/UI/CollectionTab/CollectionCombo.cs index 41aa1437..b7d379cf 100644 --- a/Penumbra/UI/CollectionTab/Collections.CollectionSelector.cs +++ b/Penumbra/UI/CollectionTab/CollectionCombo.cs @@ -8,11 +8,11 @@ using Penumbra.GameData.Actors; namespace Penumbra.UI.CollectionTab; -public sealed class CollectionSelector : FilterComboCache +public sealed class CollectionCombo : FilterComboCache { private readonly CollectionManager _collectionManager; - public CollectionSelector(CollectionManager manager, Func> items) + public CollectionCombo(CollectionManager manager, Func> items) : base(items) => _collectionManager = manager; diff --git a/Penumbra/UI/CollectionTab/Collections.IndividualCollectionUi.cs b/Penumbra/UI/CollectionTab/IndividualCollectionUi.cs similarity index 96% rename from Penumbra/UI/CollectionTab/Collections.IndividualCollectionUi.cs rename to Penumbra/UI/CollectionTab/IndividualCollectionUi.cs index 7867f2b3..226c9c8b 100644 --- a/Penumbra/UI/CollectionTab/Collections.IndividualCollectionUi.cs +++ b/Penumbra/UI/CollectionTab/IndividualCollectionUi.cs @@ -18,9 +18,9 @@ public class IndividualCollectionUi { private readonly ActorService _actorService; private readonly CollectionManager _collectionManager; - private readonly CollectionSelector _withEmpty; + private readonly CollectionCombo _withEmpty; - public IndividualCollectionUi(ActorService actors, CollectionManager collectionManager, CollectionSelector withEmpty) + public IndividualCollectionUi(ActorService actors, CollectionManager collectionManager, CollectionCombo withEmpty) { _actorService = actors; _collectionManager = collectionManager; diff --git a/Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs b/Penumbra/UI/CollectionTab/InheritanceUi.cs similarity index 100% rename from Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs rename to Penumbra/UI/CollectionTab/InheritanceUi.cs diff --git a/Penumbra/UI/CollectionTab/Collections.NpcCombo.cs b/Penumbra/UI/CollectionTab/NpcCombo.cs similarity index 100% rename from Penumbra/UI/CollectionTab/Collections.NpcCombo.cs rename to Penumbra/UI/CollectionTab/NpcCombo.cs diff --git a/Penumbra/UI/CollectionTab/Collections.SpecialCombo.cs b/Penumbra/UI/CollectionTab/SpecialCombo.cs similarity index 95% rename from Penumbra/UI/CollectionTab/Collections.SpecialCombo.cs rename to Penumbra/UI/CollectionTab/SpecialCombo.cs index 91b7f491..ab332399 100644 --- a/Penumbra/UI/CollectionTab/Collections.SpecialCombo.cs +++ b/Penumbra/UI/CollectionTab/SpecialCombo.cs @@ -1,7 +1,6 @@ using ImGuiNET; using OtterGui.Classes; using OtterGui.Widgets; -using Penumbra.Collections; using Penumbra.Collections.Manager; namespace Penumbra.UI.CollectionTab; diff --git a/Penumbra/UI/CollectionTab/Collections.WorldCombo.cs b/Penumbra/UI/CollectionTab/WorldCombo.cs similarity index 100% rename from Penumbra/UI/CollectionTab/Collections.WorldCombo.cs rename to Penumbra/UI/CollectionTab/WorldCombo.cs diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs index 7f46ee07..5370e16e 100644 --- a/Penumbra/UI/Tabs/CollectionsTab.cs +++ b/Penumbra/UI/Tabs/CollectionsTab.cs @@ -1,299 +1,592 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using Dalamud.Interface.Components; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using OtterGui.Widgets; -using Penumbra.Collections; -using Penumbra.Collections.Manager; -using Penumbra.Services; -using Penumbra.UI.CollectionTab; - -namespace Penumbra.UI.Tabs; - -public class CollectionsTab : IDisposable, ITab -{ - private readonly CommunicatorService _communicator; - private readonly Configuration _config; - private readonly CollectionManager _collectionManager; - private readonly TutorialService _tutorial; - private readonly SpecialCombo _specialCollectionCombo; - - private readonly CollectionSelector _collectionsWithEmpty; - private readonly CollectionSelector _collectionSelector; - private readonly InheritanceUi _inheritance; - private readonly IndividualCollectionUi _individualCollections; - - public CollectionsTab(ActorService actorService, CommunicatorService communicator, CollectionManager collectionManager, - TutorialService tutorial, Configuration config) - { - _communicator = communicator; - _collectionManager = collectionManager; - _tutorial = tutorial; - _config = config; - _specialCollectionCombo = new SpecialCombo(_collectionManager, "##NewSpecial", 350); - _collectionsWithEmpty = new CollectionSelector(_collectionManager, - () => _collectionManager.Storage.OrderBy(c => c.Name).Prepend(ModCollection.Empty).ToList()); - _collectionSelector = new CollectionSelector(_collectionManager, () => _collectionManager.Storage.OrderBy(c => c.Name).ToList()); - _inheritance = new InheritanceUi(_collectionManager); - _individualCollections = new IndividualCollectionUi(actorService, _collectionManager, _collectionsWithEmpty); - - _communicator.CollectionChange.Subscribe(_individualCollections.UpdateIdentifiers); - } - - public ReadOnlySpan Label - => "Collections"u8; - - /// Draw a collection selector of a certain width for a certain type. - public void DrawCollectionSelector(string label, float width, CollectionType collectionType, bool withEmpty) - => (withEmpty ? _collectionsWithEmpty : _collectionSelector).Draw(label, width, collectionType); - - public void Dispose() - => _communicator.CollectionChange.Unsubscribe(_individualCollections.UpdateIdentifiers); - - /// Draw a tutorial step regardless of tab selection. - public void DrawHeader() - => _tutorial.OpenTutorial(BasicTutorialSteps.Collections); - - public void DrawContent() - { - using var child = ImRaii.Child("##collections", -Vector2.One); - if (child) - { - DrawActiveCollectionSelectors(); - DrawMainSelectors(); - } - } - - #region New Collections - - // Input text fields. - private string _newCollectionName = string.Empty; - private bool _canAddCollection; - - /// - /// Create a new collection that is either empty or a duplicate of the current collection. - /// Resets the new collection name. - /// - private void CreateNewCollection(bool duplicate) - { - if (_collectionManager.Storage.AddCollection(_newCollectionName, duplicate ? _collectionManager.Active.Current : null)) - _newCollectionName = string.Empty; - } - - /// Draw the Clean Unused Settings button if there are any. - private void DrawCleanCollectionButton(Vector2 width) - { - if (_collectionManager.Active.Current.UnusedSettings.Count == 0) - return; - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton( - $"Clean {_collectionManager.Active.Current.UnusedSettings.Count} Unused Settings###CleanSettings", width - , "Remove all stored settings for mods not currently available and fix invalid settings.\n\nUse at own risk." - , false)) - _collectionManager.Storage.CleanUnavailableSettings(_collectionManager.Active.Current); - } - - /// Draw the new collection input as well as its buttons. - private void DrawNewCollectionInput(Vector2 width) - { - // Input for new collection name. Also checks for validity when changed. - ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); - if (ImGui.InputTextWithHint("##New Collection", "New Collection Name...", ref _newCollectionName, 64)) - _canAddCollection = _collectionManager.Storage.CanAddCollection(_newCollectionName, out _); - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "A collection is a set of settings for your installed mods, including their enabled status, their priorities and their mod-specific configuration.\n" - + "You can use multiple collections to quickly switch between sets of enabled mods."); - - // Creation buttons. - var tt = _canAddCollection - ? string.Empty - : "Please enter a unique name only consisting of symbols valid in a path but no '|' before creating a collection."; - if (ImGuiUtil.DrawDisabledButton("Create Empty Collection", width, tt, !_canAddCollection)) - CreateNewCollection(false); - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton($"Duplicate {TutorialService.SelectedCollection}", width, tt, !_canAddCollection)) - CreateNewCollection(true); - } - - #endregion - - #region Collection Selection - - /// Draw all collection assignment selections. - private void DrawActiveCollectionSelectors() - { - UiHelpers.DefaultLineSpace(); - var open = ImGui.CollapsingHeader(TutorialService.ActiveCollections, ImGuiTreeNodeFlags.DefaultOpen); - _tutorial.OpenTutorial(BasicTutorialSteps.ActiveCollections); - if (!open) - return; - - UiHelpers.DefaultLineSpace(); - - DrawDefaultCollectionSelector(); - _tutorial.OpenTutorial(BasicTutorialSteps.DefaultCollection); - DrawInterfaceCollectionSelector(); - _tutorial.OpenTutorial(BasicTutorialSteps.InterfaceCollection); - UiHelpers.DefaultLineSpace(); - - DrawSpecialAssignments(); - _tutorial.OpenTutorial(BasicTutorialSteps.SpecialCollections1); - UiHelpers.DefaultLineSpace(); - - _individualCollections.Draw(); - _tutorial.OpenTutorial(BasicTutorialSteps.SpecialCollections2); - UiHelpers.DefaultLineSpace(); - } - - private void DrawCurrentCollectionSelector(Vector2 width) - { - using var group = ImRaii.Group(); - DrawCollectionSelector("##current", UiHelpers.InputTextWidth.X, CollectionType.Current, false); - ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(TutorialService.SelectedCollection, - "This collection will be modified when using the Installed Mods tab and making changes.\nIt is not automatically assigned to anything."); - - // Deletion conditions. - var deleteCondition = _collectionManager.Active.Current.Name != ModCollection.DefaultCollectionName; - var modifierHeld = Penumbra.Config.DeleteModModifier.IsActive(); - var tt = deleteCondition - ? modifierHeld ? string.Empty : $"Hold {_config.DeleteModModifier} while clicking to delete the collection." - : $"You can not delete the collection {ModCollection.DefaultCollectionName}."; - - if (ImGuiUtil.DrawDisabledButton($"Delete {TutorialService.SelectedCollection}", width, tt, !deleteCondition || !modifierHeld)) - _collectionManager.Storage.RemoveCollection(_collectionManager.Active.Current); - - DrawCleanCollectionButton(width); - } - - /// Draw the selector for the default collection assignment. - private void DrawDefaultCollectionSelector() - { - using var group = ImRaii.Group(); - DrawCollectionSelector("##default", UiHelpers.InputTextWidth.X, CollectionType.Default, true); - ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(TutorialService.DefaultCollection, - $"Mods in the {TutorialService.DefaultCollection} are loaded for anything that is not associated with the user interface or a character in the game," - + "as well as any character for whom no more specific conditions from below apply."); - } - - /// Draw the selector for the interface collection assignment. - private void DrawInterfaceCollectionSelector() - { - using var group = ImRaii.Group(); - DrawCollectionSelector("##interface", UiHelpers.InputTextWidth.X, CollectionType.Interface, true); - ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(TutorialService.InterfaceCollection, - $"Mods in the {TutorialService.InterfaceCollection} are loaded for any file that the game categorizes as an UI file. This is mostly icons as well as the tiles that generate the user interface windows themselves."); - } - - /// Description for character groups used in multiple help markers. - private const string CharacterGroupDescription = - $"{TutorialService.CharacterGroups} apply to certain types of characters based on a condition.\n" - + $"All of them take precedence before the {TutorialService.DefaultCollection},\n" - + $"but all {TutorialService.IndividualAssignments} take precedence before them."; - - /// Draw the entire group assignment section. - private void DrawSpecialAssignments() - { - using var _ = ImRaii.Group(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(TutorialService.CharacterGroups); - ImGuiComponents.HelpMarker(CharacterGroupDescription); - ImGui.Separator(); - DrawSpecialCollections(); - ImGui.Dummy(Vector2.Zero); - DrawNewSpecialCollection(); - } - - /// Draw a new combo to select special collections as well as button to create it. - private void DrawNewSpecialCollection() - { - ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); - if (_specialCollectionCombo.CurrentIdx == -1 - || _collectionManager.Active.ByType(_specialCollectionCombo.CurrentType!.Value.Item1) != null) - { - _specialCollectionCombo.ResetFilter(); - _specialCollectionCombo.CurrentIdx = CollectionTypeExtensions.Special - .IndexOf(t => _collectionManager.Active.ByType(t.Item1) == null); - } - - if (_specialCollectionCombo.CurrentType == null) - return; - - _specialCollectionCombo.Draw(); - ImGui.SameLine(); - var disabled = _specialCollectionCombo.CurrentType == null; - var tt = disabled - ? $"Please select a condition for a {TutorialService.GroupAssignment} before creating the collection.\n\n" - + CharacterGroupDescription - : CharacterGroupDescription; - if (!ImGuiUtil.DrawDisabledButton($"Assign {TutorialService.ConditionalGroup}", new Vector2(120 * UiHelpers.Scale, 0), tt, disabled)) - return; - - _collectionManager.Active.CreateSpecialCollection(_specialCollectionCombo.CurrentType!.Value.Item1); - _specialCollectionCombo.CurrentIdx = -1; - } - - #endregion - - #region Current Collection Editing - - /// Draw the current collection selection, the creation of new collections and the inheritance block. - private void DrawMainSelectors() - { - UiHelpers.DefaultLineSpace(); - var open = ImGui.CollapsingHeader("Collection Settings", ImGuiTreeNodeFlags.DefaultOpen); - _tutorial.OpenTutorial(BasicTutorialSteps.EditingCollections); - if (!open) - return; - - var width = new Vector2((UiHelpers.InputTextWidth.X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); - UiHelpers.DefaultLineSpace(); - - DrawCurrentCollectionSelector(width); - _tutorial.OpenTutorial(BasicTutorialSteps.CurrentCollection); - UiHelpers.DefaultLineSpace(); - - DrawNewCollectionInput(width); - UiHelpers.DefaultLineSpace(); - - _inheritance.Draw(); - _tutorial.OpenTutorial(BasicTutorialSteps.Inheritance); - } - - /// Draw all currently set special collections. - private void DrawSpecialCollections() - { - foreach (var (type, name, desc) in CollectionTypeExtensions.Special) - { - var collection = _collectionManager.Active.ByType(type); - if (collection == null) - continue; - - using var id = ImRaii.PushId((int)type); - DrawCollectionSelector("##SpecialCombo", UiHelpers.InputTextWidth.X, type, true); - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize, string.Empty, - false, true)) - { - _collectionManager.Active.RemoveSpecialCollection(type); - _specialCollectionCombo.ResetFilter(); - } - - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGuiUtil.LabeledHelpMarker(name, desc); - } - } - - #endregion -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; +using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.Mods.Manager; +using Penumbra.Services; +using Penumbra.UI.Classes; +using Penumbra.UI.CollectionTab; + +namespace Penumbra.UI.Tabs; + +public sealed class CollectionTree +{ + private readonly CollectionStorage _collections; + private readonly ActiveCollections _active; + private readonly CollectionSelector2 _selector; + private readonly ActorService _actors; + private readonly TargetManager _targets; + + private static readonly IReadOnlyList<(string Name, uint Border)> Buttons = CreateButtons(); + private static readonly IReadOnlyList<(CollectionType, bool, bool, string, uint)> AdvancedTree = CreateTree(); + + public CollectionTree(CollectionManager manager, CollectionSelector2 selector, ActorService actors, + TargetManager targets) + { + _collections = manager.Storage; + _active = manager.Active; + _selector = selector; + _actors = actors; + _targets = targets; + } + + public void DrawSimple() + { + var buttonWidth = new Vector2(200 * ImGuiHelpers.GlobalScale, 2 * ImGui.GetTextLineHeightWithSpacing()); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, Vector2.Zero) + .Push(ImGuiStyleVar.FrameBorderSize, 1 * ImGuiHelpers.GlobalScale); + DrawSimpleCollectionButton(CollectionType.Default, buttonWidth); + DrawSimpleCollectionButton(CollectionType.Interface, buttonWidth); + DrawSimpleCollectionButton(CollectionType.Yourself, buttonWidth); + DrawSimpleCollectionButton(CollectionType.MalePlayerCharacter, buttonWidth); + DrawSimpleCollectionButton(CollectionType.FemalePlayerCharacter, buttonWidth); + DrawSimpleCollectionButton(CollectionType.MaleNonPlayerCharacter, buttonWidth); + DrawSimpleCollectionButton(CollectionType.FemaleNonPlayerCharacter, buttonWidth); + + var specialWidth = buttonWidth with { X = 275 * ImGuiHelpers.GlobalScale }; + var player = _actors.AwaitedService.GetCurrentPlayer(); + DrawButton($"Current Character ({(player.IsValid ? player.ToString() : "Unavailable")})", CollectionType.Individual, specialWidth, 0, + player); + ImGui.SameLine(); + + var target = _actors.AwaitedService.FromObject(_targets.Target, false, true, true); + DrawButton($"Current Target ({(target.IsValid ? target.ToString() : "Unavailable")})", CollectionType.Individual, specialWidth, 0, target); + if (_active.Individuals.Count > 0) + { + ImGui.TextUnformatted("Currently Active Individual Assignments"); + for (var i = 0; i < _active.Individuals.Count; ++i) + { + var (name, ids, coll) = _active.Individuals.Assignments[i]; + DrawButton(name, CollectionType.Individual, buttonWidth, 0, ids[0], coll); + + ImGui.SameLine(); + if (ImGui.GetContentRegionAvail().X < buttonWidth.X + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X + && i < _active.Individuals.Count - 1) + ImGui.NewLine(); + } + + ImGui.NewLine(); + } + + var first = true; + + void Button(CollectionType type) + { + var (name, border) = Buttons[(int)type]; + var collection = _active.ByType(type); + if (collection == null) + return; + + if (first) + { + ImGui.Separator(); + ImGui.TextUnformatted("Currently Active Advanced Assignments"); + first = false; + } + DrawButton(name, type, buttonWidth, border, ActorIdentifier.Invalid, collection); + ImGui.SameLine(); + if (ImGui.GetContentRegionAvail().X < buttonWidth.X + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X) + ImGui.NewLine(); + } + + Button(CollectionType.NonPlayerChild); + Button(CollectionType.NonPlayerElderly); + foreach (var race in Enum.GetValues().Skip(1)) + { + Button(CollectionTypeExtensions.FromParts(race, Gender.Male, false)); + Button(CollectionTypeExtensions.FromParts(race, Gender.Female, false)); + Button(CollectionTypeExtensions.FromParts(race, Gender.Male, true)); + Button(CollectionTypeExtensions.FromParts(race, Gender.Female, true)); + } + } + + public void DrawAdvanced() + { + using var table = ImRaii.Table("##advanced", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, Vector2.Zero) + .Push(ImGuiStyleVar.FrameBorderSize, 1 * ImGuiHelpers.GlobalScale); + + var buttonWidth = new Vector2(150 * ImGuiHelpers.GlobalScale, 2 * ImGui.GetTextLineHeightWithSpacing()); + var dummy = new Vector2(1, 0); + + foreach (var (type, pre, post, name, border) in AdvancedTree) + { + ImGui.TableNextColumn(); + if (type is CollectionType.Inactive) + continue; + + if (pre) + ImGui.Dummy(dummy); + DrawAssignmentButton(type, buttonWidth, name, border); + if (post) + ImGui.Dummy(dummy); + } + } + + private void DrawContext(bool open, ModCollection? collection, CollectionType type, ActorIdentifier identifier, char suffix = 'i') + { + var label = $"{type}{identifier}{suffix}"; + if (open) + ImGui.OpenPopup(label); + + using var context = ImRaii.Popup(label); + if (context) + { + using (var color = ImRaii.PushColor(ImGuiCol.Text, Colors.DiscordColor)) + { + if (ImGui.MenuItem("Use no mods.")) + _active.SetCollection(ModCollection.Empty, type, _active.Individuals.GetGroup(identifier)); + } + + if (collection != null) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder); + if (ImGui.MenuItem("Remove this assignment.")) + _active.SetCollection(null, type, _active.Individuals.GetGroup(identifier)); + } + + foreach (var coll in _collections) + { + if (coll != collection && ImGui.MenuItem($"Use {coll.Name}.")) + _active.SetCollection(coll, type, _active.Individuals.GetGroup(identifier)); + } + } + } + + private bool DrawButton(string text, CollectionType type, Vector2 width, uint borderColor, ActorIdentifier id, ModCollection? collection = null) + { + using var group = ImRaii.Group(); + var invalid = type == CollectionType.Individual && !id.IsValid; + var redundancy = _active.RedundancyCheck(type, id); + collection ??= _active.ByType(type, id); + using var color = ImRaii.PushColor(ImGuiCol.Button, + collection == null + ? 0 + : redundancy.Length > 0 + ? Colors.RedundantColor + : collection == _active.Current + ? Colors.SelectedColor + : collection == ModCollection.Empty + ? Colors.RedTableBgTint + : ImGui.GetColorU32(ImGuiCol.Button), !invalid) + .Push(ImGuiCol.Border, borderColor == 0 ? ImGui.GetColorU32(ImGuiCol.TextDisabled) : borderColor); + using var disabled = ImRaii.Disabled(invalid); + var button = ImGui.Button(text, width) || ImGui.IsItemClicked(ImGuiMouseButton.Right); + var hovered = redundancy.Length > 0 && ImGui.IsItemHovered(); + if (!invalid) + { + _selector.DragTarget(type, id); + var name = collection == ModCollection.Empty ? "Use No Mods" : collection?.Name ?? "Unassigned"; + var size = ImGui.CalcTextSize(name); + var textPos = ImGui.GetItemRectMax() - size - ImGui.GetStyle().FramePadding; + ImGui.GetWindowDrawList().AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), name); + DrawContext(button, collection, type, id); + } + + if (hovered) + ImGui.SetTooltip(redundancy); + + return button; + } + + private void DrawSimpleCollectionButton(CollectionType type, Vector2 width) + { + DrawButton(type.ToName(), type, width, 0, ActorIdentifier.Invalid); + ImGui.SameLine(); + var secondLine = string.Empty; + foreach (var parent in type.InheritanceOrder()) + { + var coll = _active.ByType(parent); + if (coll == null) + continue; + + secondLine = $"\nWill behave as {parent.ToName()} ({coll.Name}) while unassigned."; + break; + } + + ImGui.TextUnformatted(type.ToDescription() + secondLine); + ImGui.Separator(); + } + + private void DrawAssignmentButton(CollectionType type, Vector2 width, string name, uint color) + => DrawButton(name, type, width, color, ActorIdentifier.Invalid, _active.ByType(type)); + + private static IReadOnlyList<(string Name, uint Border)> CreateButtons() + { + var ret = Enum.GetValues().Select(t => (t.ToName(), 0u)).ToArray(); + + foreach (var race in Enum.GetValues().Skip(1)) + { + var color = race switch + { + SubRace.Midlander => 0xAA5C9FE4u, + SubRace.Highlander => 0xAA5C9FE4u, + SubRace.Wildwood => 0xAA5C9F49u, + SubRace.Duskwight => 0xAA5C9F49u, + SubRace.Plainsfolk => 0xAAEF8CB6u, + SubRace.Dunesfolk => 0xAAEF8CB6u, + SubRace.SeekerOfTheSun => 0xAA8CEFECu, + SubRace.KeeperOfTheMoon => 0xAA8CEFECu, + SubRace.Seawolf => 0xAAEFE68Cu, + SubRace.Hellsguard => 0xAAEFE68Cu, + SubRace.Raen => 0xAAB5EF8Cu, + SubRace.Xaela => 0xAAB5EF8Cu, + SubRace.Helion => 0xAAFFFFFFu, + SubRace.Lost => 0xAAFFFFFFu, + SubRace.Rava => 0xAA607FA7u, + SubRace.Veena => 0xAA607FA7u, + _ => 0u, + }; + + ret[(int)CollectionTypeExtensions.FromParts(race, Gender.Male, false)] = ($"♂ {race.ToShortName()}", color); + ret[(int)CollectionTypeExtensions.FromParts(race, Gender.Female, false)] = ($"♀ {race.ToShortName()}", color); + ret[(int)CollectionTypeExtensions.FromParts(race, Gender.Male, true)] = ($"♂ {race.ToShortName()} (NPC)", color); + ret[(int)CollectionTypeExtensions.FromParts(race, Gender.Female, true)] = ($"♀ {race.ToShortName()} (NPC)", color); + } + + ret[(int)CollectionType.MalePlayerCharacter] = ("♂ Player", 0); + ret[(int)CollectionType.FemalePlayerCharacter] = ("♀ Player", 0); + ret[(int)CollectionType.MaleNonPlayerCharacter] = ("♂ NPC", 0); + ret[(int)CollectionType.FemaleNonPlayerCharacter] = ("♀ NPC", 0); + return ret; + } + + private static IReadOnlyList<(CollectionType, bool, bool, string, uint)> CreateTree() + { + var ret = new List<(CollectionType, bool, bool, string, uint)>(Buttons.Count); + + void Add(CollectionType type, bool pre, bool post) + { + var (name, border) = (int)type >= Buttons.Count ? (type.ToName(), 0) : Buttons[(int)type]; + ret.Add((type, pre, post, name, border)); + } + + Add(CollectionType.Default, false, false); + Add(CollectionType.Interface, false, false); + Add(CollectionType.Inactive, false, false); + Add(CollectionType.Inactive, false, false); + Add(CollectionType.Yourself, false, true); + Add(CollectionType.Inactive, false, true); + Add(CollectionType.NonPlayerChild, false, true); + Add(CollectionType.NonPlayerElderly, false, true); + Add(CollectionType.MalePlayerCharacter, true, true); + Add(CollectionType.FemalePlayerCharacter, true, true); + Add(CollectionType.MaleNonPlayerCharacter, true, true); + Add(CollectionType.FemaleNonPlayerCharacter, true, true); + var pre = true; + foreach (var race in Enum.GetValues().Skip(1)) + { + Add(CollectionTypeExtensions.FromParts(race, Gender.Male, false), pre, !pre); + Add(CollectionTypeExtensions.FromParts(race, Gender.Female, false), pre, !pre); + Add(CollectionTypeExtensions.FromParts(race, Gender.Male, true), pre, !pre); + Add(CollectionTypeExtensions.FromParts(race, Gender.Female, true), pre, !pre); + pre = !pre; + } + + return ret; + } +} + +public sealed class CollectionPanel +{ + private readonly CollectionManager _manager; + private readonly ModStorage _modStorage; + private readonly InheritanceUi _inheritanceUi; + + public CollectionPanel(CollectionManager manager, ModStorage modStorage) + { + _manager = manager; + _modStorage = modStorage; + _inheritanceUi = new InheritanceUi(_manager); + } + + public void Draw() + { + var collection = _manager.Active.Current; + DrawName(collection); + DrawStatistics(collection); + _inheritanceUi.Draw(); + DrawSettingsList(collection); + DrawInactiveSettingsList(collection); + } + + private void DrawName(ModCollection collection) + { + ImGui.TextUnformatted($"{collection.Name} ({collection.AnonymizedName})"); + } + + private void DrawStatistics(ModCollection collection) + { + ImGui.TextUnformatted("Used for:"); + var sb = new StringBuilder(128); + if (_manager.Active.Default == collection) + sb.Append(CollectionType.Default.ToName()).Append(", "); + if (_manager.Active.Interface == collection) + sb.Append(CollectionType.Interface.ToName()).Append(", "); + foreach (var (type, _) in _manager.Active.SpecialAssignments.Where(p => p.Value == collection)) + sb.Append(type.ToName()).Append(", "); + foreach (var (name, _) in _manager.Active.Individuals.Where(p => p.Collection == collection)) + sb.Append(name).Append(", "); + + ImGui.SameLine(); + ImGuiUtil.TextWrapped(sb.Length == 0 ? "Nothing" : sb.ToString(0, sb.Length - 2)); + + if (collection.DirectParentOf.Count > 0) + { + ImGui.TextUnformatted("Inherited by:"); + ImGui.SameLine(); + ImGuiUtil.TextWrapped(string.Join(", ", collection.DirectParentOf.Select(c => c.Name))); + } + } + + private void DrawSettingsList(ModCollection collection) + { + using var box = ImRaii.ListBox("##activeSettings"); + if (!box) + return; + + foreach (var (mod, (settings, parent)) in _modStorage.Select(m => (m, collection[m.Index])).Where(t => t.Item2.Settings != null) + .OrderBy(t => t.m.Name)) + ImGui.TextUnformatted($"{mod}{(parent != collection ? $" (inherited from {parent.Name})" : string.Empty)}"); + } + + private void DrawInactiveSettingsList(ModCollection collection) + { + if (collection.UnusedSettings.Count == 0) + return; + + if (ImGui.Button("Clear Unused Settings")) + _manager.Storage.CleanUnavailableSettings(collection); + + using var box = ImRaii.ListBox("##inactiveSettings"); + if (!box) + return; + + foreach (var name in collection.UnusedSettings.Keys) + ImGui.TextUnformatted(name); + } +} + +public sealed class CollectionSelector2 : ItemSelector, IDisposable +{ + private readonly Configuration _config; + private readonly CommunicatorService _communicator; + private readonly CollectionStorage _storage; + private readonly ActiveCollections _active; + + private ModCollection? _dragging; + + public CollectionSelector2(Configuration config, CommunicatorService communicator, CollectionStorage storage, ActiveCollections active) + : base(new List(), Flags.Delete | Flags.Add | Flags.Duplicate | Flags.Filter) + { + _config = config; + _communicator = communicator; + _storage = storage; + _active = active; + + _communicator.CollectionChange.Subscribe(OnCollectionChange); + // Set items. + OnCollectionChange(CollectionType.Inactive, null, null, string.Empty); + // Set selection. + OnCollectionChange(CollectionType.Current, null, _active.Current, string.Empty); + } + + protected override bool OnDelete(int idx) + { + if (idx < 0 || idx >= Items.Count) + return false; + + return _storage.RemoveCollection(Items[idx]); + } + + protected override bool DeleteButtonEnabled() + => _storage.DefaultNamed != Current && _config.DeleteModModifier.IsActive(); + + protected override string DeleteButtonTooltip() + => _storage.DefaultNamed == Current + ? $"The selected collection {Current.Name} can not be deleted." + : $"Delete the currently selected collection {Current?.Name}. Hold {_config.DeleteModModifier} to delete."; + + protected override bool OnAdd(string name) + => _storage.AddCollection(name, null); + + protected override bool OnDuplicate(string name, int idx) + { + if (idx < 0 || idx >= Items.Count) + return false; + + return _storage.AddCollection(name, Items[idx]); + } + + protected override bool Filtered(int idx) + => !Items[idx].Name.Contains(Filter, StringComparison.OrdinalIgnoreCase); + + protected override bool OnDraw(int idx) + { + using var color = ImRaii.PushColor(ImGuiCol.Header, Colors.SelectedColor); + var ret = ImGui.Selectable(Items[idx].Name, idx == CurrentIdx); + using var source = ImRaii.DragDropSource(); + if (source) + { + _dragging = Items[idx]; + ImGui.SetDragDropPayload("Assignment", nint.Zero, 0); + ImGui.TextUnformatted($"Assigning {_dragging.Name} to..."); + } + + if (ret) + _active.SetCollection(Items[idx], CollectionType.Current); + + return ret; + } + + public void DragTarget(CollectionType type, ActorIdentifier identifier) + { + using var target = ImRaii.DragDropTarget(); + if (!target.Success || _dragging == null || !ImGuiUtil.IsDropping("Assignment")) + return; + + _active.SetCollection(_dragging, type, _active.Individuals.GetGroup(identifier)); + _dragging = null; + } + + public void Dispose() + { + _communicator.CollectionChange.Unsubscribe(OnCollectionChange); + } + + private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? @new, string _3) + { + switch (type) + { + case CollectionType.Temporary: return; + case CollectionType.Current: + if (@new != null) + SetCurrent(@new); + SetFilterDirty(); + return; + case CollectionType.Inactive: + Items.Clear(); + foreach (var c in _storage.OrderBy(c => c.Name)) + Items.Add(c); + + if (old == Current) + ClearCurrentSelection(); + else + TryRestoreCurrent(); + SetFilterDirty(); + return; + default: + SetFilterDirty(); + return; + } + } +} + +public class CollectionsTab : IDisposable, ITab +{ + private readonly CommunicatorService _communicator; + private readonly Configuration _configuration; + private readonly CollectionManager _collectionManager; + private readonly CollectionSelector2 _selector; + private readonly CollectionPanel _panel; + private readonly CollectionTree _tree; + + public enum PanelMode + { + SimpleAssignment, + ComplexAssignment, + Details, + }; + + public PanelMode Mode = PanelMode.SimpleAssignment; + + public CollectionsTab(CommunicatorService communicator, Configuration configuration, CollectionManager collectionManager, + ModStorage modStorage, ActorService actors, TargetManager targets) + { + _communicator = communicator; + _configuration = configuration; + _collectionManager = collectionManager; + _selector = new CollectionSelector2(_configuration, _communicator, _collectionManager.Storage, _collectionManager.Active); + _panel = new CollectionPanel(_collectionManager, modStorage); + _tree = new CollectionTree(collectionManager, _selector, actors, targets); + } + + public void Dispose() + { + _selector.Dispose(); + } + + public ReadOnlySpan Label + => "Collections"u8; + + public void DrawContent() + { + var width = ImGui.CalcTextSize("nnnnnnnnnnnnnnnnnnnnnnnn").X; + _selector.Draw(width); + ImGui.SameLine(); + using var group = ImRaii.Group(); + DrawHeaderLine(); + DrawPanel(); + } + + private void DrawHeaderLine() + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0).Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + var buttonSize = new Vector2(ImGui.GetContentRegionAvail().X / 3f, 0); + + using var _ = ImRaii.Group(); + using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.TabActive), Mode is PanelMode.SimpleAssignment); + if (ImGui.Button("Simple Assignments", buttonSize)) + Mode = PanelMode.SimpleAssignment; + + ImGui.SameLine(); + color.Pop(); + color.Push(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.TabActive), Mode is PanelMode.Details); + if (ImGui.Button("Collection Details", buttonSize)) + Mode = PanelMode.Details; + + ImGui.SameLine(); + color.Pop(); + color.Push(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.TabActive), Mode is PanelMode.ComplexAssignment); + if (ImGui.Button("Advanced Assignments", buttonSize)) + Mode = PanelMode.ComplexAssignment; + } + + private void DrawPanel() + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + using var child = ImRaii.Child("##CollectionSettings", new Vector2(-1, 0), true, ImGuiWindowFlags.HorizontalScrollbar); + if (!child) + return; + + style.Pop(); + switch (Mode) + { + case PanelMode.SimpleAssignment: + _tree.DrawSimple(); + break; + case PanelMode.ComplexAssignment: + _tree.DrawAdvanced(); + break; + case PanelMode.Details: + _panel.Draw(); + break; + } + + style.Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + } +} diff --git a/Penumbra/UI/Tabs/CollectionsTabOld.cs b/Penumbra/UI/Tabs/CollectionsTabOld.cs new file mode 100644 index 00000000..15655e2a --- /dev/null +++ b/Penumbra/UI/Tabs/CollectionsTabOld.cs @@ -0,0 +1,299 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Components; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; +using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.Services; +using Penumbra.UI.CollectionTab; + +namespace Penumbra.UI.Tabs; + +public class CollectionsTabOld : IDisposable, ITab +{ + private readonly CommunicatorService _communicator; + private readonly Configuration _config; + private readonly CollectionManager _collectionManager; + private readonly TutorialService _tutorial; + private readonly SpecialCombo _specialCollectionCombo; + + private readonly CollectionCombo _collectionsWithEmpty; + private readonly CollectionCombo _collectionCombo; + private readonly InheritanceUi _inheritance; + private readonly IndividualCollectionUi _individualCollections; + + public CollectionsTabOld(ActorService actorService, CommunicatorService communicator, CollectionManager collectionManager, + TutorialService tutorial, Configuration config) + { + _communicator = communicator; + _collectionManager = collectionManager; + _tutorial = tutorial; + _config = config; + _specialCollectionCombo = new SpecialCombo(_collectionManager, "##NewSpecial", 350); + _collectionsWithEmpty = new CollectionCombo(_collectionManager, + () => _collectionManager.Storage.OrderBy(c => c.Name).Prepend(ModCollection.Empty).ToList()); + _collectionCombo = new CollectionCombo(_collectionManager, () => _collectionManager.Storage.OrderBy(c => c.Name).ToList()); + _inheritance = new InheritanceUi(_collectionManager); + _individualCollections = new IndividualCollectionUi(actorService, _collectionManager, _collectionsWithEmpty); + + _communicator.CollectionChange.Subscribe(_individualCollections.UpdateIdentifiers); + } + + public ReadOnlySpan Label + => "Collections"u8; + + /// Draw a collection selector of a certain width for a certain type. + public void DrawCollectionSelector(string label, float width, CollectionType collectionType, bool withEmpty) + => (withEmpty ? _collectionsWithEmpty : _collectionCombo).Draw(label, width, collectionType); + + public void Dispose() + => _communicator.CollectionChange.Unsubscribe(_individualCollections.UpdateIdentifiers); + + /// Draw a tutorial step regardless of tab selection. + public void DrawHeader() + => _tutorial.OpenTutorial(BasicTutorialSteps.Collections); + + public void DrawContent() + { + using var child = ImRaii.Child("##collections", -Vector2.One); + if (child) + { + DrawActiveCollectionSelectors(); + DrawMainSelectors(); + } + } + + #region New Collections + + // Input text fields. + private string _newCollectionName = string.Empty; + private bool _canAddCollection; + + /// + /// Create a new collection that is either empty or a duplicate of the current collection. + /// Resets the new collection name. + /// + private void CreateNewCollection(bool duplicate) + { + if (_collectionManager.Storage.AddCollection(_newCollectionName, duplicate ? _collectionManager.Active.Current : null)) + _newCollectionName = string.Empty; + } + + /// Draw the Clean Unused Settings button if there are any. + private void DrawCleanCollectionButton(Vector2 width) + { + if (_collectionManager.Active.Current.UnusedSettings.Count == 0) + return; + + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton( + $"Clean {_collectionManager.Active.Current.UnusedSettings.Count} Unused Settings###CleanSettings", width + , "Remove all stored settings for mods not currently available and fix invalid settings.\n\nUse at own risk." + , false)) + _collectionManager.Storage.CleanUnavailableSettings(_collectionManager.Active.Current); + } + + /// Draw the new collection input as well as its buttons. + private void DrawNewCollectionInput(Vector2 width) + { + // Input for new collection name. Also checks for validity when changed. + ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); + if (ImGui.InputTextWithHint("##New Collection", "New Collection Name...", ref _newCollectionName, 64)) + _canAddCollection = _collectionManager.Storage.CanAddCollection(_newCollectionName, out _); + + ImGui.SameLine(); + ImGuiComponents.HelpMarker( + "A collection is a set of settings for your installed mods, including their enabled status, their priorities and their mod-specific configuration.\n" + + "You can use multiple collections to quickly switch between sets of enabled mods."); + + // Creation buttons. + var tt = _canAddCollection + ? string.Empty + : "Please enter a unique name only consisting of symbols valid in a path but no '|' before creating a collection."; + if (ImGuiUtil.DrawDisabledButton("Create Empty Collection", width, tt, !_canAddCollection)) + CreateNewCollection(false); + + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton($"Duplicate {TutorialService.SelectedCollection}", width, tt, !_canAddCollection)) + CreateNewCollection(true); + } + + #endregion + + #region Collection Selection + + /// Draw all collection assignment selections. + private void DrawActiveCollectionSelectors() + { + UiHelpers.DefaultLineSpace(); + var open = ImGui.CollapsingHeader(TutorialService.ActiveCollections, ImGuiTreeNodeFlags.DefaultOpen); + _tutorial.OpenTutorial(BasicTutorialSteps.ActiveCollections); + if (!open) + return; + + UiHelpers.DefaultLineSpace(); + + DrawDefaultCollectionSelector(); + _tutorial.OpenTutorial(BasicTutorialSteps.DefaultCollection); + DrawInterfaceCollectionSelector(); + _tutorial.OpenTutorial(BasicTutorialSteps.InterfaceCollection); + UiHelpers.DefaultLineSpace(); + + DrawSpecialAssignments(); + _tutorial.OpenTutorial(BasicTutorialSteps.SpecialCollections1); + UiHelpers.DefaultLineSpace(); + + _individualCollections.Draw(); + _tutorial.OpenTutorial(BasicTutorialSteps.SpecialCollections2); + UiHelpers.DefaultLineSpace(); + } + + private void DrawCurrentCollectionSelector(Vector2 width) + { + using var group = ImRaii.Group(); + DrawCollectionSelector("##current", UiHelpers.InputTextWidth.X, CollectionType.Current, false); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker(TutorialService.SelectedCollection, + "This collection will be modified when using the Installed Mods tab and making changes.\nIt is not automatically assigned to anything."); + + // Deletion conditions. + var deleteCondition = _collectionManager.Active.Current.Name != ModCollection.DefaultCollectionName; + var modifierHeld = Penumbra.Config.DeleteModModifier.IsActive(); + var tt = deleteCondition + ? modifierHeld ? string.Empty : $"Hold {_config.DeleteModModifier} while clicking to delete the collection." + : $"You can not delete the collection {ModCollection.DefaultCollectionName}."; + + if (ImGuiUtil.DrawDisabledButton($"Delete {TutorialService.SelectedCollection}", width, tt, !deleteCondition || !modifierHeld)) + _collectionManager.Storage.RemoveCollection(_collectionManager.Active.Current); + + DrawCleanCollectionButton(width); + } + + /// Draw the selector for the default collection assignment. + private void DrawDefaultCollectionSelector() + { + using var group = ImRaii.Group(); + DrawCollectionSelector("##default", UiHelpers.InputTextWidth.X, CollectionType.Default, true); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker(TutorialService.DefaultCollection, + $"Mods in the {TutorialService.DefaultCollection} are loaded for anything that is not associated with the user interface or a character in the game," + + "as well as any character for whom no more specific conditions from below apply."); + } + + /// Draw the selector for the interface collection assignment. + private void DrawInterfaceCollectionSelector() + { + using var group = ImRaii.Group(); + DrawCollectionSelector("##interface", UiHelpers.InputTextWidth.X, CollectionType.Interface, true); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker(TutorialService.InterfaceCollection, + $"Mods in the {TutorialService.InterfaceCollection} are loaded for any file that the game categorizes as an UI file. This is mostly icons as well as the tiles that generate the user interface windows themselves."); + } + + /// Description for character groups used in multiple help markers. + private const string CharacterGroupDescription = + $"{TutorialService.CharacterGroups} apply to certain types of characters based on a condition.\n" + + $"All of them take precedence before the {TutorialService.DefaultCollection},\n" + + $"but all {TutorialService.IndividualAssignments} take precedence before them."; + + /// Draw the entire group assignment section. + private void DrawSpecialAssignments() + { + using var _ = ImRaii.Group(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(TutorialService.CharacterGroups); + ImGuiComponents.HelpMarker(CharacterGroupDescription); + ImGui.Separator(); + DrawSpecialCollections(); + ImGui.Dummy(Vector2.Zero); + DrawNewSpecialCollection(); + } + + /// Draw a new combo to select special collections as well as button to create it. + private void DrawNewSpecialCollection() + { + ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); + if (_specialCollectionCombo.CurrentIdx == -1 + || _collectionManager.Active.ByType(_specialCollectionCombo.CurrentType!.Value.Item1) != null) + { + _specialCollectionCombo.ResetFilter(); + _specialCollectionCombo.CurrentIdx = CollectionTypeExtensions.Special + .IndexOf(t => _collectionManager.Active.ByType(t.Item1) == null); + } + + if (_specialCollectionCombo.CurrentType == null) + return; + + _specialCollectionCombo.Draw(); + ImGui.SameLine(); + var disabled = _specialCollectionCombo.CurrentType == null; + var tt = disabled + ? $"Please select a condition for a {TutorialService.GroupAssignment} before creating the collection.\n\n" + + CharacterGroupDescription + : CharacterGroupDescription; + if (!ImGuiUtil.DrawDisabledButton($"Assign {TutorialService.ConditionalGroup}", new Vector2(120 * UiHelpers.Scale, 0), tt, disabled)) + return; + + _collectionManager.Active.CreateSpecialCollection(_specialCollectionCombo.CurrentType!.Value.Item1); + _specialCollectionCombo.CurrentIdx = -1; + } + + #endregion + + #region Current Collection Editing + + /// Draw the current collection selection, the creation of new collections and the inheritance block. + private void DrawMainSelectors() + { + UiHelpers.DefaultLineSpace(); + var open = ImGui.CollapsingHeader("Collection Settings", ImGuiTreeNodeFlags.DefaultOpen); + _tutorial.OpenTutorial(BasicTutorialSteps.EditingCollections); + if (!open) + return; + + var width = new Vector2((UiHelpers.InputTextWidth.X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + UiHelpers.DefaultLineSpace(); + + DrawCurrentCollectionSelector(width); + _tutorial.OpenTutorial(BasicTutorialSteps.CurrentCollection); + UiHelpers.DefaultLineSpace(); + + DrawNewCollectionInput(width); + UiHelpers.DefaultLineSpace(); + + _inheritance.Draw(); + _tutorial.OpenTutorial(BasicTutorialSteps.Inheritance); + } + + /// Draw all currently set special collections. + private void DrawSpecialCollections() + { + foreach (var (type, name, desc) in CollectionTypeExtensions.Special) + { + var collection = _collectionManager.Active.ByType(type); + if (collection == null) + continue; + + using var id = ImRaii.PushId((int)type); + DrawCollectionSelector("##SpecialCombo", UiHelpers.InputTextWidth.X, type, true); + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize, string.Empty, + false, true)) + { + _collectionManager.Active.RemoveSpecialCollection(type); + _specialCollectionCombo.ResetFilter(); + } + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGuiUtil.LabeledHelpMarker(name, desc); + } + } + + #endregion +} diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs index 2437f96e..18db156c 100644 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ b/Penumbra/UI/Tabs/ModsTab.cs @@ -9,15 +9,15 @@ using System.Numerics; using Dalamud.Interface; using OtterGui.Widgets; using Penumbra.Api.Enums; -using Penumbra.Interop; using Penumbra.Interop.Services; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI.ModsTab; using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector; -using Penumbra.Collections.Manager; - +using Penumbra.Collections.Manager; +using Penumbra.UI.CollectionTab; + namespace Penumbra.UI.Tabs; public class ModsTab : ITab @@ -25,23 +25,23 @@ public class ModsTab : ITab private readonly ModFileSystemSelector _selector; private readonly ModPanel _panel; private readonly TutorialService _tutorial; - private readonly ModManager _modManager; - private readonly CollectionManager _collectionManager; + private readonly ModManager _modManager; + private readonly ActiveCollections _activeCollections; private readonly RedrawService _redrawService; private readonly Configuration _config; - private readonly CollectionsTab _collectionsTab; + private readonly CollectionCombo _collectionCombo; public ModsTab(ModManager modManager, CollectionManager collectionManager, ModFileSystemSelector selector, ModPanel panel, - TutorialService tutorial, RedrawService redrawService, Configuration config, CollectionsTab collectionsTab) + TutorialService tutorial, RedrawService redrawService, Configuration config) { _modManager = modManager; - _collectionManager = collectionManager; + _activeCollections = collectionManager.Active; _selector = selector; _panel = panel; _tutorial = tutorial; _redrawService = redrawService; _config = config; - _collectionsTab = collectionsTab; + _collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Name).ToList()); } public bool IsVisible @@ -62,14 +62,14 @@ public class ModsTab : ITab { try { - _selector.Draw(GetModSelectorSize()); + _selector.Draw(GetModSelectorSize(_config)); ImGui.SameLine(); using var group = ImRaii.Group(); DrawHeaderLine(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - using (var child = ImRaii.Child("##ModsTabMod", new Vector2(-1, Penumbra.Config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()), + using (var child = ImRaii.Child("##ModsTabMod", new Vector2(-1, _config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()), true, ImGuiWindowFlags.HorizontalScrollbar)) { style.Pop(); @@ -86,16 +86,29 @@ public class ModsTab : ITab { Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}"); Penumbra.Log.Error($"{_modManager.Count} Mods\n" - + $"{_collectionManager.Active.Current.AnonymizedName} Current Collection\n" - + $"{_collectionManager.Active.Current.Settings.Count} Settings\n" + + $"{_activeCollections.Current.AnonymizedName} Current Collection\n" + + $"{_activeCollections.Current.Settings.Count} Settings\n" + $"{_selector.SortMode.Name} Sort Mode\n" + $"{_selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n" + $"{_selector.Selected?.Name ?? "NULL"} Selected Mod\n" - + $"{string.Join(", ", _collectionManager.Active.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n" + + $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n" + $"{_selector.SelectedSettingCollection.AnonymizedName} Collection\n"); } } + /// Get the correct size for the mod selector based on current config. + public static float GetModSelectorSize(Configuration config) + { + var absoluteSize = Math.Clamp(config.ModSelectorAbsoluteSize, Configuration.Constants.MinAbsoluteSize, + Math.Min(Configuration.Constants.MaxAbsoluteSize, ImGui.GetContentRegionAvail().X - 100)); + var relativeSize = config.ScaleModSelector + ? Math.Clamp(config.ModSelectorScaledSize, Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize) + : 0; + return !config.ScaleModSelector + ? absoluteSize + : Math.Max(absoluteSize, relativeSize * ImGui.GetContentRegionAvail().X / 100); + } + private void DrawRedrawLine() { if (Penumbra.Config.HideRedrawBar) @@ -159,32 +172,32 @@ public class ModsTab : ITab ImGui.SameLine(); DrawInheritedCollectionButton(3 * buttonSize); ImGui.SameLine(); - _collectionsTab.DrawCollectionSelector("##collectionSelector", 2 * buttonSize.X, CollectionType.Current, false); + _collectionCombo.Draw("##collectionSelector", 2 * buttonSize.X, CollectionType.Current); } _tutorial.OpenTutorial(BasicTutorialSteps.CollectionSelectors); - if (!_collectionManager.Active.CurrentCollectionInUse) + if (!_activeCollections.CurrentCollectionInUse) ImGuiUtil.DrawTextButton("The currently selected collection is not used in any way.", -Vector2.UnitX, Colors.PressEnterWarningBg); } private void DrawDefaultCollectionButton(Vector2 width) { - var name = $"{TutorialService.DefaultCollection} ({_collectionManager.Active.Default.Name})"; - var isCurrent = _collectionManager.Active.Default == _collectionManager.Active.Current; - var isEmpty = _collectionManager.Active.Default == ModCollection.Empty; + var name = $"{TutorialService.DefaultCollection} ({_activeCollections.Default.Name})"; + var isCurrent = _activeCollections.Default == _activeCollections.Current; + var isEmpty = _activeCollections.Default == ModCollection.Empty; var tt = isCurrent ? $"The current collection is already the configured {TutorialService.DefaultCollection}." : isEmpty ? $"The {TutorialService.DefaultCollection} is configured to be empty." : $"Set the {TutorialService.SelectedCollection} to the configured {TutorialService.DefaultCollection}."; if (ImGuiUtil.DrawDisabledButton(name, width, tt, isCurrent || isEmpty)) - _collectionManager.Active.SetCollection(_collectionManager.Active.Default, CollectionType.Current); + _activeCollections.SetCollection(_activeCollections.Default, CollectionType.Current); } private void DrawInheritedCollectionButton(Vector2 width) { var noModSelected = _selector.Selected == null; var collection = _selector.SelectedSettingCollection; - var modInherited = collection != _collectionManager.Active.Current; + var modInherited = collection != _activeCollections.Current; var (name, tt) = (noModSelected, modInherited) switch { (true, _) => ("Inherited Collection", "No mod selected."), @@ -193,19 +206,6 @@ public class ModsTab : ITab (false, false) => ("Not Inherited", "The selected mod does not inherit its settings."), }; if (ImGuiUtil.DrawDisabledButton(name, width, tt, noModSelected || !modInherited)) - _collectionManager.Active.SetCollection(collection, CollectionType.Current); - } - - /// Get the correct size for the mod selector based on current config. - private float GetModSelectorSize() - { - var absoluteSize = Math.Clamp(_config.ModSelectorAbsoluteSize, Configuration.Constants.MinAbsoluteSize, - Math.Min(Configuration.Constants.MaxAbsoluteSize, ImGui.GetContentRegionAvail().X - 100)); - var relativeSize = _config.ScaleModSelector - ? Math.Clamp(_config.ModSelectorScaledSize, Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize) - : 0; - return !_config.ScaleModSelector - ? absoluteSize - : Math.Max(absoluteSize, relativeSize * ImGui.GetContentRegionAvail().X / 100); + _activeCollections.SetCollection(collection, CollectionType.Current); } }