using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using Server.Commands.Generic; using Server.Engines.BulkOrders; using Server.Items; using Server.Network; namespace Server.Commands { public class Docs { private static Dictionary m_Types; private static Dictionary> m_Namespaces; public static void Initialize() { CommandSystem.Register("DocGen", AccessLevel.Administrator, DocGen_OnCommand); } [Usage("DocGen")] [Description("Generates RunUO documentation.")] private static void DocGen_OnCommand(CommandEventArgs e) { World.Broadcast(0x35, true, "Documentation is being generated, please wait."); Console.WriteLine("Documentation is being generated, please wait."); NetState.Pause(); DateTime startTime = DateTime.UtcNow; bool generated = Document(); DateTime endTime = DateTime.UtcNow; NetState.Resume(); if (generated) { World.Broadcast(0x35, true, "Documentation has been completed. The entire process took {0:F1} seconds.", (endTime - startTime).TotalSeconds); Console.WriteLine("Documentation complete."); } else { World.Broadcast(0x35, true, "Docmentation failed: Documentation directories are locked and in use. Please close all open files and directories and try again."); Console.WriteLine("Documentation failed."); } } private static void LoadTypes(Assembly a, Assembly[] asms) { Type[] types = a.GetTypes(); for (int i = 0; i < types.Length; ++i) { Type type = types[i]; string nspace = type.Namespace; if (nspace == null || type.IsSpecialName) continue; TypeInfo info = new TypeInfo(type); m_Types[type] = info; if (!m_Namespaces.TryGetValue(nspace, out List nspaces)) m_Namespaces[nspace] = nspaces = new List(); nspaces.Add(info); Type baseType = info.m_BaseType; if (baseType != null && InAssemblies(baseType, asms)) { m_Types.TryGetValue(baseType, out TypeInfo baseInfo); if (baseInfo == null) m_Types[baseType] = baseInfo = new TypeInfo(baseType); if (baseInfo.m_Derived == null) baseInfo.m_Derived = new List(); baseInfo.m_Derived.Add(info); } Type decType = info.m_Declaring; if (decType != null) { m_Types.TryGetValue(decType, out TypeInfo decInfo); if (decInfo == null) m_Types[decType] = decInfo = new TypeInfo(decType); if (decInfo.m_Nested == null) decInfo.m_Nested = new List(); decInfo.m_Nested.Add(info); } for (int j = 0; j < info.m_Interfaces.Length; ++j) { Type iface = info.m_Interfaces[j]; if (!InAssemblies(iface, asms)) continue; m_Types.TryGetValue(iface, out TypeInfo ifaceInfo); if (ifaceInfo == null) m_Types[iface] = ifaceInfo = new TypeInfo(iface); if (ifaceInfo.m_Derived == null) ifaceInfo.m_Derived = new List(); ifaceInfo.m_Derived.Add(info); } } } private static bool InAssemblies(Type t, Assembly[] asms) { Assembly a = t.Assembly; for (int i = 0; i < asms.Length; ++i) if (a == asms[i]) return true; return false; } private static void DocumentLoadedTypes() { using StreamWriter indexHtml = GetWriter("docs/", "overview.html"); indexHtml.WriteLine(""); indexHtml.WriteLine(" "); indexHtml.WriteLine(" RunUO Documentation - Class Overview"); indexHtml.WriteLine(" "); indexHtml.WriteLine( " "); indexHtml.WriteLine("

Back to the index

"); indexHtml.WriteLine("

Namespaces

"); SortedList> nspaces = new SortedList>(m_Namespaces); foreach (KeyValuePair> kvp in nspaces) { kvp.Value.Sort(new TypeComparer()); SaveNamespace(kvp.Key, kvp.Value, indexHtml); } indexHtml.WriteLine(" "); indexHtml.WriteLine(""); } private static void SaveNamespace(string name, List types, StreamWriter indexHtml) { string fileName = GetFileName("docs/namespaces/", name, ".html"); indexHtml.WriteLine(" {1}
", fileName, name); using StreamWriter nsHtml = GetWriter("docs/namespaces/", fileName); nsHtml.WriteLine(""); nsHtml.WriteLine(" "); nsHtml.WriteLine(" RunUO Documentation - Class Overview - {0}", name); nsHtml.WriteLine(" "); nsHtml.WriteLine( " "); nsHtml.WriteLine("

Back to the namespace index

"); nsHtml.WriteLine("

{0}

", name); for (int i = 0; i < types.Count; ++i) SaveType(types[i], nsHtml, fileName, name); nsHtml.WriteLine(" "); nsHtml.WriteLine(""); } private static void SaveType(TypeInfo info, StreamWriter nsHtml, string nsFileName, string nsName) { if (info.m_Declaring == null) nsHtml.WriteLine($" {info.LinkName("../types/")}
"); using StreamWriter typeHtml = GetWriter(info.FileName); typeHtml.WriteLine(""); typeHtml.WriteLine(" "); typeHtml.WriteLine(" RunUO Documentation - Class Overview - {0}", info.TypeName); typeHtml.WriteLine(" "); typeHtml.WriteLine( " "); typeHtml.WriteLine("

Back to {1}

", nsFileName, nsName); if (info.m_Type.IsEnum) WriteEnum(info, typeHtml); else WriteType(info, typeHtml); typeHtml.WriteLine(" "); typeHtml.WriteLine(""); } public static void FormatGeneric(Type type, out string typeName, out string fileName, out string linkName) { string name = null; string fnam = null; string link = null; if (type.IsGenericType) { int index = type.Name.IndexOf('`'); if (index > 0) { string rootType = type.Name.Substring(0, index); StringBuilder nameBuilder = new StringBuilder(rootType); StringBuilder fnamBuilder = new StringBuilder($"docs/types/{SanitizeType(rootType)}"); StringBuilder linkBuilder; linkBuilder = DontLink(type) ? new StringBuilder($"{rootType}") : new StringBuilder($"{rootType}"); nameBuilder.Append("<"); fnamBuilder.Append("-"); linkBuilder.Append("<"); Type[] typeArguments = type.GetGenericArguments(); for (int i = 0; i < typeArguments.Length; i++) { if (i != 0) { nameBuilder.Append(','); fnamBuilder.Append(','); linkBuilder.Append(','); } string sanitizedName = SanitizeType(typeArguments[i].Name); string aliasedName = AliasForName(sanitizedName); nameBuilder.Append(sanitizedName); fnamBuilder.Append("T"); if (DontLink(typeArguments[i])) //if ( DontLink( typeArguments[i].Name ) ) linkBuilder.Append($"{aliasedName}"); else linkBuilder.Append( $"{aliasedName}"); } nameBuilder.Append(">"); fnamBuilder.Append("-"); linkBuilder.Append(">"); name = nameBuilder.ToString(); fnam = fnamBuilder.ToString(); link = linkBuilder.ToString(); } } typeName = name ?? type.Name; if (fnam == null) fileName = $"docs/types/{SanitizeType(type.Name)}.html"; else fileName = $"{fnam}.html"; if (link == null) { if (DontLink(type)) //if ( DontLink( type.Name ) ) linkName = $"{SanitizeType(type.Name)}"; else linkName = $"{SanitizeType(type.Name)}"; } else { linkName = link; } //Console.WriteLine( typeName+":"+fileName+":"+linkName ); } public static string SanitizeType(string name) { bool anonymousType = name.Contains("<"); StringBuilder sb = new StringBuilder(name); for (int i = 0; i < ReplaceChars.Length; ++i) sb.Replace(ReplaceChars[i], '-'); if (anonymousType) return $"(Anonymous-Type){sb}"; return sb.ToString(); } public static string AliasForName(string name) { for (int i = 0; i < m_AliasLength; ++i) if (m_Aliases[i, 0] == name) return m_Aliases[i, 1]; return name; } /* // For stuff we don't want to links to private static string[] m_DontLink = new string[] { "List", "Stack", "Queue", "Dictionary", "LinkedList", "SortedList", "SortedDictionary", "IComparable", "IComparer", "ICloneable", "Type" }; public static bool DontLink( string name ) { foreach( string dontLink in m_DontLink ) if ( dontLink == name ) return true; return false; } */ public static bool DontLink(Type type) { // MONO: type.Namespace is null/empty for generic arguments if (type.Name == "T" || string.IsNullOrEmpty(type.Namespace) || m_Namespaces == null) return true; if (type.Namespace.StartsWith("Server")) return false; return !m_Namespaces.ContainsKey(type.Namespace); } private class MemberComparer : IComparer { public int Compare(object x, object y) { if (x == y) return 0; ConstructorInfo aCtor = x as ConstructorInfo; ConstructorInfo bCtor = y as ConstructorInfo; PropertyInfo aProp = x as PropertyInfo; PropertyInfo bProp = y as PropertyInfo; MethodInfo aMethod = x as MethodInfo; MethodInfo bMethod = y as MethodInfo; bool aStatic = GetStaticFor(aCtor, aProp, aMethod); bool bStatic = GetStaticFor(bCtor, bProp, bMethod); if (aStatic && !bStatic) return -1; if (!aStatic && bStatic) return 1; int v = 0; if (aCtor != null) { if (bCtor == null) v = -1; } else if (bCtor != null) { v = 1; } else if (aProp != null) { if (bProp == null) v = -1; } else if (bProp != null) { v = 1; } if (v == 0) v = GetNameFrom(aCtor, aProp, aMethod).CompareTo(GetNameFrom(bCtor, bProp, bMethod)); if (v == 0 && aCtor != null && bCtor != null) v = aCtor.GetParameters().Length.CompareTo(bCtor.GetParameters().Length); else if (v == 0 && aMethod != null && bMethod != null) v = aMethod.GetParameters().Length.CompareTo(bMethod.GetParameters().Length); return v; } private bool GetStaticFor(ConstructorInfo ctor, PropertyInfo prop, MethodInfo method) { if (ctor != null) return ctor.IsStatic; if (method != null) return method.IsStatic; if (prop != null) { MethodInfo getMethod = prop.GetGetMethod(); MethodInfo setMethod = prop.GetGetMethod(); return getMethod?.IsStatic == true || setMethod?.IsStatic == true; } return false; } private string GetNameFrom(ConstructorInfo ctor, PropertyInfo prop, MethodInfo method) => ctor?.DeclaringType?.Name ?? prop?.Name ?? method?.Name ?? ""; } private class TypeComparer : IComparer { public int Compare(TypeInfo x, TypeInfo y) => x == null && y == null ? 0 : x == null ? -1 : y == null ? 1 : x.TypeName.CompareTo(y.TypeName); } private class TypeInfo { public List m_Derived, m_Nested; private string m_FileName, m_TypeName, m_LinkName; public Type[] m_Interfaces; public Type m_Type, m_BaseType, m_Declaring; public TypeInfo(Type type) { m_Type = type; m_BaseType = type.BaseType; m_Declaring = type.DeclaringType; m_Interfaces = type.GetInterfaces(); FormatGeneric(m_Type, out m_TypeName, out m_FileName, out m_LinkName); } public string FileName => m_FileName; public string TypeName => m_TypeName; public string LinkName(string dirRoot) => m_LinkName.Replace("@directory@", dirRoot); } #region FileSystem private static readonly char[] ReplaceChars = "<>".ToCharArray(); public static string GetFileName(string root, string name, string ext) { if (name.IndexOfAny(ReplaceChars) >= 0) { StringBuilder sb = new StringBuilder(name); for (int i = 0; i < ReplaceChars.Length; ++i) sb.Replace(ReplaceChars[i], '-'); name = sb.ToString(); } int index = 0; string file = string.Concat(name, ext); while (File.Exists(Path.Combine(root, file))) file = string.Concat(name, ++index, ext); return file; } private static string m_RootDirectory = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]); private static void EnsureDirectory(string path) { path = Path.Combine(m_RootDirectory, path); if (!Directory.Exists(path)) Directory.CreateDirectory(path); } private static void DeleteDirectory(string path) { path = Path.Combine(m_RootDirectory, path); if (Directory.Exists(path)) Directory.Delete(path, true); } private static StreamWriter GetWriter(string root, string name) => new StreamWriter(Path.Combine(Path.Combine(m_RootDirectory, root), name)); private static StreamWriter GetWriter(string path) => new StreamWriter(Path.Combine(m_RootDirectory, path)); #endregion #region GetPair private static string[,] m_Aliases = { { "System.Object", "object" }, { "System.String", "string" }, { "System.Boolean", "bool" }, { "System.Byte", "byte" }, { "System.SByte", "sbyte" }, { "System.Int16", "short" }, { "System.UInt16", "ushort" }, { "System.Int32", "int" }, { "System.UInt32", "uint" }, { "System.Int64", "long" }, { "System.UInt64", "ulong" }, { "System.Single", "float" }, { "System.Double", "double" }, { "System.Decimal", "decimal" }, { "System.Char", "char" }, { "System.Void", "void" } }; private static int m_AliasLength = m_Aliases.GetLength(0); public static string GetPair(Type varType, string name, bool ignoreRef) { string prepend = ""; StringBuilder append = new StringBuilder(); Type realType = varType; if (varType.IsByRef) { if (!ignoreRef) prepend = RefString; realType = varType.GetElementType(); } if (realType?.IsPointer == true) { if (realType.IsArray) { append.Append('*'); do { append.Append('['); for (int i = 1; i < realType.GetArrayRank(); ++i) append.Append(','); append.Append(']'); realType = realType.GetElementType(); } while (realType?.IsArray == true); append.Append(' '); } else { realType = realType.GetElementType(); append.Append(" *"); } } else if (realType?.IsArray == true) { do { append.Append('['); for (int i = 1; i < realType.GetArrayRank(); ++i) append.Append(','); append.Append(']'); realType = realType.GetElementType(); } while (realType?.IsArray == true); append.Append(' '); } else { append.Append(' '); } string fullName = realType?.FullName ?? "(-null-)"; string aliased = null; // = realType.Name; if (realType != null && m_Types.TryGetValue(realType, out TypeInfo info)) { aliased = $"{info.LinkName(null)}"; } else { if (realType?.IsGenericType == true) { FormatGeneric(realType, out _, out _, out string linkName); aliased = linkName.Replace("@directory@", null); } else { for (int i = 0; i < m_AliasLength; ++i) if (m_Aliases[i, 0] == fullName) { aliased = m_Aliases[i, 1]; break; } } if (aliased == null) aliased = realType?.Name ?? ""; } return string.Concat(prepend, aliased, append, name); } #endregion #region Root documentation private static bool Document() { try { DeleteDirectory("docs/"); } catch { return false; } EnsureDirectory("docs/"); EnsureDirectory("docs/namespaces/"); EnsureDirectory("docs/types/"); EnsureDirectory("docs/bods/"); GenerateStyles(); GenerateIndex(); DocumentCommands(); DocumentKeywords(); DocumentBodies(); DocumentBulkOrders(); m_Types = new Dictionary(); m_Namespaces = new Dictionary>(); List assemblies = new List { Core.Assembly }; foreach (Assembly asm in AssemblyHandler.Assemblies) assemblies.Add(asm); Assembly[] asms = assemblies.ToArray(); for (int i = 0; i < asms.Length; ++i) LoadTypes(asms[i], asms); DocumentLoadedTypes(); DocumentConstructibleObjects(); return true; } private static void AddIndexLink(StreamWriter html, string filePath, string label, string desc) { html.WriteLine("

{2}

", filePath, desc, label); } private static void GenerateStyles() { using StreamWriter css = GetWriter("docs/", "styles.css"); css.WriteLine("body { background-color: #FFFFFF; font-family: verdana, arial; font-size: 11px; }"); css.WriteLine("a { color: #28435E; }"); css.WriteLine("a:hover { color: #4878A9; }"); css.WriteLine("td.header { background-color: #9696AA; font-weight: bold; font-size: 12px; }"); css.WriteLine("td.lentry { background-color: #D7D7EB; width: 10%; }"); css.WriteLine("td.rentry { background-color: #FFFFFF; width: 90%; }"); css.WriteLine("td.entry { background-color: #FFFFFF; }"); css.WriteLine("td { font-size: 11px; }"); css.WriteLine(".tbl-border { background-color: #46465A; }"); css.WriteLine("td.ir {{ background-color: #{0:X6}; }}", Iron); css.WriteLine("td.du {{ background-color: #{0:X6}; }}", DullCopper); css.WriteLine("td.sh {{ background-color: #{0:X6}; }}", ShadowIron); css.WriteLine("td.co {{ background-color: #{0:X6}; }}", Copper); css.WriteLine("td.br {{ background-color: #{0:X6}; }}", Bronze); css.WriteLine("td.go {{ background-color: #{0:X6}; }}", Gold); css.WriteLine("td.ag {{ background-color: #{0:X6}; }}", Agapite); css.WriteLine("td.ve {{ background-color: #{0:X6}; }}", Verite); css.WriteLine("td.va {{ background-color: #{0:X6}; }}", Valorite); css.WriteLine("td.cl {{ background-color: #{0:X6}; }}", Cloth); css.WriteLine("td.pl {{ background-color: #{0:X6}; }}", Plain); css.WriteLine("td.sp {{ background-color: #{0:X6}; }}", Core.AOS ? SpinedAOS : SpinedLBR); css.WriteLine("td.ho {{ background-color: #{0:X6}; }}", Core.AOS ? HornedAOS : HornedLBR); css.WriteLine("td.ba {{ background-color: #{0:X6}; }}", Core.AOS ? BarbedAOS : BarbedLBR); } private static void GenerateIndex() { using StreamWriter html = GetWriter("docs/", "index.html"); html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Index"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); AddIndexLink(html, "commands.html", "Commands", "Every available command. This contains command name, usage, aliases, and description."); AddIndexLink(html, "objects.html", "Constructible Objects", "Every constructible item or npc. This contains object name and usage. Hover mouse over parameters to see type description."); AddIndexLink(html, "keywords.html", "Speech Keywords", "Lists speech keyword numbers and associated match patterns. These are used in some scripts for multi-language matching of client speech."); AddIndexLink(html, "bodies.html", "Body List", "Every usable body number and name. Table is generated from a UO:3D client datafile. If you do not have UO:3D installed, this may be blank."); AddIndexLink(html, "overview.html", "Class Overview", "Scripting reference. Contains every class type and contained methods in the core and scripts."); AddIndexLink(html, "bods/bod_smith_rewards.html", "Bulk Order Rewards: Smithing", "Reference table for large and small smithing bulk order deed rewards."); AddIndexLink(html, "bods/bod_tailor_rewards.html", "Bulk Order Rewards: Tailoring", "Reference table for large and small tailoring bulk order deed rewards."); html.WriteLine(" "); html.WriteLine(""); } #endregion #region BODs private const int Iron = 0xCCCCDD; private const int DullCopper = 0xAAAAAA; private const int ShadowIron = 0x777799; private const int Copper = 0xDDCC99; private const int Bronze = 0xAA8866; private const int Gold = 0xDDCC55; private const int Agapite = 0xDDAAAA; private const int Verite = 0x99CC77; private const int Valorite = 0x88AABB; private const int Cloth = 0xDDDDDD; private const int Plain = 0xCCAA88; private const int SpinedAOS = 0x99BBBB; private const int HornedAOS = 0xCC8888; private const int BarbedAOS = 0xAABBAA; private const int SpinedLBR = 0xAA8833; private const int HornedLBR = 0xBBBBAA; private const int BarbedLBR = 0xCCAA88; private static void DocumentBulkOrders() { using (StreamWriter html = GetWriter("docs/bods/", "bod_smith_rewards.html")) { html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Bulk Orders - Smith Rewards"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); SmallBOD sbod = new SmallSmithBOD(); sbod.Type = typeof(Katana); sbod.Material = BulkMaterialType.None; sbod.AmountMax = 10; WriteSmithBODHeader(html, "(Small) Weapons"); sbod.RequireExceptional = false; DocumentSmithBOD(html, sbod.ComputeRewards(true), "10, 15, 20: Normal", sbod.Material); sbod.RequireExceptional = true; DocumentSmithBOD(html, sbod.ComputeRewards(true), "10, 15, 20: Exceptional", sbod.Material); WriteSmithBODFooter(html); html.WriteLine("

"); html.WriteLine("

"); sbod.Type = typeof(PlateArms); WriteSmithBODHeader(html, "(Small) Armor: Normal"); sbod.RequireExceptional = false; for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Valorite; ++mat) { sbod.Material = mat; sbod.AmountMax = 10; DocumentSmithBOD(html, sbod.ComputeRewards(true), "10, 15, 20", sbod.Material); } WriteSmithBODFooter(html); html.WriteLine("

"); WriteSmithBODHeader(html, "(Small) Armor: Exceptional"); sbod.RequireExceptional = true; for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Valorite; ++mat) { sbod.Material = mat; for (int amt = 15; amt <= 20; amt += 5) { sbod.AmountMax = amt; DocumentSmithBOD(html, sbod.ComputeRewards(true), amt == 20 ? "20" : "10, 15", sbod.Material); } } WriteSmithBODFooter(html); html.WriteLine("

"); html.WriteLine("

"); sbod.Delete(); WriteSmithLBOD(html, "Ringmail", LargeBulkEntry.LargeRing); WriteSmithLBOD(html, "Chainmail", LargeBulkEntry.LargeChain); WriteSmithLBOD(html, "Platemail", LargeBulkEntry.LargePlate); html.WriteLine(" "); html.WriteLine(""); } using (StreamWriter html = GetWriter("docs/bods/", "bod_tailor_rewards.html")) { html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Bulk Orders - Tailor Rewards"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); SmallBOD sbod = new SmallTailorBOD(); WriteTailorBODHeader(html, "Small Bulk Order"); html.WriteLine(" "); html.WriteLine(" Regular: 10, 15"); html.WriteLine(" "); sbod.AmountMax = 10; sbod.RequireExceptional = false; sbod.Type = typeof(SkullCap); sbod.Material = BulkMaterialType.None; DocumentTailorBOD(html, sbod.ComputeRewards(true), "10, 15", sbod.Material, sbod.Type); sbod.Type = typeof(LeatherCap); for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Barbed; ++mat) { if (mat >= BulkMaterialType.DullCopper && mat <= BulkMaterialType.Valorite) continue; sbod.Material = mat; DocumentTailorBOD(html, sbod.ComputeRewards(true), "10, 15", sbod.Material, sbod.Type); } html.WriteLine(" "); html.WriteLine(" Regular: 20"); html.WriteLine(" "); sbod.AmountMax = 20; sbod.RequireExceptional = false; sbod.Type = typeof(SkullCap); sbod.Material = BulkMaterialType.None; DocumentTailorBOD(html, sbod.ComputeRewards(true), "20", sbod.Material, sbod.Type); sbod.Type = typeof(LeatherCap); for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Barbed; ++mat) { if (mat >= BulkMaterialType.DullCopper && mat <= BulkMaterialType.Valorite) continue; sbod.Material = mat; DocumentTailorBOD(html, sbod.ComputeRewards(true), "20", sbod.Material, sbod.Type); } html.WriteLine(" "); html.WriteLine( " Exceptional: 10, 15"); html.WriteLine(" "); sbod.AmountMax = 10; sbod.RequireExceptional = true; sbod.Type = typeof(SkullCap); sbod.Material = BulkMaterialType.None; DocumentTailorBOD(html, sbod.ComputeRewards(true), "10, 15", sbod.Material, sbod.Type); sbod.Type = typeof(LeatherCap); for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Barbed; ++mat) { if (mat >= BulkMaterialType.DullCopper && mat <= BulkMaterialType.Valorite) continue; sbod.Material = mat; DocumentTailorBOD(html, sbod.ComputeRewards(true), "10, 15", sbod.Material, sbod.Type); } html.WriteLine(" "); html.WriteLine(" Exceptional: 20"); html.WriteLine(" "); sbod.AmountMax = 20; sbod.RequireExceptional = true; sbod.Type = typeof(SkullCap); sbod.Material = BulkMaterialType.None; DocumentTailorBOD(html, sbod.ComputeRewards(true), "20", sbod.Material, sbod.Type); sbod.Type = typeof(LeatherCap); for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Barbed; ++mat) { if (mat >= BulkMaterialType.DullCopper && mat <= BulkMaterialType.Valorite) continue; sbod.Material = mat; DocumentTailorBOD(html, sbod.ComputeRewards(true), "20", sbod.Material, sbod.Type); } WriteTailorBODFooter(html); html.WriteLine("

"); html.WriteLine("

"); sbod.Delete(); WriteTailorLBOD(html, "Large Bulk Order: 4-part", LargeBulkEntry.Gypsy, true, true); WriteTailorLBOD(html, "Large Bulk Order: 5-part", LargeBulkEntry.TownCrier, true, true); WriteTailorLBOD(html, "Large Bulk Order: 6-part", LargeBulkEntry.MaleLeatherSet, false, true); html.WriteLine(" "); html.WriteLine(""); } } #region Tailor Bods private static void WriteTailorLBOD(StreamWriter html, string name, SmallBulkEntry[] entries, bool expandCloth, bool expandPlain) { WriteTailorBODHeader(html, name); LargeBOD lbod = new LargeTailorBOD(); lbod.Entries = LargeBulkEntry.ConvertEntries(lbod, entries); Type type = entries[0].Type; bool showCloth = !(type.IsSubclassOf(typeof(BaseArmor)) || type.IsSubclassOf(typeof(BaseShoes))); html.WriteLine(" "); html.WriteLine(" Regular"); html.WriteLine(" "); lbod.RequireExceptional = false; lbod.AmountMax = 10; if (showCloth) { lbod.Material = BulkMaterialType.None; if (expandCloth) { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15", lbod.Material, type); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, type); } else { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, type); } } lbod.Material = BulkMaterialType.None; if (expandPlain) { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, typeof(LeatherCap)); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, typeof(LeatherCap)); } else { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, typeof(LeatherCap)); } for (BulkMaterialType mat = BulkMaterialType.Spined; mat <= BulkMaterialType.Barbed; ++mat) { lbod.Material = mat; lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15", lbod.Material, type); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, type); } html.WriteLine(" "); html.WriteLine(" Exceptional"); html.WriteLine(" "); lbod.RequireExceptional = true; lbod.AmountMax = 10; if (showCloth) { lbod.Material = BulkMaterialType.None; if (expandCloth) { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15", lbod.Material, type); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, type); } else { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, type); } } lbod.Material = BulkMaterialType.None; if (expandPlain) { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, typeof(LeatherCap)); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, typeof(LeatherCap)); } else { lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material, typeof(LeatherCap)); } for (BulkMaterialType mat = BulkMaterialType.Spined; mat <= BulkMaterialType.Barbed; ++mat) { lbod.Material = mat; lbod.AmountMax = 10; DocumentTailorBOD(html, lbod.ComputeRewards(true), "10, 15", lbod.Material, type); lbod.AmountMax = 20; DocumentTailorBOD(html, lbod.ComputeRewards(true), "20", lbod.Material, type); } WriteTailorBODFooter(html); html.WriteLine("

"); html.WriteLine("

"); } private static void WriteTailorBODHeader(StreamWriter html, string title) { html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" ", title); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); } private static void WriteTailorBODFooter(StreamWriter html) { html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("
{0}
\"Colored
\"Colored
\"Colored
\"Colored
\"Colored
\"Colored
Power Scrolls
\"Small
\"Medium
\"Light
\"Dark
\"Brown
\"Polar
\"Clothing
Runic Kits
+5
+10
+15
+20
\"Runic
\"Runic
\"Runic
 
\"Colored
\"Colored
\"Colored
\"Colored
\"Colored
\"Colored
+5
+10
+15
+20
\"Small
\"Medium
\"Light
\"Dark
\"Brown
\"Polar
\"Clothing
\"Runic
\"Runic
\"Runic
Power Scrolls
Runic Kits
"); } private static void DocumentTailorBOD(StreamWriter html, List items, string amt, BulkMaterialType material, Type type) { bool[] rewards = new bool[20]; for (int i = 0; i < items.Count; ++i) { Item item = items[i].Construct(); if (item is Sandals) { rewards[5] = true; } else if (item is SmallStretchedHideEastDeed || item is SmallStretchedHideSouthDeed) { rewards[10] = rewards[11] = true; } else if (item is MediumStretchedHideEastDeed || item is MediumStretchedHideSouthDeed) { rewards[10] = rewards[11] = true; } else if (item is LightFlowerTapestryEastDeed || item is LightFlowerTapestrySouthDeed) { rewards[12] = rewards[13] = true; } else if (item is DarkFlowerTapestryEastDeed || item is DarkFlowerTapestrySouthDeed) { rewards[12] = rewards[13] = true; } else if (item is BrownBearRugEastDeed || item is BrownBearRugSouthDeed) { rewards[14] = rewards[15] = true; } else if (item is PolarBearRugEastDeed || item is PolarBearRugSouthDeed) { rewards[14] = rewards[15] = true; } else if (item is ClothingBlessDeed) { rewards[16] = true; } else if (item is PowerScroll ps) { if (ps.Value == 105.0) rewards[6] = true; else if (ps.Value == 110.0) rewards[7] = true; else if (ps.Value == 115.0) rewards[8] = true; else if (ps.Value == 120.0) rewards[9] = true; } else if (item is UncutCloth) { if (item.Hue == 0x483 || item.Hue == 0x48C || item.Hue == 0x488 || item.Hue == 0x48A) rewards[0] = true; else if (item.Hue == 0x495 || item.Hue == 0x48B || item.Hue == 0x486 || item.Hue == 0x485) rewards[1] = true; else if (item.Hue == 0x48D || item.Hue == 0x490 || item.Hue == 0x48E || item.Hue == 0x491) rewards[2] = true; else if (item.Hue == 0x48F || item.Hue == 0x494 || item.Hue == 0x484 || item.Hue == 0x497) rewards[3] = true; else rewards[4] = true; } else if (item is RunicSewingKit rkit) { rewards[16 + CraftResources.GetIndex(rkit.Resource)] = true; } item.Delete(); } string style = null; string name = null; switch (material) { case BulkMaterialType.None: { if (type.IsSubclassOf(typeof(BaseArmor)) || type.IsSubclassOf(typeof(BaseShoes))) { style = "pl"; name = "Plain"; } else { style = "cl"; name = "Cloth"; } break; } case BulkMaterialType.Spined: style = "sp"; name = "Spined"; break; case BulkMaterialType.Horned: style = "ho"; name = "Horned"; break; case BulkMaterialType.Barbed: style = "ba"; name = "Barbed"; break; } html.WriteLine(" "); html.WriteLine("  - {0} {1}", name, amt); int index = 0; while (index < 20) if (rewards[index]) { html.WriteLine("
X
", style); ++index; } else { int count = 0; while (index < 20 && !rewards[index]) { ++count; ++index; if (index == 5 || index == 6 || index == 10 || index == 17) break; } html.WriteLine("  ", count * 25, count == 1 ? "" : $" colspan=\"{count}\""); } html.WriteLine(" "); } #endregion #region Smith Bods private static void WriteSmithLBOD(StreamWriter html, string name, SmallBulkEntry[] entries) { LargeBOD lbod = new LargeSmithBOD(); lbod.Entries = LargeBulkEntry.ConvertEntries(lbod, entries); WriteSmithBODHeader(html, $"(Large) {name}: Normal"); lbod.RequireExceptional = false; for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Valorite; ++mat) { lbod.Material = mat; lbod.AmountMax = 10; DocumentSmithBOD(html, lbod.ComputeRewards(true), "10, 15, 20", lbod.Material); } WriteSmithBODFooter(html); html.WriteLine("

