using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Server.Items; using Server.Json; using Server.Utilities; namespace Server.Commands { public static class Categorization { private static CategoryEntry m_RootItems, m_RootMobiles; private static readonly Type typeofItem = typeof(Item); private static readonly Type typeofMobile = typeof(Mobile); private static readonly Type typeofConstructible = typeof(ConstructibleAttribute); public static CategoryEntry Items { get { if (m_RootItems == null) { Load(); } return m_RootItems; } } public static CategoryEntry Mobiles { get { if (m_RootMobiles == null) { Load(); } return m_RootMobiles; } } public static void Initialize() { CommandSystem.Register("RebuildCategorization", AccessLevel.Administrator, RebuildCategorization_OnCommand); } [Usage("RebuildCategorization"), Description("Rebuilds the categorization data file used by the Add command.")] public static void RebuildCategorization_OnCommand(CommandEventArgs e) { var root = new CategoryEntry(null, "Add Menu", new[] { Items, Mobiles }); var ceList = new List(); ceList.AddRange(root.SubCategories); Export(ceList, "Data/objects.json"); e.Mobile.SendMessage("Categorization menu rebuilt."); } public static void Export(List ceList, string fileName) { var list = new List(); foreach (var ce in ceList) { RecurseExport(list, ce, null); } JsonConfig.Serialize(fileName, list); } public static void RecurseExport(List list, CategoryEntry ce, string category) { category = string.IsNullOrWhiteSpace(category) ? ce.Title : $"{category}{ce.Title}"; if (ce.Matched.Count > 0) { list.Add( new CAGJson { Category = category, Objects = ce.Matched.Select( cte => { if (cte.Object is Item item) { var itemID = item.ItemID; if (item is BaseAddon addon && addon.Components.Count == 1) { itemID = addon.Components[0].ItemID; } if (itemID > TileData.MaxItemValue) { itemID = 1; } int? hue = item.Hue & 0x7FFF; if ((hue & 0x4000) != 0) { hue = 0; } return new CAGObject { Type = cte.Type, ItemID = itemID, Hue = hue == 0 ? null : hue }; } if (cte.Object is Mobile m) { var itemID = ShrinkTable.Lookup(m, 1); int? hue = m.Hue & 0x7FFF; if ((hue & 0x4000) != 0) { hue = 0; } return new CAGObject { Type = cte.Type, ItemID = itemID, Hue = hue == 0 ? null : hue }; } throw new InvalidCastException( $"Categorization Type Entry: {cte.Type.Name} is not a valid type." ); } ) .ToArray() } ); } var subCats = new List(ce.SubCategories); subCats.Sort(new CategorySorter()); for (var i = 0; i < subCats.Count; i++) { var subCat = subCats[i]; RecurseExport(list, subCat, category); } } public static void Load() { var types = new List(); AddTypes(Core.Assembly, types); for (var i = 0; i < AssemblyHandler.Assemblies.Length; ++i) { AddTypes(AssemblyHandler.Assemblies[i], types); } m_RootItems = Load(types, "Data/items.cfg"); m_RootMobiles = Load(types, "Data/mobiles.cfg"); } private static CategoryEntry Load(List types, string config) { var lines = CategoryLine.Load(config); if (lines.Length <= 0) { return new CategoryEntry(); } var index = 0; var root = new CategoryEntry(null, lines, ref index); Fill(root, types); return root; } private static bool IsConstructible(Type type) { if (!type.IsSubclassOf(typeofItem) && !type.IsSubclassOf(typeofMobile)) { return false; } var ctor = type.GetConstructor(Type.EmptyTypes); return ctor?.IsDefined(typeofConstructible, false) == true; } private static void AddTypes(Assembly asm, List types) { var allTypes = asm.GetTypes(); for (var i = 0; i < allTypes.Length; ++i) { var type = allTypes[i]; if (type.IsAbstract) { continue; } if (IsConstructible(type)) { types.Add(type); } } } private static void Fill(CategoryEntry root, List list) { for (var i = 0; i < list.Count; ++i) { var type = list[i]; var match = GetDeepestMatch(root, type); if (match == null) { continue; } try { match.Matched.Add(new CategoryTypeEntry(type)); } catch { // ignored } } } private static CategoryEntry GetDeepestMatch(CategoryEntry root, Type type) { if (!root.IsMatch(type)) { return null; } for (var i = 0; i < root.SubCategories.Length; ++i) { var check = GetDeepestMatch(root.SubCategories[i], type); if (check != null) { return check; } } return root; } } public class CategorySorter : IComparer { public int Compare(CategoryEntry x, CategoryEntry y) => string.CompareOrdinal(x?.Title, y?.Title); } public class CategoryTypeSorter : IComparer { public int Compare(CategoryTypeEntry x, CategoryTypeEntry y) => string.CompareOrdinal(x?.Type.Name, y?.Type.Name); } public class CategoryTypeEntry { public CategoryTypeEntry(Type type) { Type = type; Object = type.CreateInstance(); } public Type Type { get; } public object Object { get; } } public class CategoryEntry { public CategoryEntry(CategoryEntry parent = null, string title = "(empty)", CategoryEntry[] subCats = null) { Parent = parent; Title = title; SubCategories = subCats ?? Array.Empty(); Matches = Array.Empty(); Matched = new List(); } public CategoryEntry(CategoryEntry parent, CategoryLine[] lines, ref int index) { Parent = parent; var text = lines[index].Text; var start = text.IndexOfOrdinal('('); if (start < 0) { throw new FormatException($"Input string not correctly formatted ('{text}')"); } Title = text.Substring(0, start).Trim(); var end = text.IndexOf(')', ++start); if (end < start) { throw new FormatException($"Input string not correctly formatted ('{text}')"); } text = text.Substring(start, end - start); var split = text.Split(';'); var list = new List(); for (var i = 0; i < split.Length; ++i) { var type = AssemblyHandler.FindFirstTypeForName(split[i].Trim()); if (type == null) { Console.WriteLine("Match type not found ('{0}')", split[i].Trim()); } else { list.Add(type); } } Matches = list.ToArray(); list.Clear(); var ourIndentation = lines[index].Indentation; ++index; var entryList = new List(); while (index < lines.Length && lines[index].Indentation > ourIndentation) { entryList.Add(new CategoryEntry(this, lines, ref index)); } SubCategories = entryList.ToArray(); entryList.Clear(); Matched = new List(); } public string Title { get; } public Type[] Matches { get; } public CategoryEntry Parent { get; } public CategoryEntry[] SubCategories { get; } public List Matched { get; } public bool IsMatch(Type type) { var isMatch = false; for (var i = 0; !isMatch && i < Matches.Length; ++i) { isMatch = type == Matches[i] || type.IsSubclassOf(Matches[i]); } return isMatch; } } public class CategoryLine { public CategoryLine(string input) { int index; for (index = 0; index < input.Length; ++index) { if (char.IsLetter(input, index)) { break; } } if (index >= input.Length) { throw new FormatException($"Input string not correctly formatted ('{input}')"); } Indentation = index; Text = input.Substring(index); } public int Indentation { get; } public string Text { get; } public static CategoryLine[] Load(string path) { var list = new List(); if (File.Exists(path)) { using var ip = new StreamReader(path); string line; while ((line = ip.ReadLine()) != null) { list.Add(new CategoryLine(line)); } } return list.ToArray(); } } }