redscript-mc 1.2.27 → 1.2.29
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/README.md +24 -13
- package/README.zh.md +16 -5
- package/dist/__tests__/cli.test.js +13 -13
- package/dist/__tests__/optimizer-advanced.test.js +4 -4
- package/dist/cli.js +13 -5
- package/dist/codegen/mcfunction/index.d.ts +4 -0
- package/dist/codegen/mcfunction/index.js +9 -4
- package/dist/compile.d.ts +5 -0
- package/dist/compile.js +9 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -6
- package/dist/lowering/index.d.ts +3 -0
- package/dist/lowering/index.js +95 -65
- package/dist/optimizer/commands.d.ts +1 -0
- package/dist/optimizer/commands.js +18 -11
- package/dist/optimizer/structure.d.ts +1 -0
- package/dist/optimizer/structure.js +6 -1
- package/editors/vscode/out/extension.js +1797 -1157
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/src/hover.ts +18 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +12 -3
- package/examples/math-showcase.mcrs +146 -0
- package/examples/readme-demo.mcrs +92 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +13 -13
- package/src/__tests__/optimizer-advanced.test.ts +4 -4
- package/src/cli.ts +14 -5
- package/src/codegen/mcfunction/index.ts +14 -5
- package/src/compile.ts +16 -2
- package/src/index.ts +18 -7
- package/src/lowering/index.ts +95 -64
- package/src/optimizer/commands.ts +18 -12
- package/src/optimizer/structure.ts +6 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.37",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "redscript-vscode",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.37",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"redscript": "file:../../"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"../..": {
|
|
25
25
|
"name": "redscript-mc",
|
|
26
|
-
"version": "1.2.
|
|
26
|
+
"version": "1.2.28",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"bin": {
|
|
29
29
|
"redscript": "dist/cli.js",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
3
|
"displayName": "RedScript for Minecraft",
|
|
4
4
|
"description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.37",
|
|
6
6
|
"publisher": "bkmashiro",
|
|
7
7
|
"icon": "icon.png",
|
|
8
8
|
"license": "MIT",
|
|
@@ -1009,6 +1009,24 @@ export function registerHoverProvider(context: vscode.ExtensionContext): void {
|
|
|
1009
1009
|
provideHover(document, position) {
|
|
1010
1010
|
const line = document.lineAt(position.line).text
|
|
1011
1011
|
|
|
1012
|
+
// ── #rs — special compiler token ────────────────────────
|
|
1013
|
+
const rsRange = document.getWordRangeAtPosition(position, /#rs\b/)
|
|
1014
|
+
if (rsRange) {
|
|
1015
|
+
const md = new vscode.MarkdownString('', true)
|
|
1016
|
+
md.isTrusted = true
|
|
1017
|
+
md.appendCodeblock('#rs', 'redscript')
|
|
1018
|
+
md.appendMarkdown('**RS Internal Scoreboard Objective** *(compiler token)*\n\n')
|
|
1019
|
+
md.appendMarkdown('Resolves to the current datapack\'s internal scoreboard objective at compile time.\n\n')
|
|
1020
|
+
md.appendMarkdown('Default: `__<namespace>` (e.g. `__mygame` for namespace `mygame`).\n\n')
|
|
1021
|
+
md.appendMarkdown('Use `#rs` in `scoreboard_get` / `scoreboard_set` when you need to read or write\n')
|
|
1022
|
+
md.appendMarkdown('the compiler\'s own variable slots — such as in stdlib implementations.\n\n')
|
|
1023
|
+
md.appendMarkdown('> ⚠️ Unlike other `#name` tokens, `#rs` does **not** compile to the literal string `rs`.\n')
|
|
1024
|
+
md.appendMarkdown('> It tracks the `--scoreboard` flag or the `__<namespace>` default.\n\n')
|
|
1025
|
+
md.appendMarkdown('**Example:**\n')
|
|
1026
|
+
md.appendCodeblock('scoreboard_set("timer_ticks", #rs, 0);\n// compiles to: scoreboard players set timer_ticks __mygame 0', 'redscript')
|
|
1027
|
+
return new vscode.Hover(md, rsRange)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1012
1030
|
// ── #mc_name hover ──────────────────────────────────────
|
|
1013
1031
|
const mcRange = document.getWordRangeAtPosition(position, /#[a-zA-Z_][a-zA-Z0-9_]*/)
|
|
1014
1032
|
if (mcRange) {
|
|
@@ -307,9 +307,18 @@
|
|
|
307
307
|
},
|
|
308
308
|
|
|
309
309
|
"mc-name": {
|
|
310
|
-
"comment": "#objective, #tag, #team — unquoted MC identifier literal",
|
|
311
|
-
"
|
|
312
|
-
|
|
310
|
+
"comment": "#objective, #tag, #team — unquoted MC identifier literal. #rs is the special RS-internal scoreboard objective token.",
|
|
311
|
+
"patterns": [
|
|
312
|
+
{
|
|
313
|
+
"comment": "#rs — special compiler token: resolves to the current datapack's internal scoreboard objective (__<namespace>)",
|
|
314
|
+
"name": "support.constant.rs-objective.redscript",
|
|
315
|
+
"match": "#rs\\b"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"name": "variable.other.mc-name.redscript",
|
|
319
|
+
"match": "#[a-zA-Z_][a-zA-Z0-9_]*"
|
|
320
|
+
}
|
|
321
|
+
]
|
|
313
322
|
},
|
|
314
323
|
|
|
315
324
|
"operators": {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// math-showcase.mcrs — Fancy math examples for README / demo
|
|
2
|
+
// Run each fn individually: /function mathshow:<name>
|
|
3
|
+
//
|
|
4
|
+
// Compile: node dist/cli.js compile examples/math-showcase.mcrs -o <dp> --namespace mathshow
|
|
5
|
+
|
|
6
|
+
import "../src/stdlib/math.mcrs"
|
|
7
|
+
import "../src/stdlib/vec.mcrs"
|
|
8
|
+
import "../src/stdlib/advanced.mcrs"
|
|
9
|
+
import "../src/stdlib/bigint.mcrs"
|
|
10
|
+
|
|
11
|
+
// ─── 1. Trig table — print sin & cos for 0°, 15°, 30° … 90° ────────────────
|
|
12
|
+
// NOTE: sin_fixed/cos_fixed return values in ×1000 space (sin(30°) = 500, not 0.5).
|
|
13
|
+
// s*s + c*c ≈ 1000000 because (sin×1000)²+(cos×1000)² = 10⁶×(sin²+cos²) = 10⁶.
|
|
14
|
+
// Requires __load to have run (initializes the sin table).
|
|
15
|
+
fn trig_table() {
|
|
16
|
+
say("§e=== Trig Table (×1000 fixed-point, needs __load) ===");
|
|
17
|
+
let deg: int = 0;
|
|
18
|
+
while (deg <= 90) {
|
|
19
|
+
let s: int = sin_fixed(deg);
|
|
20
|
+
let c: int = cos_fixed(deg);
|
|
21
|
+
say(f" {deg}° sin={s} cos={c} s²+c²={s*s + c*c} (expect ~1000000)");
|
|
22
|
+
deg = deg + 15;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── 2. Newton–Raphson sqrt — compare isqrt vs sqrt_fixed ──────────────────
|
|
27
|
+
// isqrt(n) → floor(sqrt(n)), raw integer
|
|
28
|
+
// sqrt_fixed(x) → sqrt of x/1000 expressed in ×1000 format
|
|
29
|
+
// e.g. sqrt_fixed(2000) = sqrt(2.0)×1000 = 1414
|
|
30
|
+
// Input must be in ×1000 space. Max input ≈ 2147000 (before overflow).
|
|
31
|
+
// NOTE: Each isqrt call runs a convergence loop (bits+guess+Newton).
|
|
32
|
+
// Large values = more loop iterations = more MC commands.
|
|
33
|
+
// Keep inputs moderate to stay under Paper's maxCommandChainLength (65536).
|
|
34
|
+
fn sqrt_showcase() {
|
|
35
|
+
say("§e=== Integer Square Root (isqrt) ===");
|
|
36
|
+
say(f" isqrt(2) = {isqrt(2)} (exact: 1)");
|
|
37
|
+
say(f" isqrt(144) = {isqrt(144)} (exact: 12)");
|
|
38
|
+
say(f" isqrt(10000) = {isqrt(10000)} (exact: 100)");
|
|
39
|
+
say(f" isqrt(1000000) = {isqrt(1000000)} (exact: 1000)");
|
|
40
|
+
|
|
41
|
+
say("§e=== Fixed-Point sqrt (input&output in ×1000) ===");
|
|
42
|
+
say(f" sqrt_fixed(1000) = {sqrt_fixed(1000)} (sqrt(1.0) = 1.000)");
|
|
43
|
+
say(f" sqrt_fixed(2000) = {sqrt_fixed(2000)} (sqrt(2.0) ≈ 1.414)");
|
|
44
|
+
say(f" sqrt_fixed(4000) = {sqrt_fixed(4000)} (sqrt(4.0) = 2.000)");
|
|
45
|
+
say(f" sqrt_fixed(9000) = {sqrt_fixed(9000)} (sqrt(9.0) = 3.000)");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── 3. 2D vector geometry ──────────────────────────────────────────────────
|
|
49
|
+
fn vector_showcase() {
|
|
50
|
+
say("§e=== 2D Vector Geometry ===");
|
|
51
|
+
// 3-4-5 right triangle
|
|
52
|
+
let d345: int = distance2d_fixed(0, 0, 300, 400);
|
|
53
|
+
say(f" dist((0,0)→(300,400)) = {d345} (expect 500)");
|
|
54
|
+
// unit circle
|
|
55
|
+
let d_unit: int = length2d_fixed(1000, 0);
|
|
56
|
+
say(f" length(1000, 0) = {d_unit} (expect 1000)");
|
|
57
|
+
// atan2
|
|
58
|
+
say(f" atan2(1000, 0) = {atan2_fixed(1000, 0)}° (expect 90)");
|
|
59
|
+
say(f" atan2(1000, 1000) = {atan2_fixed(1000, 1000)}° (expect 45)");
|
|
60
|
+
say(f" atan2(-1, 0) = {atan2_fixed(-1, 0)}° (expect 270)");
|
|
61
|
+
// dot product: orthogonal vectors → 0
|
|
62
|
+
say(f" dot2d(1000,0, 0,1000) = {dot2d(1000, 0, 0, 1000)} (expect 0, orthogonal)");
|
|
63
|
+
say(f" dot2d(1000,0, 1000,0) = {dot2d(1000, 0, 1000, 0)} (expect 1000000, parallel)");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── 4. Number theory ───────────────────────────────────────────────────────
|
|
67
|
+
fn number_theory() {
|
|
68
|
+
say("§e=== Number Theory ===");
|
|
69
|
+
say(f" gcd(360, 252) = {gcd(360, 252)} (expect 36)");
|
|
70
|
+
say(f" lcm(12, 18) = {lcm(12, 18)} (expect 36)");
|
|
71
|
+
say(f" is_prime(97) = {is_prime(97)} (expect 1)");
|
|
72
|
+
say(f" is_prime(100) = {is_prime(100)} (expect 0)");
|
|
73
|
+
say(f" fib(20) = {fib(20)} (expect 6765)");
|
|
74
|
+
say(f" collatz_steps(27) = {collatz_steps(27)} (expect 111)");
|
|
75
|
+
say(f" digit_sum(123456789) = {digit_sum(123456789)} (expect 45)");
|
|
76
|
+
say(f" mod_pow(2, 10, 1000) = {mod_pow(2, 10, 1000)} (expect 24)");
|
|
77
|
+
say(f" mod_pow(7, 5, 13) = {mod_pow(7, 5, 13)} (expect 11)");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── 5. Fixed-point interpolation & easing ──────────────────────────────────
|
|
81
|
+
fn easing_showcase() {
|
|
82
|
+
say("§e=== Easing & Interpolation (t = 0..1000) ===");
|
|
83
|
+
let t: int = 0;
|
|
84
|
+
while (t <= 1000) {
|
|
85
|
+
let lin: int = lerp(0, 1000, t);
|
|
86
|
+
let sm: int = smoothstep(0, 1000, t);
|
|
87
|
+
let smr: int = smootherstep(0, 1000, t);
|
|
88
|
+
say(f" t={t} lerp={lin} smooth={sm} smoother={smr}");
|
|
89
|
+
t = t + 250;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── 6. Hash & noise ────────────────────────────────────────────────────────
|
|
94
|
+
fn hash_showcase() {
|
|
95
|
+
say("§e=== Hash & Procedural Noise ===");
|
|
96
|
+
say(f" hash_int(0) = {hash_int(0)}");
|
|
97
|
+
say(f" hash_int(1) = {hash_int(1)}");
|
|
98
|
+
say(f" hash_int(42) = {hash_int(42)}");
|
|
99
|
+
say(f" noise1d(0) = {noise1d(0)} (0..1000)");
|
|
100
|
+
say(f" noise1d(100) = {noise1d(100)}");
|
|
101
|
+
say(f" noise1d(200) = {noise1d(200)}");
|
|
102
|
+
say(f" noise1d(300) = {noise1d(300)}");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── 7. BigInt Fibonacci — numbers beyond INT32 ─────────────────────────────
|
|
106
|
+
fn bigint_showcase() {
|
|
107
|
+
say("§e=== BigInt — Arbitrary Precision (base 10000, 8 limbs) ===");
|
|
108
|
+
bigint_fib(50);
|
|
109
|
+
let l0: int = bigint_get_a(0);
|
|
110
|
+
let l1: int = bigint_get_a(1);
|
|
111
|
+
let l2: int = bigint_get_a(2);
|
|
112
|
+
say(f" fib(50) limbs: [{l2}][{l1}][{l0}]");
|
|
113
|
+
say(f" = {l2}×10⁸ + {l1}×10⁴ + {l0}");
|
|
114
|
+
say(f" = 12,586,269,025 (verify: l0=9025 l1=8626 l2=125)");
|
|
115
|
+
|
|
116
|
+
bigint_fib(70);
|
|
117
|
+
let m0: int = bigint_get_a(0);
|
|
118
|
+
let m1: int = bigint_get_a(1);
|
|
119
|
+
let m2: int = bigint_get_a(2);
|
|
120
|
+
let m3: int = bigint_get_a(3);
|
|
121
|
+
say(f" fib(70) limbs: [{m3}][{m2}][{m1}][{m0}]");
|
|
122
|
+
say(f" = 190,392,490,709,135");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── 8. Mandelbrot / Julia — fractals in integer arithmetic ─────────────────
|
|
126
|
+
fn fractal_showcase() {
|
|
127
|
+
say("§e=== Mandelbrot & Julia (fixed-point, scale=1000) ===");
|
|
128
|
+
// Mandelbrot: does (cr, ci) escape in <max_iter> steps?
|
|
129
|
+
say(f" mandelbrot_iter(0, 0, 64) = {mandelbrot_iter(0, 0, 64)} (inside, expect 64)");
|
|
130
|
+
say(f" mandelbrot_iter(2000, 0, 64) = {mandelbrot_iter(2000, 0, 64)} (outside, < 64)");
|
|
131
|
+
say(f" mandelbrot_iter(-500, 500, 64) = {mandelbrot_iter(-500, 500, 64)}");
|
|
132
|
+
say(f" julia_iter(0, 0, -500, 250, 64) = {julia_iter(0, 0, -500, 250, 64)}");
|
|
133
|
+
say(f" julia_iter(500, 0, -500, 250, 64)= {julia_iter(500, 0, -500, 250, 64)}");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── 9. Run everything ──────────────────────────────────────────────────────
|
|
137
|
+
fn run_all() {
|
|
138
|
+
trig_table();
|
|
139
|
+
sqrt_showcase();
|
|
140
|
+
vector_showcase();
|
|
141
|
+
number_theory();
|
|
142
|
+
easing_showcase();
|
|
143
|
+
hash_showcase();
|
|
144
|
+
bigint_showcase();
|
|
145
|
+
fractal_showcase();
|
|
146
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// readme-demo.mcrs — RedScript v1.2.27 README visual showcase
|
|
2
|
+
// What 55 lines of RedScript can do on a vanilla Minecraft server.
|
|
3
|
+
//
|
|
4
|
+
// Compile: node dist/cli.js compile examples/readme-demo.mcrs -o <datapack> --namespace rsdemo
|
|
5
|
+
// In-game: /function rsdemo:start (then record your gif)
|
|
6
|
+
// /function rsdemo:stop
|
|
7
|
+
|
|
8
|
+
import "../src/stdlib/math.mcrs"
|
|
9
|
+
|
|
10
|
+
let t: int = 0; // 0..359 degrees, 4° per tick = full cycle every 1.5 s
|
|
11
|
+
let frame: int = 0;
|
|
12
|
+
let on: bool = false;
|
|
13
|
+
|
|
14
|
+
@load fn _init() {
|
|
15
|
+
t = 0;
|
|
16
|
+
frame = 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@tick fn _wave_tick() {
|
|
20
|
+
if (!on) { return; }
|
|
21
|
+
|
|
22
|
+
t = (t + 4) % 360;
|
|
23
|
+
frame = frame + 1;
|
|
24
|
+
|
|
25
|
+
let s: int = sin_fixed(t); // -1000 .. 1000
|
|
26
|
+
let c: int = cos_fixed(t); // -1000 .. 1000
|
|
27
|
+
let mag: int = abs(s); // 0 .. 1000
|
|
28
|
+
|
|
29
|
+
// ── Particle rings: spread values scale with sin(t) ─────────────────
|
|
30
|
+
// Inner ring — always visible, small tight shimmer
|
|
31
|
+
foreach (p in @a) at @s {
|
|
32
|
+
particle("minecraft:end_rod", ~0, ~1, ~0, 0.25, 0.08, 0.25, 0.02, 6);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Mid ring — appears when sin > 0 (upper half of cycle)
|
|
36
|
+
if (s > 0) {
|
|
37
|
+
foreach (p in @a) at @s {
|
|
38
|
+
particle("minecraft:end_rod", ~0, ~1, ~0, 0.9, 0.25, 0.9, 0.02, 12);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Outer ring + soul fire — sin > 600
|
|
43
|
+
if (s > 600) {
|
|
44
|
+
foreach (p in @a) at @s {
|
|
45
|
+
particle("minecraft:end_rod", ~0, ~1, ~0, 1.8, 0.4, 1.8, 0.02, 18);
|
|
46
|
+
particle("minecraft:soul_fire_flame", ~0, ~2, ~0, 0.9, 0.3, 0.9, 0.03, 8);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Peak flash — fires ~4 ticks per cycle
|
|
51
|
+
if (s > 940) {
|
|
52
|
+
foreach (p in @a) at @s {
|
|
53
|
+
particle("minecraft:flash", ~0, ~3, ~0, 0.4, 0.3, 0.4, 0.1, 3);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Negative trough — ground portal swirl
|
|
58
|
+
if (s < -700) {
|
|
59
|
+
foreach (p in @a) at @s {
|
|
60
|
+
particle("minecraft:portal", ~0, ~0.5, ~0, 1.2, 0.4, 1.2, 0.05, 12);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Actionbar: live math rotating every 3 s ──────────────────────────
|
|
65
|
+
let seg: int = (frame / 45) % 4;
|
|
66
|
+
if (seg == 0) {
|
|
67
|
+
actionbar(@a, f"§3⚡ §bsin({t}°) = §e{s}‰ cos({t}°) = §e{c}‰");
|
|
68
|
+
}
|
|
69
|
+
if (seg == 1) {
|
|
70
|
+
actionbar(@a, f"§3⚡ §bsmoothstep(|sin|) = §e{smoothstep(0, 1000, mag)}‰ isqrt({frame}) = §e{isqrt(frame)}");
|
|
71
|
+
}
|
|
72
|
+
if (seg == 2) {
|
|
73
|
+
actionbar(@a, f"§3⚡ §bgcd(360, {t + 1}) = §e{gcd(360, t + 1)} log2({mag + 1}) = §e{log2_int(mag + 1)}");
|
|
74
|
+
}
|
|
75
|
+
if (seg == 3) {
|
|
76
|
+
actionbar(@a, f"§3⚡ §bpow(2, {(frame / 30) % 10 + 1}) = §e{pow_int(2, (frame / 30) % 10 + 1)} clamp(sin) = §e{clamp(s, -500, 500)}");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn start() {
|
|
81
|
+
on = true;
|
|
82
|
+
t = 0;
|
|
83
|
+
frame = 0;
|
|
84
|
+
title(@a, f"§e✦ §bRedScript§e ✦", f"§7trig-driven particle rings");
|
|
85
|
+
say("§a▶ readme-demo started — /function rsdemo:stop to end");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn stop() {
|
|
89
|
+
on = false;
|
|
90
|
+
actionbar(@a, f"§7[ demo stopped ]");
|
|
91
|
+
say("§c■ readme-demo stopped");
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -55,7 +55,7 @@ describe('CLI API', () => {
|
|
|
55
55
|
const result = compile(source, { namespace: 'mygame', filePath: mainPath })
|
|
56
56
|
const tickTimer = result.files.find(file => file.path.endsWith('/tick_timer.mcfunction'))
|
|
57
57
|
|
|
58
|
-
expect(tickTimer?.content).toContain('scoreboard players set #rs
|
|
58
|
+
expect(tickTimer?.content).toContain('scoreboard players set #rs __mygame.timer_ticks 1')
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
it('adds a call-site hash for stdlib internal scoreboard objectives', () => {
|
|
@@ -89,7 +89,7 @@ describe('CLI API', () => {
|
|
|
89
89
|
expect(timerFns).toHaveLength(2)
|
|
90
90
|
|
|
91
91
|
const objectives = timerFns
|
|
92
|
-
.flatMap(file => [...file.content.matchAll(/
|
|
92
|
+
.flatMap(file => [...file.content.matchAll(/mygame\._timer_([0-9a-f]{4})/g)].map(match => match[0]))
|
|
93
93
|
|
|
94
94
|
expect(new Set(objectives).size).toBe(2)
|
|
95
95
|
})
|
|
@@ -113,8 +113,8 @@ describe('CLI API', () => {
|
|
|
113
113
|
|
|
114
114
|
expect(result.typeErrors).toEqual([])
|
|
115
115
|
const newFn = result.files.find(file => file.path.endsWith('/Timer_new.mcfunction'))
|
|
116
|
-
expect(newFn?.content).toContain('scoreboard players set timer_ticks
|
|
117
|
-
expect(newFn?.content).toContain('scoreboard players set timer_active
|
|
116
|
+
expect(newFn?.content).toContain('scoreboard players set timer_ticks __timernew 0')
|
|
117
|
+
expect(newFn?.content).toContain('scoreboard players set timer_active __timernew 0')
|
|
118
118
|
})
|
|
119
119
|
|
|
120
120
|
it('Timer.start/pause/reset', () => {
|
|
@@ -142,9 +142,9 @@ describe('CLI API', () => {
|
|
|
142
142
|
const pauseFn = result.files.find(file => file.path.endsWith('/Timer_pause.mcfunction'))
|
|
143
143
|
const resetFn = result.files.find(file => file.path.endsWith('/Timer_reset.mcfunction'))
|
|
144
144
|
|
|
145
|
-
expect(startFn?.content).toContain('scoreboard players set timer_active
|
|
146
|
-
expect(pauseFn?.content).toContain('scoreboard players set timer_active
|
|
147
|
-
expect(resetFn?.content).toContain('scoreboard players set timer_ticks
|
|
145
|
+
expect(startFn?.content).toContain('scoreboard players set timer_active __timerstate 1')
|
|
146
|
+
expect(pauseFn?.content).toContain('scoreboard players set timer_active __timerstate 0')
|
|
147
|
+
expect(resetFn?.content).toContain('scoreboard players set timer_ticks __timerstate 0')
|
|
148
148
|
})
|
|
149
149
|
|
|
150
150
|
it('Timer.done returns bool', () => {
|
|
@@ -171,9 +171,9 @@ describe('CLI API', () => {
|
|
|
171
171
|
expect(result.typeErrors).toEqual([])
|
|
172
172
|
const doneFn = result.files.find(file => file.path.endsWith('/Timer_done.mcfunction'))
|
|
173
173
|
const mainFn = result.files.find(file => file.path.endsWith('/main.mcfunction'))
|
|
174
|
-
expect(doneFn?.content).toContain('scoreboard players get timer_ticks
|
|
174
|
+
expect(doneFn?.content).toContain('scoreboard players get timer_ticks __timerdone')
|
|
175
175
|
expect(doneFn?.content).toContain('return run scoreboard players get')
|
|
176
|
-
expect(mainFn?.content).toContain('execute if score $main_finished
|
|
176
|
+
expect(mainFn?.content).toContain('execute if score $main_finished __timerdone matches 1..')
|
|
177
177
|
})
|
|
178
178
|
|
|
179
179
|
it('Timer.tick increments', () => {
|
|
@@ -201,10 +201,10 @@ describe('CLI API', () => {
|
|
|
201
201
|
.map(file => file.content)
|
|
202
202
|
.join('\n')
|
|
203
203
|
|
|
204
|
-
expect(tickOutput).toContain('scoreboard players get timer_active
|
|
205
|
-
expect(tickOutput).toContain('scoreboard players get timer_ticks
|
|
206
|
-
expect(tickOutput).toContain(' += $const_1
|
|
207
|
-
expect(tickOutput).toContain('execute store result score timer_ticks
|
|
204
|
+
expect(tickOutput).toContain('scoreboard players get timer_active __timertick')
|
|
205
|
+
expect(tickOutput).toContain('scoreboard players get timer_ticks __timertick')
|
|
206
|
+
expect(tickOutput).toContain(' += $const_1 __timertick')
|
|
207
|
+
expect(tickOutput).toContain('execute store result score timer_ticks __timertick run scoreboard players get $_')
|
|
208
208
|
})
|
|
209
209
|
})
|
|
210
210
|
|
|
@@ -28,7 +28,7 @@ fn turret_tick() {
|
|
|
28
28
|
const parent = getFileContent(result.files, 'data/test/function/turret_tick.mcfunction')
|
|
29
29
|
const loopBody = getFileContent(result.files, 'data/test/function/turret_tick/foreach_0.mcfunction')
|
|
30
30
|
|
|
31
|
-
const hoistedRead = 'execute store result score $_0
|
|
31
|
+
const hoistedRead = 'execute store result score $_0 __test run scoreboard players get config test.turret_range'
|
|
32
32
|
const executeCall = 'execute as @e[tag=turret] run function test:turret_tick/foreach_0'
|
|
33
33
|
|
|
34
34
|
expect(parent).toContain(hoistedRead)
|
|
@@ -54,7 +54,7 @@ fn read_twice() {
|
|
|
54
54
|
const readMatches = fn.match(/scoreboard players get @s test\.coins/g) ?? []
|
|
55
55
|
|
|
56
56
|
expect(readMatches).toHaveLength(1)
|
|
57
|
-
expect(fn).toContain('scoreboard players operation $_1
|
|
57
|
+
expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test')
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
test('reuses duplicate arithmetic sequences', () => {
|
|
@@ -71,10 +71,10 @@ fn math() {
|
|
|
71
71
|
|
|
72
72
|
const result = compile(source, { namespace: 'test' })
|
|
73
73
|
const fn = getFileContent(result.files, 'data/test/function/math.mcfunction')
|
|
74
|
-
const addMatches = fn.match(/\+= \$const_2
|
|
74
|
+
const addMatches = fn.match(/\+= \$const_2 __test/g) ?? []
|
|
75
75
|
|
|
76
76
|
expect(addMatches).toHaveLength(1)
|
|
77
|
-
expect(fn).toContain('scoreboard players operation $_1
|
|
77
|
+
expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test')
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
package/src/cli.ts
CHANGED
|
@@ -29,7 +29,7 @@ function printUsage(): void {
|
|
|
29
29
|
RedScript Compiler
|
|
30
30
|
|
|
31
31
|
Usage:
|
|
32
|
-
redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--target <target>] [--no-dce]
|
|
32
|
+
redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--scoreboard <obj>] [--target <target>] [--no-dce]
|
|
33
33
|
redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
|
|
34
34
|
redscript check <file>
|
|
35
35
|
redscript fmt <file.mcrs> [file2.mcrs ...]
|
|
@@ -54,6 +54,9 @@ Options:
|
|
|
54
54
|
--target <target> Output target: datapack (default), cmdblock, or structure
|
|
55
55
|
--no-dce Disable AST dead code elimination
|
|
56
56
|
--no-mangle Disable variable name mangling (use readable names)
|
|
57
|
+
--scoreboard <obj> Scoreboard objective for variables (default: namespace).
|
|
58
|
+
Each datapack automatically uses its namespace as the objective
|
|
59
|
+
so multiple datapacks can coexist without collisions.
|
|
57
60
|
--stats Print optimizer statistics
|
|
58
61
|
--hot-reload <url> After each successful compile, POST to <url>/reload
|
|
59
62
|
(use with redscript-testharness; e.g. http://localhost:25561)
|
|
@@ -168,6 +171,7 @@ function parseArgs(args: string[]): {
|
|
|
168
171
|
hotReload?: string
|
|
169
172
|
dce?: boolean
|
|
170
173
|
mangle?: boolean
|
|
174
|
+
scoreboardObjective?: string
|
|
171
175
|
} {
|
|
172
176
|
const result: ReturnType<typeof parseArgs> = { dce: true, mangle: true }
|
|
173
177
|
let i = 0
|
|
@@ -199,6 +203,9 @@ function parseArgs(args: string[]): {
|
|
|
199
203
|
} else if (arg === '--no-mangle') {
|
|
200
204
|
result.mangle = false
|
|
201
205
|
i++
|
|
206
|
+
} else if (arg === '--scoreboard') {
|
|
207
|
+
result.scoreboardObjective = args[++i]
|
|
208
|
+
i++
|
|
202
209
|
} else if (arg === '--hot-reload') {
|
|
203
210
|
result.hotReload = args[++i]
|
|
204
211
|
i++
|
|
@@ -262,7 +269,8 @@ function compileCommand(
|
|
|
262
269
|
target: string = 'datapack',
|
|
263
270
|
showStats = false,
|
|
264
271
|
dce = true,
|
|
265
|
-
mangle = true
|
|
272
|
+
mangle = true,
|
|
273
|
+
scoreboardObjective: string | undefined = undefined
|
|
266
274
|
): void {
|
|
267
275
|
// Read source file
|
|
268
276
|
if (!fs.existsSync(file)) {
|
|
@@ -274,7 +282,7 @@ function compileCommand(
|
|
|
274
282
|
|
|
275
283
|
try {
|
|
276
284
|
if (target === 'cmdblock') {
|
|
277
|
-
const result = compile(source, { namespace, filePath: file, dce, mangle })
|
|
285
|
+
const result = compile(source, { namespace, filePath: file, dce, mangle, scoreboardObjective })
|
|
278
286
|
printWarnings(result.warnings)
|
|
279
287
|
|
|
280
288
|
// Generate command block JSON
|
|
@@ -305,7 +313,7 @@ function compileCommand(
|
|
|
305
313
|
printOptimizationStats(structure.stats)
|
|
306
314
|
}
|
|
307
315
|
} else {
|
|
308
|
-
const result = compile(source, { namespace, filePath: file, dce, mangle })
|
|
316
|
+
const result = compile(source, { namespace, filePath: file, dce, mangle, scoreboardObjective })
|
|
309
317
|
printWarnings(result.warnings)
|
|
310
318
|
|
|
311
319
|
// Default: generate datapack
|
|
@@ -508,7 +516,8 @@ async function main(): Promise<void> {
|
|
|
508
516
|
target,
|
|
509
517
|
parsed.stats,
|
|
510
518
|
parsed.dce,
|
|
511
|
-
parsed.mangle
|
|
519
|
+
parsed.mangle,
|
|
520
|
+
parsed.scoreboardObjective // undefined = derive from namespace in compile()
|
|
512
521
|
)
|
|
513
522
|
}
|
|
514
523
|
break
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { IRBlock, IRFunction, IRInstr, IRModule, Operand, Terminator } from '../../ir/types'
|
|
20
|
-
import { optimizeCommandFunctions, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
|
|
20
|
+
import { optimizeCommandFunctions, setOptimizerObjective, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
|
|
21
21
|
import { EVENT_TYPES, isEventTypeName, type EventTypeName } from '../../events/types'
|
|
22
22
|
import { VarAllocator } from '../var-allocator'
|
|
23
23
|
|
|
@@ -25,7 +25,8 @@ import { VarAllocator } from '../var-allocator'
|
|
|
25
25
|
// Utilities
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Default scoreboard objective — overridden per-compilation via DatapackGenerationOptions.scoreboardObjective
|
|
29
|
+
let OBJ = 'rs'
|
|
29
30
|
|
|
30
31
|
function operandToScore(op: Operand, alloc: VarAllocator): string {
|
|
31
32
|
if (op.kind === 'var') return `${alloc.alloc(op.name)} ${OBJ}`
|
|
@@ -284,6 +285,10 @@ export interface DatapackGenerationResult {
|
|
|
284
285
|
export interface DatapackGenerationOptions {
|
|
285
286
|
optimizeCommands?: boolean
|
|
286
287
|
mangle?: boolean
|
|
288
|
+
/** Scoreboard objective used for all scoreboard variables.
|
|
289
|
+
* Defaults to 'rs'. Override per-datapack to avoid collisions
|
|
290
|
+
* when multiple RedScript datapacks are loaded simultaneously. */
|
|
291
|
+
scoreboardObjective?: string
|
|
287
292
|
}
|
|
288
293
|
|
|
289
294
|
export function countMcfunctionCommands(files: DatapackFile[]): number {
|
|
@@ -354,7 +359,11 @@ export function generateDatapackWithStats(
|
|
|
354
359
|
module: IRModule,
|
|
355
360
|
options: DatapackGenerationOptions = {},
|
|
356
361
|
): DatapackGenerationResult {
|
|
357
|
-
const { optimizeCommands = true, mangle = false } = options
|
|
362
|
+
const { optimizeCommands = true, mangle = false, scoreboardObjective = 'rs' } = options
|
|
363
|
+
// Set module-level OBJ so all helper functions in this module use the correct objective.
|
|
364
|
+
// This is safe because compilation is synchronous.
|
|
365
|
+
OBJ = scoreboardObjective
|
|
366
|
+
setOptimizerObjective(scoreboardObjective)
|
|
358
367
|
const alloc = new VarAllocator(mangle)
|
|
359
368
|
const files: DatapackFile[] = []
|
|
360
369
|
const advancements: DatapackFile[] = []
|
|
@@ -402,9 +411,9 @@ export function generateDatapackWithStats(
|
|
|
402
411
|
for (const eventType of eventTypes) {
|
|
403
412
|
const detection = EVENT_TYPES[eventType].detection
|
|
404
413
|
if (eventType === 'PlayerDeath') {
|
|
405
|
-
loadLines.push(
|
|
414
|
+
loadLines.push(`scoreboard objectives add ${OBJ}.deaths deathCount`)
|
|
406
415
|
} else if (eventType === 'EntityKill') {
|
|
407
|
-
loadLines.push(
|
|
416
|
+
loadLines.push(`scoreboard objectives add ${OBJ}.kills totalKillCount`)
|
|
408
417
|
} else if (eventType === 'ItemUse') {
|
|
409
418
|
loadLines.push('# ItemUse detection requires a project-specific objective/tag setup')
|
|
410
419
|
} else if (detection === 'tag' || detection === 'advancement') {
|
package/src/compile.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as path from 'path'
|
|
|
9
9
|
|
|
10
10
|
import { Lexer } from './lexer'
|
|
11
11
|
import { Parser } from './parser'
|
|
12
|
-
import { Lowering } from './lowering'
|
|
12
|
+
import { Lowering, setScoreboardObjective } from './lowering'
|
|
13
13
|
import { optimize } from './optimizer/passes'
|
|
14
14
|
import { eliminateDeadCode } from './optimizer/dce'
|
|
15
15
|
import { generateDatapackWithStats, DatapackFile } from './codegen/mcfunction'
|
|
@@ -27,6 +27,11 @@ export interface CompileOptions {
|
|
|
27
27
|
optimize?: boolean
|
|
28
28
|
dce?: boolean
|
|
29
29
|
mangle?: boolean
|
|
30
|
+
/** Scoreboard objective used for all variable slots.
|
|
31
|
+
* Defaults to '__<namespace>' (e.g. '__mathshow') — the double-underscore
|
|
32
|
+
* prefix signals compiler-internal and avoids occupying the user's namespace.
|
|
33
|
+
* Each datapack gets a unique objective automatically; no manual setup needed. */
|
|
34
|
+
scoreboardObjective?: string
|
|
30
35
|
/** Additional source files that should be treated as *library* code.
|
|
31
36
|
* Functions in these files are DCE-eligible: they are only compiled into
|
|
32
37
|
* the datapack when actually called from user code. Each string is parsed
|
|
@@ -262,6 +267,12 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
262
267
|
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst) : { program: parsedAst, warnings: [] }
|
|
263
268
|
const ast = dceResult.program
|
|
264
269
|
|
|
270
|
+
// Configure scoreboard objective for this compilation.
|
|
271
|
+
// Default: use the datapack namespace so each datapack gets its own objective
|
|
272
|
+
// automatically, preventing variable collisions when multiple datapacks coexist.
|
|
273
|
+
const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`
|
|
274
|
+
setScoreboardObjective(scoreboardObj)
|
|
275
|
+
|
|
265
276
|
// Lowering
|
|
266
277
|
const ir = new Lowering(namespace, preprocessed.ranges).lower(ast)
|
|
267
278
|
|
|
@@ -272,7 +283,10 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
272
283
|
|
|
273
284
|
// Code generation — mangle=true by default to prevent cross-function
|
|
274
285
|
// scoreboard variable collisions in the global MC scoreboard namespace.
|
|
275
|
-
const generated = generateDatapackWithStats(optimized, {
|
|
286
|
+
const generated = generateDatapackWithStats(optimized, {
|
|
287
|
+
mangle: options.mangle ?? true,
|
|
288
|
+
scoreboardObjective: scoreboardObj,
|
|
289
|
+
})
|
|
276
290
|
|
|
277
291
|
return {
|
|
278
292
|
success: true,
|