"); WriteSmithBODHeader(html, $"(Large) {name}: Exceptional"); lbod.RequireExceptional = true; for (BulkMaterialType mat = BulkMaterialType.None; mat <= BulkMaterialType.Valorite; ++mat) { lbod.Material = mat; for (int amt = 15; amt <= 20; amt += 5) { lbod.AmountMax = amt; DocumentSmithBOD(html, lbod.ComputeRewards(true), amt == 20 ? "20" : "10, 15", lbod.Material); } } WriteSmithBODFooter(html); html.WriteLine("

"); html.WriteLine("

"); } private static void WriteSmithBODHeader(StreamWriter html, string title) { html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" ", title); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); } private static void WriteSmithBODFooter(StreamWriter html) { html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine( " "); html.WriteLine(" "); html.WriteLine("
{0}
\"Sturdy
Gloves
\"Gargoyles
\"Prospectors
\"Powder
\"Colored
Power Scrolls
Runic Hammers
Ancient Hammers
+1
+3
+5
+5
+10
+15
+20
Du
Sh
Co
Br
Go
Ag
Ve
Va
+10
+15
+30
+60
 
\"Sturdy
+1
 
+3
 
+5
 
\"Gargoyles
\"Prospectors
\"Powder
\"Colored
+5
+10
+15
+20
Du
Sh
Co
Br
Go
Ag
Ve
Va
+10
+15
+30
+60
Gloves
Power Scrolls
Runic Hammers
Ancient Hammers
"); } private static void DocumentSmithBOD(StreamWriter html, List items, string amt, BulkMaterialType material) { bool[] rewards = new bool[24]; for (int i = 0; i < items.Count; ++i) { Item item = items[i].Construct(); if (item is SturdyPickaxe || item is SturdyShovel) { rewards[0] = true; } else if (item is LeatherGlovesOfMining) { rewards[1] = true; } else if (item is StuddedGlovesOfMining) { rewards[2] = true; } else if (item is RingmailGlovesOfMining) { rewards[3] = true; } else if (item is GargoylesPickaxe) { rewards[4] = true; } else if (item is ProspectorsTool) { rewards[5] = true; } else if (item is PowderOfTemperament) { rewards[6] = true; } else if (item is ColoredAnvil) { rewards[7] = true; } else if (item is PowerScroll ps) { if (ps.Value == 105.0) rewards[8] = true; else if (ps.Value == 110.0) rewards[9] = true; else if (ps.Value == 115.0) rewards[10] = true; else if (ps.Value == 120.0) rewards[11] = true; } else if (item is RunicHammer rh) { rewards[11 + CraftResources.GetIndex(rh.Resource)] = true; } else if (item is AncientSmithyHammer ash) { if (ash.Bonus == 10) rewards[20] = true; else if (ash.Bonus == 15) rewards[21] = true; else if (ash.Bonus == 30) rewards[22] = true; else if (ash.Bonus == 60) rewards[23] = true; } item.Delete(); } string style = null; string name = null; switch (material) { case BulkMaterialType.None: style = "ir"; name = "Iron"; break; case BulkMaterialType.DullCopper: style = "du"; name = "Dull Copper"; break; case BulkMaterialType.ShadowIron: style = "sh"; name = "Shadow Iron"; break; case BulkMaterialType.Copper: style = "co"; name = "Copper"; break; case BulkMaterialType.Bronze: style = "br"; name = "Bronze"; break; case BulkMaterialType.Gold: style = "go"; name = "Gold"; break; case BulkMaterialType.Agapite: style = "ag"; name = "Agapite"; break; case BulkMaterialType.Verite: style = "ve"; name = "Verite"; break; case BulkMaterialType.Valorite: style = "va"; name = "Valorite"; break; } html.WriteLine(" "); html.WriteLine(" {0} {1}", name, amt); int index = 0; while (index < 24) if (rewards[index]) { html.WriteLine("
X
", style); ++index; } else { int count = 0; while (index < 24 && !rewards[index]) { ++count; ++index; if (index == 4 || index == 8 || index == 12 || index == 20) break; } html.WriteLine("  ", count * 25, count == 1 ? "" : $" colspan=\"{count}\""); } html.WriteLine(" "); } #endregion #endregion #region Bodies public static List LoadBodies() { List list = new List(); string path = Core.FindDataFile("models/models.txt"); if (File.Exists(path)) { using StreamReader ip = new StreamReader(path); string line; while ((line = ip.ReadLine()) != null) { line = line.Trim(); if (line.Length == 0 || line.StartsWith("#")) continue; string[] split = line.Split('\t'); if (split.Length >= 9) { Body body = Utility.ToInt32(split[0]); ModelBodyType type = (ModelBodyType)Utility.ToInt32(split[1]); string name = split[8]; BodyEntry entry = new BodyEntry(body, type, name); if (!list.Contains(entry)) list.Add(entry); } } } return list; } private static void DocumentBodies() { List list = LoadBodies(); using StreamWriter html = GetWriter("docs/", "bodies.html"); html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Body List"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("

