redscript-mc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish-extension.yml +35 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh.md +261 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +140 -0
- package/dist/__tests__/codegen.test.d.ts +1 -0
- package/dist/__tests__/codegen.test.js +121 -0
- package/dist/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/__tests__/diagnostics.test.js +149 -0
- package/dist/__tests__/e2e.test.d.ts +6 -0
- package/dist/__tests__/e2e.test.js +1528 -0
- package/dist/__tests__/lexer.test.d.ts +1 -0
- package/dist/__tests__/lexer.test.js +316 -0
- package/dist/__tests__/lowering.test.d.ts +1 -0
- package/dist/__tests__/lowering.test.js +819 -0
- package/dist/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/__tests__/mc-integration.test.js +395 -0
- package/dist/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/__tests__/mc-syntax.test.js +112 -0
- package/dist/__tests__/nbt.test.d.ts +1 -0
- package/dist/__tests__/nbt.test.js +82 -0
- package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/__tests__/optimizer.test.d.ts +1 -0
- package/dist/__tests__/optimizer.test.js +118 -0
- package/dist/__tests__/parser.test.d.ts +1 -0
- package/dist/__tests__/parser.test.js +717 -0
- package/dist/__tests__/repl.test.d.ts +1 -0
- package/dist/__tests__/repl.test.js +27 -0
- package/dist/__tests__/runtime.test.d.ts +1 -0
- package/dist/__tests__/runtime.test.js +276 -0
- package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/__tests__/structure-optimizer.test.js +33 -0
- package/dist/__tests__/typechecker.test.d.ts +1 -0
- package/dist/__tests__/typechecker.test.js +364 -0
- package/dist/ast/types.d.ts +357 -0
- package/dist/ast/types.js +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +407 -0
- package/dist/codegen/cmdblock/index.d.ts +26 -0
- package/dist/codegen/cmdblock/index.js +45 -0
- package/dist/codegen/mcfunction/index.d.ts +34 -0
- package/dist/codegen/mcfunction/index.js +413 -0
- package/dist/codegen/structure/index.d.ts +18 -0
- package/dist/codegen/structure/index.js +249 -0
- package/dist/compile.d.ts +30 -0
- package/dist/compile.js +152 -0
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/diagnostics/index.d.ts +44 -0
- package/dist/diagnostics/index.js +140 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +126 -0
- package/dist/ir/builder.d.ts +32 -0
- package/dist/ir/builder.js +99 -0
- package/dist/ir/types.d.ts +117 -0
- package/dist/ir/types.js +15 -0
- package/dist/lexer/index.d.ts +36 -0
- package/dist/lexer/index.js +458 -0
- package/dist/lowering/index.d.ts +106 -0
- package/dist/lowering/index.js +2041 -0
- package/dist/mc-test/client.d.ts +128 -0
- package/dist/mc-test/client.js +174 -0
- package/dist/mc-test/runner.d.ts +28 -0
- package/dist/mc-test/runner.js +150 -0
- package/dist/mc-test/setup.d.ts +11 -0
- package/dist/mc-test/setup.js +98 -0
- package/dist/mc-validator/index.d.ts +17 -0
- package/dist/mc-validator/index.js +322 -0
- package/dist/nbt/index.d.ts +86 -0
- package/dist/nbt/index.js +250 -0
- package/dist/optimizer/commands.d.ts +36 -0
- package/dist/optimizer/commands.js +349 -0
- package/dist/optimizer/passes.d.ts +34 -0
- package/dist/optimizer/passes.js +227 -0
- package/dist/optimizer/structure.d.ts +8 -0
- package/dist/optimizer/structure.js +344 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +76 -0
- package/dist/parser/index.js +1193 -0
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +165 -0
- package/dist/runtime/index.d.ts +101 -0
- package/dist/runtime/index.js +1288 -0
- package/dist/typechecker/index.d.ts +42 -0
- package/dist/typechecker/index.js +629 -0
- package/docs/COMPILATION_STATS.md +142 -0
- package/docs/IMPLEMENTATION_GUIDE.md +512 -0
- package/docs/LANGUAGE_REFERENCE.md +415 -0
- package/docs/MC_MAPPING.md +280 -0
- package/docs/STRUCTURE_TARGET.md +80 -0
- package/docs/mc-reference/commands.md +259 -0
- package/editors/vscode/.vscodeignore +10 -0
- package/editors/vscode/LICENSE +21 -0
- package/editors/vscode/README.md +78 -0
- package/editors/vscode/build.mjs +28 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/mcfunction-language-configuration.json +28 -0
- package/editors/vscode/out/extension.js +7236 -0
- package/editors/vscode/package-lock.json +566 -0
- package/editors/vscode/package.json +137 -0
- package/editors/vscode/redscript-language-configuration.json +28 -0
- package/editors/vscode/snippets/redscript.json +114 -0
- package/editors/vscode/src/codeactions.ts +89 -0
- package/editors/vscode/src/completion.ts +130 -0
- package/editors/vscode/src/extension.ts +239 -0
- package/editors/vscode/src/hover.ts +1120 -0
- package/editors/vscode/src/symbols.ts +207 -0
- package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
- package/editors/vscode/tsconfig.json +13 -0
- package/jest.config.js +5 -0
- package/package.json +38 -0
- package/src/__tests__/cli.test.ts +130 -0
- package/src/__tests__/codegen.test.ts +128 -0
- package/src/__tests__/diagnostics.test.ts +195 -0
- package/src/__tests__/e2e.test.ts +1721 -0
- package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
- package/src/__tests__/formatter.test.ts +46 -0
- package/src/__tests__/lexer.test.ts +356 -0
- package/src/__tests__/lowering.test.ts +962 -0
- package/src/__tests__/mc-integration.test.ts +409 -0
- package/src/__tests__/mc-syntax.test.ts +96 -0
- package/src/__tests__/nbt.test.ts +58 -0
- package/src/__tests__/optimizer-advanced.test.ts +144 -0
- package/src/__tests__/optimizer.test.ts +129 -0
- package/src/__tests__/parser.test.ts +800 -0
- package/src/__tests__/repl.test.ts +33 -0
- package/src/__tests__/runtime.test.ts +289 -0
- package/src/__tests__/structure-optimizer.test.ts +38 -0
- package/src/__tests__/typechecker.test.ts +395 -0
- package/src/ast/types.ts +248 -0
- package/src/cli.ts +445 -0
- package/src/codegen/cmdblock/index.ts +63 -0
- package/src/codegen/mcfunction/index.ts +471 -0
- package/src/codegen/structure/index.ts +305 -0
- package/src/compile.ts +188 -0
- package/src/diagnostics/index.ts +186 -0
- package/src/examples/README.md +77 -0
- package/src/examples/SHOWCASE_GAME.md +43 -0
- package/src/examples/arena.rs +44 -0
- package/src/examples/counter.rs +12 -0
- package/src/examples/pvp_arena.rs +131 -0
- package/src/examples/quiz.rs +90 -0
- package/src/examples/rpg.rs +13 -0
- package/src/examples/shop.rs +30 -0
- package/src/examples/showcase_game.rs +552 -0
- package/src/examples/stdlib_demo.rs +181 -0
- package/src/examples/turret.rs +27 -0
- package/src/examples/world_manager.rs +23 -0
- package/src/formatter/index.ts +22 -0
- package/src/index.ts +161 -0
- package/src/ir/builder.ts +114 -0
- package/src/ir/types.ts +119 -0
- package/src/lexer/index.ts +555 -0
- package/src/lowering/index.ts +2406 -0
- package/src/mc-test/client.ts +259 -0
- package/src/mc-test/runner.ts +140 -0
- package/src/mc-test/setup.ts +70 -0
- package/src/mc-validator/index.ts +367 -0
- package/src/nbt/index.ts +321 -0
- package/src/optimizer/commands.ts +416 -0
- package/src/optimizer/passes.ts +233 -0
- package/src/optimizer/structure.ts +441 -0
- package/src/parser/index.ts +1437 -0
- package/src/repl.ts +165 -0
- package/src/runtime/index.ts +1403 -0
- package/src/stdlib/README.md +156 -0
- package/src/stdlib/combat.rs +20 -0
- package/src/stdlib/cooldown.rs +45 -0
- package/src/stdlib/math.rs +49 -0
- package/src/stdlib/mobs.rs +99 -0
- package/src/stdlib/player.rs +29 -0
- package/src/stdlib/strings.rs +7 -0
- package/src/stdlib/timer.rs +51 -0
- package/src/templates/README.md +126 -0
- package/src/templates/combat.rs +96 -0
- package/src/templates/economy.rs +40 -0
- package/src/templates/mini-game-framework.rs +117 -0
- package/src/templates/quest.rs +78 -0
- package/src/test_programs/zombie_game.rs +25 -0
- package/src/typechecker/index.ts +737 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Builtin documentation database
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
interface BuiltinDoc {
|
|
8
|
+
signature: string
|
|
9
|
+
description: string
|
|
10
|
+
params?: { name: string; type: string; optional?: boolean; desc: string }[]
|
|
11
|
+
returns?: string
|
|
12
|
+
example?: string
|
|
13
|
+
mc?: string // compiled MC command
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const BUILTINS: Record<string, BuiltinDoc> = {
|
|
17
|
+
// --- Chat & Display ---
|
|
18
|
+
say: {
|
|
19
|
+
signature: 'say(msg: string)',
|
|
20
|
+
description: 'Broadcast a message to all players as the server.',
|
|
21
|
+
params: [{ name: 'msg', type: 'string', desc: 'Message to broadcast' }],
|
|
22
|
+
example: 'say("Hello world!");',
|
|
23
|
+
mc: 'say <msg>'
|
|
24
|
+
},
|
|
25
|
+
tell: {
|
|
26
|
+
signature: 'tell(target: selector, msg: string)',
|
|
27
|
+
description: 'Send a private message to a player or selector.',
|
|
28
|
+
params: [
|
|
29
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
30
|
+
{ name: 'msg', type: 'string', desc: 'Message to send' }
|
|
31
|
+
],
|
|
32
|
+
example: 'tell(@s, "You scored a point!");',
|
|
33
|
+
mc: 'tellraw <target> {"text":"<msg>"}'
|
|
34
|
+
},
|
|
35
|
+
announce: {
|
|
36
|
+
signature: 'announce(msg: string)',
|
|
37
|
+
description: 'Send a message to all players in chat.',
|
|
38
|
+
params: [{ name: 'msg', type: 'string', desc: 'Message text' }],
|
|
39
|
+
example: 'announce("Game over!");',
|
|
40
|
+
mc: 'tellraw @a {"text":"<msg>"}'
|
|
41
|
+
},
|
|
42
|
+
title: {
|
|
43
|
+
signature: 'title(target: selector, msg: string)',
|
|
44
|
+
description: 'Show a large title on screen for target players.',
|
|
45
|
+
params: [
|
|
46
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
47
|
+
{ name: 'msg', type: 'string', desc: 'Title text' }
|
|
48
|
+
],
|
|
49
|
+
example: 'title(@a, "Round 1");',
|
|
50
|
+
mc: 'title <target> title {"text":"<msg>"}'
|
|
51
|
+
},
|
|
52
|
+
subtitle: {
|
|
53
|
+
signature: 'subtitle(target: selector, msg: string)',
|
|
54
|
+
description: 'Show subtitle text below the title.',
|
|
55
|
+
params: [
|
|
56
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
57
|
+
{ name: 'msg', type: 'string', desc: 'Subtitle text' }
|
|
58
|
+
],
|
|
59
|
+
example: 'subtitle(@a, "Fight!");',
|
|
60
|
+
mc: 'title <target> subtitle {"text":"<msg>"}'
|
|
61
|
+
},
|
|
62
|
+
actionbar: {
|
|
63
|
+
signature: 'actionbar(target: selector, msg: string)',
|
|
64
|
+
description: 'Show text in the action bar (above hotbar).',
|
|
65
|
+
params: [
|
|
66
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
67
|
+
{ name: 'msg', type: 'string', desc: 'Action bar text' }
|
|
68
|
+
],
|
|
69
|
+
example: 'actionbar(@a, "⏱ ${time}s remaining");',
|
|
70
|
+
mc: 'title <target> actionbar {"text":"<msg>"}'
|
|
71
|
+
},
|
|
72
|
+
title_times: {
|
|
73
|
+
signature: 'title_times(target: selector, fadeIn: int, stay: int, fadeOut: int)',
|
|
74
|
+
description: 'Set title display timing (in ticks).',
|
|
75
|
+
params: [
|
|
76
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
77
|
+
{ name: 'fadeIn', type: 'int', desc: 'Fade-in ticks' },
|
|
78
|
+
{ name: 'stay', type: 'int', desc: 'Stay ticks' },
|
|
79
|
+
{ name: 'fadeOut', type: 'int', desc: 'Fade-out ticks' }
|
|
80
|
+
],
|
|
81
|
+
example: 'title_times(@a, 10, 40, 10);',
|
|
82
|
+
mc: 'title <target> times <fadeIn> <stay> <fadeOut>'
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// --- Player ---
|
|
86
|
+
give: {
|
|
87
|
+
signature: 'give(target: selector, item: string, count?: int)',
|
|
88
|
+
description: 'Give item(s) to a player.',
|
|
89
|
+
params: [
|
|
90
|
+
{ name: 'target', type: 'selector', desc: 'Target player(s)' },
|
|
91
|
+
{ name: 'item', type: 'string', desc: 'Item ID (e.g. "minecraft:diamond")' },
|
|
92
|
+
{ name: 'count', type: 'int', optional: true, desc: 'Amount (default: 1)' }
|
|
93
|
+
],
|
|
94
|
+
example: 'give(@s, "minecraft:diamond", 5);',
|
|
95
|
+
mc: 'give <target> <item> [count]'
|
|
96
|
+
},
|
|
97
|
+
kill: {
|
|
98
|
+
signature: 'kill(target?: selector)',
|
|
99
|
+
description: 'Kill entity/entities. Defaults to @s.',
|
|
100
|
+
params: [{ name: 'target', type: 'selector', optional: true, desc: 'Target (default: @s)' }],
|
|
101
|
+
example: 'kill(@e[type=minecraft:zombie]);',
|
|
102
|
+
mc: 'kill [target]'
|
|
103
|
+
},
|
|
104
|
+
effect: {
|
|
105
|
+
signature: 'effect(target: selector, effect: string, duration?: int, amplifier?: int)',
|
|
106
|
+
description: 'Apply a status effect.',
|
|
107
|
+
params: [
|
|
108
|
+
{ name: 'target', type: 'selector', desc: 'Target entity/player' },
|
|
109
|
+
{ name: 'effect', type: 'string', desc: 'Effect ID (e.g. "minecraft:speed")' },
|
|
110
|
+
{ name: 'duration', type: 'int', optional: true, desc: 'Seconds (default: 30)' },
|
|
111
|
+
{ name: 'amplifier', type: 'int', optional: true, desc: 'Level 0-255 (default: 0)' }
|
|
112
|
+
],
|
|
113
|
+
example: 'effect(@s, "minecraft:speed", 60, 1);',
|
|
114
|
+
mc: 'effect give <target> <effect> [duration] [amplifier]'
|
|
115
|
+
},
|
|
116
|
+
clear: {
|
|
117
|
+
signature: 'clear(target: selector, item?: string)',
|
|
118
|
+
description: 'Remove items from inventory.',
|
|
119
|
+
params: [
|
|
120
|
+
{ name: 'target', type: 'selector', desc: 'Target player' },
|
|
121
|
+
{ name: 'item', type: 'string', optional: true, desc: 'Specific item to remove (default: all)' }
|
|
122
|
+
],
|
|
123
|
+
example: 'clear(@s, "minecraft:dirt");',
|
|
124
|
+
mc: 'clear <target> [item]'
|
|
125
|
+
},
|
|
126
|
+
kick: {
|
|
127
|
+
signature: 'kick(player: selector, reason?: string)',
|
|
128
|
+
description: 'Kick a player from the server.',
|
|
129
|
+
params: [
|
|
130
|
+
{ name: 'player', type: 'selector', desc: 'Target player' },
|
|
131
|
+
{ name: 'reason', type: 'string', optional: true, desc: 'Kick message' }
|
|
132
|
+
],
|
|
133
|
+
example: 'kick(@s, "You lost!");',
|
|
134
|
+
mc: 'kick <player> [reason]'
|
|
135
|
+
},
|
|
136
|
+
xp_add: {
|
|
137
|
+
signature: 'xp_add(target: selector, amount: int, type?: string)',
|
|
138
|
+
description: 'Add experience to a player.',
|
|
139
|
+
params: [
|
|
140
|
+
{ name: 'target', type: 'selector', desc: 'Target player' },
|
|
141
|
+
{ name: 'amount', type: 'int', desc: 'Amount to add' },
|
|
142
|
+
{ name: 'type', type: 'string', optional: true, desc: '"points" or "levels" (default: "points")' }
|
|
143
|
+
],
|
|
144
|
+
example: 'xp_add(@s, 100);',
|
|
145
|
+
mc: 'xp add <target> <amount> [type]'
|
|
146
|
+
},
|
|
147
|
+
xp_set: {
|
|
148
|
+
signature: 'xp_set(target: selector, amount: int, type?: string)',
|
|
149
|
+
description: 'Set a player\'s experience.',
|
|
150
|
+
params: [
|
|
151
|
+
{ name: 'target', type: 'selector', desc: 'Target player' },
|
|
152
|
+
{ name: 'amount', type: 'int', desc: 'New value' },
|
|
153
|
+
{ name: 'type', type: 'string', optional: true, desc: '"points" or "levels"' }
|
|
154
|
+
],
|
|
155
|
+
example: 'xp_set(@s, 0, "levels");',
|
|
156
|
+
mc: 'xp set <target> <amount> [type]'
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// --- Teleport ---
|
|
160
|
+
tp: {
|
|
161
|
+
signature: 'tp(target: selector, destination: selector | BlockPos)',
|
|
162
|
+
description: 'Teleport entity to a player or coordinates.',
|
|
163
|
+
params: [
|
|
164
|
+
{ name: 'target', type: 'selector', desc: 'Entity to teleport' },
|
|
165
|
+
{ name: 'destination', type: 'selector | BlockPos', desc: 'Target player or position' }
|
|
166
|
+
],
|
|
167
|
+
example: 'tp(@s, (0, 64, 0));\ntp(@a, @s);',
|
|
168
|
+
mc: 'tp <target> <dest>'
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// --- World ---
|
|
172
|
+
setblock: {
|
|
173
|
+
signature: 'setblock(pos: BlockPos, block: string)',
|
|
174
|
+
description: 'Place a block at coordinates.',
|
|
175
|
+
params: [
|
|
176
|
+
{ name: 'pos', type: 'BlockPos', desc: 'Target position e.g. (0, 64, 0) or (~1, ~0, ~0)' },
|
|
177
|
+
{ name: 'block', type: 'string', desc: 'Block ID (e.g. "minecraft:stone")' }
|
|
178
|
+
],
|
|
179
|
+
example: 'setblock((0, 64, 0), "minecraft:stone");',
|
|
180
|
+
mc: 'setblock <x> <y> <z> <block>'
|
|
181
|
+
},
|
|
182
|
+
fill: {
|
|
183
|
+
signature: 'fill(from: BlockPos, to: BlockPos, block: string)',
|
|
184
|
+
description: 'Fill a region with blocks.',
|
|
185
|
+
params: [
|
|
186
|
+
{ name: 'from', type: 'BlockPos', desc: 'Start corner' },
|
|
187
|
+
{ name: 'to', type: 'BlockPos', desc: 'End corner' },
|
|
188
|
+
{ name: 'block', type: 'string', desc: 'Block to fill with' }
|
|
189
|
+
],
|
|
190
|
+
example: 'fill((0, 64, 0), (10, 64, 10), "minecraft:grass_block");',
|
|
191
|
+
mc: 'fill <x1> <y1> <z1> <x2> <y2> <z2> <block>'
|
|
192
|
+
},
|
|
193
|
+
clone: {
|
|
194
|
+
signature: 'clone(from: BlockPos, to: BlockPos, dest: BlockPos)',
|
|
195
|
+
description: 'Clone a region of blocks to a new location.',
|
|
196
|
+
params: [
|
|
197
|
+
{ name: 'from', type: 'BlockPos', desc: 'Source start corner' },
|
|
198
|
+
{ name: 'to', type: 'BlockPos', desc: 'Source end corner' },
|
|
199
|
+
{ name: 'dest', type: 'BlockPos', desc: 'Destination corner' }
|
|
200
|
+
],
|
|
201
|
+
example: 'clone((0,64,0), (10,64,10), (20,64,0));',
|
|
202
|
+
mc: 'clone <x1> <y1> <z1> <x2> <y2> <z2> <dx> <dy> <dz>'
|
|
203
|
+
},
|
|
204
|
+
summon: {
|
|
205
|
+
signature: 'summon(type: string, pos: BlockPos)',
|
|
206
|
+
description: 'Spawn an entity at a location.',
|
|
207
|
+
params: [
|
|
208
|
+
{ name: 'type', type: 'string', desc: 'Entity type ID (e.g. "minecraft:zombie")' },
|
|
209
|
+
{ name: 'pos', type: 'BlockPos', desc: 'Spawn position' }
|
|
210
|
+
],
|
|
211
|
+
example: 'summon("minecraft:zombie", (0, 64, 0));',
|
|
212
|
+
mc: 'summon <type> <x> <y> <z>'
|
|
213
|
+
},
|
|
214
|
+
weather: {
|
|
215
|
+
signature: 'weather(type: string)',
|
|
216
|
+
description: 'Set the weather.',
|
|
217
|
+
params: [{ name: 'type', type: 'string', desc: '"clear", "rain", or "thunder"' }],
|
|
218
|
+
example: 'weather("clear");',
|
|
219
|
+
mc: 'weather <type>'
|
|
220
|
+
},
|
|
221
|
+
time_set: {
|
|
222
|
+
signature: 'time_set(value: int | string)',
|
|
223
|
+
description: 'Set the world time.',
|
|
224
|
+
params: [{ name: 'value', type: 'int | string', desc: 'Time in ticks, or "day"/"night"/"noon"/"midnight"' }],
|
|
225
|
+
example: 'time_set(0); // dawn\ntime_set("noon");',
|
|
226
|
+
mc: 'time set <value>'
|
|
227
|
+
},
|
|
228
|
+
time_add: {
|
|
229
|
+
signature: 'time_add(ticks: int)',
|
|
230
|
+
description: 'Advance world time by ticks.',
|
|
231
|
+
params: [{ name: 'ticks', type: 'int', desc: 'Ticks to add' }],
|
|
232
|
+
example: 'time_add(6000);',
|
|
233
|
+
mc: 'time add <ticks>'
|
|
234
|
+
},
|
|
235
|
+
gamerule: {
|
|
236
|
+
signature: 'gamerule(rule: string, value: bool | int)',
|
|
237
|
+
description: 'Set a gamerule value.',
|
|
238
|
+
params: [
|
|
239
|
+
{ name: 'rule', type: 'string', desc: 'Gamerule name (e.g. "keepInventory")' },
|
|
240
|
+
{ name: 'value', type: 'bool | int', desc: 'New value' }
|
|
241
|
+
],
|
|
242
|
+
example: 'gamerule("keepInventory", true);\ngamerule("randomTickSpeed", 3);',
|
|
243
|
+
mc: 'gamerule <rule> <value>'
|
|
244
|
+
},
|
|
245
|
+
difficulty: {
|
|
246
|
+
signature: 'difficulty(level: string)',
|
|
247
|
+
description: 'Set the game difficulty.',
|
|
248
|
+
params: [{ name: 'level', type: 'string', desc: '"peaceful", "easy", "normal", or "hard"' }],
|
|
249
|
+
example: 'difficulty("hard");',
|
|
250
|
+
mc: 'difficulty <level>'
|
|
251
|
+
},
|
|
252
|
+
particle: {
|
|
253
|
+
signature: 'particle(name: string, pos: BlockPos)',
|
|
254
|
+
description: 'Spawn a particle effect.',
|
|
255
|
+
params: [
|
|
256
|
+
{ name: 'name', type: 'string', desc: 'Particle type (e.g. "minecraft:flame")' },
|
|
257
|
+
{ name: 'pos', type: 'BlockPos', desc: 'Position' }
|
|
258
|
+
],
|
|
259
|
+
example: 'particle("minecraft:flame", (~0, ~1, ~0));',
|
|
260
|
+
mc: 'particle <name> <x> <y> <z>'
|
|
261
|
+
},
|
|
262
|
+
playsound: {
|
|
263
|
+
signature: 'playsound(sound: string, source: string, target: selector, pos?: BlockPos, volume?: float, pitch?: float)',
|
|
264
|
+
description: 'Play a sound for a player.',
|
|
265
|
+
params: [
|
|
266
|
+
{ name: 'sound', type: 'string', desc: 'Sound event ID' },
|
|
267
|
+
{ name: 'source', type: 'string', desc: 'Category: "master", "music", "record", "weather", "block", "hostile", "neutral", "player", "ambient", "voice"' },
|
|
268
|
+
{ name: 'target', type: 'selector', desc: 'Target player' },
|
|
269
|
+
{ name: 'pos', type: 'BlockPos', optional: true, desc: 'Origin position' },
|
|
270
|
+
{ name: 'volume', type: 'float', optional: true, desc: 'Volume (default: 1.0)' },
|
|
271
|
+
{ name: 'pitch', type: 'float', optional: true, desc: 'Pitch (default: 1.0)' }
|
|
272
|
+
],
|
|
273
|
+
example: 'playsound("entity.experience_orb.pickup", "player", @s);',
|
|
274
|
+
mc: 'playsound <sound> <source> <target>'
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// --- Tags ---
|
|
278
|
+
tag_add: {
|
|
279
|
+
signature: 'tag_add(target: selector, tag: string)',
|
|
280
|
+
description: 'Add an entity tag.',
|
|
281
|
+
params: [
|
|
282
|
+
{ name: 'target', type: 'selector', desc: 'Target entity' },
|
|
283
|
+
{ name: 'tag', type: 'string', desc: 'Tag name' }
|
|
284
|
+
],
|
|
285
|
+
example: 'tag_add(@s, "hasKey");',
|
|
286
|
+
mc: 'tag <target> add <tag>'
|
|
287
|
+
},
|
|
288
|
+
tag_remove: {
|
|
289
|
+
signature: 'tag_remove(target: selector, tag: string)',
|
|
290
|
+
description: 'Remove an entity tag.',
|
|
291
|
+
params: [
|
|
292
|
+
{ name: 'target', type: 'selector', desc: 'Target entity' },
|
|
293
|
+
{ name: 'tag', type: 'string', desc: 'Tag name' }
|
|
294
|
+
],
|
|
295
|
+
example: 'tag_remove(@s, "hasKey");',
|
|
296
|
+
mc: 'tag <target> remove <tag>'
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// --- Scoreboard ---
|
|
300
|
+
scoreboard_get: {
|
|
301
|
+
signature: 'scoreboard_get(target: selector | string, objective: string) -> int',
|
|
302
|
+
description: 'Read a scoreboard value.',
|
|
303
|
+
params: [
|
|
304
|
+
{ name: 'target', type: 'selector | string', desc: 'Player/entity or fake player name (e.g. "#counter")' },
|
|
305
|
+
{ name: 'objective', type: 'string', desc: 'Scoreboard objective name' }
|
|
306
|
+
],
|
|
307
|
+
returns: 'int',
|
|
308
|
+
example: 'let hp: int = scoreboard_get(@s, "health");',
|
|
309
|
+
mc: 'scoreboard players get <target> <objective>'
|
|
310
|
+
},
|
|
311
|
+
score: {
|
|
312
|
+
signature: 'score(target: selector | string, objective: string) -> int',
|
|
313
|
+
description: 'Alias for scoreboard_get. Read a scoreboard value.',
|
|
314
|
+
params: [
|
|
315
|
+
{ name: 'target', type: 'selector | string', desc: 'Player/entity or fake player name' },
|
|
316
|
+
{ name: 'objective', type: 'string', desc: 'Scoreboard objective name' }
|
|
317
|
+
],
|
|
318
|
+
returns: 'int',
|
|
319
|
+
example: 'let kills: int = score(@s, "kills");',
|
|
320
|
+
mc: 'scoreboard players get <target> <objective>'
|
|
321
|
+
},
|
|
322
|
+
scoreboard_set: {
|
|
323
|
+
signature: 'scoreboard_set(target: selector | string, objective: string, value: int)',
|
|
324
|
+
description: 'Set a scoreboard value.',
|
|
325
|
+
params: [
|
|
326
|
+
{ name: 'target', type: 'selector | string', desc: 'Player/entity or fake player' },
|
|
327
|
+
{ name: 'objective', type: 'string', desc: 'Objective name' },
|
|
328
|
+
{ name: 'value', type: 'int', desc: 'New value' }
|
|
329
|
+
],
|
|
330
|
+
example: 'scoreboard_set("#game", "timer", 300);',
|
|
331
|
+
mc: 'scoreboard players set <target> <objective> <value>'
|
|
332
|
+
},
|
|
333
|
+
scoreboard_add: {
|
|
334
|
+
signature: 'scoreboard_add(target: selector | string, objective: string, amount: int)',
|
|
335
|
+
description: 'Add to a scoreboard value.',
|
|
336
|
+
params: [
|
|
337
|
+
{ name: 'target', type: 'selector | string', desc: 'Player/entity or fake player' },
|
|
338
|
+
{ name: 'objective', type: 'string', desc: 'Objective name' },
|
|
339
|
+
{ name: 'amount', type: 'int', desc: 'Amount to add (can be negative)' }
|
|
340
|
+
],
|
|
341
|
+
example: 'scoreboard_add(@s, "kills", 1);',
|
|
342
|
+
mc: 'scoreboard players add <target> <objective> <amount>'
|
|
343
|
+
},
|
|
344
|
+
scoreboard_display: {
|
|
345
|
+
signature: 'scoreboard_display(slot: string, objective: string)',
|
|
346
|
+
description: 'Display a scoreboard objective in a slot.',
|
|
347
|
+
params: [
|
|
348
|
+
{ name: 'slot', type: 'string', desc: '"list", "sidebar", or "belowName"' },
|
|
349
|
+
{ name: 'objective', type: 'string', desc: 'Objective name' }
|
|
350
|
+
],
|
|
351
|
+
example: 'scoreboard_display("sidebar", "kills");',
|
|
352
|
+
mc: 'scoreboard objectives setdisplay <slot> <objective>'
|
|
353
|
+
},
|
|
354
|
+
scoreboard_add_objective: {
|
|
355
|
+
signature: 'scoreboard_add_objective(name: string, criteria: string)',
|
|
356
|
+
description: 'Create a new scoreboard objective.',
|
|
357
|
+
params: [
|
|
358
|
+
{ name: 'name', type: 'string', desc: 'Objective name' },
|
|
359
|
+
{ name: 'criteria', type: 'string', desc: 'Criteria (e.g. "dummy", "playerKillCount")' }
|
|
360
|
+
],
|
|
361
|
+
example: 'scoreboard_add_objective("kills", "playerKillCount");',
|
|
362
|
+
mc: 'scoreboard objectives add <name> <criteria>'
|
|
363
|
+
},
|
|
364
|
+
scoreboard_remove_objective: {
|
|
365
|
+
signature: 'scoreboard_remove_objective(name: string)',
|
|
366
|
+
description: 'Remove a scoreboard objective.',
|
|
367
|
+
params: [{ name: 'name', type: 'string', desc: 'Objective name' }],
|
|
368
|
+
example: 'scoreboard_remove_objective("kills");',
|
|
369
|
+
mc: 'scoreboard objectives remove <name>'
|
|
370
|
+
},
|
|
371
|
+
scoreboard_hide: {
|
|
372
|
+
signature: 'scoreboard_hide(slot: string)',
|
|
373
|
+
description: 'Clear the display in a scoreboard slot.',
|
|
374
|
+
params: [{ name: 'slot', type: 'string', desc: '"list", "sidebar", or "belowName"' }],
|
|
375
|
+
example: 'scoreboard_hide("sidebar");',
|
|
376
|
+
mc: 'scoreboard objectives setdisplay <slot>'
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
// --- Random ---
|
|
380
|
+
random: {
|
|
381
|
+
signature: 'random(min: int, max: int) -> int',
|
|
382
|
+
description: 'Generate a random integer in range [min, max] using scoreboard arithmetic.',
|
|
383
|
+
params: [
|
|
384
|
+
{ name: 'min', type: 'int', desc: 'Minimum value (inclusive)' },
|
|
385
|
+
{ name: 'max', type: 'int', desc: 'Maximum value (inclusive)' }
|
|
386
|
+
],
|
|
387
|
+
returns: 'int',
|
|
388
|
+
example: 'let roll: int = random(1, 6);',
|
|
389
|
+
},
|
|
390
|
+
random_native: {
|
|
391
|
+
signature: 'random_native(min: int, max: int) -> int',
|
|
392
|
+
description: 'Generate a random integer using /random command (MC 1.20.3+). Faster than random().',
|
|
393
|
+
params: [
|
|
394
|
+
{ name: 'min', type: 'int', desc: 'Minimum value (inclusive)' },
|
|
395
|
+
{ name: 'max', type: 'int', desc: 'Maximum value (inclusive)' }
|
|
396
|
+
],
|
|
397
|
+
returns: 'int',
|
|
398
|
+
example: 'let n: int = random_native(1, 100);',
|
|
399
|
+
mc: 'random value <min> <max>'
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
// --- Strings ---
|
|
403
|
+
str_len: {
|
|
404
|
+
signature: 'str_len(s: string) -> int',
|
|
405
|
+
description: 'Get the length of a string (stored in NBT storage).',
|
|
406
|
+
params: [{ name: 's', type: 'string', desc: 'Input string' }],
|
|
407
|
+
returns: 'int',
|
|
408
|
+
example: 'let n: int = str_len("hello"); // 5',
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
// --- Arrays ---
|
|
412
|
+
push: {
|
|
413
|
+
signature: 'push(arr: T[], value: T)',
|
|
414
|
+
description: 'Append a value to the end of an array.',
|
|
415
|
+
params: [
|
|
416
|
+
{ name: 'arr', type: 'T[]', desc: 'Target array' },
|
|
417
|
+
{ name: 'value', type: 'T', desc: 'Value to append' }
|
|
418
|
+
],
|
|
419
|
+
example: 'let scores: int[] = [];\npush(scores, 42);',
|
|
420
|
+
mc: 'data modify storage rs:heap <arr> append value <value>'
|
|
421
|
+
},
|
|
422
|
+
pop: {
|
|
423
|
+
signature: 'pop(arr: T[]) -> T',
|
|
424
|
+
description: 'Remove and return the last element of an array.',
|
|
425
|
+
params: [{ name: 'arr', type: 'T[]', desc: 'Target array' }],
|
|
426
|
+
returns: 'T',
|
|
427
|
+
example: 'let last: int = pop(scores);',
|
|
428
|
+
mc: 'data remove storage rs:heap <arr>[-1]'
|
|
429
|
+
},
|
|
430
|
+
len: {
|
|
431
|
+
signature: 'arr.len',
|
|
432
|
+
description: 'Get the number of elements in an array (property access, not a function call).',
|
|
433
|
+
example: 'let n: int = scores.len;',
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// --- Data ---
|
|
437
|
+
data_get: {
|
|
438
|
+
signature: 'data_get(target: string, path: string) -> int',
|
|
439
|
+
description: 'Read NBT data from entity/block/storage.',
|
|
440
|
+
params: [
|
|
441
|
+
{ name: 'target', type: 'string', desc: 'Target selector or storage path' },
|
|
442
|
+
{ name: 'path', type: 'string', desc: 'NBT path (e.g. "Health")' }
|
|
443
|
+
],
|
|
444
|
+
returns: 'int',
|
|
445
|
+
example: 'let hp: int = data_get("@s", "Health");',
|
|
446
|
+
mc: 'execute store result score $rs_tmp rs_tmp run data get entity <target> <path>'
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
// --- Bossbar ---
|
|
450
|
+
bossbar_add: {
|
|
451
|
+
signature: 'bossbar_add(id: string, name: string)',
|
|
452
|
+
description: 'Create a new boss bar.',
|
|
453
|
+
params: [
|
|
454
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID (e.g. "minecraft:health")' },
|
|
455
|
+
{ name: 'name', type: 'string', desc: 'Display name' }
|
|
456
|
+
],
|
|
457
|
+
example: 'bossbar_add("mymod:timer", "Time Left");',
|
|
458
|
+
mc: 'bossbar add <id> {"text":"<name>"}'
|
|
459
|
+
},
|
|
460
|
+
bossbar_set_value: {
|
|
461
|
+
signature: 'bossbar_set_value(id: string, value: int)',
|
|
462
|
+
description: 'Set boss bar current value.',
|
|
463
|
+
params: [
|
|
464
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
465
|
+
{ name: 'value', type: 'int', desc: 'Current value' }
|
|
466
|
+
],
|
|
467
|
+
example: 'bossbar_set_value("mymod:timer", 60);',
|
|
468
|
+
mc: 'bossbar set <id> value <value>'
|
|
469
|
+
},
|
|
470
|
+
bossbar_set_max: {
|
|
471
|
+
signature: 'bossbar_set_max(id: string, max: int)',
|
|
472
|
+
description: 'Set boss bar maximum value.',
|
|
473
|
+
params: [
|
|
474
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
475
|
+
{ name: 'max', type: 'int', desc: 'Maximum value' }
|
|
476
|
+
],
|
|
477
|
+
example: 'bossbar_set_max("mymod:timer", 300);',
|
|
478
|
+
mc: 'bossbar set <id> max <max>'
|
|
479
|
+
},
|
|
480
|
+
bossbar_remove: {
|
|
481
|
+
signature: 'bossbar_remove(id: string)',
|
|
482
|
+
description: 'Remove a boss bar.',
|
|
483
|
+
params: [{ name: 'id', type: 'string', desc: 'Boss bar ID' }],
|
|
484
|
+
example: 'bossbar_remove("mymod:timer");',
|
|
485
|
+
mc: 'bossbar remove <id>'
|
|
486
|
+
},
|
|
487
|
+
bossbar_set_players: {
|
|
488
|
+
signature: 'bossbar_set_players(id: string, target: selector)',
|
|
489
|
+
description: 'Set which players see the boss bar.',
|
|
490
|
+
params: [
|
|
491
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
492
|
+
{ name: 'target', type: 'selector', desc: 'Target players' }
|
|
493
|
+
],
|
|
494
|
+
example: 'bossbar_set_players("mymod:timer", @a);',
|
|
495
|
+
mc: 'bossbar set <id> players <target>'
|
|
496
|
+
},
|
|
497
|
+
bossbar_set_color: {
|
|
498
|
+
signature: 'bossbar_set_color(id: string, color: string)',
|
|
499
|
+
description: 'Set boss bar color.',
|
|
500
|
+
params: [
|
|
501
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
502
|
+
{ name: 'color', type: 'string', desc: '"blue", "green", "pink", "purple", "red", "white", "yellow"' }
|
|
503
|
+
],
|
|
504
|
+
example: 'bossbar_set_color("mymod:timer", "red");',
|
|
505
|
+
mc: 'bossbar set <id> color <color>'
|
|
506
|
+
},
|
|
507
|
+
bossbar_set_style: {
|
|
508
|
+
signature: 'bossbar_set_style(id: string, style: string)',
|
|
509
|
+
description: 'Set boss bar segmentation style.',
|
|
510
|
+
params: [
|
|
511
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
512
|
+
{ name: 'style', type: 'string', desc: '"notched_6", "notched_10", "notched_12", "notched_20", "progress"' }
|
|
513
|
+
],
|
|
514
|
+
example: 'bossbar_set_style("mymod:timer", "notched_10");',
|
|
515
|
+
},
|
|
516
|
+
bossbar_set_visible: {
|
|
517
|
+
signature: 'bossbar_set_visible(id: string, visible: bool)',
|
|
518
|
+
description: 'Show or hide a boss bar.',
|
|
519
|
+
params: [
|
|
520
|
+
{ name: 'id', type: 'string', desc: 'Boss bar ID' },
|
|
521
|
+
{ name: 'visible', type: 'bool', desc: 'Visibility state' }
|
|
522
|
+
],
|
|
523
|
+
example: 'bossbar_set_visible("mymod:timer", true);',
|
|
524
|
+
},
|
|
525
|
+
bossbar_get_value: {
|
|
526
|
+
signature: 'bossbar_get_value(id: string) -> int',
|
|
527
|
+
description: 'Get the current value of a boss bar.',
|
|
528
|
+
params: [{ name: 'id', type: 'string', desc: 'Boss bar ID' }],
|
|
529
|
+
returns: 'int',
|
|
530
|
+
example: 'let v: int = bossbar_get_value("mymod:timer");',
|
|
531
|
+
mc: 'execute store result score $rs_tmp rs_tmp run bossbar get <id> value'
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
// --- Teams ---
|
|
535
|
+
team_add: {
|
|
536
|
+
signature: 'team_add(name: string)',
|
|
537
|
+
description: 'Create a new team.',
|
|
538
|
+
params: [{ name: 'name', type: 'string', desc: 'Team name' }],
|
|
539
|
+
example: 'team_add("red");',
|
|
540
|
+
mc: 'team add <name>'
|
|
541
|
+
},
|
|
542
|
+
team_remove: {
|
|
543
|
+
signature: 'team_remove(name: string)',
|
|
544
|
+
description: 'Remove a team.',
|
|
545
|
+
params: [{ name: 'name', type: 'string', desc: 'Team name' }],
|
|
546
|
+
example: 'team_remove("red");',
|
|
547
|
+
mc: 'team remove <name>'
|
|
548
|
+
},
|
|
549
|
+
team_join: {
|
|
550
|
+
signature: 'team_join(name: string, target: selector)',
|
|
551
|
+
description: 'Add entities to a team.',
|
|
552
|
+
params: [
|
|
553
|
+
{ name: 'name', type: 'string', desc: 'Team name' },
|
|
554
|
+
{ name: 'target', type: 'selector', desc: 'Entities to add' }
|
|
555
|
+
],
|
|
556
|
+
example: 'team_join("red", @s);',
|
|
557
|
+
mc: 'team join <name> <target>'
|
|
558
|
+
},
|
|
559
|
+
team_leave: {
|
|
560
|
+
signature: 'team_leave(target: selector)',
|
|
561
|
+
description: 'Remove entities from their team.',
|
|
562
|
+
params: [{ name: 'target', type: 'selector', desc: 'Entities to remove' }],
|
|
563
|
+
example: 'team_leave(@s);',
|
|
564
|
+
mc: 'team leave <target>'
|
|
565
|
+
},
|
|
566
|
+
team_option: {
|
|
567
|
+
signature: 'team_option(name: string, option: string, value: string)',
|
|
568
|
+
description: 'Set a team option.',
|
|
569
|
+
params: [
|
|
570
|
+
{ name: 'name', type: 'string', desc: 'Team name' },
|
|
571
|
+
{ name: 'option', type: 'string', desc: 'Option name (e.g. "color", "friendlyFire")' },
|
|
572
|
+
{ name: 'value', type: 'string', desc: 'Option value' }
|
|
573
|
+
],
|
|
574
|
+
example: 'team_option("red", "color", "red");',
|
|
575
|
+
mc: 'team modify <name> <option> <value>'
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
// --- Decorators ---
|
|
579
|
+
tick: {
|
|
580
|
+
signature: '@tick | @tick(rate: int)',
|
|
581
|
+
description: 'Run this function every tick (rate=1) or every N ticks.',
|
|
582
|
+
params: [{ name: 'rate', type: 'int', optional: true, desc: 'Tick interval (default: 1). @tick(rate=20) = every second.' }],
|
|
583
|
+
example: '@tick(rate=20)\nfn every_second() { ... }',
|
|
584
|
+
},
|
|
585
|
+
on_advancement: {
|
|
586
|
+
signature: '@on_advancement(id: string)',
|
|
587
|
+
description: 'Trigger when a player earns an advancement.',
|
|
588
|
+
params: [{ name: 'id', type: 'string', desc: 'Advancement ID (e.g. "story/mine_diamond")' }],
|
|
589
|
+
example: '@on_advancement("story/mine_diamond")\nfn got_diamond() { give(@s, "minecraft:diamond", 5); }',
|
|
590
|
+
},
|
|
591
|
+
on_death: {
|
|
592
|
+
signature: '@on_death',
|
|
593
|
+
description: 'Trigger when the executing entity dies.',
|
|
594
|
+
example: '@on_death\nfn died() { scoreboard_add(@s, "deaths", 1); }',
|
|
595
|
+
},
|
|
596
|
+
on_craft: {
|
|
597
|
+
signature: '@on_craft(item: string)',
|
|
598
|
+
description: 'Trigger when a player crafts an item.',
|
|
599
|
+
params: [{ name: 'item', type: 'string', desc: 'Crafted item ID' }],
|
|
600
|
+
example: '@on_craft("minecraft:diamond_sword")\nfn crafted_sword() { tell(@s, "Nice sword!"); }',
|
|
601
|
+
},
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
// Hover Provider
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
607
|
+
|
|
608
|
+
function formatDoc(doc: BuiltinDoc): vscode.MarkdownString {
|
|
609
|
+
const md = new vscode.MarkdownString('', true)
|
|
610
|
+
md.isTrusted = true
|
|
611
|
+
md.supportHtml = false
|
|
612
|
+
|
|
613
|
+
// Signature (code block)
|
|
614
|
+
md.appendCodeblock(doc.signature, 'redscript')
|
|
615
|
+
|
|
616
|
+
// Description
|
|
617
|
+
md.appendText('\n')
|
|
618
|
+
md.appendMarkdown(doc.description)
|
|
619
|
+
md.appendText('\n')
|
|
620
|
+
|
|
621
|
+
// Parameters
|
|
622
|
+
if (doc.params?.length) {
|
|
623
|
+
md.appendText('\n')
|
|
624
|
+
md.appendMarkdown('**Parameters:**\n')
|
|
625
|
+
for (const p of doc.params) {
|
|
626
|
+
const opt = p.optional ? '?' : ''
|
|
627
|
+
md.appendMarkdown(`- \`${p.name}${opt}: ${p.type}\` — ${p.desc}\n`)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Return type
|
|
632
|
+
if (doc.returns) {
|
|
633
|
+
md.appendMarkdown(`\n**Returns:** \`${doc.returns}\`\n`)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Compiled MC command
|
|
637
|
+
if (doc.mc) {
|
|
638
|
+
md.appendText('\n')
|
|
639
|
+
md.appendMarkdown('**Compiles to:**\n')
|
|
640
|
+
md.appendCodeblock(doc.mc, 'mcfunction')
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Example
|
|
644
|
+
if (doc.example) {
|
|
645
|
+
md.appendMarkdown('**Example:**\n')
|
|
646
|
+
md.appendCodeblock(doc.example, 'redscript')
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return md
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ---------------------------------------------------------------------------
|
|
653
|
+
// Selector documentation
|
|
654
|
+
// ---------------------------------------------------------------------------
|
|
655
|
+
|
|
656
|
+
const SELECTOR_DOCS: Record<string, { name: string; desc: string; tip?: string }> = {
|
|
657
|
+
'@s': { name: '@s — Self', desc: 'The entity that ran the current command (the executing entity).', tip: 'Always refers to exactly 1 entity.' },
|
|
658
|
+
'@a': { name: '@a — All Players', desc: 'All online players.', tip: 'Use `@a[limit=1]` to restrict to one player.' },
|
|
659
|
+
'@e': { name: '@e — All Entities', desc: 'All loaded entities (players + mobs + items + …).', tip: 'Usually combined with filters: `@e[type=minecraft:zombie,limit=5]`' },
|
|
660
|
+
'@p': { name: '@p — Nearest Player',desc: 'The single nearest player to the command origin.', tip: 'Exactly 1 player; errors if none are in range.' },
|
|
661
|
+
'@r': { name: '@r — Random Player', desc: 'A random online player.', tip: 'Use `@e[type=minecraft:player,sort=random,limit=1]` for full control.' },
|
|
662
|
+
'@n': { name: '@n — Nearest Entity',desc: 'The single nearest entity (including non-players).', tip: 'MC 1.21+ only.' },
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Selector argument documentation (MC built-in selector arguments). */
|
|
666
|
+
const SELECTOR_ARG_DOCS: Record<string, { name: string; desc: string; example?: string }> = {
|
|
667
|
+
'type': { name: 'type', desc: 'Filter by entity type.', example: 'type=minecraft:zombie' },
|
|
668
|
+
'tag': { name: 'tag', desc: 'Filter by scoreboard tag. Use `tag=!name` to exclude.', example: 'tag=my_tag, tag=!excluded' },
|
|
669
|
+
'name': { name: 'name', desc: 'Filter by entity custom name.', example: 'name="Steve"' },
|
|
670
|
+
'team': { name: 'team', desc: 'Filter by team membership. Empty string = no team.', example: 'team=red, team=' },
|
|
671
|
+
'scores': { name: 'scores', desc: 'Filter by scoreboard scores. Uses `{obj=range}` syntax.', example: 'scores={kills=1..}' },
|
|
672
|
+
'nbt': { name: 'nbt', desc: 'Filter by NBT data match.', example: 'nbt={OnGround:1b}' },
|
|
673
|
+
'predicate': { name: 'predicate', desc: 'Filter by datapack predicate.', example: 'predicate=my_pack:is_valid' },
|
|
674
|
+
'gamemode': { name: 'gamemode', desc: 'Filter players by gamemode.', example: 'gamemode=survival, gamemode=!creative' },
|
|
675
|
+
'distance': { name: 'distance', desc: 'Filter by distance from command origin. Supports ranges.', example: 'distance=..10, distance=5..20' },
|
|
676
|
+
'level': { name: 'level', desc: 'Filter players by XP level.', example: 'level=10.., level=1..5' },
|
|
677
|
+
'x_rotation': { name: 'x_rotation', desc: 'Filter by vertical head rotation (pitch). -90=up, 90=down.', example: 'x_rotation=-90..0' },
|
|
678
|
+
'y_rotation': { name: 'y_rotation', desc: 'Filter by horizontal head rotation (yaw). South=0.', example: 'y_rotation=0..90' },
|
|
679
|
+
'x': { name: 'x', desc: 'Override X coordinate for distance/volume calculations.', example: 'x=100' },
|
|
680
|
+
'y': { name: 'y', desc: 'Override Y coordinate for distance/volume calculations.', example: 'y=64' },
|
|
681
|
+
'z': { name: 'z', desc: 'Override Z coordinate for distance/volume calculations.', example: 'z=-200' },
|
|
682
|
+
'dx': { name: 'dx', desc: 'X-size of selection box from x,y,z.', example: 'dx=10' },
|
|
683
|
+
'dy': { name: 'dy', desc: 'Y-size of selection box from x,y,z.', example: 'dy=5' },
|
|
684
|
+
'dz': { name: 'dz', desc: 'Z-size of selection box from x,y,z.', example: 'dz=10' },
|
|
685
|
+
'limit': { name: 'limit', desc: 'Maximum number of entities to select.', example: 'limit=1, limit=5' },
|
|
686
|
+
'sort': { name: 'sort', desc: 'Sort order: nearest, furthest, random, arbitrary.', example: 'sort=random' },
|
|
687
|
+
'advancements':{ name: 'advancements', desc: 'Filter by advancement completion.', example: 'advancements={story/mine_diamond=true}' },
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function formatSelectorHover(raw: string): vscode.MarkdownString {
|
|
691
|
+
const key = raw.replace(/\[.*/, '') as keyof typeof SELECTOR_DOCS
|
|
692
|
+
const info = SELECTOR_DOCS[key]
|
|
693
|
+
const md = new vscode.MarkdownString('', true)
|
|
694
|
+
if (info) {
|
|
695
|
+
md.appendMarkdown(`**${info.name}**\n\n`)
|
|
696
|
+
md.appendMarkdown(info.desc + '\n')
|
|
697
|
+
if (info.tip) md.appendMarkdown(`\n> 💡 ${info.tip}`)
|
|
698
|
+
} else {
|
|
699
|
+
md.appendMarkdown(`**Selector** \`${raw}\`\n\nEntity target selector.`)
|
|
700
|
+
}
|
|
701
|
+
return md
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function formatSelectorArgHover(arg: string): vscode.MarkdownString | null {
|
|
705
|
+
const info = SELECTOR_ARG_DOCS[arg]
|
|
706
|
+
if (!info) return null
|
|
707
|
+
const md = new vscode.MarkdownString('', true)
|
|
708
|
+
md.appendMarkdown(`**${info.name}** (selector argument)\n\n`)
|
|
709
|
+
md.appendMarkdown(info.desc)
|
|
710
|
+
if (info.example) {
|
|
711
|
+
md.appendText('\n\n')
|
|
712
|
+
md.appendCodeblock(info.example, 'redscript')
|
|
713
|
+
}
|
|
714
|
+
return md
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ---------------------------------------------------------------------------
|
|
718
|
+
// JSDoc comment parser
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Look backwards from `line` in `document` for a /** ... *\/ block.
|
|
723
|
+
* Returns the cleaned comment text, or null.
|
|
724
|
+
*/
|
|
725
|
+
function findJsDocAbove(document: vscode.TextDocument, declLine: number): string | null {
|
|
726
|
+
// Walk up from the declaration line, skipping blank lines
|
|
727
|
+
let end = declLine - 1
|
|
728
|
+
while (end >= 0 && document.lineAt(end).text.trim() === '') end--
|
|
729
|
+
if (end < 0) return null
|
|
730
|
+
|
|
731
|
+
const endText = document.lineAt(end).text.trim()
|
|
732
|
+
if (!endText.endsWith('*/')) return null
|
|
733
|
+
|
|
734
|
+
// Find the opening /**
|
|
735
|
+
let start = end
|
|
736
|
+
while (start >= 0 && !document.lineAt(start).text.includes('/**')) start--
|
|
737
|
+
if (start < 0) return null
|
|
738
|
+
|
|
739
|
+
// Extract and clean comment lines
|
|
740
|
+
const lines: string[] = []
|
|
741
|
+
for (let i = start; i <= end; i++) {
|
|
742
|
+
let line = document.lineAt(i).text
|
|
743
|
+
.replace(/^\s*\/\*\*?\s?/, '') // remove leading /**
|
|
744
|
+
.replace(/\s*\*\/\s*$/, '') // remove trailing */
|
|
745
|
+
.replace(/^\s*\*\s?/, '') // remove leading * on middle lines
|
|
746
|
+
.trim()
|
|
747
|
+
if (line) lines.push(line)
|
|
748
|
+
}
|
|
749
|
+
return lines.length ? lines.join('\n') : null
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Find the line where `fn <name>` is declared in the document.
|
|
754
|
+
*/
|
|
755
|
+
function findFnDeclLine(document: vscode.TextDocument, name: string): number | null {
|
|
756
|
+
const re = new RegExp(`\\bfn\\s+${escapeRe(name)}\\s*\\(`, 'm')
|
|
757
|
+
const text = document.getText()
|
|
758
|
+
const match = re.exec(text)
|
|
759
|
+
if (!match) return null
|
|
760
|
+
return document.positionAt(match.index).line
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Extract the full function signature: fn name(params) -> ReturnType
|
|
765
|
+
*/
|
|
766
|
+
function findFnSignature(document: vscode.TextDocument, name: string): string | null {
|
|
767
|
+
const text = document.getText()
|
|
768
|
+
// Match: fn name(params) or fn name(params) -> Type
|
|
769
|
+
const re = new RegExp(`\\bfn\\s+${escapeRe(name)}\\s*\\(([^)]*)\\)(?:\\s*->\\s*([A-Za-z_][A-Za-z0-9_\\[\\]]*))?`, 'm')
|
|
770
|
+
const match = re.exec(text)
|
|
771
|
+
if (!match) return null
|
|
772
|
+
const params = match[1].trim()
|
|
773
|
+
const returnType = match[2]
|
|
774
|
+
if (returnType) {
|
|
775
|
+
return `fn ${name}(${params}) -> ${returnType}`
|
|
776
|
+
}
|
|
777
|
+
return `fn ${name}(${params})`
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function escapeRe(s: string): string {
|
|
781
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
// Variable / let / const hover
|
|
786
|
+
// ---------------------------------------------------------------------------
|
|
787
|
+
|
|
788
|
+
interface VarDecl { name: string; type: string; kind: 'let' | 'const' | 'param' }
|
|
789
|
+
|
|
790
|
+
function findVarDecls(document: vscode.TextDocument): VarDecl[] {
|
|
791
|
+
const text = document.getText()
|
|
792
|
+
const decls: VarDecl[] = []
|
|
793
|
+
|
|
794
|
+
// Find let/const declarations
|
|
795
|
+
const letRe = /\b(let|const)\s+(\w+)\s*:\s*([A-Za-z_][A-Za-z0-9_\[\]]*)/g
|
|
796
|
+
let m: RegExpExecArray | null
|
|
797
|
+
while ((m = letRe.exec(text)) !== null) {
|
|
798
|
+
decls.push({ kind: m[1] as 'let' | 'const', name: m[2], type: m[3] })
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return decls
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
interface FnParam { name: string; type: string; fnName: string; fnStartLine: number; fnEndLine: number }
|
|
805
|
+
|
|
806
|
+
/** Find all function parameters with their scope (function body range). */
|
|
807
|
+
function findFnParams(document: vscode.TextDocument): FnParam[] {
|
|
808
|
+
const text = document.getText()
|
|
809
|
+
const params: FnParam[] = []
|
|
810
|
+
|
|
811
|
+
// Match: fn name(param1: Type1, param2: Type2) { ... }
|
|
812
|
+
// Need to find the function body range to scope parameters correctly
|
|
813
|
+
const fnRe = /\bfn\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*\w+)?\s*\{/g
|
|
814
|
+
let fnMatch: RegExpExecArray | null
|
|
815
|
+
|
|
816
|
+
while ((fnMatch = fnRe.exec(text)) !== null) {
|
|
817
|
+
const fnName = fnMatch[1]
|
|
818
|
+
const paramsStr = fnMatch[2]
|
|
819
|
+
const fnStartOffset = fnMatch.index
|
|
820
|
+
const fnStartLine = document.positionAt(fnStartOffset).line
|
|
821
|
+
|
|
822
|
+
// Find matching closing brace for function body
|
|
823
|
+
const bodyStart = fnMatch.index + fnMatch[0].length - 1 // position of '{'
|
|
824
|
+
let braceCount = 1
|
|
825
|
+
let pos = bodyStart + 1
|
|
826
|
+
while (pos < text.length && braceCount > 0) {
|
|
827
|
+
if (text[pos] === '{') braceCount++
|
|
828
|
+
else if (text[pos] === '}') braceCount--
|
|
829
|
+
pos++
|
|
830
|
+
}
|
|
831
|
+
const fnEndLine = document.positionAt(pos).line
|
|
832
|
+
|
|
833
|
+
// Parse parameters: name: Type, name: Type = default
|
|
834
|
+
const paramRe = /(\w+)\s*:\s*([A-Za-z_][A-Za-z0-9_\[\]]*)/g
|
|
835
|
+
let paramMatch: RegExpExecArray | null
|
|
836
|
+
while ((paramMatch = paramRe.exec(paramsStr)) !== null) {
|
|
837
|
+
params.push({
|
|
838
|
+
name: paramMatch[1],
|
|
839
|
+
type: paramMatch[2],
|
|
840
|
+
fnName,
|
|
841
|
+
fnStartLine,
|
|
842
|
+
fnEndLine
|
|
843
|
+
})
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return params
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ---------------------------------------------------------------------------
|
|
851
|
+
// Struct hover
|
|
852
|
+
// ---------------------------------------------------------------------------
|
|
853
|
+
|
|
854
|
+
interface StructField { name: string; type: string; line: number; doc?: string }
|
|
855
|
+
interface StructDecl { name: string; fields: StructField[]; line: number; doc?: string }
|
|
856
|
+
|
|
857
|
+
function findStructDecls(document: vscode.TextDocument): StructDecl[] {
|
|
858
|
+
const text = document.getText()
|
|
859
|
+
// Match: struct Name { field: Type, ... }
|
|
860
|
+
const structRe = /\bstruct\s+(\w+)\s*\{([^}]*)\}/gs
|
|
861
|
+
const decls: StructDecl[] = []
|
|
862
|
+
let m: RegExpExecArray | null
|
|
863
|
+
|
|
864
|
+
while ((m = structRe.exec(text)) !== null) {
|
|
865
|
+
const name = m[1]
|
|
866
|
+
const body = m[2]
|
|
867
|
+
const structLine = document.positionAt(m.index).line
|
|
868
|
+
const bodyStartOffset = m.index + m[0].indexOf('{') + 1
|
|
869
|
+
|
|
870
|
+
// Find JSDoc above struct
|
|
871
|
+
const structDoc = findJsDocAbove(document, structLine)
|
|
872
|
+
|
|
873
|
+
// Parse fields with their line numbers
|
|
874
|
+
const fieldRe = /\b(\w+)\s*:\s*([A-Za-z_][A-Za-z0-9_\[\]]*)/g
|
|
875
|
+
const fields: StructField[] = []
|
|
876
|
+
let fm: RegExpExecArray | null
|
|
877
|
+
while ((fm = fieldRe.exec(body)) !== null) {
|
|
878
|
+
const fieldOffset = bodyStartOffset + fm.index
|
|
879
|
+
const fieldLine = document.positionAt(fieldOffset).line
|
|
880
|
+
// Check for inline comment: field: Type, // comment
|
|
881
|
+
const lineText = document.lineAt(fieldLine).text
|
|
882
|
+
const inlineMatch = lineText.match(/\/\/\s*(.+)$/)
|
|
883
|
+
// Check for JSDoc/comment above field
|
|
884
|
+
const docAbove = findFieldDocAbove(document, fieldLine)
|
|
885
|
+
const fieldDoc = inlineMatch?.[1] || docAbove || undefined
|
|
886
|
+
fields.push({ name: fm[1], type: fm[2], line: fieldLine, doc: fieldDoc })
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
decls.push({ name, fields, line: structLine, doc: structDoc ?? undefined })
|
|
890
|
+
}
|
|
891
|
+
return decls
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/** Find comment above a struct field (single // comment or /** block). */
|
|
895
|
+
function findFieldDocAbove(document: vscode.TextDocument, fieldLine: number): string | null {
|
|
896
|
+
if (fieldLine === 0) return null
|
|
897
|
+
const prevLine = document.lineAt(fieldLine - 1).text.trim()
|
|
898
|
+
// Check for // comment
|
|
899
|
+
if (prevLine.startsWith('//')) {
|
|
900
|
+
return prevLine.replace(/^\/\/\s*/, '')
|
|
901
|
+
}
|
|
902
|
+
// Check for /** */ on single line
|
|
903
|
+
const blockMatch = prevLine.match(/\/\*\*?\s*(.*?)\s*\*\//)
|
|
904
|
+
if (blockMatch) return blockMatch[1]
|
|
905
|
+
// Multi-line block comment
|
|
906
|
+
if (prevLine.endsWith('*/')) {
|
|
907
|
+
return findJsDocAbove(document, fieldLine)
|
|
908
|
+
}
|
|
909
|
+
return null
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function formatStructHover(decl: StructDecl): vscode.MarkdownString {
|
|
913
|
+
const md = new vscode.MarkdownString('', true)
|
|
914
|
+
const lines = [`struct ${decl.name} {`]
|
|
915
|
+
for (const f of decl.fields) {
|
|
916
|
+
const comment = f.doc ? ` // ${f.doc}` : ''
|
|
917
|
+
lines.push(` ${f.name}: ${f.type},${comment}`)
|
|
918
|
+
}
|
|
919
|
+
lines.push('}')
|
|
920
|
+
md.appendCodeblock(lines.join('\n'), 'redscript')
|
|
921
|
+
if (decl.doc) {
|
|
922
|
+
md.appendText('\n')
|
|
923
|
+
md.appendMarkdown(decl.doc)
|
|
924
|
+
}
|
|
925
|
+
return md
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function formatFieldHover(structName: string, field: StructField): vscode.MarkdownString {
|
|
929
|
+
const md = new vscode.MarkdownString('', true)
|
|
930
|
+
md.appendCodeblock(`(field) ${structName}.${field.name}: ${field.type}`, 'redscript')
|
|
931
|
+
if (field.doc) {
|
|
932
|
+
md.appendText('\n')
|
|
933
|
+
md.appendMarkdown(field.doc)
|
|
934
|
+
}
|
|
935
|
+
return md
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// ---------------------------------------------------------------------------
|
|
939
|
+
// #mc_name hover
|
|
940
|
+
// ---------------------------------------------------------------------------
|
|
941
|
+
|
|
942
|
+
function formatMcNameHover(name: string): vscode.MarkdownString {
|
|
943
|
+
const md = new vscode.MarkdownString('', true)
|
|
944
|
+
md.appendCodeblock(`#${name}`, 'redscript')
|
|
945
|
+
md.appendMarkdown(`MC identifier \`${name}\`\n\nUsed as an objective, tag, team, or gamerule name. Compiles to the bare name \`${name}\` without quotes.`)
|
|
946
|
+
return md
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ---------------------------------------------------------------------------
|
|
950
|
+
// Hover provider
|
|
951
|
+
// ---------------------------------------------------------------------------
|
|
952
|
+
|
|
953
|
+
export function registerHoverProvider(context: vscode.ExtensionContext): void {
|
|
954
|
+
context.subscriptions.push(
|
|
955
|
+
vscode.languages.registerHoverProvider('redscript', {
|
|
956
|
+
provideHover(document, position) {
|
|
957
|
+
const line = document.lineAt(position.line).text
|
|
958
|
+
|
|
959
|
+
// ── #mc_name hover ──────────────────────────────────────
|
|
960
|
+
const mcRange = document.getWordRangeAtPosition(position, /#[a-zA-Z_][a-zA-Z0-9_]*/)
|
|
961
|
+
if (mcRange) {
|
|
962
|
+
const raw = document.getText(mcRange)
|
|
963
|
+
return new vscode.Hover(formatMcNameHover(raw.slice(1)), mcRange)
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// ── Selector base hover (@a, @e, @s, etc.) ─────────────
|
|
967
|
+
// Match just the @x part (2 chars)
|
|
968
|
+
const baseSelectorRange = document.getWordRangeAtPosition(position, /@[aesprnAESPRN]/)
|
|
969
|
+
if (baseSelectorRange) {
|
|
970
|
+
const base = document.getText(baseSelectorRange)
|
|
971
|
+
return new vscode.Hover(formatSelectorHover(base), baseSelectorRange)
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ── Selector argument hover (inside [...]) ──────────────
|
|
975
|
+
// First get the word at cursor
|
|
976
|
+
const wordAtCursor = document.getWordRangeAtPosition(position, /[a-zA-Z_][a-zA-Z0-9_]*/)
|
|
977
|
+
if (wordAtCursor) {
|
|
978
|
+
const wordText = document.getText(wordAtCursor)
|
|
979
|
+
// Check if this word is a known selector argument AND is followed by '='
|
|
980
|
+
if (SELECTOR_ARG_DOCS[wordText]) {
|
|
981
|
+
const afterWord = line.slice(wordAtCursor.end.character).trimStart()
|
|
982
|
+
if (afterWord.startsWith('=')) {
|
|
983
|
+
// Verify we're actually inside selector brackets by looking backwards for @x[
|
|
984
|
+
const beforeWord = line.slice(0, wordAtCursor.start.character)
|
|
985
|
+
// Check if there's an unclosed @x[ before this word
|
|
986
|
+
const openBracket = beforeWord.lastIndexOf('[')
|
|
987
|
+
const closeBracket = beforeWord.lastIndexOf(']')
|
|
988
|
+
if (openBracket > closeBracket) {
|
|
989
|
+
// We're inside brackets, check if preceded by @x
|
|
990
|
+
const beforeBracket = beforeWord.slice(0, openBracket)
|
|
991
|
+
if (/@[aesprnAESPRN]\s*$/.test(beforeBracket)) {
|
|
992
|
+
const argDoc = formatSelectorArgHover(wordText)
|
|
993
|
+
if (argDoc) {
|
|
994
|
+
return new vscode.Hover(argDoc, wordAtCursor)
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const range = document.getWordRangeAtPosition(position, /[a-zA-Z_][a-zA-Z0-9_]*/)
|
|
1003
|
+
if (!range) return undefined
|
|
1004
|
+
|
|
1005
|
+
const word = document.getText(range)
|
|
1006
|
+
|
|
1007
|
+
// ── Discard/wildcard pattern _ ──────────────────────────
|
|
1008
|
+
if (word === '_') {
|
|
1009
|
+
const md = new vscode.MarkdownString('', true)
|
|
1010
|
+
md.appendCodeblock('_', 'redscript')
|
|
1011
|
+
md.appendMarkdown('**Wildcard pattern** (discard)\n\nMatches any value. Used in `match` expressions as a catch-all case, or to ignore unused values.')
|
|
1012
|
+
return new vscode.Hover(md, range)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// ── Function parameter hover ────────────────────────────
|
|
1016
|
+
// Check if this word is a function parameter (must be inside that function's scope)
|
|
1017
|
+
const fnParams = findFnParams(document)
|
|
1018
|
+
const currentLine = position.line
|
|
1019
|
+
const param = fnParams.find(p =>
|
|
1020
|
+
p.name === word &&
|
|
1021
|
+
currentLine >= p.fnStartLine &&
|
|
1022
|
+
currentLine <= p.fnEndLine
|
|
1023
|
+
)
|
|
1024
|
+
if (param) {
|
|
1025
|
+
const md = new vscode.MarkdownString('', true)
|
|
1026
|
+
md.appendCodeblock(`(parameter) ${param.name}: ${param.type}`, 'redscript')
|
|
1027
|
+
return new vscode.Hover(md, range)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// ── Variable / let / const hover ────────────────────────
|
|
1031
|
+
// Check if this word has a let/const declaration in the document
|
|
1032
|
+
const varDecls = findVarDecls(document)
|
|
1033
|
+
const varDecl = varDecls.find(v => v.name === word)
|
|
1034
|
+
if (varDecl) {
|
|
1035
|
+
const md = new vscode.MarkdownString('', true)
|
|
1036
|
+
md.appendCodeblock(`${varDecl.kind} ${varDecl.name}: ${varDecl.type}`, 'redscript')
|
|
1037
|
+
return new vscode.Hover(md, range)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// ── Builtin function (only when used as a call, not variable name) ──
|
|
1041
|
+
// Only show builtin docs if the word is followed by '(' on the same line
|
|
1042
|
+
const afterWord = line.slice(range.end.character).trimStart()
|
|
1043
|
+
const isCall = afterWord.startsWith('(')
|
|
1044
|
+
if (isCall) {
|
|
1045
|
+
const builtin = BUILTINS[word]
|
|
1046
|
+
if (builtin) return new vscode.Hover(formatDoc(builtin), range)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// ── Struct type hover ───────────────────────────────────
|
|
1050
|
+
const structDecls = findStructDecls(document)
|
|
1051
|
+
const structDecl = structDecls.find(s => s.name === word)
|
|
1052
|
+
if (structDecl) {
|
|
1053
|
+
return new vscode.Hover(formatStructHover(structDecl), range)
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// ── Member access: turret.tag ───────────────────────────
|
|
1057
|
+
// Check if word is preceded by '.' (member access)
|
|
1058
|
+
const charBefore = range.start.character > 0
|
|
1059
|
+
? line.slice(range.start.character - 1, range.start.character)
|
|
1060
|
+
: ''
|
|
1061
|
+
if (charBefore === '.') {
|
|
1062
|
+
// Find the object name before the dot
|
|
1063
|
+
const beforeDot = line.slice(0, range.start.character - 1)
|
|
1064
|
+
const objMatch = beforeDot.match(/([A-Za-z_]\w*)$/)
|
|
1065
|
+
if (objMatch) {
|
|
1066
|
+
const objName = objMatch[1]
|
|
1067
|
+
// Find the type of the object from variable declarations
|
|
1068
|
+
const objVar = varDecls.find(v => v.name === objName)
|
|
1069
|
+
if (objVar) {
|
|
1070
|
+
const objStruct = structDecls.find(s => s.name === objVar.type)
|
|
1071
|
+
if (objStruct) {
|
|
1072
|
+
const field = objStruct.fields.find(f => f.name === word)
|
|
1073
|
+
if (field) {
|
|
1074
|
+
return new vscode.Hover(formatFieldHover(objStruct.name, field), range)
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// ── Struct literal field key: { phase: value } ──────────
|
|
1082
|
+
// Check if word is followed by ':' (struct literal field)
|
|
1083
|
+
const afterWordTrimmed = afterWord
|
|
1084
|
+
if (afterWordTrimmed.startsWith(':')) {
|
|
1085
|
+
// Find the struct type from context: let x: StructType = { ... }
|
|
1086
|
+
const textBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position))
|
|
1087
|
+
const letMatch = textBefore.match(/let\s+\w+\s*:\s*(\w+)\s*=\s*\{[^}]*$/)
|
|
1088
|
+
const fnMatch = textBefore.match(/->\s*(\w+)\s*\{[^}]*return\s*\{[^}]*$/)
|
|
1089
|
+
const structType = letMatch?.[1] || fnMatch?.[1]
|
|
1090
|
+
if (structType) {
|
|
1091
|
+
const targetStruct = structDecls.find(s => s.name === structType)
|
|
1092
|
+
if (targetStruct) {
|
|
1093
|
+
const field = targetStruct.fields.find(f => f.name === word)
|
|
1094
|
+
if (field) {
|
|
1095
|
+
return new vscode.Hover(formatFieldHover(targetStruct.name, field), range)
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// ── User-defined function + JSDoc ───────────────────────
|
|
1102
|
+
// Only if used as a call
|
|
1103
|
+
if (isCall) {
|
|
1104
|
+
const declLine = findFnDeclLine(document, word)
|
|
1105
|
+
if (declLine !== null) {
|
|
1106
|
+
const md = new vscode.MarkdownString('', true)
|
|
1107
|
+
const jsdoc = findJsDocAbove(document, declLine)
|
|
1108
|
+
const sig = findFnSignature(document, word) || `fn ${word}(...)`
|
|
1109
|
+
md.appendCodeblock(sig, 'redscript')
|
|
1110
|
+
if (jsdoc) { md.appendText('\n'); md.appendMarkdown(jsdoc) }
|
|
1111
|
+
return new vscode.Hover(md, range)
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
return undefined
|
|
1116
|
+
}
|
|
1117
|
+
})
|
|
1118
|
+
)
|
|
1119
|
+
}
|
|
1120
|
+
|