using System; using System.Collections.Generic; using System.Linq; namespace codelanguage; public sealed class ReferenceTopic { public string Id { get; set; } = ""; public HashSet Aliases { get; } = new(StringComparer.OrdinalIgnoreCase); public string Title { get; set; } = ""; public string Summary { get; set; } = ""; public List Categories { get; } = new(); public List
Sections { get; } = new(); public string? SeeAlso { get; set; } } public abstract class ContentLine { public string? Text { get; set; } } public sealed class ParagraphContentLine : ContentLine { public ParagraphContentLine(string text) { Text = text; } } public sealed class CodeContentLine : ContentLine { public string Code { get; set; } = ""; public string? Caption { get; set; } public CodeContentLine(string code, string? caption = null) { Code = code; Caption = caption; } } public sealed class BulletContentLine : ContentLine { public BulletContentLine(string text) { Text = text; } } public static class ReferenceData { private static readonly List _implementedBuiltins = BuiltinProcs.GetBuiltinNames().ToList(); private static readonly string _builtinsFormatted = string.Join(", ", _implementedBuiltins); public static string GetBuiltinsFormatted() => _builtinsFormatted; private static readonly Lazy> _allTopics = new(BuildTopics); public static List GetAllTopics() => _allTopics.Value; public static ReferenceTopic? GetTopic(string id) => GetAllTopics().FirstOrDefault(t => t.Id.Equals(id, StringComparison.OrdinalIgnoreCase) || t.Aliases.Contains(id)); internal static HashSet GetAuthoredTitles() => _topics.Select(t => t.Title).ToHashSet(StringComparer.OrdinalIgnoreCase); public static List GetRelatedTopics(ReferenceTopic topic) { var all = GetAllTopics(); var related = (topic.SeeAlso ?? "").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) .Select(key => ResolveTopic(all, key)) .Where(t => t != null).Cast().Where(t => t.Id != topic.Id).DistinctBy(t => t.Id, StringComparer.OrdinalIgnoreCase).ToList(); void Add(string id) { var match = all.FirstOrDefault(t => t.Id.Equals(id, StringComparison.OrdinalIgnoreCase)); if (match != null && match.Id != topic.Id && related.All(t => !t.Id.Equals(match.Id, StringComparison.OrdinalIgnoreCase))) related.Add(match); } if (topic.Id.StartsWith("typeproc-", StringComparison.OrdinalIgnoreCase)) { Add("parent-call"); } else if (topic.Id.StartsWith("var-", StringComparison.OrdinalIgnoreCase)) { Add("var"); } if (topic.Categories.Contains("operators")) { Add("syntax"); Add("var"); Add("proc"); } if (topic.Categories.Contains("builtins")) { Add("proc"); Add("list"); Add("world"); } if (topic.Categories.Contains("keywords") || topic.Categories.Contains("controlflow")) { Add("syntax"); Add("proc"); Add("var"); } if (related.Count == 0) { Add("syntax"); Add("proc"); Add("var"); } return related; } private static ReferenceTopic? ResolveTopic(List all, string key) { var match = all.FirstOrDefault(t => t.Id.Equals(key, StringComparison.OrdinalIgnoreCase) || t.Aliases.Contains(key) || t.Title.Equals(key, StringComparison.OrdinalIgnoreCase)); if (match != null) return match; match = all.FirstOrDefault(t => t.Title.EndsWith("()", StringComparison.Ordinal) && t.Title[..^2].Equals(key, StringComparison.OrdinalIgnoreCase)); if (match != null) return match; return all.FirstOrDefault(t => t.Title.Equals(key + " var", StringComparison.OrdinalIgnoreCase)); } private static List BuildTopics() { TypeRegistry.EnsureInitialized(); var typeMethodNames = TypeRegistry.GetAllTypes().SelectMany(path => { var type = TypeRegistry.Get(path); return type == null ? Array.Empty() : type.Procs.Keys.Concat(type.Verbs.Keys); }).ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var topic in _topics) { if (topic.Categories.Contains("types") && topic.Title.StartsWith('/')) topic.Title = topic.Title.TrimStart('/'); if (topic.Categories.Contains("builtins") && !topic.Categories.Contains("types") && _implementedBuiltins.Contains(topic.Id, StringComparer.OrdinalIgnoreCase)) topic.Title = topic.Id + "()"; if ((topic.Categories.Contains("verbs") || topic.Categories.Contains("procs")) && typeMethodNames.Contains(topic.Id)) topic.Title = topic.Id + "()"; } var generatedTypes = TypeReferenceTopics.Create().ToList(); var generatedTypeTitles = generatedTypes.Where(t => t.Categories.Contains("types")) .Select(t => t.Title).ToHashSet(StringComparer.OrdinalIgnoreCase); var candidates = generatedTypes .Concat(BuiltinReferenceTopics.Create(_implementedBuiltins)) .Concat(LanguageReferenceTopics.Create()) .Concat(KeywordReferenceTopics.Create()) .Concat(PreprocessorReferenceTopics.Create()) .Concat(SpecialSyntaxTopics.Create()) .Concat(_topics.Where(t => !t.Categories.Contains("types") || !generatedTypeTitles.Contains(t.Title.TrimStart('/')))) .Where(t => t.Id is not ".." and not "byond-516-modern-apis").ToList(); candidates.AddRange(ReferenceIndexTopics.Create(candidates)); var unique = candidates.GroupBy(t => t.Id, StringComparer.OrdinalIgnoreCase).Select(g => MergeTopics(g)) .GroupBy(t => t.Title, StringComparer.OrdinalIgnoreCase).Select(g => MergeTopics(g)) .ToList(); var oldParentArticle = unique.FirstOrDefault(t => t.Id == ".."); var parentArticle = unique.FirstOrDefault(t => t.Id == "parent-call"); if (oldParentArticle != null && parentArticle != null) { parentArticle.Aliases.Add(oldParentArticle.Id); parentArticle.Aliases.UnionWith(oldParentArticle.Aliases); foreach (var section in oldParentArticle.Sections) if (!parentArticle.Sections.Any(s => string.Equals(s.Heading, section.Heading, StringComparison.OrdinalIgnoreCase))) parentArticle.Sections.Add(section); unique.Remove(oldParentArticle); } var builtinIndex = unique.FirstOrDefault(t => t.Id == "builtins-index"); if (builtinIndex != null) builtinIndex.SeeAlso = string.Join(",", unique.Where(t => t.Categories.Contains("builtins") && t.Title.EndsWith("()", StringComparison.Ordinal) && t.Id != "builtins-index").Select(t => t.Id)); foreach (var topic in unique) EnsureBeginnerAndExpertExamples(topic); return unique; } private static ReferenceTopic MergeTopics(IEnumerable source) { var items = source.ToList(); var result = items[0]; foreach (var extra in items.Skip(1)) { if (!string.IsNullOrWhiteSpace(extra.Id)) result.Aliases.Add(extra.Id); result.Aliases.UnionWith(extra.Aliases); foreach (var category in extra.Categories) if (!result.Categories.Contains(category)) result.Categories.Add(category); foreach (var section in extra.Sections) if (!result.Sections.Any(s => string.Equals(s.Heading, section.Heading, StringComparison.OrdinalIgnoreCase))) result.Sections.Add(section); var links = (result.SeeAlso ?? "").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) .Concat((extra.SeeAlso ?? "").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)) .Distinct(StringComparer.OrdinalIgnoreCase); result.SeeAlso = string.Join(",", links); } return result; } private static readonly List _topics = new() { new() { Id = "language-overview", Title = "Craft++ Language Overview", Summary = "Starting point for the Craft++ language reference.", Categories = { "all" }, SeeAlso = "syntax, builtins-index, types-index, operators-index, controlflow-index", Sections = { new Section("Start here", new ParagraphContentLine("Choose one language element from the contents list, or search for its name. Each language entry is documented on its own page.")) } }, new() { Id = "builtins-index", Title = "Built-in Proc Index", Summary = "Navigation page for the individual built-in procedure articles.", Categories = { "builtins" }, SeeAlso = "proc, types, preprocessor", Sections = { new Section("Browse the procedures") { new ParagraphContentLine("Every registered built-in proc has its own article with a format, explanation, Beginner example, Expert example, and links to related language topics. Search by proc name or choose an entry from the topic list.") } } }, new() { Id = "var", Title = "var", Summary = "Declares a variable with optional type path.", Categories = { "variables", "procs" }, SeeAlso = "local, global, tmp, static, const", Sections = { new Section("Syntax") { new ParagraphContentLine("Declares a variable. The optional type path documents the expected value and does not restrict runtime assignment."), }, new Section("Form") { new CodeContentLine("var/type/path name = expression", "Variable declaration"), }, new Section("Examples") { new CodeContentLine("var/number = 1"), }, new Section("Notes") { new BulletContentLine("If no type path is given, the variable is untyped."), new BulletContentLine("A namespaced variable declaration has its own reference topic."), }, } }, new() { Id = "new", Title = "new", Summary = "Creates an instance of a type.", Categories = { "procs", "builtins" }, SeeAlso = "proc, list", Sections = { new Section("Syntax") { new ParagraphContentLine("Creates a new instance and returns it. Arguments are passed to the type's New() proc."), }, new Section("Examples") { new CodeContentLine("var/obj/item/key = new/obj/item/key()"), }, new Section("Notes") { new BulletContentLine("The type path is slash-separated and case-insensitive in paths."), new BulletContentLine("Missing parent types are auto-created."), }, } }, new() { Id = "proc", Title = "proc", Summary = "Defines a procedure.", Categories = { "procs" }, SeeAlso = "verb, ..()", Sections = { new Section("Syntax") { new ParagraphContentLine("Defines a procedure. Procedures may be global or attached to a type path."), }, new Section("Form") { new CodeContentLine("proc/name(arg1, arg2)\n return arg1"), }, new Section("Examples") { new CodeContentLine("proc/add(a, b)\n return a + b"), }, new Section("Notes") { new BulletContentLine("Use ..() to call the parent implementation when overriding."), new BulletContentLine("Return values are optional; the proc returns the last expression if no return is used."), new BulletContentLine("Each verb property such as set category or set hidden has its own topic."), }, } }, new() { Id = "verb", Title = "verb", Summary = "Defines a player-facing verb on a type.", Categories = { "verbs", "procs" }, SeeAlso = "proc", Sections = { new Section("Syntax") { new ParagraphContentLine("Verbs are procedures exposed to the player. They can be attached to any type."), }, new Section("Examples") { new CodeContentLine("mob/verb/Say(msg as text)\n set category = \"Communication\"\n world << \"[src.name]: [msg]\""), new CodeContentLine("mob/verb/Emote(msg as text)\n set category = \"Communication\"\n world << \"[src.name] [msg]\""), }, new Section("Notes") { new BulletContentLine("Use set category, set desc, and set hidden to control the verb panel."), new BulletContentLine("The first argument can declare an expected type, e.g., as text or as /obj/item in the user's hand."), }, } }, new() { Id = "list", Title = "list", Summary = "A 1-based DM-style list.", Categories = { "types" }, SeeAlso = "len, +=, -=, sort, filter", Sections = { new Section("Syntax") { new ParagraphContentLine("Lists can be numeric-indexed or associative. They start at index 1."), }, new Section("Examples") { new CodeContentLine("var/list/L = list(1, 2, 3)\nvar/list/assoc = list(\"key\" = \"value\")"), new CodeContentLine("L += \"new item\"\nL -= 1\nL[1] = \"replaced\""), }, new Section("Notes") { new BulletContentLine("Lists are reference types; copying a list variable copies the reference."), new BulletContentLine("Use CopyText or a manual loop to duplicate list contents."), }, } }, new() { Id = "world", Title = "world", Summary = "The global world object.", Categories = { "types", "builtins" }, SeeAlso = "locate, view, <<", Sections = { new Section("Common Variables") { new BulletContentLine("world.name — world name"), new BulletContentLine("world.maxx / world.maxy / world.maxz — map bounds"), new BulletContentLine("world.time — world tick counter"), new BulletContentLine("world.tick_lag — delay between ticks"), new BulletContentLine("world.fps — frames per second"), new BulletContentLine("world.mob / world.area / world.turf / world.obj — default root types"), }, new Section("Output") { new ParagraphContentLine("world << value writes to the world output stream."), new CodeContentLine("world << \"Hello, world!\""), }, new Section("Saving") { new ParagraphContentLine("world.savefile returns the world's savefile."), }, } }, new() { Id = "locate", Title = "locate", Summary = "Locates an object by coordinates, type, or variable.", Categories = { "builtins" }, SeeAlso = "view, world", Sections = { new Section("Syntax") { new ParagraphContentLine("Searches the world for an object matching the given criteria."), }, new Section("Examples") { new CodeContentLine("var/atom/a = locate(/obj/item) in world\nvar/atom/b = locate(x, y, z) in world"), new CodeContentLine("var/mob/m = locate(/mob/player) in oview(5, src)"), }, new Section("Notes") { new BulletContentLine("in limits the search to a container or view."), new BulletContentLine("Returns null if nothing matches."), }, } }, new() { Id = "view", Title = "view", Summary = "Returns the current viewer or a list of atoms in view.", Categories = { "builtins" }, SeeAlso = "locate, oview", Sections = { new Section("Examples") { new CodeContentLine("var/list/seen = view(src, 7)\nvar/mob/target = locate(/mob) in view(src)"), }, new Section("Notes") { new BulletContentLine("view() without arguments returns the client's view distance."), new BulletContentLine("view(atom, range) returns atoms within range."), }, } }, new() { Id = "sleep", Title = "sleep", Summary = "Pauses the current proc for a number of ticks.", Categories = { "builtins" }, SeeAlso = "spawn", Sections = { new Section("Syntax") { new ParagraphContentLine("Pauses execution for the given number of ticks. The world continues ticking."), }, new Section("Examples") { new CodeContentLine("world << \"Waiting...\"\nsleep(10)\nworld << \"Done.\""), }, new Section("Notes") { new BulletContentLine("sleep(0) yields to other suspended procs."), new BulletContentLine("The current proc can be woken by events such as alerts."), }, } }, new() { Id = "spawn", Title = "spawn", Summary = "Runs a proc or block asynchronously after a delay.", Categories = { "builtins" }, SeeAlso = "sleep", Sections = { new Section("Syntax") { new ParagraphContentLine("Spawns a new concurrent execution. The current proc continues immediately."), }, new Section("Examples") { new CodeContentLine("spawn(10)\n world << \"Delayed\""), new CodeContentLine("spawn()\n // runs on next tick"), }, new Section("Notes") { new BulletContentLine("The delay is in ticks. Use 0 for next tick."), new BulletContentLine("Spawned procs inherit the source's src and usr context."), }, } }, new() { Id = "alert", Title = "alert", Summary = "Shows a modal alert dialog and returns the clicked button.", Categories = { "builtins" }, SeeAlso = "prompt, input", Sections = { new Section("Syntax") { new ParagraphContentLine("Displays a message box with one or more buttons."), }, new Section("Examples") { new CodeContentLine("var/choice = alert(\"Continue?\", \"Confirm\", \"Yes\", \"No\")"), new CodeContentLine("if (alert(\"Attack?\") == \"Yes\")\n Attack()"), }, new Section("Notes") { new BulletContentLine("The first argument is the message. Optional title, then button labels."), new BulletContentLine("Returns the button label the user clicked."), }, } }, new() { Id = "prompt", Title = "prompt", Summary = "Shows a modal input dialog and returns the entered text.", Categories = { "builtins" }, SeeAlso = "alert, input", Sections = { new Section("Syntax") { new ParagraphContentLine("Displays a text input dialog."), }, new Section("Examples") { new CodeContentLine("var/name = prompt(\"Enter your name:\", \"Name\", \"Player\")"), new CodeContentLine("if (name)\n world << \"Hello, [name]\""), }, new Section("Notes") { new BulletContentLine("The third argument is the default text."), new BulletContentLine("Returns null if the user cancels."), }, } }, new() { Id = "input", Title = "input", Summary = "Gets a single key or text input from the user.", Categories = { "builtins" }, SeeAlso = "alert, prompt", Sections = { new Section("Examples") { new CodeContentLine("var/key = input(\"Press a key:\", \"Input\") as null|anything\nworld << \"You pressed: [key]\""), }, new Section("Notes") { new BulletContentLine("Can accept text, numbers, files, colors, or key codes depending on the as clause."), }, } }, new() { Id = "stat", Title = "stat", Summary = "Adds or clears entries on a stat panel.", Categories = { "builtins" }, SeeAlso = "statpanel, stat_tabs", Sections = { new Section("Examples") { new CodeContentLine("stat(\"Health\", hp)\nstat(\"Name\", name)"), new CodeContentLine("stat(null) // clears all stats in the current panel"), }, new Section("Notes") { new BulletContentLine("Use statpanel(\"panelname\") to switch panels."), new BulletContentLine("Stats update automatically when the client repaints."), }, } }, new() { Id = "icon", Title = "icon", Summary = "The icon file type (.cpi).", Categories = { "types" }, SeeAlso = "image, flick, icon_state", Sections = { new Section("Built-in Vars") { new BulletContentLine("icon.width — frame width"), new BulletContentLine("icon.height — frame height"), new BulletContentLine("icon.frames — frame count"), new BulletContentLine("icon.dirs — direction count"), }, new Section("Notes") { new BulletContentLine("Icons are loaded from .cpi files in the project directory."), new BulletContentLine("Use the Icon Editor to edit states and directions."), }, } }, new() { Id = "image", Title = "image", Summary = "An overlay image with appearance properties.", Categories = { "types" }, SeeAlso = "icon, overlay, underlay", Sections = { new Section("Default Variables") { new BulletContentLine("image.icon, image.icon_state"), new BulletContentLine("image.layer, image.plane, image.alpha"), new BulletContentLine("image.pixel_x, image.pixel_y"), new BulletContentLine("image.dir, image.color"), }, new Section("Notes") { new BulletContentLine("Images are created with new/image()."), new BulletContentLine("Append to mob.overlays or mob.underlays to display."), }, } }, new() { Id = "savefile", Title = "savefile", Summary = "Persistent JSON-based save data.", Categories = { "types" }, SeeAlso = "ExportText, ImportText", Sections = { new Section("Examples") { new CodeContentLine("var/savefile/S = new/savefile()\nS[\"key\"] << \"value\"\nS.ExportText(\"save.json\")"), new CodeContentLine("var/savefile/S = new/savefile()\nS.ImportText(\"save.json\")\nvar/value = S[\"key\"]"), }, new Section("Notes") { new BulletContentLine("Save data is stored as JSON."), new BulletContentLine("Index access with [key] reads and writes."), }, } }, new() { Id = "matrix", Title = "matrix", Summary = "A 3x3 transformation matrix.", Categories = { "types" }, SeeAlso = "Turn, Scale, Translate, Multiply", Sections = { new Section("Default Variables") { new BulletContentLine("matrix.a through matrix.i — nine transform components"), }, new Section("Notes") { new BulletContentLine("Identity matrix: 1,0,0, 0,1,0, 0,0,1"), }, } }, new() { Id = "sound", Title = "sound", Summary = "A sound resource.", Categories = { "types" }, SeeAlso = "SPlaySound, BroadcastSound", Sections = { new Section("Default Variables") { new BulletContentLine("sound.file, sound.volume"), new BulletContentLine("sound.channel, sound.priority"), new BulletContentLine("sound.status, sound.frequency"), }, new Section("Notes") { new BulletContentLine("SoundService manages MediaPlayer playback."), }, } }, new() { Id = "if", Title = "if", Summary = "Conditional branching.", Categories = { "controlflow" }, SeeAlso = "else, switch", Sections = { new Section("Syntax") { new ParagraphContentLine("Executes a block when the condition is true."), }, new Section("Examples") { new CodeContentLine("if (x > 0)\n world << \"positive\"\nelse\n world << \"non-positive\""), new CodeContentLine("if (x)\n world << \"truthy\"\nelse if (x == 2)\n world << \"two\"\nelse\n world << \"other\""), }, new Section("Notes") { new BulletContentLine("Indented DM-style branches without braces are supported."), new BulletContentLine("else if and else are optional."), }, } }, new() { Id = "while", Title = "while", Summary = "Repeats while a condition is true.", Categories = { "controlflow" }, SeeAlso = "do, for, break", Sections = { new Section("Examples") { new CodeContentLine("while (i > 0)\n world << i\n i--"), }, } }, new() { Id = "do", Title = "do while", Summary = "Repeats a block at least once, then while a condition is true.", Categories = { "controlflow" }, SeeAlso = "while, for, break", Sections = { new Section("Examples") { new CodeContentLine("do\n world << i\n i--\nwhile (i > 0)"), }, } }, new() { Id = "for", Title = "for", Summary = "C-style and list iteration loop.", Categories = { "controlflow" }, SeeAlso = "while, foreach, break", Sections = { new Section("Examples") { new CodeContentLine("for (var/x = 1; x <= 10; x++)\n world << x"), new CodeContentLine("for (var/item in items)\n world << item"), }, new Section("Notes") { new BulletContentLine("The list form iterates over any list-like value."), new BulletContentLine("break and continue work inside both forms."), }, } }, new() { Id = "switch", Title = "switch", Summary = "Branches based on a value.", Categories = { "controlflow" }, SeeAlso = "if, case", Sections = { new Section("Examples") { new CodeContentLine("switch (cmd)\n if (\"north\", \"n\")\n dir = NORTH\n if (\"south\", \"s\")\n dir = SOUTH\n else\n world << \"Unknown\""), }, new Section("Notes") { new BulletContentLine("case labels are if expressions within the switch block."), new BulletContentLine("The first matching branch executes; fallthrough is not automatic."), }, } }, new() { Id = "break", Title = "break", Summary = "Exits the innermost loop.", Categories = { "controlflow" }, SeeAlso = "continue, return", Sections = { new Section("Examples") { new CodeContentLine("for (var/i = 0; i < 100; i++)\n if (i == 10)\n break"), }, } }, new() { Id = "continue", Title = "continue", Summary = "Skips to the next loop iteration.", Categories = { "controlflow" }, SeeAlso = "break, return", Sections = { new Section("Examples") { new CodeContentLine("for (var/i = 0; i < 100; i++)\n if (i % 2)\n continue\n world << i"), }, } }, new() { Id = "return", Title = "return", Summary = "Returns from the current proc.", Categories = { "controlflow" }, SeeAlso = "break, spawn", Sections = { new Section("Examples") { new CodeContentLine("proc/add(a, b)\n return a + b"), new CodeContentLine("proc/Main()\n if (done)\n return\n DoWork()"), }, new Section("Notes") { new BulletContentLine("A bare return exits without a value."), new BulletContentLine("If omitted, the last expression value is returned when possible."), }, } }, new() { Id = "try", Title = "try / catch", Summary = "Catches runtime errors and thrown exceptions.", Categories = { "controlflow" }, SeeAlso = "throw, crash", Sections = { new Section("Examples") { new CodeContentLine("try\n risky()\ncatch (var/e)\n world << \"Error: [e]\""), }, new Section("Notes") { new BulletContentLine("catch without an argument catches any runtime error."), new BulletContentLine("crash() raises an uncatchable runtime error."), new BulletContentLine("throw(value) raises a catchable exception."), }, } }, new() { Id = "throw", Title = "throw", Summary = "Throws a catchable exception.", Categories = { "controlflow" }, SeeAlso = "try, catch, crash", Sections = { new Section("Examples") { new CodeContentLine("if (invalid)\n throw(\"bad input\")"), }, new Section("Notes") { new BulletContentLine("The argument is converted to a CraftValue and passed to catch."), }, } }, new() { Id = "..", Title = "..()", Summary = "Calls the parent proc implementation.", Categories = { "procs" }, SeeAlso = "proc, verb", Sections = { new Section("Examples") { new CodeContentLine("mob/Login()\n ..()\n world << \"Welcome back\""), }, new Section("Notes") { new BulletContentLine("..() forwards the current arguments by default."), new BulletContentLine("You can pass explicit arguments: ..(arg1, arg2)."), }, } }, new() { Id = "locate", Title = "locate", Summary = "Finds an object by type or coordinates.", Categories = { "builtins" }, SeeAlso = "view, oview, range", Sections = { new Section("Syntax") { new ParagraphContentLine("Returns the first object matching the type, or the atom at the given coordinates."), }, new Section("Examples") { new CodeContentLine("var/obj/o = locate(/obj/item) in world\nvar/turf/t = locate(5, 5, 1) in world"), new CodeContentLine("var/mob/m = locate(/mob) in view(src, 7)"), }, new Section("Notes") { new BulletContentLine("Combine with in to limit scope."), new BulletContentLine("Returns null when nothing matches."), }, } }, new() { Id = "text", Title = "text", Summary = "Formats a message with embedded expressions.", Categories = { "builtins" }, SeeAlso = "num2text, text2num, time2text", Sections = { new Section("Examples") { new CodeContentLine("text(\"Hello, [name]!\")\ntext(\"Score: [score]\")"), }, new Section("Notes") { new BulletContentLine("[expr] interpolation is supported in string literals."), new BulletContentLine("text() is optional when using \"[expr]\" directly."), }, } }, new() { Id = "rand", Title = "rand", Summary = "Returns a random number.", Categories = { "builtins" }, SeeAlso = "pick", Sections = { new Section("Examples") { new CodeContentLine("var/n = rand(1, 100)\nvar/f = rand() // 0.0 to 1.0"), }, new Section("Notes") { new BulletContentLine("rand(max) returns 1 to max inclusive."), new BulletContentLine("rand(min, max) returns min to max inclusive."), new BulletContentLine("rand() with no arguments returns a fractional value 0-1."), }, } }, new() { Id = "pick", Title = "pick", Summary = "Returns a random element from a list or arguments.", Categories = { "builtins" }, SeeAlso = "rand, shuffle", Sections = { new Section("Examples") { new CodeContentLine("var/color = pick(\"red\", \"green\", \"blue\")\nvar/item = pick(items)"), }, } }, new() { Title = "is...()", Summary = "Type-checking built-in procs.", Categories = { "builtins" }, Id = "istype", SeeAlso = "as, to", Sections = { new Section("Implemented Checks") { new BulletContentLine("istype(A, /type) — generic hierarchy check"), new BulletContentLine("ismob(A), isobj(A), isturf(A), isarea(A)"), new BulletContentLine("issaved(A) — has /var/saved"), new BulletContentLine("isloc(A), isfile(A), isicon(A), isclient(A)"), }, new Section("Notes") { new BulletContentLine("The second argument defaults to /mob for the single-arg forms."), new BulletContentLine("as casts return null on mismatch; to checks return a boolean."), }, } }, new() { Id = "rgb", Title = "rgb / rgba", Summary = "Creates a color from components.", Categories = { "builtins" }, SeeAlso = "color", Sections = { new Section("Examples") { new CodeContentLine("var/c = rgb(255, 0, 0)\nvar/c2 = rgba(255, 0, 0, 128)"), }, new Section("Notes") { new BulletContentLine("rgb takes 3 or 4 arguments: red, green, blue, optional alpha."), new BulletContentLine("Values are clamped to 0-255."), }, } }, new() { Id = "turn", Title = "turn", Summary = "Rotates a direction or matrix.", Categories = { "builtins" }, SeeAlso = "matrix", Sections = { new Section("Examples") { new CodeContentLine("dir = turn(dir, 45)\ndir = turn(dir, 90)"), }, new Section("Notes") { new BulletContentLine("Angles are in degrees."), new BulletContentLine("Supports SOUTH, NORTH, EAST, WEST, SOUTHEAST, etc."), }, } }, new() { Id = "flick", Title = "flick", Summary = "Shows an icon state briefly.", Categories = { "builtins" }, SeeAlso = "icon, icon_state", Sections = { new Section("Examples") { new CodeContentLine("flick(src, \"attack\")"), }, new Section("Notes") { new BulletContentLine("After the delay, the previous icon_state is restored."), }, } }, new() { Id = "browse", Title = "browse", Summary = "Shows an HTML browse window for a client.", Categories = { "builtins" }, SeeAlso = "show_browser, hide_browser", Sections = { new Section("Examples") { new CodeContentLine("browse(src, \"Hello\", \"window=info\")"), }, new Section("Notes") { new BulletContentLine("The second argument can be a window name."), new BulletContentLine("Cleared by browse(src, null) or browse_rsc."), }, } }, new() { Id = "url_encode", Title = "url_encode / url_decode", Summary = "Encodes or decodes URL-unsafe characters.", Categories = { "builtins" }, SeeAlso = "html_encode, html_decode", Sections = { new Section("Examples") { new CodeContentLine("var/encoded = url_encode(\"a b&c=d\")\nvar/decoded = url_decode(encoded)"), }, } }, new() { Id = "html_encode", Title = "html_encode / html_decode", Summary = "Escapes or unescapes HTML entities.", Categories = { "builtins" }, SeeAlso = "url_encode, url_decode", Sections = { new Section("Examples") { new CodeContentLine("html_encode(\"bold\") // <b>bold</b>"), }, } }, new() { Id = "sound", Title = "sound", Summary = "Plays a sound resource.", Categories = { "builtins" }, SeeAlso = "sound type, BroadcastSound", Sections = { new Section("Examples") { new CodeContentLine("sound.play('boom.wav')\nsound.stop()"), }, new Section("Notes") { new BulletContentLine("SoundService wraps MediaPlayer playback."), new BulletContentLine("Paths are resolved relative to the project directory."), }, } }, new() { Id = "overlay", Title = "overlay / underlay", Summary = "Attaches an image overlay to an atom.", Categories = { "builtins" }, SeeAlso = "image, flick", Sections = { new Section("Examples") { new CodeContentLine("var/image/i = new/image()\ni.icon = 'blood.cpi'\ni.icon_state = \"splat\"\nsrc.overlays += i"), }, new Section("Notes") { new BulletContentLine("underlays render below the base icon; overlays render above."), new BulletContentLine("Multiple overlays are supported."), }, } }, new() { Id = "mob", Title = "/mob", Summary = "Mobile object type.", Categories = { "types" }, SeeAlso = "obj, turf, area", Sections = { new Section("Default Variables") { new BulletContentLine("mob.name — display name"), new BulletContentLine("mob.icon, mob.icon_state — appearance"), new BulletContentLine("mob.x, mob.y, mob.z — map position"), new BulletContentLine("mob.density — collision"), new BulletContentLine("mob.dir — facing direction"), new BulletContentLine("mob.overlays, mob.underlays — image lists"), }, new Section("Procs") { new BulletContentLine("Login() — called when a mob enters the world"), new BulletContentLine("Logout() — called when a mob leaves"), new BulletContentLine("Life() — called every tick"), new BulletContentLine("Click() — called on mouse click"), new BulletContentLine("Bump(atom/A) — called on collision"), }, } }, new() { Id = "obj", Title = "/obj", Summary = "Object type for items and world objects.", Categories = { "types" }, SeeAlso = "mob, turf, image", Sections = { new Section("Default Variables") { new BulletContentLine("obj.name, obj.icon, obj.icon_state"), new BulletContentLine("obj.x, obj.y, obj.z"), new BulletContentLine("obj.density, obj.dir"), }, } }, new() { Id = "turf", Title = "/turf", Summary = "Map tile type.", Categories = { "types" }, SeeAlso = "obj, area, map.cpm", Sections = { new Section("Default Variables") { new BulletContentLine("turf.name, turf.icon, turf.icon_state"), new BulletContentLine("turf.x, turf.y, turf.z"), new BulletContentLine("turf.density"), }, new Section("Notes") { new BulletContentLine("Turfs are edited in the Map Editor and saved to .cpm files."), new BulletContentLine("Use turf/floor, turf/wall, etc. to define subtypes."), }, } }, new() { Id = "area", Title = "/area", Summary = "Non-solid area type.", Categories = { "types" }, SeeAlso = "turf, obj", Sections = { new Section("Default Variables") { new BulletContentLine("area.name, area.icon, area.icon_state"), }, } }, new() { Id = "client", Title = "/client", Summary = "Player connection type.", Categories = { "types" }, SeeAlso = "mob, statpanel, browse", Sections = { new Section("Default Variables") { new BulletContentLine("client.mob — the connected mob"), new BulletContentLine("client.ckey — lowercase key"), new BulletContentLine("client.view — view radius"), new BulletContentLine("client.statpanel — active stat panel"), new BulletContentLine("client.verbs — visible verb list"), new BulletContentLine("client.show_verb_panel"), }, } }, new() { Id = "world", Title = "/world", Summary = "The world singleton type.", Categories = { "types" }, SeeAlso = "locate, view, sleep, spawn", Sections = { new Section("Default Variables") { new BulletContentLine("world.name, world.time, world.tick_lag, world.fps"), new BulletContentLine("world.maxx, world.maxy, world.maxz"), new BulletContentLine("world.mob, world.obj, world.turf, world.area"), new BulletContentLine("world.client, world.list, world.matrix, world.color, world.savefile, world.icon, world.sound, world.image, world.screen"), }, new Section("Notes") { new BulletContentLine("world << value outputs text."), new BulletContentLine("world.savefile returns the world savefile."), }, } }, new() { Id = "preprocessor", Title = "Preprocessor", Summary = "Compile-time directives.", Categories = { "builtins" }, SeeAlso = "#include, #define, #undef, #ifdef", Sections = { new Section("Directives") { new BulletContentLine("#include \"file.dm\" — includes a source file"), new BulletContentLine("#define NAME value — defines a macro"), new BulletContentLine("#undef NAME — undefines a macro"), new BulletContentLine("#ifdef NAME / #ifndef NAME — conditional compilation"), }, new Section("Notes") { new BulletContentLine("Includes are relative to the current file's directory."), new BulletContentLine("Macros expand inside [bracket] interpolation."), }, } }, new() { Id = "#include", Title = "#include", Summary = "Includes a source file at compile time.", Categories = { "preprocessor" }, SeeAlso = "#define, #ifdef", Sections = { new Section("Examples") { new CodeContentLine("#include \"code.dm\"\n#include "), }, new Section("Notes") { new BulletContentLine("Quoted includes are relative to the current file."), new BulletContentLine("Angle-bracket includes use the project include path."), }, } }, new() { Id = "#define", Title = "#define", Summary = "Defines a preprocessor macro.", Categories = { "preprocessor" }, SeeAlso = "#undef, #ifdef", Sections = { new Section("Examples") { new CodeContentLine("#define MAX_PLAYERS 16\n#define GREETING \"Hello, [name]!\""), }, new Section("Notes") { new BulletContentLine("Macros expand in [bracket] interpolation."), }, } }, new() { Id = "#ifdef", Title = "#ifdef / #ifndef", Summary = "Conditional compilation blocks.", Categories = { "preprocessor" }, SeeAlso = "#define, #undef, #else, #endif", Sections = { new Section("Examples") { new CodeContentLine("#ifdef DEBUG\n world << \"Debug mode\"\n#endif"), }, } }, new() { Id = "icon_state", Title = "icon_state", Summary = "Selects a state from an icon file.", Categories = { "builtins" }, SeeAlso = "icon, flick, image", Sections = { new Section("Examples") { new CodeContentLine("obj/coin\n icon = \"obj.cpi\"\n icon_state = \"gold_coin\""), }, new Section("Notes") { new BulletContentLine("icon_state matches the state name in the .cpi Description chunk."), new BulletContentLine("Use the Icon Editor to add or rename states."), }, } }, new() { Id = "dir", Title = "dir", Summary = "Atom facing direction.", Categories = { "builtins" }, SeeAlso = "turn, SOUTH, NORTH, EAST, WEST", Sections = { new Section("Directions") { new BulletContentLine("SOUTH = 1, NORTH = 2, EAST = 4, WEST = 8"), new BulletContentLine("SOUTHEAST = 5, SOUTHWEST = 9, NORTHEAST = 6, NORTHWEST = 10"), }, new Section("Notes") { new BulletContentLine("dir affects which icon frame is displayed."), new BulletContentLine("turn() rotates by angle; turn(src, 90) turns east to north."), }, } }, new() { Id = "pixel_x", Title = "pixel_x / pixel_y", Summary = "Pixel offset for fine positioning.", Categories = { "builtins" }, SeeAlso = "icon_state, image", Sections = { new Section("Range") { new BulletContentLine("pixel_x: -16 to 16"), new BulletContentLine("pixel_y: -16 to 16"), }, new Section("Examples") { new CodeContentLine("obj/bullet\n pixel_x = rand(-4, 4)\n pixel_y = rand(-4, 4)"), }, } }, new() { Id = "density", Title = "density", Summary = "Collision flag.", Categories = { "builtins" }, SeeAlso = "Bump, Click, Enter, Exit", Sections = { new Section("Examples") { new CodeContentLine("turf/wall\n density = 1\nturf/floor\n density = 0"), }, new Section("Notes") { new BulletContentLine("Dense atoms block movement."), new BulletContentLine("Bump() is called when a dense movable hits a dense atom."), }, } }, new() { Id = "Click", Title = "Click", Summary = "Called when an atom is clicked.", Categories = { "verbs" }, SeeAlso = "Bump, Enter, Exit", Sections = { new Section("Examples") { new CodeContentLine("obj/button\n Click()\n world << \"Button clicked\""), }, new Section("Notes") { new BulletContentLine("Click() is a proc on the clicked atom."), new BulletContentLine("src is the atom; usr is the clicking mob."), }, } }, new() { Id = "Bump", Title = "Bump", Summary = "Called when a movable bumps into a dense atom.", Categories = { "verbs" }, SeeAlso = "Click, Enter, Exit", Sections = { new Section("Examples") { new CodeContentLine("obj/wall\n density = 1\n Bump(atom/A)\n world << \"Bumped [A]\""), }, new Section("Notes") { new BulletContentLine("Only triggered when both objects are density."), }, } }, new() { Id = "Enter", Title = "Enter / Exit", Summary = "Called when a movable enters or exits an atom's footprint.", Categories = { "verbs" }, SeeAlso = "Click, Bump", Sections = { new Section("Examples") { new CodeContentLine("turf/lava\n Enter(atom/movable/A)\n if (ismob(A))\n A.hp -= 1\n return ..()"), }, } }, new() { Id = "statpanel", Title = "statpanel", Summary = "Selects the active stat panel.", Categories = { "builtins" }, SeeAlso = "stat, stat_tabs", Sections = { new Section("Examples") { new CodeContentLine("statpanel(\"mob\")\nstat(\"Name\", name)\nstat(\"HP\", hp)"), }, new Section("Notes") { new BulletContentLine("Stat panels appear in the Dream Seeker sidebar."), }, } }, new() { Id = "savefile", Title = "savefile", Summary = "Persistent key-value save data.", Categories = { "types" }, SeeAlso = "ExportText, ImportText", Sections = { new Section("Examples") { new CodeContentLine("var/savefile/S = new/savefile()\nS[\"player\"] << name\nS[\"score\"] << score\nS.ExportText(\"save.json\")"), }, new Section("Notes") { new BulletContentLine("Savefiles are exported as JSON."), new BulletContentLine("Use ImportText() to load."), }, } }, new() { Id = "matrix", Title = "matrix", Summary = "3x3 transformation matrix for 2D transforms.", Categories = { "types" }, SeeAlso = "Turn, Scale, Translate", Sections = { new Section("Default Variables") { new BulletContentLine("matrix.a, matrix.b, matrix.c\nmatrix.d, matrix.e, matrix.f\nmatrix.g, matrix.h, matrix.i"), }, new Section("Examples") { new CodeContentLine("var/matrix/M = new/matrix()\nM.Turn(45)\nM.Scale(2, 2)"), }, } }, new() { Id = "color", Title = "color", Summary = "RGBA color value.", Categories = { "types" }, SeeAlso = "rgb, rgba", Sections = { new Section("Default Variables") { new BulletContentLine("color.r, color.g, color.b, color.a — 0 to 255"), }, new Section("Examples") { new CodeContentLine("var/color/c = new/color(255, 0, 0, 255) // red"), }, } }, new() { Id = "list-comprehension", Title = "List transformations", Summary = "Choose a single-purpose list operation. Each operation has its own article.", Categories = { "lists" }, SeeAlso = "list,sort(),filter(),map(),reduce()", Sections = { new Section("Choose an operation") { new ParagraphContentLine("Use sort() to order entries, filter() to select entries, map() to transform entries, or reduce() to combine entries into one value. Each linked page documents only that operation."), }, new Section("Beginner example") { new CodeContentLine("var/list/numbers = list(3, 1, 2)\nsort(numbers)\nworld << numbers[1]") }, new Section("Expert example") { new CodeContentLine("var/list/positive = filter(values, /proc/is_positive)\nvar/list/labels = map(positive, /proc/format_value)\nvar/total = reduce(positive, /proc/add_values, 0)") }, } }, new() { Id = "syntax", Title = "Craft++ Syntax", Summary = "Quick reference for language syntax.", Categories = { "all" }, SeeAlso = "var,proc,if,for,switch,try,operators-index,type-paths,strings,comments", Sections = { new Section("Structure") { new BulletContentLine("Blocks can use braces or DM-style indentation."), new BulletContentLine("Statements end with newline or semicolon."), new BulletContentLine("Comments: // single line"), new BulletContentLine("Strings use double quotes; [expr] interpolates."), }, new Section("Type Paths") { new BulletContentLine("Type paths are slash-separated: /mob/player"), new BulletContentLine("Declare with a type path: var/mob/player/p = new/mob/player()"), new BulletContentLine("Inherit with colons: turf/wall : turf"), }, new Section("Indentation") { new BulletContentLine("Indentation is parsed into block structure when braces are omitted."), new BulletContentLine("Mixed brace + indentation style is supported."), }, } }, new() { Id = "byond-516-modern-apis", Title = "BYOND 516 Modern APIs", Summary = "Compatibility notes for the current BYOND 516 language and runtime additions supported by Craft++.", Categories = { "builtins", "types", "operators", "preprocessor" }, SeeAlso = "vector, pixloc, alist, matrix, preprocessor", Sections = { new Section("Language additions") { new BulletContentLine("vector() and pixloc() provide numeric movement/location primitives."), new BulletContentLine("/vector values expose x, y, z, len, and size fields with Dot, Cross, Normalize, Interpolate, and Turn methods."), new BulletContentLine("vector(existing_vector) constructs a vector from an existing vector; a /vector.Copy() proc is not listed in the BYOND 516 reference."), new BulletContentLine("pixloc(existing_pixloc) constructs a pixloc copy; confirm whether Craft++'s separate Copy() method is available before relying on it."), new BulletContentLine("/client.Login() and /client.Logout() expose typed connection lifecycle hooks and update the headless connected state."), new BulletContentLine("Client movement methods Move, Center, and the eight directional helpers are registered and return headless movement success."), new BulletContentLine("Client command, topic, stat, mouse, sound, API, upload, passport, and rendering procedure names are registered for overrides and dynamic dispatch."), new BulletContentLine("Atom lifecycle, mouse, collision, entry/exit, click, stat, and movable movement hooks are registered through the /atom hierarchy."), new BulletContentLine("The documented /list type is registered with len and Add/Copy/Cut/Find/Insert/Join/Remove/RemoveAll/Splice/Swap procedures for IDE type resolution."), new BulletContentLine("The /matrix type includes the documented New constructor hook alongside its transformation procedures."), new BulletContentLine("The /regex type includes the documented New constructor hook alongside its matching procedures."), new BulletContentLine("Client identity/runtime fields include byond_build, connection, fps, bounds, and authenticate for current 516 compatibility."), new BulletContentLine("World lifecycle, administration, credits, medal, configuration, topic, and import/export procedure names are registered for dynamic dispatch and overrides."), new BulletContentLine("Base /datum New, Del, Read, Write, and Topic hooks are registered and inherited by typed datums."), new BulletContentLine("Datum reflection exposes live type, parent_type, tag, vars, and procs values for runtime introspection."), new BulletContentLine("/generator values expose generator_type, a, b, and distribution and support Rand()."), new BulletContentLine("/regex values expose pattern, flags, match metadata, and Match, Find, Replace, and Split methods."), new BulletContentLine("/matrix exposes Turn, Scale, Translate, Multiply, Add, Subtract, Invert, and Interpolate methods."), new BulletContentLine("/sound exposes Play and Stop methods alongside linked and 3D sound fields."), new BulletContentLine("/savefile exposes ExportText, ImportText, Lock, Unlock, Flush, Read, and Write methods."), new BulletContentLine("Specialized datum methods are available through both dotted calls and dynamic call()/event dispatch."), new BulletContentLine("List methods, including Length, Add, Remove, Find, Sort, and related helpers, are also available through dynamic dispatch."), new BulletContentLine("Core typed datums /image, /screen, /matrix, /color, /icon, and /savefile expose their registered fields to completion and the parity audit."), new BulletContentLine("The API inventory reports registered global handlers, modern type fields, and typed procedures separately; registration does not prove behavior parity."), new BulletContentLine("ByondHeadlessSmoke.VerifyAsync provides a WPF-free audit and dynamic-dispatch smoke check for CI and headless environments."), new BulletContentLine("Launching the headless net8.0 target with --parity-smoke runs the compatibility smoke check without WPF or the IDE window."), new BulletContentLine("The command prints registration counts for each API layer before reporting PASS; it does not calculate an overall parity percentage."), new BulletContentLine("The smoke check also exercises database connection, query creation, execution, columns, row iteration, and column retrieval through a host adapter."), new BulletContentLine("The smoke check additionally validates compatibility pragmas, screen_loc pixel layout, and particle attach/detach lifecycle."), new BulletContentLine("The smoke check validates the 516.1688 world metadata, live world.area object, and /callee introspection fields."), new BulletContentLine("The /callee smoke check compiles and invokes a real object procedure that reads callee.name."), new BulletContentLine("Nested procedure invocation is smoke-tested through caller.name as well."), new BulletContentLine("nameof() is smoke-tested against callee and world values, including their runtime names."), new BulletContentLine("Headless sound Play and Stop preserve /sound status semantics even when no audio backend is present."), new BulletContentLine("/sound exposes extended 516 fields including offset, len, pan, params, pitch, environment, and echo."), new BulletContentLine("The smoke check exercises the /filter typed datum and /particles field mutation for modern appearance APIs."), new BulletContentLine("filter(type, name?) constructs a /filter appearance datum; filter(list, ...) remains the list operation."), new BulletContentLine("Assigning a named /filter to atom.filters stores it in a keyed filter list for name lookup."), new BulletContentLine("Named filter syntax such as filter(type=\"blur\", name=\"foo\", size=1) is parsed and evaluated into a /filter datum."), new BulletContentLine("The smoke check exercises global vector() and generator() construction with Dot() and Rand() dispatch."), new BulletContentLine("vector() accepts numeric components and string forms separated by commas or x/X."), new BulletContentLine("The smoke check validates regex Replace/Split and matrix Scale/Invert behavior in addition to their registered APIs."), new BulletContentLine("lerp() interpolates numbers, vectors, pixlocs, and matrices using their typed fields; all three object forms are smoke-tested."), new BulletContentLine("The /pixloc typed datum exposes tile coordinates, step offsets, and loc for 516 movement/location APIs."), new BulletContentLine("Deleting the built-in world.area now creates a replacement /area instance, matching BYOND 516.1688."), new BulletContentLine("world.contents, world.log, and world.view are exposed as typed world members and included in the parity audit."), new BulletContentLine("The client audit covers identity, camera, view, images, screen, DPI, and BYOND version fields."), new BulletContentLine("Modern atom.pixloc includes bound_x and bound_y offsets, matching bound_pixloc(atom, SOUTHWEST)."), new BulletContentLine("Runtime-created /file, /proc, and /missile values are registered for semantic analysis, completion, and parity auditing."), new BulletContentLine("newlist(N) creates N null slots; newlist(value1, value2, ...) preserves item-construction behavior."), new BulletContentLine("Negative numeric newlist sizes safely produce an empty list, matching current BYOND behavior."), new BulletContentLine("Sized list forms with multiple numeric dimensions create nested null-filled lists, matching new/list(width, height, ...)."), new BulletContentLine("typesof(type1, type2, ...) returns the deduplicated descendants of every supplied base type."), new BulletContentLine("Procedure calls accept named arguments such as Proc(second=2, first=1); named values bind by parameter name while positional values retain their order."), new BulletContentLine("arguments() with no explicit values exposes the active procedure's passed argument list; arguments(a, b) constructs an explicit list."), new BulletContentLine("list(key=value, ...) creates an associative list; positional and named entries may be mixed."), new BulletContentLine("List.Copy(Start=..., End=...) and List.Cut(Start=..., End=...) accept BYOND-style named bounds."), new BulletContentLine("List.Add(list) appends the list's items, preserves source associations, and returns null rather than inserting a nested list, matching BYOND."), new BulletContentLine("List.Cut(Start, End) removes the selected range from the source list and returns null."), new BulletContentLine("List.Insert(Index, ...) flattens list arguments and returns the index following the inserted items."), new BulletContentLine("List.Remove(...) flattens list arguments, removes one matching item per argument from the end, and returns a boolean success value."), new BulletContentLine("List.RemoveAll(...) flattens list arguments, removes every matching occurrence, and returns the number removed."), new BulletContentLine("List.Find(Elem, Start, End) searches only the half-open requested index range."), new BulletContentLine("List.Splice(Start, End, ...) mutates the source, flattens inserted list arguments, and performs cut-and-insert replacement."), new BulletContentLine("List.Join(Glue, Start, End) joins only the requested half-open list range."), new BulletContentLine("List.Swap(Index1, Index2) exchanges items in place and returns null."), new BulletContentLine("Typed matrix and vector methods normalize documented named parameters such as Scale(x=2, y=3) and Interpolate(vector=v, amount=0.5)."), new BulletContentLine("The in operator is parsed as an expression and tests membership in positional or associative lists."), new BulletContentLine("alist() is marked associative-only: key lookup works, while numeric random access returns null."), new BulletContentLine("values_dot() matches associative keys; values_cut_over/under preserve qualifying key/value pairs."), new BulletContentLine("values_cut_over/under also preserve the filtered associative keys for list iteration."), new BulletContentLine("alist() creates an associative-only list; values_sum(), values_product(), values_dot(), values_cut_over(), and values_cut_under() operate on associated values."), new BulletContentLine("astype() performs a typed conditional cast, and <=> is the three-way comparison operator."), new BulletContentLine("load_ext() can preload an external procedure for call_ext()."), new BulletContentLine("#pragma syntax C enables C-style for/switch behavior; #pragma compatibility selects a BYOND compatibility target.") }, new Section("Runtime/display additions") { new BulletContentLine("Appearance anchor vars icon_w and icon_z are supported in the type system."), new BulletContentLine("/filter and /particles typed datums expose the documented appearance and particle fields for headless and graphical runtimes."), new BulletContentLine("/appearance provides a reusable render description with icon, transform, overlay, underlay, filter, and anchor fields."), new BulletContentLine("Reading atom.appearance materializes an /appearance snapshot from the atom's current visual fields."), new BulletContentLine("Assigning atom.particles attaches a particle set; assigning null detaches it and clears the active attachment."), new BulletContentLine("516.1680+ browser controls expose browser-options, inner-background-color, mouse-pos variants, and the byond-ready event."), new BulletContentLine("Predefined compatibility macros DM_VERSION=516, BYOND_VERSION=516, and BYOND_BUILD=1688 are available to conditional compilation."), new BulletContentLine("#pragma compatibility N updates DM_VERSION/BYOND_VERSION for version guards; #pragma push and #pragma pop restore the compatibility state."), new BulletContentLine("#pragma math fast|accurate and #pragma syntax C|DM expose the selected DM_MATH and DM_SYNTAX modes to conditional compilation."), new BulletContentLine("#pragma warn NAME records a warning; #pragma ignore NAME and #pragma error NAME record the selected warning policy."), new BulletContentLine("#pragma multiple permits a previously included source file to be included again."), new BulletContentLine("world.byond_version and world.byond_build identify the active compatibility baseline as 516.1688."), new BulletContentLine("world.area is initialized as a live /area instance so map ownership and area replacement remain object-backed."), new BulletContentLine("screen_loc parsing accepts numeric coordinates, CENTER/MIDDLE and directional anchors, SCREEN_* anchors, map prefixes, percentage-style offsets, and pixel offsets."), new BulletContentLine("ScreenLocLayout.Resolve converts parsed locations into viewport pixel coordinates for HUD/map renderers."), new BulletContentLine("client.screen is a collection of screen objects; ScreenLocLayout.TryResolve provides the renderer-facing coordinate conversion."), new BulletContentLine("Named animation/filter features and ANIMATION_END_LOOP are represented by the runtime compatibility surface."), new BulletContentLine("SCREEN_* screen location keywords, MIDDLE positioning, browser mouse coordinates, and DPI are documented compatibility targets."), new BulletContentLine("sound.atom and sound.transform support moving/3D-linked sounds where the active client runtime provides them.") }, new Section("Database API surface") { new BulletContentLine("/database and /database/query types are available for host database adapters."), new BulletContentLine("Query methods include Add, Clear, Close, Columns, Error, ErrorMsg, Execute, GetColumn, GetRowData, New, NextRow, Reset, and RowsAffected."), new BulletContentLine("Proc introspection exposes /callee values through callee and caller in invoked procedures.") }, new Section("Compatibility") { new ParagraphContentLine("Craft++ tracks official BYOND 516 behavior in measured phases. A recognized API is not treated as behaviorally complete until its conformance tests pass.") } } }, }; private static void EnsureBeginnerAndExpertExamples(ReferenceTopic topic) { var code = topic.Sections.SelectMany(s => s.Lines).OfType().ToList(); var beginner = code.FirstOrDefault()?.Code ?? $"world << \"Reference topic: {topic.Title}\""; var expert = code.LastOrDefault()?.Code ?? $"proc/reference_{topic.Id.Replace('-', '_')}()\n return \"{topic.Title}\""; if (!topic.Sections.Any(s => string.Equals(s.Heading, "Beginner example", StringComparison.OrdinalIgnoreCase))) topic.Sections.Add(new Section("Beginner example", new CodeContentLine(beginner))); if (!topic.Sections.Any(s => string.Equals(s.Heading, "Expert example", StringComparison.OrdinalIgnoreCase))) topic.Sections.Add(new Section("Expert example", new CodeContentLine(expert))); } private static Section Section(params ContentLine[] lines) => new Section { Lines = lines.ToList() }; } public sealed class Section : IEnumerable { public List Lines { get; set; } = new(); public string? Heading { get; set; } public Section() { } public Section(string heading, params ContentLine[] lines) { Heading = heading; Lines = lines.ToList(); } public void Add(ContentLine line) => Lines.Add(line); public IEnumerator GetEnumerator() => Lines.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => Lines.GetEnumerator(); public static implicit operator Section(ContentLine line) => new Section { Lines = new List { line } }; }