Back to the index

"); if (list.Count > 0) { html.WriteLine("

Body List

"); list.Sort(new BodyEntrySorter()); ModelBodyType lastType = ModelBodyType.Invalid; for (int i = 0; i < list.Count; ++i) { BodyEntry entry = list[i]; ModelBodyType type = entry.BodyType; if (type != lastType) { if (lastType != ModelBodyType.Invalid) html.WriteLine("
"); lastType = type; html.WriteLine(" ", type); switch (type) { case ModelBodyType.Monsters: html.WriteLine( " Monsters | Sea | Animals | Human | Equipment

"); break; case ModelBodyType.Sea: html.WriteLine( " Monsters | Sea | Animals | Human | Equipment

"); break; case ModelBodyType.Animals: html.WriteLine( " Monsters | Sea | Animals | Human | Equipment

"); break; case ModelBodyType.Human: html.WriteLine( " Monsters | Sea | Animals | Human | Equipment

"); break; case ModelBodyType.Equipment: html.WriteLine( " Monsters | Sea | Animals | Human | Equipment

"); break; } html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" ", type); } html.WriteLine(" ", entry.Body.BodyID, entry.Name); } html.WriteLine("
{0}
{0}{1}
"); } else { html.WriteLine(" This feature requires a UO:3D installation."); } html.WriteLine(" "); html.WriteLine(""); } #endregion #region Speech private static void DocumentKeywords() { List> tables = LoadSpeechFile(); using StreamWriter html = GetWriter("docs/", "keywords.html"); html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Speech Keywords"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("

Back to the index

"); html.WriteLine("

Speech Keywords

"); for (int p = 0; p < 1 && p < tables.Count; ++p) { Dictionary table = tables[p]; if (p > 0) html.WriteLine("
"); html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" "); List list = new List(table.Values); list.Sort(new SpeechEntrySorter()); for (int i = 0; i < list.Count; ++i) { SpeechEntry entry = list[i]; html.Write(" "); } html.WriteLine("
NumberText
0x{0:X4}", entry.Index); entry.Strings.Sort(); //( new EnglishPrioStringSorter() ); for (int j = 0; j < entry.Strings.Count; ++j) { if (j > 0) html.Write("
"); string v = entry.Strings[j]; for (int k = 0; k < v.Length; ++k) { char c = v[k]; if (c == '<') html.Write("<"); else if (c == '>') html.Write(">"); else if (c == '&') html.Write("&"); else if (c == '"') html.Write("""); else if (c == '\'') html.Write("'"); else if (c >= 0x20 && c < 0x7F) html.Write(c); else html.Write("&#{0};", (int)c); } } html.WriteLine("
"); } html.WriteLine(" "); html.WriteLine(""); } private class SpeechEntry { public SpeechEntry(int index) { Index = index; Strings = new List(); } public int Index{ get; } public List Strings{ get; } } private class SpeechEntrySorter : IComparer { public int Compare(SpeechEntry x, SpeechEntry y) { if (x == null && y == null) return 0; return x?.Index.CompareTo(y?.Index) ?? 1; } } private static List> LoadSpeechFile() { List> tables = new List>(); int lastIndex = -1; Dictionary table = null; string path = Core.FindDataFile("speech.mul"); if (File.Exists(path)) { using FileStream ip = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); BinaryReader bin = new BinaryReader(ip); while (bin.PeekChar() >= 0) { int index = bin.ReadByte() << 8 | bin.ReadByte(); int length = bin.ReadByte() << 8 | bin.ReadByte(); string text = Encoding.UTF8.GetString(bin.ReadBytes(length)).Trim(); if (text.Length == 0) continue; if (table == null || lastIndex > index) { if (index == 0 && text == "*withdraw*") tables.Insert(0, table = new Dictionary()); else tables.Add(table = new Dictionary()); } lastIndex = index; if (!table.TryGetValue(index, out SpeechEntry entry)) table[index] = entry = new SpeechEntry(index); entry.Strings.Add(text); } } return tables; } #endregion #region Commands public class DocCommandEntry { public DocCommandEntry(AccessLevel accessLevel, string name, string[] aliases, string usage, string description) { AccessLevel = accessLevel; Name = name; Aliases = aliases; Usage = usage; Description = description; } public AccessLevel AccessLevel{ get; } public string Name{ get; } public string[] Aliases{ get; } public string Usage{ get; } public string Description{ get; } } public class CommandEntrySorter : IComparer { public int Compare(DocCommandEntry a, DocCommandEntry b) { if (a == null && b == null) return 0; int v = b?.AccessLevel.CompareTo(a?.AccessLevel) ?? 1; if (v != 0) return v; return a?.Name.CompareTo(b?.Name) ?? 1; } } private static void DocumentCommands() { using StreamWriter html = GetWriter("docs/", "commands.html"); html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Commands"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("

Back to the index

"); html.WriteLine("

Commands

"); List commands = new List(CommandSystem.Entries.Values); List list = new List(); commands.Sort(); commands.Reverse(); Clean(commands); for (int i = 0; i < commands.Count; ++i) { CommandEntry e = commands[i]; MethodInfo mi = e.Handler.Method; object[] attrs = mi.GetCustomAttributes(typeof(UsageAttribute), false); if (attrs.Length == 0) continue; UsageAttribute usage = attrs[0] as UsageAttribute; attrs = mi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attrs.Length == 0) continue; if (usage == null || !(attrs[0] is DescriptionAttribute desc)) continue; attrs = mi.GetCustomAttributes(typeof(AliasesAttribute), false); AliasesAttribute aliases = attrs.Length == 0 ? null : attrs[0] as AliasesAttribute; string descString = desc.Description.Replace("<", "<").Replace(">", ">"); if (aliases == null) list.Add(new DocCommandEntry(e.AccessLevel, e.Command, null, usage.Usage, descString)); else list.Add(new DocCommandEntry(e.AccessLevel, e.Command, aliases.Aliases, usage.Usage, descString)); } for (int i = 0; i < TargetCommands.AllCommands.Count; ++i) { BaseCommand command = TargetCommands.AllCommands[i]; string usage = command.Usage; string desc = command.Description; if (usage == null || desc == null) continue; string[] cmds = command.Commands; string cmd = cmds[0]; string[] aliases = new string[cmds.Length - 1]; for (int j = 0; j < aliases.Length; ++j) aliases[j] = cmds[j + 1]; desc = desc.Replace("<", "<").Replace(">", ">"); if (command.Supports != CommandSupport.Single) { StringBuilder sb = new StringBuilder(50 + desc.Length); sb.Append("Modifiers: "); if ((command.Supports & CommandSupport.Global) != 0) sb.Append("Global, "); if ((command.Supports & CommandSupport.Online) != 0) sb.Append("Online, "); if ((command.Supports & CommandSupport.Region) != 0) sb.Append("Region, "); if ((command.Supports & CommandSupport.Contained) != 0) sb.Append("Contained, "); if ((command.Supports & CommandSupport.Multi) != 0) sb.Append("Multi, "); if ((command.Supports & CommandSupport.Area) != 0) sb.Append("Area, "); if ((command.Supports & CommandSupport.Self) != 0) sb.Append("Self, "); sb.Remove(sb.Length - 2, 2); sb.Append("
"); sb.Append(desc); desc = sb.ToString(); } list.Add(new DocCommandEntry(command.AccessLevel, cmd, aliases, usage, desc)); } List commandImpls = BaseCommandImplementor.Implementors; for (int i = 0; i < commandImpls.Count; ++i) { BaseCommandImplementor command = commandImpls[i]; string usage = command.Usage; string desc = command.Description; if (usage == null || desc == null) continue; string[] cmds = command.Accessors; string cmd = cmds[0]; string[] aliases = new string[cmds.Length - 1]; for (int j = 0; j < aliases.Length; ++j) aliases[j] = cmds[j + 1]; desc = desc.Replace("<", "<").Replace(">", ">"); list.Add(new DocCommandEntry(command.AccessLevel, cmd, aliases, usage, desc)); } list.Sort(new CommandEntrySorter()); AccessLevel last = AccessLevel.Player; foreach (DocCommandEntry e in list) { if (e.AccessLevel != last) { if (last != AccessLevel.Player) html.WriteLine("

"); last = e.AccessLevel; html.WriteLine(" ", last); switch (last) { case AccessLevel.Administrator: html.WriteLine( " Administrator | Game Master | Counselor | Player

"); break; case AccessLevel.GameMaster: html.WriteLine( " Administrator | Game Master | Counselor | Player

"); break; case AccessLevel.Seer: html.WriteLine( " Administrator | Game Master | Counselor | Player

"); break; case AccessLevel.Counselor: html.WriteLine( " Administrator | Game Master | Counselor | Player

"); break; case AccessLevel.Player: html.WriteLine( " Administrator | Game Master | Counselor | Player

"); break; } html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" ", last == AccessLevel.GameMaster ? "Game Master" : last.ToString()); } DocumentCommand(html, e); } html.WriteLine("
{0}
"); html.WriteLine(" "); html.WriteLine(""); } public static void Clean(List list) { for (int i = 0; i < list.Count; ++i) { CommandEntry e = list[i]; for (int j = i + 1; j < list.Count; ++j) { CommandEntry c = list[j]; if (e.Handler.Method == c.Handler.Method) { list.RemoveAt(j); --j; } } } } private static void DocumentCommand(StreamWriter html, DocCommandEntry e) { string usage = e.Usage; string desc = e.Description; string[] aliases = e.Aliases; html.Write(" {0}", e.Name); if (aliases == null || aliases.Length == 0) { html.Write("Usage: {0}
{1}", usage.Replace("<", "<").Replace(">", ">"), desc); } else { html.Write("Usage: {0}
Alias{1}: ", usage.Replace("<", "<").Replace(">", ">"), aliases.Length == 1 ? "" : "es"); for (int i = 0; i < aliases.Length; ++i) { if (i != 0) html.Write(", "); html.Write(aliases[i]); } html.Write("
{0}", desc); } html.WriteLine(""); } #endregion #region Constructible Objects private static Type typeofItem = typeof(Item), typeofMobile = typeof(Mobile), typeofMap = typeof(Map); private static Type typeofCustomEnum = typeof(CustomEnumAttribute); private static bool IsConstructible(Type t, out bool isItem) => (isItem = typeofItem.IsAssignableFrom(t)) || typeofMobile.IsAssignableFrom(t); private static bool IsConstructible(ConstructorInfo ctor) => ctor.IsDefined(typeof(ConstructibleAttribute), false); private static void DocumentConstructibleObjects() { List types = new List(m_Types.Values); types.Sort(new TypeComparer()); List<(Type, ConstructorInfo[])> items = new List<(Type, ConstructorInfo[])>(); List<(Type, ConstructorInfo[])> mobiles = new List<(Type, ConstructorInfo[])>(); for (int i = 0; i < types.Count; ++i) { Type t = types[i].m_Type; if (t.IsAbstract || !IsConstructible(t, out bool isItem)) continue; ConstructorInfo[] ctors = t.GetConstructors(); bool anyConstructible = false; for (int j = 0; !anyConstructible && j < ctors.Length; ++j) anyConstructible = IsConstructible(ctors[j]); if (anyConstructible) (isItem ? items : mobiles).Add((t, ctors)); } using StreamWriter html = GetWriter("docs/", "objects.html"); html.WriteLine(""); html.WriteLine(" "); html.WriteLine(" RunUO Documentation - Constructible Objects"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("

Back to the index

"); html.WriteLine( "

Constructible Items and Mobiles

"); html.WriteLine(" "); html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" "); items.ForEach(tuple => { var (type, constructors) = tuple; DocumentConstructibleObject(html, type, constructors); }); html.WriteLine("
Item NameUsage


"); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine("
"); html.WriteLine(" "); html.WriteLine(" "); mobiles.ForEach(tuple => { var (type, constructors) = tuple; DocumentConstructibleObject(html, type, constructors); }); html.WriteLine("
Mobile NameUsage
"); html.WriteLine(" "); html.WriteLine(""); } private static void DocumentConstructibleObject(StreamWriter html, Type t, ConstructorInfo[] ctors) { html.Write(" {0}", t.Name); bool first = true; for (int i = 0; i < ctors.Length; ++i) { ConstructorInfo ctor = ctors[i]; if (!IsConstructible(ctor)) continue; if (!first) html.Write("
"); first = false; html.Write("{0}Add {1}", CommandSystem.Prefix, t.Name); ParameterInfo[] parms = ctor.GetParameters(); for (int j = 0; j < parms.Length; ++j) { html.Write("
{1}", GetTooltipFor(parms[j]), parms[j].Name); } } html.WriteLine(""); } #endregion #region Tooltips private const string HtmlNewLine = " "; private static object[,] m_Tooltips = { { typeof(byte), "Numeric value in the range from 0 to 255, inclusive." }, { typeof(sbyte), "Numeric value in the range from negative 128 to positive 127, inclusive." }, { typeof(ushort), "Numeric value in the range from 0 to 65,535, inclusive." }, { typeof(short), "Numeric value in the range from negative 32,768 to positive 32,767, inclusive." }, { typeof(uint), "Numeric value in the range from 0 to 4,294,967,295, inclusive." }, { typeof(int), "Numeric value in the range from negative 2,147,483,648 to positive 2,147,483,647, inclusive." }, { typeof(ulong), "Numeric value in the range from 0 through about 10^20." }, { typeof(long), "Numeric value in the approximate range from negative 10^19 through 10^19." }, { typeof(string), "Text value. To specify a value containing spaces, encapsulate the value in quote characters:{0}{0}"Spaced text example"" }, { typeof(bool), "Boolean value which can be either True or False." }, { typeof(Map), "Map or facet name. Possible values include:{0}{0}- Felucca{0}- Trammel{0}- Ilshenar{0}- Malas" }, { typeof(Poison), "Poison name or level. Possible values include:{0}{0}- Lesser{0}- Regular{0}- Greater{0}- Deadly{0}- Lethal" }, { typeof(Point3D), "Three-dimensional coordinate value. Format as follows:{0}{0}"(, , )"" } }; private static string GetTooltipFor(ParameterInfo param) { Type paramType = param.ParameterType; for (int i = 0; i < m_Tooltips.GetLength(0); ++i) { Type checkType = (Type)m_Tooltips[i, 0]; if (paramType == checkType) return string.Format((string)m_Tooltips[i, 1], HtmlNewLine); } if (paramType.IsEnum) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("Enumeration value or name. Possible named values include:{0}", HtmlNewLine); string[] names = Enum.GetNames(paramType); for (int i = 0; i < names.Length; ++i) sb.AppendFormat("{0}- {1}", HtmlNewLine, names[i]); return sb.ToString(); } if (paramType.IsDefined(typeofCustomEnum, false)) { object[] attributes = paramType.GetCustomAttributes(typeofCustomEnum, false); if (attributes.Length > 0 && attributes[0] is CustomEnumAttribute attr) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("Enumeration value or name. Possible named values include:{0}", HtmlNewLine); string[] names = attr.Names; for (int i = 0; i < names.Length; ++i) sb.AppendFormat("{0}- {1}", HtmlNewLine, names[i]); return sb.ToString(); } } else if (paramType == typeofMap) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("Enumeration value or name. Possible named values include:{0}", HtmlNewLine); string[] names = Map.GetMapNames(); for (int i = 0; i < names.Length; ++i) sb.AppendFormat("{0}- {1}", HtmlNewLine, names[i]); return sb.ToString(); } return ""; } #endregion #region Const Strings private const string RefString = "ref "; private const string GetString = " get;"; private const string SetString = " set;"; private const string InString = "in "; private const string OutString = "out "; private const string VirtString = "virtual "; private const string CtorString = "(ctor) "; private const string StaticString = "(static) "; #endregion #region Write[...] private static void WriteEnum(TypeInfo info, StreamWriter typeHtml) { Type type = info.m_Type; typeHtml.WriteLine("

{0} (Enum)

", info.TypeName); string[] names = Enum.GetNames(type); bool flags = type.IsDefined(typeof(FlagsAttribute), false); string format; if (flags) format = " {0:G} = 0x{1:X}{2}
"; else format = " {0:G} = {1:D}{2}
"; for (int i = 0; i < names.Length; ++i) { object value = Enum.Parse(type, names[i]); typeHtml.WriteLine(format, names[i], value, i < names.Length - 1 ? "," : ""); } } private static void WriteType(TypeInfo info, StreamWriter typeHtml) { Type type = info.m_Type; typeHtml.Write("

"); Type decType = info.m_Declaring; if (decType != null) { // We are a nested type typeHtml.Write('('); m_Types.TryGetValue(decType, out TypeInfo decInfo); if (decInfo == null) typeHtml.Write(decType.Name); else //typeHtml.Write( "{1}", decInfo.m_FileName, decInfo.m_TypeName ); typeHtml.Write(decInfo.LinkName(null)); typeHtml.Write(") - "); } typeHtml.Write(info.TypeName); Type[] ifaces = info.m_Interfaces; Type baseType = info.m_BaseType; int extendCount = 0; if (baseType != typeof(object) && baseType != typeof(ValueType) && baseType?.IsPrimitive == false) { typeHtml.Write(" : "); m_Types.TryGetValue(baseType, out TypeInfo baseInfo); if (baseInfo == null) typeHtml.Write(baseType.Name); else typeHtml.Write($"{baseInfo.LinkName(null)}"); ++extendCount; } if (ifaces.Length > 0) { if (extendCount == 0) typeHtml.Write(" : "); for (int i = 0; i < ifaces.Length; ++i) { Type iface = ifaces[i]; m_Types.TryGetValue(iface, out TypeInfo ifaceInfo); if (extendCount != 0) typeHtml.Write(", "); ++extendCount; if (ifaceInfo == null) { FormatGeneric(iface, out _, out _, out string linkName); typeHtml.Write($"{linkName.Replace("@directory@", null)}"); } else { typeHtml.Write($"{ifaceInfo.LinkName(null)}"); } } } typeHtml.WriteLine("

"); List derived = info.m_Derived; if (derived != null) { typeHtml.Write("

Derived Types: "); derived.Sort(new TypeComparer()); for (int i = 0; i < derived.Count; ++i) { TypeInfo derivedInfo = derived[i]; if (i != 0) typeHtml.Write(", "); //typeHtml.Write( "{1}", derivedInfo.m_FileName, derivedInfo.m_TypeName ); typeHtml.Write($"{derivedInfo.LinkName(null)}"); } typeHtml.WriteLine("

"); } List nested = info.m_Nested; if (nested != null) { typeHtml.Write("

Nested Types: "); nested.Sort(new TypeComparer()); for (int i = 0; i < nested.Count; ++i) { TypeInfo nestedInfo = nested[i]; if (i != 0) typeHtml.Write(", "); //typeHtml.Write( "{1}", nestedInfo.m_FileName, nestedInfo.m_TypeName ); typeHtml.Write($"{nestedInfo.LinkName(null)}"); } typeHtml.WriteLine("

"); } MemberInfo[] membs = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); Array.Sort(membs, new MemberComparer()); for (int i = 0; i < membs.Length; ++i) { MemberInfo mi = membs[i]; if (mi is PropertyInfo propertyInfo) WriteProperty(propertyInfo, typeHtml); else if (mi is ConstructorInfo constructorInfo) WriteCtor(info.TypeName, constructorInfo, typeHtml); else if (mi is MethodInfo methodInfo) WriteMethod(methodInfo, typeHtml); } } private static void WriteProperty(PropertyInfo pi, StreamWriter html) { html.Write(" "); MethodInfo getMethod = pi.GetGetMethod(); MethodInfo setMethod = pi.GetSetMethod(); if (getMethod?.IsStatic == true || setMethod?.IsStatic == true) html.Write(StaticString); html.Write(GetPair(pi.PropertyType, pi.Name, false)); html.Write('('); if (pi.CanRead) html.Write(GetString); if (pi.CanWrite) html.Write(SetString); html.WriteLine(" )
"); } private static void WriteCtor(string name, ConstructorInfo ctor, StreamWriter html) { if (ctor.IsStatic) return; html.Write(" "); html.Write(CtorString); html.Write(name); html.Write('('); ParameterInfo[] parms = ctor.GetParameters(); if (parms.Length > 0) { html.Write(' '); for (int i = 0; i < parms.Length; ++i) { ParameterInfo pi = parms[i]; if (i != 0) html.Write(", "); if (pi.IsIn) html.Write(InString); else if (pi.IsOut) html.Write(OutString); html.Write(GetPair(pi.ParameterType, pi.Name, pi.IsOut)); } html.Write(' '); } html.WriteLine(")
"); } private static void WriteMethod(MethodInfo mi, StreamWriter html) { if (mi.IsSpecialName) return; html.Write(" "); if (mi.IsStatic) html.Write(StaticString); if (mi.IsVirtual) html.Write(VirtString); html.Write(GetPair(mi.ReturnType, mi.Name, false)); html.Write('('); ParameterInfo[] parms = mi.GetParameters(); if (parms.Length > 0) { html.Write(' '); for (int i = 0; i < parms.Length; ++i) { ParameterInfo pi = parms[i]; if (i != 0) html.Write(", "); if (pi.IsIn) html.Write(InString); else if (pi.IsOut) html.Write(OutString); html.Write(GetPair(pi.ParameterType, pi.Name, pi.IsOut)); } html.Write(' '); } html.WriteLine(")
"); } #endregion } #region BodyEntry & BodyType public enum ModelBodyType { Invalid = -1, Monsters, Sea, Animals, Human, Equipment } public class BodyEntry { public BodyEntry(Body body, ModelBodyType bodyType, string name) { Body = body; BodyType = bodyType; Name = name; } public Body Body{ get; } public ModelBodyType BodyType{ get; } public string Name{ get; } public override bool Equals(object obj) { BodyEntry e = obj as BodyEntry; return Body == e?.Body && BodyType == e.BodyType && Name == e.Name; } public override int GetHashCode() => Body.BodyID ^ (int)BodyType ^ Name.GetHashCode(); } public class BodyEntrySorter : IComparer { public int Compare(BodyEntry a, BodyEntry b) { if (a == null && b == null) return 0; int v = a?.BodyType.CompareTo(b?.BodyType) ?? 1; if (v == 0) v = a?.Body.BodyID.CompareTo(b?.Body.BodyID) ?? 1; if (v != 0) return v; return a?.Name.CompareTo(b?.Name) ?? 1; } } #endregion }