Skip to content

Commit 8b6df5d

Browse files
Match reference search words and show method enum choices
1 parent d5158fd commit 8b6df5d

4 files changed

Lines changed: 129 additions & 41 deletions

File tree

‎VS Code Extension/out/ser-language.json‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@
327327
"description": "Formats a duration into a special format.",
328328
"subgroup": "Time",
329329
"essential": false,
330-
"additionalDescription": "Here are some examples: \n\n#1) \"HH:SS\"\nShows only minutes and seconds, perfect for round timers or cooldowns.\nResult: 01:30\n\n#2) \"DD days, HH hours and MM minutes\"\nCreates a readable sentence. The system automatically handles the spaces and commas.\nResult: 2 days, 04 hours and 15 minutes\n\n#3) \"MMm SSs FFFms\"\nDisplays minutes, seconds, and milliseconds for pinpoint accuracy.\nResult: 01m 22s 450ms",
330+
"additionalDescription": "Here are some examples: \n\n#1) \"MM:SS\"\nShows only minutes and seconds, perfect for round timers or cooldowns.\nResult: 01:30\n\n#2) \"DD days, HH hours and MM minutes\"\nCreates a readable sentence. The system automatically handles the spaces and commas.\nResult: 2 days, 04 hours and 15 minutes\n\n#3) \"MMm SSs FFFms\"\nDisplays minutes, seconds, and milliseconds for pinpoint accuracy.\nResult: 01m 22s 450ms",
331331
"requiredFramework": null,
332332
"returns": "text value",
333333
"arguments": [
@@ -351,7 +351,7 @@
351351
"argumentKind": "TextArgument",
352352
"mustBeProvided": true,
353353
"type": "Any text e.g. \"Hello, World!\"",
354-
"description": "Use these tags to format your time: DD for days, HH for hours, MM for minutes, and SS for seconds. \nYou can separate them with colons (:) or dashes (-).",
354+
"description": "Use these tags to format your time: DD for days, HH for hours, MM for minutes, and SS for seconds. \nFFF shows milliseconds. Any other text, including apostrophes, is shown exactly as you wrote it.",
355355
"defaultValue": null,
356356
"consumesRemainingValues": false,
357357
"syntax": "\"format\"",
@@ -2539,7 +2539,9 @@
25392539
"options": null
25402540
}
25412541
],
2542-
"errors": []
2542+
"errors": [
2543+
"The chosen property cannot be used to filter players. Pick a real player property, not 'none'."
2544+
]
25432545
},
25442546
"Intersect": {
25452547
"syntax": "Intersect @first_value @other_values...",
@@ -2612,7 +2614,7 @@
26122614
},
26132615
"Overlapping": {
26142616
"syntax": "Overlapping @first_value @other_values...",
2615-
"description": "Checks if provided player variables have the exact same players.",
2617+
"description": "Checks if provided player variables have the exact same players, in any order.",
26162618
"subgroup": "PlayerValue",
26172619
"essential": false,
26182620
"additionalDescription": null,
@@ -4235,10 +4237,10 @@
42354237
},
42364238
"AddPickupToInventory": {
42374239
"syntax": "AddPickupToInventory @player *pickup",
4238-
"description": "Forces a pickup to be added to the player's inventory.",
4240+
"description": "Moves a pickup from the ground into the player's inventory.",
42394241
"subgroup": "Pickup",
42404242
"essential": false,
4241-
"additionalDescription": "Pickup will not be added if the player's inventory is full.",
4243+
"additionalDescription": "The pickup disappears from the ground once the player has it. If the player's inventory is full, nothing happens and the pickup stays where it is.",
42424244
"requiredFramework": null,
42434245
"returns": null,
42444246
"arguments": [
@@ -4964,10 +4966,10 @@
49644966
"arguments": [
49654967
{
49664968
"name": "number to round",
4967-
"argumentKind": "FloatArgument",
4969+
"argumentKind": "ValueArgument`1",
49684970
"mustBeProvided": true,
4969-
"type": "Any number",
4970-
"description": null,
4971+
"type": "a value of type NumberValue",
4972+
"description": "Any number.",
49714973
"defaultValue": null,
49724974
"consumesRemainingValues": false,
49734975
"syntax": "number_to_round",
@@ -4981,8 +4983,8 @@
49814983
"name": "decimal places",
49824984
"argumentKind": "IntArgument",
49834985
"mustBeProvided": false,
4984-
"type": "A whole number bigger or equal 0",
4985-
"description": "The number of decimal places to round to.",
4986+
"type": "A whole number between 0 and 28 (inclusive)",
4987+
"description": "The number of decimal places to round to, from 0 to 28. Halves round away from zero, so 2.5 becomes 3.",
49864988
"defaultValue": "rounds to whole number (0)",
49874989
"consumesRemainingValues": false,
49884990
"syntax": "decimal_places?",
@@ -10410,7 +10412,7 @@
1041010412
},
1041110413
"CRole.SetCallbacks": {
1041210414
"syntax": "CRole.SetCallbacks custom_role on_spawning? on_removing?",
10413-
"description": "Sets the callbacks for a provided custom role. Calling it again from the same script replaces that role's callbacks without changing callbacks for other roles.",
10415+
"description": "Sets the callbacks for a provided custom role. Calling it again from the same script replaces both of that role's callbacks: a callback you leave out is turned off. Callbacks for other roles and from other scripts stay as they are.",
1041410416
"subgroup": "CustomRole",
1041510417
"essential": false,
1041610418
"additionalDescription": null,
@@ -11323,7 +11325,7 @@
1132311325
"argumentKind": "DurationArgument",
1132411326
"mustBeProvided": true,
1132511327
"type": "Duration in format #ms, #s, #m, #h (e.g. 5s or 2m)",
11326-
"description": null,
11328+
"description": "Broadcasts are shown in whole seconds, so a part of a second rounds up (500ms shows for 1 second). The longest broadcast is 65535 seconds (about 18 hours).",
1132711329
"defaultValue": null,
1132811330
"consumesRemainingValues": false,
1132911331
"syntax": "duration",
@@ -11349,7 +11351,9 @@
1134911351
"options": null
1135011352
}
1135111353
],
11352-
"errors": []
11354+
"errors": [
11355+
"A broadcast can last at most 65535 seconds (about 18 hours). Use a shorter duration."
11356+
]
1135311357
},
1135411358
"ClearBroadcasts": {
1135511359
"syntax": "ClearBroadcasts @players",
@@ -16306,7 +16310,7 @@
1630616310
},
1630716311
{
1630816312
"name": "arguments",
16309-
"description": "The arguments that this command expects in order to run. \nThe script cannot run unless every single argument is specified. \n\nWhen the command is ran, the provided values for the arguments turn into their own literal local variables for you to use in the script. \nFor example: making a command with an argument 'name' will then create a local variable '$name' in your script.\n\nIf you want some arguments to be optional, you can use the '?' symbol after the argument name. \nFor example: 'reason?' argument will be optional, and if the sender does not provide a value for it, the script will still run.\nThen, the '$reason' command will not be created in your script.\n\n(the '?' suffix is NOT added to the variable names)",
16313+
"description": "The arguments that this command expects in order to run. \nThe script cannot run unless every single argument is specified. \n\nWhen the command is ran, the provided values for the arguments turn into their own literal local variables for you to use in the script. \nFor example: making a command with an argument 'name' will then create a local variable '$name' in your script.\nThe variable keeps your exact spelling, so an argument 'Name' creates '$Name'.\nEach argument needs a different name (upper and lower case count as the same).\n\nIf you want some arguments to be optional, you can use the '?' symbol after the argument name. \nFor example: 'reason?' argument will be optional, and if the sender does not provide a value for it, the script will still run.\nThen, the '$reason' command will not be created in your script.\n\n(the '?' suffix is NOT added to the variable names)",
1631016314
"required": false,
1631116315
"example": "-- arguments id time reason?"
1631216316
}

‎website/data/ser-truth-table.json‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@
327327
"description": "Formats a duration into a special format.",
328328
"subgroup": "Time",
329329
"essential": false,
330-
"additionalDescription": "Here are some examples: \n\n#1) \"HH:SS\"\nShows only minutes and seconds, perfect for round timers or cooldowns.\nResult: 01:30\n\n#2) \"DD days, HH hours and MM minutes\"\nCreates a readable sentence. The system automatically handles the spaces and commas.\nResult: 2 days, 04 hours and 15 minutes\n\n#3) \"MMm SSs FFFms\"\nDisplays minutes, seconds, and milliseconds for pinpoint accuracy.\nResult: 01m 22s 450ms",
330+
"additionalDescription": "Here are some examples: \n\n#1) \"MM:SS\"\nShows only minutes and seconds, perfect for round timers or cooldowns.\nResult: 01:30\n\n#2) \"DD days, HH hours and MM minutes\"\nCreates a readable sentence. The system automatically handles the spaces and commas.\nResult: 2 days, 04 hours and 15 minutes\n\n#3) \"MMm SSs FFFms\"\nDisplays minutes, seconds, and milliseconds for pinpoint accuracy.\nResult: 01m 22s 450ms",
331331
"requiredFramework": null,
332332
"returns": "text value",
333333
"arguments": [
@@ -351,7 +351,7 @@
351351
"argumentKind": "TextArgument",
352352
"mustBeProvided": true,
353353
"type": "Any text e.g. \"Hello, World!\"",
354-
"description": "Use these tags to format your time: DD for days, HH for hours, MM for minutes, and SS for seconds. \nYou can separate them with colons (:) or dashes (-).",
354+
"description": "Use these tags to format your time: DD for days, HH for hours, MM for minutes, and SS for seconds. \nFFF shows milliseconds. Any other text, including apostrophes, is shown exactly as you wrote it.",
355355
"defaultValue": null,
356356
"consumesRemainingValues": false,
357357
"syntax": "\"format\"",
@@ -2539,7 +2539,9 @@
25392539
"options": null
25402540
}
25412541
],
2542-
"errors": []
2542+
"errors": [
2543+
"The chosen property cannot be used to filter players. Pick a real player property, not 'none'."
2544+
]
25432545
},
25442546
"Intersect": {
25452547
"syntax": "Intersect @first_value @other_values...",
@@ -2612,7 +2614,7 @@
26122614
},
26132615
"Overlapping": {
26142616
"syntax": "Overlapping @first_value @other_values...",
2615-
"description": "Checks if provided player variables have the exact same players.",
2617+
"description": "Checks if provided player variables have the exact same players, in any order.",
26162618
"subgroup": "PlayerValue",
26172619
"essential": false,
26182620
"additionalDescription": null,
@@ -4235,10 +4237,10 @@
42354237
},
42364238
"AddPickupToInventory": {
42374239
"syntax": "AddPickupToInventory @player *pickup",
4238-
"description": "Forces a pickup to be added to the player's inventory.",
4240+
"description": "Moves a pickup from the ground into the player's inventory.",
42394241
"subgroup": "Pickup",
42404242
"essential": false,
4241-
"additionalDescription": "Pickup will not be added if the player's inventory is full.",
4243+
"additionalDescription": "The pickup disappears from the ground once the player has it. If the player's inventory is full, nothing happens and the pickup stays where it is.",
42424244
"requiredFramework": null,
42434245
"returns": null,
42444246
"arguments": [
@@ -4964,10 +4966,10 @@
49644966
"arguments": [
49654967
{
49664968
"name": "number to round",
4967-
"argumentKind": "FloatArgument",
4969+
"argumentKind": "ValueArgument`1",
49684970
"mustBeProvided": true,
4969-
"type": "Any number",
4970-
"description": null,
4971+
"type": "a value of type NumberValue",
4972+
"description": "Any number.",
49714973
"defaultValue": null,
49724974
"consumesRemainingValues": false,
49734975
"syntax": "number_to_round",
@@ -4981,8 +4983,8 @@
49814983
"name": "decimal places",
49824984
"argumentKind": "IntArgument",
49834985
"mustBeProvided": false,
4984-
"type": "A whole number bigger or equal 0",
4985-
"description": "The number of decimal places to round to.",
4986+
"type": "A whole number between 0 and 28 (inclusive)",
4987+
"description": "The number of decimal places to round to, from 0 to 28. Halves round away from zero, so 2.5 becomes 3.",
49864988
"defaultValue": "rounds to whole number (0)",
49874989
"consumesRemainingValues": false,
49884990
"syntax": "decimal_places?",
@@ -10410,7 +10412,7 @@
1041010412
},
1041110413
"CRole.SetCallbacks": {
1041210414
"syntax": "CRole.SetCallbacks custom_role on_spawning? on_removing?",
10413-
"description": "Sets the callbacks for a provided custom role. Calling it again from the same script replaces that role's callbacks without changing callbacks for other roles.",
10415+
"description": "Sets the callbacks for a provided custom role. Calling it again from the same script replaces both of that role's callbacks: a callback you leave out is turned off. Callbacks for other roles and from other scripts stay as they are.",
1041410416
"subgroup": "CustomRole",
1041510417
"essential": false,
1041610418
"additionalDescription": null,
@@ -11323,7 +11325,7 @@
1132311325
"argumentKind": "DurationArgument",
1132411326
"mustBeProvided": true,
1132511327
"type": "Duration in format #ms, #s, #m, #h (e.g. 5s or 2m)",
11326-
"description": null,
11328+
"description": "Broadcasts are shown in whole seconds, so a part of a second rounds up (500ms shows for 1 second). The longest broadcast is 65535 seconds (about 18 hours).",
1132711329
"defaultValue": null,
1132811330
"consumesRemainingValues": false,
1132911331
"syntax": "duration",
@@ -11349,7 +11351,9 @@
1134911351
"options": null
1135011352
}
1135111353
],
11352-
"errors": []
11354+
"errors": [
11355+
"A broadcast can last at most 65535 seconds (about 18 hours). Use a shorter duration."
11356+
]
1135311357
},
1135411358
"ClearBroadcasts": {
1135511359
"syntax": "ClearBroadcasts @players",
@@ -16306,7 +16310,7 @@
1630616310
},
1630716311
{
1630816312
"name": "arguments",
16309-
"description": "The arguments that this command expects in order to run. \nThe script cannot run unless every single argument is specified. \n\nWhen the command is ran, the provided values for the arguments turn into their own literal local variables for you to use in the script. \nFor example: making a command with an argument 'name' will then create a local variable '$name' in your script.\n\nIf you want some arguments to be optional, you can use the '?' symbol after the argument name. \nFor example: 'reason?' argument will be optional, and if the sender does not provide a value for it, the script will still run.\nThen, the '$reason' command will not be created in your script.\n\n(the '?' suffix is NOT added to the variable names)",
16313+
"description": "The arguments that this command expects in order to run. \nThe script cannot run unless every single argument is specified. \n\nWhen the command is ran, the provided values for the arguments turn into their own literal local variables for you to use in the script. \nFor example: making a command with an argument 'name' will then create a local variable '$name' in your script.\nThe variable keeps your exact spelling, so an argument 'Name' creates '$Name'.\nEach argument needs a different name (upper and lower case count as the same).\n\nIf you want some arguments to be optional, you can use the '?' symbol after the argument name. \nFor example: 'reason?' argument will be optional, and if the sender does not provide a value for it, the script will still run.\nThen, the '$reason' command will not be created in your script.\n\n(the '?' suffix is NOT added to the variable names)",
1631016314
"required": false,
1631116315
"example": "-- arguments id time reason?"
1631216316
}

‎website/scripts/prepare-site.mjs‎

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,59 @@ function category(directory, label, position) {
113113
fs.writeFileSync(target, `${JSON.stringify({label, position, collapsed: true}, null, 2)}\n`);
114114
}
115115

116+
// Matches the heading ids Docusaurus generates, so table links can jump to them.
117+
function headingAnchor(heading) {
118+
return heading
119+
.toLocaleLowerCase()
120+
.replace(/[^\p{L}\p{N}\s_-]/gu, '')
121+
.trim()
122+
.replace(/\s/g, '-');
123+
}
124+
125+
function enumChoicesHeading(argument) {
126+
return `Choices for ${argument.name}`;
127+
}
128+
129+
function enumTypeName(argument) {
130+
return /^(\w+) enum value/.exec(normalize(argument.type))?.[1] ?? '';
131+
}
132+
133+
// Lists the values an enum argument accepts, so authors can pick one without
134+
// running serhelp on a server.
135+
function enumChoices(argument) {
136+
const values = argument.enumValues;
137+
const descriptions = argument.enumDescriptions ?? {};
138+
const hasDescriptions = values.some(value => normalize(descriptions[value]));
139+
const typeName = enumTypeName(argument);
140+
const parts = [`### ${prose(enumChoicesHeading(argument))}`, ''];
141+
142+
if (argument.enumDescription) parts.push(prose(argument.enumDescription), '');
143+
144+
if (argument.isEnumFlags) {
145+
const combinable = values.filter(value => value !== 'None' && value !== 'All');
146+
const example = combinable.length >= 2 ? ` For example: ${inlineCode(`${combinable[0]}|${combinable[1]}`)}.` : '';
147+
parts.push(`You can combine several of these with ${inlineCode('|')}.${example}`, '');
148+
}
149+
150+
if (hasDescriptions) {
151+
parts.push(...values.map(value => {
152+
const description = normalize(descriptions[value]);
153+
return description ? `- ${inlineCode(value)}: ${prose(description)}` : `- ${inlineCode(value)}`;
154+
}));
155+
} else {
156+
parts.push(values.map(value => inlineCode(value)).join(', '));
157+
}
158+
159+
parts.push(
160+
'',
161+
typeName
162+
? `Game updates can change this list. To see the list for your server, run ${inlineCode(`serhelp ${typeName}`)}.`
163+
: 'Game updates can change this list. If your server rejects a value, check its help.',
164+
'',
165+
);
166+
return parts;
167+
}
168+
116169
function methodPage(name, method, exampleNames) {
117170
const parts = [
118171
frontMatter(name, {sidebar_label: name}),
@@ -137,11 +190,19 @@ function methodPage(name, method, exampleNames) {
137190
const fallbackDescription = argument.options?.length
138191
? `Options: ${argument.options.map(option => inlineCode(option.value)).join(', ')}`
139192
: '';
193+
const description = cell(argument.description || fallbackDescription || argument.defaultValue && `Default: ${argument.defaultValue}`);
194+
const choicesLink = argument.enumValues?.length
195+
? `[See the choices](#${headingAnchor(enumChoicesHeading(argument))}).`
196+
: '';
140197
parts.push(
141-
`| ${inlineCode(argument.name)} | ${argument.mustBeProvided ? 'Yes' : 'No'} | ${cell(argument.type || argument.argumentKind)} | ${cell(argument.description || fallbackDescription || argument.defaultValue && `Default: ${argument.defaultValue}`)} |`,
198+
`| ${inlineCode(argument.name)} | ${argument.mustBeProvided ? 'Yes' : 'No'} | ${cell(argument.type || argument.argumentKind)} | ${[choicesLink && description && !/[.!?]$/.test(description) ? `${description}.` : description, choicesLink].filter(Boolean).join(' ')} |`,
142199
);
143200
}
144201
parts.push('');
202+
203+
for (const argument of method.arguments) {
204+
if (argument.enumValues?.length) parts.push(...enumChoices(argument));
205+
}
145206
}
146207

147208
if (method.additionalDescription) {
@@ -376,14 +437,21 @@ function buildExamples() {
376437

377438
function buildReference(exampleMaps) {
378439
const constructs = [];
379-
const addConstruct = (type, name, description, syntax, group, href) => {
380-
constructs.push({type, name, description: normalize(description), syntax: normalize(syntax), group: normalize(group), href});
440+
const addConstruct = (type, name, description, syntax, group, href, keywords = []) => {
441+
const construct = {type, name, description: normalize(description), syntax: normalize(syntax), group: normalize(group), href};
442+
if (keywords.length) construct.keywords = keywords;
443+
constructs.push(construct);
381444
};
382445

383446
for (const [name, method] of Object.entries(manifest.methods).sort(([a], [b]) => a.localeCompare(b))) {
384447
const filename = `reference/methods/${slug(name)}.md`;
385448
write(filename, methodPage(name, method, exampleMaps.methodExamples.get(name) || []));
386-
addConstruct('Method', name, method.description, method.syntax, method.subgroup, `/${filename.replace(/\.md$/, '/')}`);
449+
// Enum names and their values make e.g. "ItemType" or "KeycardJanitor" find GiveItem.
450+
const keywords = [...new Set((method.arguments ?? [])
451+
.filter(argument => argument.enumValues?.length)
452+
.flatMap(argument => [enumTypeName(argument), ...argument.enumValues])
453+
.filter(Boolean))];
454+
addConstruct('Method', name, method.description, method.syntax, method.subgroup, `/${filename.replace(/\.md$/, '/')}`, keywords);
387455
}
388456

389457
for (const [name, keyword] of Object.entries(manifest.keywords).sort(([a], [b]) => a.localeCompare(b))) {

0 commit comments

Comments
 (0)