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,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code generator: IR → mcfunction datapack
|
|
3
|
+
*
|
|
4
|
+
* Output structure:
|
|
5
|
+
* <namespace>/
|
|
6
|
+
* functions/
|
|
7
|
+
* <fn_name>.mcfunction
|
|
8
|
+
* <fn_name>/<block_label>.mcfunction (for control-flow continuations)
|
|
9
|
+
* load.mcfunction (objective setup)
|
|
10
|
+
*
|
|
11
|
+
* Variable mapping:
|
|
12
|
+
* scoreboard objective: "rs"
|
|
13
|
+
* fake player: "$<varname>"
|
|
14
|
+
* temporaries: "$t0", "$t1", ...
|
|
15
|
+
* return value: "$ret"
|
|
16
|
+
* parameters: "$p0", "$p1", ...
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { IRBlock, IRFunction, IRModule, Operand, Terminator } from '../../ir/types'
|
|
20
|
+
import { optimizeCommandFunctions, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Utilities
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const OBJ = 'rs' // scoreboard objective name
|
|
27
|
+
|
|
28
|
+
function varRef(name: string): string {
|
|
29
|
+
// Ensure fake player prefix
|
|
30
|
+
return name.startsWith('$') ? name : `$${name}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function operandToScore(op: Operand): string {
|
|
34
|
+
if (op.kind === 'var') return `${varRef(op.name)} ${OBJ}`
|
|
35
|
+
if (op.kind === 'const') return `$const_${op.value} ${OBJ}`
|
|
36
|
+
throw new Error(`Cannot convert storage operand to score: ${op.path}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function constSetup(value: number): string {
|
|
40
|
+
return `scoreboard players set $const_${value} ${OBJ} ${value}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Collect all constants used in a function for pre-setup
|
|
44
|
+
function collectConsts(fn: IRFunction): Set<number> {
|
|
45
|
+
const consts = new Set<number>()
|
|
46
|
+
for (const block of fn.blocks) {
|
|
47
|
+
for (const instr of block.instrs) {
|
|
48
|
+
if (instr.op === 'assign' && instr.src.kind === 'const') consts.add(instr.src.value)
|
|
49
|
+
if (instr.op === 'binop') {
|
|
50
|
+
if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
|
|
51
|
+
if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
|
|
52
|
+
}
|
|
53
|
+
if (instr.op === 'cmp') {
|
|
54
|
+
if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
|
|
55
|
+
if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const t = block.term
|
|
59
|
+
if (t.op === 'return' && t.value?.kind === 'const') consts.add(t.value.value)
|
|
60
|
+
}
|
|
61
|
+
return consts
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MC scoreboard operation suffix
|
|
65
|
+
const BOP_OP: Record<string, string> = {
|
|
66
|
+
'+': '+=', '-': '-=', '*': '*=', '/': '/=', '%': '%=',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Instruction codegen
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function emitInstr(instr: ReturnType<typeof Object.assign> & { op: string }, ns: string): string[] {
|
|
74
|
+
const lines: string[] = []
|
|
75
|
+
|
|
76
|
+
switch (instr.op) {
|
|
77
|
+
case 'assign': {
|
|
78
|
+
const dst = varRef(instr.dst)
|
|
79
|
+
const src = instr.src as Operand
|
|
80
|
+
if (src.kind === 'const') {
|
|
81
|
+
lines.push(`scoreboard players set ${dst} ${OBJ} ${src.value}`)
|
|
82
|
+
} else if (src.kind === 'var') {
|
|
83
|
+
lines.push(`scoreboard players operation ${dst} ${OBJ} = ${varRef(src.name)} ${OBJ}`)
|
|
84
|
+
} else {
|
|
85
|
+
lines.push(`execute store result score ${dst} ${OBJ} run data get storage ${src.path}`)
|
|
86
|
+
}
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case 'binop': {
|
|
91
|
+
const dst = varRef(instr.dst)
|
|
92
|
+
const bop = BOP_OP[instr.bop as string] ?? '+='
|
|
93
|
+
// Copy lhs → dst, then apply op with rhs
|
|
94
|
+
lines.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, ns))
|
|
95
|
+
lines.push(`scoreboard players operation ${dst} ${OBJ} ${bop} ${operandToScore(instr.rhs)}`)
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'cmp': {
|
|
100
|
+
// MC doesn't have a direct compare-to-register; use execute store
|
|
101
|
+
const dst = varRef(instr.dst)
|
|
102
|
+
const lhsScore = operandToScore(instr.lhs)
|
|
103
|
+
const rhsScore = operandToScore(instr.rhs)
|
|
104
|
+
lines.push(`scoreboard players set ${dst} ${OBJ} 0`)
|
|
105
|
+
switch (instr.cop) {
|
|
106
|
+
case '==':
|
|
107
|
+
lines.push(`execute if score ${lhsScore} = ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
108
|
+
break
|
|
109
|
+
case '!=':
|
|
110
|
+
lines.push(`execute unless score ${lhsScore} = ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
111
|
+
break
|
|
112
|
+
case '<':
|
|
113
|
+
lines.push(`execute if score ${lhsScore} < ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
114
|
+
break
|
|
115
|
+
case '<=':
|
|
116
|
+
lines.push(`execute if score ${lhsScore} <= ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
117
|
+
break
|
|
118
|
+
case '>':
|
|
119
|
+
lines.push(`execute if score ${lhsScore} > ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
120
|
+
break
|
|
121
|
+
case '>=':
|
|
122
|
+
lines.push(`execute if score ${lhsScore} >= ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case 'call': {
|
|
129
|
+
// Push args as fake players $p0, $p1, ...
|
|
130
|
+
for (let i = 0; i < instr.args.length; i++) {
|
|
131
|
+
lines.push(...emitInstr({ op: 'assign', dst: `$p${i}`, src: instr.args[i] }, ns))
|
|
132
|
+
}
|
|
133
|
+
lines.push(`function ${ns}:${instr.fn}`)
|
|
134
|
+
if (instr.dst) {
|
|
135
|
+
lines.push(`scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $ret ${OBJ}`)
|
|
136
|
+
}
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'raw':
|
|
141
|
+
lines.push(instr.cmd as string)
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return lines
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Terminator codegen
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
function emitTerm(term: Terminator, ns: string, fnName: string): string[] {
|
|
153
|
+
const lines: string[] = []
|
|
154
|
+
switch (term.op) {
|
|
155
|
+
case 'jump':
|
|
156
|
+
lines.push(`function ${ns}:${fnName}/${term.target}`)
|
|
157
|
+
break
|
|
158
|
+
case 'jump_if':
|
|
159
|
+
lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.then}`)
|
|
160
|
+
lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.else_}`)
|
|
161
|
+
break
|
|
162
|
+
case 'jump_unless':
|
|
163
|
+
lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.then}`)
|
|
164
|
+
lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.else_}`)
|
|
165
|
+
break
|
|
166
|
+
case 'return':
|
|
167
|
+
if (term.value) {
|
|
168
|
+
lines.push(...emitInstr({ op: 'assign', dst: '$ret', src: term.value }, ns))
|
|
169
|
+
}
|
|
170
|
+
// In MC 1.20+, use `return` command
|
|
171
|
+
if (term.value?.kind === 'const') {
|
|
172
|
+
lines.push(`return ${term.value.value}`)
|
|
173
|
+
} else if (term.value?.kind === 'var') {
|
|
174
|
+
lines.push(`return run scoreboard players get ${varRef(term.value.name)} ${OBJ}`)
|
|
175
|
+
}
|
|
176
|
+
break
|
|
177
|
+
case 'tick_yield':
|
|
178
|
+
lines.push(`schedule function ${ns}:${fnName}/${term.continuation} 1t replace`)
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
return lines
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Public API
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
export interface DatapackFile {
|
|
189
|
+
path: string // relative to datapack root, e.g. "data/mypack/functions/add.mcfunction"
|
|
190
|
+
content: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function toFunctionName(file: DatapackFile): string | null {
|
|
194
|
+
const match = file.path.match(/^data\/[^/]+\/function\/(.+)\.mcfunction$/)
|
|
195
|
+
return match?.[1] ?? null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function applyFunctionOptimization(
|
|
199
|
+
files: DatapackFile[],
|
|
200
|
+
): { files: DatapackFile[]; stats: OptimizationStats } {
|
|
201
|
+
const functionFiles = files
|
|
202
|
+
.map(file => {
|
|
203
|
+
const functionName = toFunctionName(file)
|
|
204
|
+
if (!functionName) return null
|
|
205
|
+
const commands = file.content
|
|
206
|
+
.split('\n')
|
|
207
|
+
.map(line => line.trim())
|
|
208
|
+
.filter(line => line !== '' && !line.startsWith('#'))
|
|
209
|
+
.map(cmd => ({ cmd }))
|
|
210
|
+
return { file, functionName, commands }
|
|
211
|
+
})
|
|
212
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== null)
|
|
213
|
+
|
|
214
|
+
const optimized = optimizeCommandFunctions(functionFiles.map(entry => ({
|
|
215
|
+
name: entry.functionName,
|
|
216
|
+
commands: entry.commands,
|
|
217
|
+
})))
|
|
218
|
+
const commandMap = new Map(optimized.functions.map(fn => [fn.name, fn.commands]))
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
files: files.map(file => {
|
|
222
|
+
const functionName = toFunctionName(file)
|
|
223
|
+
if (!functionName) return file
|
|
224
|
+
const commands = commandMap.get(functionName)
|
|
225
|
+
if (!commands) return file
|
|
226
|
+
const lines = file.content.split('\n')
|
|
227
|
+
const header = lines.filter(line => line.trim().startsWith('#'))
|
|
228
|
+
return {
|
|
229
|
+
...file,
|
|
230
|
+
content: [...header, ...commands.map(command => command.cmd)].join('\n'),
|
|
231
|
+
}
|
|
232
|
+
}),
|
|
233
|
+
stats: optimized.stats,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface DatapackGenerationResult {
|
|
238
|
+
files: DatapackFile[]
|
|
239
|
+
advancements: DatapackFile[]
|
|
240
|
+
stats: OptimizationStats
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface DatapackGenerationOptions {
|
|
244
|
+
optimizeCommands?: boolean
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function countMcfunctionCommands(files: DatapackFile[]): number {
|
|
248
|
+
return files.reduce((sum, file) => {
|
|
249
|
+
if (!toFunctionName(file)) {
|
|
250
|
+
return sum
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return sum + file.content
|
|
254
|
+
.split('\n')
|
|
255
|
+
.map(line => line.trim())
|
|
256
|
+
.filter(line => line !== '' && !line.startsWith('#'))
|
|
257
|
+
.length
|
|
258
|
+
}, 0)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function generateDatapackWithStats(
|
|
262
|
+
module: IRModule,
|
|
263
|
+
options: DatapackGenerationOptions = {},
|
|
264
|
+
): DatapackGenerationResult {
|
|
265
|
+
const { optimizeCommands = true } = options
|
|
266
|
+
const files: DatapackFile[] = []
|
|
267
|
+
const advancements: DatapackFile[] = []
|
|
268
|
+
const ns = module.namespace
|
|
269
|
+
|
|
270
|
+
// Collect all trigger handlers
|
|
271
|
+
const triggerHandlers = module.functions.filter(fn => fn.isTriggerHandler && fn.triggerName)
|
|
272
|
+
const triggerNames = new Set(triggerHandlers.map(fn => fn.triggerName!))
|
|
273
|
+
|
|
274
|
+
// Collect all tick functions
|
|
275
|
+
const tickFunctionNames: string[] = []
|
|
276
|
+
for (const fn of module.functions) {
|
|
277
|
+
if (fn.isTickLoop) {
|
|
278
|
+
tickFunctionNames.push(fn.name)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// pack.mcmeta
|
|
283
|
+
files.push({
|
|
284
|
+
path: 'pack.mcmeta',
|
|
285
|
+
content: JSON.stringify({
|
|
286
|
+
pack: { pack_format: 26, description: `${ns} datapack — compiled by redscript` }
|
|
287
|
+
}, null, 2),
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// __load.mcfunction — create scoreboard objective + trigger registrations
|
|
291
|
+
const loadLines = [
|
|
292
|
+
`# RedScript runtime init`,
|
|
293
|
+
`scoreboard objectives add ${OBJ} dummy`,
|
|
294
|
+
]
|
|
295
|
+
for (const g of module.globals) {
|
|
296
|
+
loadLines.push(`scoreboard players set ${varRef(g)} ${OBJ} 0`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Add trigger objectives
|
|
300
|
+
for (const triggerName of triggerNames) {
|
|
301
|
+
loadLines.push(`scoreboard objectives add ${triggerName} trigger`)
|
|
302
|
+
loadLines.push(`scoreboard players enable @a ${triggerName}`)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Generate trigger dispatch functions
|
|
306
|
+
for (const triggerName of triggerNames) {
|
|
307
|
+
const handlers = triggerHandlers.filter(fn => fn.triggerName === triggerName)
|
|
308
|
+
|
|
309
|
+
// __trigger_{name}_dispatch.mcfunction
|
|
310
|
+
const dispatchLines = [
|
|
311
|
+
`# Trigger dispatch for ${triggerName}`,
|
|
312
|
+
]
|
|
313
|
+
for (const handler of handlers) {
|
|
314
|
+
dispatchLines.push(`function ${ns}:${handler.name}`)
|
|
315
|
+
}
|
|
316
|
+
dispatchLines.push(`scoreboard players set @s ${triggerName} 0`)
|
|
317
|
+
dispatchLines.push(`scoreboard players enable @s ${triggerName}`)
|
|
318
|
+
|
|
319
|
+
files.push({
|
|
320
|
+
path: `data/${ns}/function/__trigger_${triggerName}_dispatch.mcfunction`,
|
|
321
|
+
content: dispatchLines.join('\n'),
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Generate each function (and collect constants for load)
|
|
326
|
+
for (const fn of module.functions) {
|
|
327
|
+
// Constant setup — place constants in __load.mcfunction
|
|
328
|
+
const consts = collectConsts(fn)
|
|
329
|
+
if (consts.size > 0) {
|
|
330
|
+
loadLines.push(...Array.from(consts).map(constSetup))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Entry block → <fn_name>.mcfunction
|
|
334
|
+
// Continuation blocks → <fn_name>/<label>.mcfunction
|
|
335
|
+
for (let i = 0; i < fn.blocks.length; i++) {
|
|
336
|
+
const block = fn.blocks[i]
|
|
337
|
+
const lines: string[] = [`# block: ${block.label}`]
|
|
338
|
+
|
|
339
|
+
// Param setup in entry block
|
|
340
|
+
if (i === 0) {
|
|
341
|
+
for (let j = 0; j < fn.params.length; j++) {
|
|
342
|
+
lines.push(`scoreboard players operation ${varRef(fn.params[j])} ${OBJ} = $p${j} ${OBJ}`)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (const instr of block.instrs) {
|
|
347
|
+
lines.push(...emitInstr(instr as any, ns))
|
|
348
|
+
}
|
|
349
|
+
lines.push(...emitTerm(block.term, ns, fn.name))
|
|
350
|
+
|
|
351
|
+
const filePath = i === 0
|
|
352
|
+
? `data/${ns}/function/${fn.name}.mcfunction`
|
|
353
|
+
: `data/${ns}/function/${fn.name}/${block.label}.mcfunction`
|
|
354
|
+
|
|
355
|
+
files.push({ path: filePath, content: lines.join('\n') })
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Write __load.mcfunction
|
|
360
|
+
files.push({
|
|
361
|
+
path: `data/${ns}/function/__load.mcfunction`,
|
|
362
|
+
content: loadLines.join('\n'),
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// minecraft:load tag pointing to __load
|
|
366
|
+
files.push({
|
|
367
|
+
path: `data/minecraft/tags/function/load.json`,
|
|
368
|
+
content: JSON.stringify({ values: [`${ns}:__load`] }, null, 2),
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
// __tick.mcfunction — calls all @tick functions + trigger check
|
|
372
|
+
const tickLines = ['# RedScript tick dispatcher']
|
|
373
|
+
|
|
374
|
+
// Call all @tick functions
|
|
375
|
+
for (const fnName of tickFunctionNames) {
|
|
376
|
+
tickLines.push(`function ${ns}:${fnName}`)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Call trigger check if there are triggers
|
|
380
|
+
if (triggerNames.size > 0) {
|
|
381
|
+
tickLines.push(`# Trigger checks`)
|
|
382
|
+
for (const triggerName of triggerNames) {
|
|
383
|
+
tickLines.push(`execute as @a[scores={${triggerName}=1..}] run function ${ns}:__trigger_${triggerName}_dispatch`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Only generate __tick if there's something to run
|
|
388
|
+
if (tickFunctionNames.length > 0 || triggerNames.size > 0) {
|
|
389
|
+
files.push({
|
|
390
|
+
path: `data/${ns}/function/__tick.mcfunction`,
|
|
391
|
+
content: tickLines.join('\n'),
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
// minecraft:tick tag pointing to __tick
|
|
395
|
+
files.push({
|
|
396
|
+
path: `data/minecraft/tags/function/tick.json`,
|
|
397
|
+
content: JSON.stringify({ values: [`${ns}:__tick`] }, null, 2),
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const fn of module.functions) {
|
|
402
|
+
const eventTrigger = fn.eventTrigger
|
|
403
|
+
if (!eventTrigger) {
|
|
404
|
+
continue
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let path = ''
|
|
408
|
+
let criteria: Record<string, unknown> = {}
|
|
409
|
+
|
|
410
|
+
switch (eventTrigger.kind) {
|
|
411
|
+
case 'advancement':
|
|
412
|
+
path = `data/${ns}/advancements/on_advancement_${fn.name}.json`
|
|
413
|
+
criteria = {
|
|
414
|
+
trigger: {
|
|
415
|
+
trigger: `minecraft:${eventTrigger.value}`,
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
break
|
|
419
|
+
case 'craft':
|
|
420
|
+
path = `data/${ns}/advancements/on_craft_${fn.name}.json`
|
|
421
|
+
criteria = {
|
|
422
|
+
crafted: {
|
|
423
|
+
trigger: 'minecraft:inventory_changed',
|
|
424
|
+
conditions: {
|
|
425
|
+
items: [
|
|
426
|
+
{
|
|
427
|
+
items: [eventTrigger.value],
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
break
|
|
434
|
+
case 'death':
|
|
435
|
+
path = `data/${ns}/advancements/on_death_${fn.name}.json`
|
|
436
|
+
criteria = {
|
|
437
|
+
death: {
|
|
438
|
+
trigger: 'minecraft:entity_killed_player',
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
break
|
|
442
|
+
case 'login':
|
|
443
|
+
case 'join_team':
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
advancements.push({
|
|
448
|
+
path,
|
|
449
|
+
content: JSON.stringify({
|
|
450
|
+
criteria,
|
|
451
|
+
rewards: {
|
|
452
|
+
function: `${ns}:${fn.name}`,
|
|
453
|
+
},
|
|
454
|
+
}, null, 2),
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const stats = createEmptyOptimizationStats()
|
|
459
|
+
if (!optimizeCommands) {
|
|
460
|
+
return { files, advancements, stats }
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const optimized = applyFunctionOptimization(files)
|
|
464
|
+
mergeOptimizationStats(stats, optimized.stats)
|
|
465
|
+
return { files: optimized.files, advancements, stats }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function generateDatapack(module: IRModule): DatapackFile[] {
|
|
469
|
+
const generated = generateDatapackWithStats(module)
|
|
470
|
+
return [...generated.files, ...generated.advancements]
|
|
471
|
+
}
|