diff --git a/FilterExtension/ConfigNodes/Check.cs b/FilterExtension/ConfigNodes/Check.cs index 29b09fd4..6ef9afb5 100644 --- a/FilterExtension/ConfigNodes/Check.cs +++ b/FilterExtension/ConfigNodes/Check.cs @@ -7,7 +7,7 @@ namespace FilterExtensions.ConfigNodes { using Utility; - public class Check : ICloneable + public class Check : IEquatable, ICloneable { public enum CheckType { @@ -139,18 +139,18 @@ public Check(ConfigNode node) public Check(Check c) { type = c.type; - if (c.values != null) - values = (string[])c.values.Clone(); invert = c.invert; contains = c.contains; + equality = c.equality; + if (c.values != null) + values = (string[])c.values.Clone(); if (c.checks != null) { checks = new List(c.checks.Count); for (int i = 0; i < c.checks.Count; ++i) checks.Add(new Check(c.checks[i])); } - equality = c.equality; } public Check(string Type, string Value, bool Invert = false, bool Contains = true, Equality Compare = Equality.Equals) diff --git a/FilterExtension/ConfigNodes/Filter.cs b/FilterExtension/ConfigNodes/Filter.cs index 86ce947d..f87f7d91 100644 --- a/FilterExtension/ConfigNodes/Filter.cs +++ b/FilterExtension/ConfigNodes/Filter.cs @@ -5,7 +5,7 @@ namespace FilterExtensions.ConfigNodes { - public class Filter : ICloneable + public class Filter : IEquatable, ICloneable { public List checks { get; set; } // checks are processed in serial (a && b), inversion gives (!a || !b) logic public bool invert { get; set; } diff --git a/FilterExtension/ConfigNodes/customCategory.cs b/FilterExtension/ConfigNodes/customCategory.cs index ba82ac85..1dcfd579 100644 --- a/FilterExtension/ConfigNodes/customCategory.cs +++ b/FilterExtension/ConfigNodes/customCategory.cs @@ -8,39 +8,44 @@ namespace FilterExtensions.ConfigNodes using Utility; using KSP.UI.Screens; - public enum categoryTypeAndBehaviour + public class customCategory : IEquatable, ICloneable { - None, - Engines, - StockAdd, - StockReplace, - ModAdd, - ModReplace - } + public enum categoryType + { + New = 1, // new category + Stock = 2, // modification to stock category + Mod = 4, // modification to a mod cateogry + } + + public enum categoryBehaviour + { + Add = 1, // only add to existing categories + Replace = 2, // wipe existing categories + Engines = 4 // generate unique engine types + } - public class customCategory : IEquatable - { public string categoryName { get; set; } public string iconName { get; set; } public Color colour { get; set; } - public categoryTypeAndBehaviour behaviour { get; set; } + public categoryType type { get; set; } + public categoryBehaviour behaviour { get; set; } public bool all { get; set; } // has an all parts subCategory public List subCategories { get; set; } // array of subcategories public List templates { get; set; } // Checks to add to every Filter in a category with the template tag - private static readonly List categoryNames = new List { "Pods", "Engines", "Fuel Tanks", "Command and Control", "Structural", "Aerodynamics", "Utility", "Science" }; - public customCategory(ConfigNode node) { - bool tmp; + bool tmpBool; + string tmpStr = string.Empty; + categoryName = node.GetValue("name"); iconName = node.GetValue("icon"); colour = convertToColor(node.GetValue("colour")); makeTemplate(node); - bool.TryParse(node.GetValue("all"), out tmp); - this.all = tmp; + bool.TryParse(node.GetValue("all"), out tmpBool); + this.all = tmpBool; ConfigNode[] subcategoryList = node.GetNodes("SUBCATEGORIES"); subCategories = new List(); @@ -84,7 +89,62 @@ public customCategory(ConfigNode node) subCategories.AddUniqueRange(unorderedSubCats); // tack unordered subcats on to the end subCategories.RemoveAll(s => s == null); } - typeSwitch(node.GetValue("type"), node.GetValue("value")); + + if (node.TryGetValue("type", ref tmpStr)) + tmpStr = tmpStr.ToLower(); + switch (tmpStr) + { + case "stock": + type = categoryType.Stock; + break; + case "mod": + type = categoryType.Mod; + break; + case "new": + default: + type = categoryType.New; + break; + } + if (node.TryGetValue("value", ref tmpStr)) + { + if (string.Equals(tmpStr, "replace", StringComparison.OrdinalIgnoreCase)) + behaviour = categoryBehaviour.Replace; + else if (string.Equals(tmpStr, "engine", StringComparison.OrdinalIgnoreCase)) + { + behaviour = categoryBehaviour.Engines; + foreach (List combo in Core.Instance.propellantCombos) + { + string dummy = string.Empty, subcatName = string.Join(",", combo.ToArray()); + Core.Instance.SetNameAndIcon(ref subcatName, ref dummy); + subCategories.AddUnique(new subCategoryItem(subcatName)); + } + } + else + behaviour = categoryBehaviour.Add; + } + } + + public customCategory(customCategory c) + { + categoryName = c.categoryName; + iconName = c.iconName; + colour = c.colour; + type = c.type; + behaviour = c.behaviour; + all = c.all; + + subCategories = new List(); + for (int i = 0; i < c.subCategories.Count; ++i) + subCategories.Add(new subCategoryItem(c.subCategories[i].subcategoryName, c.subCategories[i].applyTemplate)); + + templates = new List(); + for (int i = 0; i < c.templates.Count; ++i) + templates.Add(new Filter(c.templates[i])); + } + + public object Clone() + { + return new customCategory(this); } public void initialise() @@ -100,7 +160,7 @@ public void initialise() return; } PartCategorizer.Category category; - if (behaviour == categoryTypeAndBehaviour.None || behaviour == categoryTypeAndBehaviour.Engines) + if (type == categoryType.New) { RUI.Icons.Selectable.Icon icon = Core.getIcon(iconName); PartCategorizer.AddCustomFilter(categoryName, icon, colour); @@ -116,11 +176,8 @@ public void initialise() Core.Log("No category of this name was found to manipulate: " + categoryName); return; } - else - { - if (behaviour == categoryTypeAndBehaviour.StockReplace || behaviour == categoryTypeAndBehaviour.ModReplace) - category.subcategories.Clear(); - } + else if (behaviour == categoryBehaviour.Replace) + category.subcategories.Clear(); } List subcategoryNames = new List(); @@ -162,7 +219,7 @@ public void initialise() try { - if (Core.checkSubCategoryHasParts(sC, categoryName)) + if (Editor.subcategoriesChecked || sC.checkSubCategoryHasParts(categoryName)) sC.initialise(category); } catch (Exception ex) @@ -175,35 +232,9 @@ public void initialise() } } - private void typeSwitch(string type, string value) + private void typeSwitch(string Type, string Replace) { - switch (type) - { - case "engine": - behaviour = categoryTypeAndBehaviour.Engines; - foreach (List combo in Core.Instance.propellantCombos) - { - string dummy = string.Empty, subcatName = string.Join(",", combo.ToArray()); - Core.Instance.SetNameAndIcon(ref subcatName, ref dummy); - subCategories.AddUnique(new subCategoryItem(subcatName)); - } - break; - case "stock": - if (value == "replace") - behaviour = categoryTypeAndBehaviour.StockReplace; - else - behaviour = categoryTypeAndBehaviour.StockAdd; - break; - case "mod": - if (value == "replace") - behaviour = categoryTypeAndBehaviour.ModReplace; - else - behaviour = categoryTypeAndBehaviour.ModAdd; - break; - default: - behaviour = categoryTypeAndBehaviour.None; - break; - } + } private void makeTemplate(ConfigNode node) diff --git a/FilterExtension/ConfigNodes/customSubCategory.cs b/FilterExtension/ConfigNodes/customSubCategory.cs index da9d7b8b..87560e4f 100644 --- a/FilterExtension/ConfigNodes/customSubCategory.cs +++ b/FilterExtension/ConfigNodes/customSubCategory.cs @@ -6,7 +6,7 @@ namespace FilterExtensions.ConfigNodes { using KSP.UI.Screens; - public class customSubCategory : ICloneable + public class customSubCategory : IEquatable, ICloneable { public string subCategoryTitle { get; set; } // title of this subcategory public string iconName { get; set; } // default icon to use @@ -123,6 +123,46 @@ public bool checkFilters(AvailablePart part, int depth = 0) return ((!template.Any() || template.Any(t => t.checkFilter(part, depth))) && filters.Any(f => f.checkFilter(part, depth))); // part passed a template if present, and a subcategory filter } + /// + /// if a subcategory doesn't have any parts, it shouldn't be used. Doesn't account for the blackListed parts the first time the editor is entered + /// + /// the subcat to check + /// the category for logging purposes + /// true if the subcategory contains any parts + public bool checkSubCategoryHasParts(string category) + { + PartModuleFilter pmf; + AvailablePart p; + for (int i = 0; i < PartLoader.Instance.parts.Count; i++) + { + pmf = null; + p = PartLoader.Instance.parts[i]; + if (Core.Instance.filterModules.TryGetValue(p.name, out pmf)) + { + if (pmf.CheckForForceAdd(subCategoryTitle)) + return true; + if (pmf.CheckForForceBlock(subCategoryTitle)) + return false; + } + if (checkFilters(PartLoader.Instance.parts[i])) + return true; + } + + // only need to do this the first time we hit the editor + customCategory C = Core.Instance.Categories.FirstOrDefault(c => c.categoryName == category); + if (C != null) + C.subCategories.RemoveAll(s => s.subcategoryName == subCategoryTitle); + + if (Settings.debug) + { + if (!string.IsNullOrEmpty(category)) + Core.Log(subCategoryTitle + " in category " + category + " has no valid parts and was not initialised"); + else + Core.Log(subCategoryTitle + " has no valid parts and was not initialised"); + } + return false; + } + /// /// check to see if any checks in a subcategory match a given check /// diff --git a/FilterExtension/Core.cs b/FilterExtension/Core.cs index 37f057d0..803128ef 100644 --- a/FilterExtension/Core.cs +++ b/FilterExtension/Core.cs @@ -290,7 +290,6 @@ private void generateEngineTypes() string name = propList; // rename function needs to be parsed from string icon = propList; SetNameAndIcon(ref name, ref icon); - Core.Log(name); if (!string.IsNullOrEmpty(name) && !subCategoriesDict.ContainsKey(name)) { @@ -406,58 +405,16 @@ private void checkAndMarkConflicts() } } - /// - /// checks all subcategories and edits their names/icons if required - /// - public void namesAndIcons(PartCategorizer.Category category) - { - HashSet toRemove = new HashSet(); - foreach (PartCategorizer.Category c in category.subcategories) - { - if (removeSubCategory.Contains(c.button.categoryName)) - toRemove.Add(c.button.categoryName); - else - { - string tmp; - if (Rename.TryGetValue(c.button.categoryName, out tmp)) // update the name first - c.button.categoryName = tmp; - - RUI.Icons.Selectable.Icon icon; - if (tryGetIcon(tmp, out icon) || tryGetIcon(c.button.categoryName, out icon)) // if there is an explicit setIcon for the subcategory or if the name matches an icon - c.button.SetIcon(icon); // change the icon - } - } - category.subcategories.RemoveAll(c => toRemove.Contains(c.button.categoryName)); - } - /// /// loads all textures between 25 and 40 px in dimensions into a dictionary using the filename as a key /// private static void loadIcons() { - List texList = GameDatabase.Instance.databaseTexture.Where(t => t.texture != null && t.texture.height <= 40 && t.texture.width <= 40 && t.texture.width >= 25 && t.texture.height >= 25).ToList(); - - Dictionary texDict = new Dictionary(); - // using a dictionary for looking up _selected textures. Else the list has to be iterated over for every texture - foreach(GameDatabase.TextureInfo t in texList) - { - if (!texDict.ContainsKey(t.name)) - texDict.Add(t.name, t); - else - { - int i = 1; - while (texDict.ContainsKey(t.name + i.ToString()) && i < 1000) - i++; - if (i < 1000) - { - texDict.Add(t.name + i.ToString(), t); - Log(t.name + i.ToString()); - } - } - } + GameDatabase.TextureInfo[] texArray = GameDatabase.Instance.databaseTexture.Where(t => t.texture != null && t.texture.height == 32 && t.texture.width == 32).ToArray(); + Dictionary texDict = texArray.ToDictionary(k => k.name); Texture2D selectedTex = null; - foreach (GameDatabase.TextureInfo t in texList) + foreach (GameDatabase.TextureInfo t in texArray) { GameDatabase.TextureInfo texInfo; if (texDict.TryGetValue(t.name + "_selected", out texInfo)) @@ -466,17 +423,6 @@ private static void loadIcons() selectedTex = t.texture; string name = t.name.Split(new char[] { '/', '\\' }).Last(); - if (Instance.iconDict.ContainsKey(name)) - { - int i = 1; - while (Instance.iconDict.ContainsKey(name + i.ToString()) && i < 1000) - i++; - if (i != 1000) - name = name + i.ToString(); - if (Settings.debug) - Log(string.Format("Duplicated texture name \"{0}\" at:\r\n{1}\r\n New reference is: {2}", t.name.Split(new char[] { '/', '\\' }).Last(), t.name, name)); - } - RUI.Icons.Selectable.Icon icon = new RUI.Icons.Selectable.Icon(name, t.texture, selectedTex, false); Instance.iconDict.TryAdd(icon.name, icon); } @@ -531,30 +477,6 @@ private void RepairAvailablePartUrl(AvailablePart ap) ap.partUrl = url.url; } - /// - /// if a subcategory doesn't have any parts, it shouldn't be used. Doesn't account for the blackListed parts the first time the editor is entered - /// - /// the subcat to check - /// the category for logging purposes - /// true if the subcategory contains any parts - public static bool checkSubCategoryHasParts(customSubCategory sC, string category) - { - for (int i = 0; i < PartLoader.Instance.parts.Count; i++) - { - if (sC.checkFilters(PartLoader.Instance.parts[i])) - return true; - } - - if (Settings.debug) - { - if (!string.IsNullOrEmpty(category)) - Log(sC.subCategoryTitle + " in category " + category + " has no valid parts and was not initialised"); - else - Log(sC.subCategoryTitle + " has no valid parts and was not initialised"); - } - return false; - } - /// /// check the name and icon against the sets for renaming and setting a different icon /// diff --git a/FilterExtension/Editor.cs b/FilterExtension/Editor.cs index 1a06c802..ee69f78d 100644 --- a/FilterExtension/Editor.cs +++ b/FilterExtension/Editor.cs @@ -14,6 +14,7 @@ namespace FilterExtensions class Editor : MonoBehaviour { public static Editor instance; + public static bool subcategoriesChecked; public bool ready = false; void Start() { @@ -41,11 +42,8 @@ IEnumerator editorInit() foreach (PartCategorizer.Category C in PartCategorizer.Instance.filters) { customCategory cat; - if (Core.Instance.Categories.TryGetValue(c => c.categoryName == C.button.categoryName, out cat)) - { - if (cat.hasSubCategories() && (cat.behaviour == categoryTypeAndBehaviour.StockReplace || cat.behaviour == categoryTypeAndBehaviour.StockAdd)) - cat.initialise(); - } + if (Core.Instance.Categories.TryGetValue(c => c.categoryName == C.button.categoryName, out cat) && cat.type == customCategory.categoryType.Stock) + cat.initialise(); } // custom categories @@ -62,7 +60,7 @@ IEnumerator editorInit() // all FE categories foreach (customCategory c in Core.Instance.Categories) { - if (c.behaviour == categoryTypeAndBehaviour.None || c.behaviour == categoryTypeAndBehaviour.Engines) + if (c.type == customCategory.categoryType.New) c.initialise(); } @@ -82,13 +80,13 @@ IEnumerator editorInit() // this is to be used for altering subcategories in a category added by another mod foreach (customCategory c in Core.Instance.Categories) { - if (c.behaviour == categoryTypeAndBehaviour.ModAdd || c.behaviour == categoryTypeAndBehaviour.ModReplace) + if (c.type == customCategory.categoryType.Mod) c.initialise(); } // foreach (PartCategorizer.Category c in PartCategorizer.Instance.filters) - Core.Instance.namesAndIcons(c); + namesAndIcons(c); // Remove any category with no subCategories (causes major breakages if selected). for (int i = 0; i < 4; i++) @@ -112,7 +110,31 @@ IEnumerator editorInit() Core.Log("Refreshing parts list"); setSelectedCategory(); - ready = true; + subcategoriesChecked = ready = true; + } + + /// + /// In the editor, checks all subcategories of a category and edits their names/icons if required + /// + public void namesAndIcons(PartCategorizer.Category category) + { + HashSet toRemove = new HashSet(); + foreach (PartCategorizer.Category c in category.subcategories) + { + if (Core.Instance.removeSubCategory.Contains(c.button.categoryName)) + toRemove.Add(c.button.categoryName); + else + { + string tmp; + if (Core.Instance.Rename.TryGetValue(c.button.categoryName, out tmp)) // update the name first + c.button.categoryName = tmp; + + RUI.Icons.Selectable.Icon icon; + if (Core.tryGetIcon(tmp, out icon) || Core.tryGetIcon(c.button.categoryName, out icon)) // if there is an explicit setIcon for the subcategory or if the name matches an icon + c.button.SetIcon(icon); // change the icon + } + } + category.subcategories.RemoveAll(c => toRemove.Contains(c.button.categoryName)); } /// diff --git a/GameData/000_FilterExtensions Configs/Default/02_Engines.cfg b/GameData/000_FilterExtensions Configs/Default/02_Engines.cfg index a4ea04d5..d9019572 100644 --- a/GameData/000_FilterExtensions Configs/Default/02_Engines.cfg +++ b/GameData/000_FilterExtensions Configs/Default/02_Engines.cfg @@ -4,7 +4,7 @@ CATEGORY icon = EngineRocket colour = #FF90F090 all = true - type = engine + value = engine SUBCATEGORIES { diff --git a/GameData/000_FilterExtensions/FilterExtensions.dll b/GameData/000_FilterExtensions/FilterExtensions.dll index fc3186b3..41f3c8fd 100644 Binary files a/GameData/000_FilterExtensions/FilterExtensions.dll and b/GameData/000_FilterExtensions/FilterExtensions.dll differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop.dds index caa7cbf6..1f399637 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop_selected.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop_selected.dds index ed034b54..d6ddc408 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop_selected.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_Icons_Mods/ChopShop_selected.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS.dds index 4aad0d12..c2c721cb 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS_selected.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS_selected.dds index a03c6cc6..f9020cd8 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS_selected.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/RCS_selected.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts.dds index 195b7550..9abfcf4b 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts_selected.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts_selected.dds index b1249396..1a1f1bf4 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts_selected.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/ScienceParts_selected.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels.dds index f51a57c4..edd3ff89 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels.dds differ diff --git a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels_selected.dds b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels_selected.dds index a39f467a..6862ff51 100644 Binary files a/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels_selected.dds and b/GameData/000_FilterExtensions/Icons/Autoloaded_PatchIcon/Wheels_selected.dds differ