redscript-mc 1.2.27 → 1.2.28

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 CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  <img src="./logo.png" alt="RedScript Logo" width="64" />
4
4
 
5
- <img src="https://img.shields.io/badge/RedScript-1.2.26-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
5
+ <img src="https://img.shields.io/badge/RedScript-1.2.27-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
6
6
 
7
7
  **A typed scripting language that compiles to Minecraft datapacks.**
8
8
 
9
9
  Write clean game logic. RedScript handles the scoreboard spaghetti.
10
10
 
11
11
  [![CI](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml)
12
- [![Tests](https://img.shields.io/badge/tests-917%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
12
+ [![Tests](https://img.shields.io/badge/tests-918%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
13
13
  [![npm](https://img.shields.io/npm/v/redscript-mc?color=cb3837)](https://www.npmjs.com/package/redscript-mc)
14
14
  [![npm downloads](https://img.shields.io/npm/dm/redscript-mc?color=cb3837)](https://www.npmjs.com/package/redscript-mc)
15
15
  [![VSCode](https://img.shields.io/badge/VSCode-Extension-007ACC?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=bkmashiro.redscript-vscode)
@@ -76,17 +76,6 @@ fn stop() {
76
76
 
77
77
  ---
78
78
 
79
- ### What's New in v1.2.26
80
-
81
- - **Math stdlib** (`math.mcrs`): 18 fixed-point functions — `abs`, `sign`, `min`, `max`, `clamp`, `lerp`, `isqrt`, `sqrt_fixed`, `pow_int`, `gcd`, `lcm`, `sin_fixed`, `cos_fixed`, `map`, `ceil_div`, `log2_int`, `mulfix`, `divfix`, `smoothstep`, `smootherstep`
82
- - **Vector stdlib** (`vec.mcrs`): 2D and 3D geometry — dot/cross products, `length2d_fixed`, `atan2_fixed` (binary search, O(log 46)), `normalize2d`, `rotate2d`, `lerp2d`, full 3D cross product
83
- - **Advanced stdlib** (`advanced.mcrs`): number theory (`fib`, `is_prime`, `collatz_steps`, `gcd`, `mod_pow`), hash/noise (`hash_int` splitmix32, `noise1d`), curves (`bezier_quad`), fractals (`mandelbrot_iter`, `julia_iter`), geometry experiments
84
- - **BigInt** (`bigint.mcrs`): arbitrary precision integers — base 10,000 × 8 limbs = up to 32 decimal digits; `bigint_add/sub/compare/mul/fib` running on MC scoreboard + NBT storage
85
- - **`module library;` pragma**: declare a file as a library; functions are tree-shaken out unless called — stdlib never bloats your pack
86
- - **`storage_get_int` / `storage_set_int` builtins**: dynamic NBT int array read/write with runtime indices via MC 1.20.2 macro sub-functions
87
- - **`@require_on_load(fn)` decorator**: declarative load-time dependency tracking for stdlib initializers (sin/cos table setup etc.)
88
- - **Compiler fixes**: `isqrt` large-number convergence, optimizer copy propagation alias invalidation, cross-function variable collision, MCRuntime array-index regex
89
-
90
79
  ### What's New in v1.2.25
91
80
 
92
81
  - `impl` blocks and methods for object-style APIs on structs
@@ -343,8 +332,30 @@ fn show_fib() {
343
332
 
344
333
  ---
345
334
 
335
+ ### What's New in v1.2.27
336
+
337
+ - **BigInt confirmed working in real Minecraft** (`bigint.mcrs`): arbitrary precision integers on MC scoreboard + NBT — base 10,000 × 8 limbs = up to 32 decimal digits; `bigint_add`, `bigint_sub`, `bigint_compare`, `bigint_mul`, `bigint_fib(50)` = 12,586,269,025 all verified on Paper 1.21.4
338
+ - **`storage_set_int` macro fix**: dynamic NBT array writes now use `execute store result storage` instead of `data modify set value $(n)` — avoids a silent Minecraft macro substitution bug with integer values
339
+ - **Full stdlib** (`math.mcrs`, `vec.mcrs`, `advanced.mcrs`, `bigint.mcrs`, `showcase.mcrs`): 18 math functions, 14 vector geometry functions, 20+ advanced number-theory and fractal functions
340
+ - **`module library;` pragma**: tree-shaking for library files — stdlib never bloats your pack
341
+ - **`storage_get_int` / `storage_set_int` builtins**: dynamic NBT int array read/write with runtime indices via MC 1.20.2+ macro sub-functions
342
+ - **`@require_on_load(fn)` decorator**: declarative load-time dependency tracking for sin/cos/atan table initializers
343
+
344
+ ### What's New in v1.2.26
345
+
346
+ - **Math stdlib** (`math.mcrs`): 18 fixed-point functions — `abs`, `sign`, `min`, `max`, `clamp`, `lerp`, `isqrt`, `sqrt_fixed`, `pow_int`, `gcd`, `lcm`, `sin_fixed`, `cos_fixed`, `map`, `ceil_div`, `log2_int`, `mulfix`, `divfix`, `smoothstep`, `smootherstep`
347
+ - **Vector stdlib** (`vec.mcrs`): 2D and 3D geometry — dot/cross products, `length2d_fixed`, `atan2_fixed` (binary search, O(log 46)), `normalize2d`, `rotate2d`, `lerp2d`, full 3D cross product
348
+ - **Advanced stdlib** (`advanced.mcrs`): number theory (`fib`, `is_prime`, `collatz_steps`, `gcd`, `mod_pow`), hash/noise (`hash_int` splitmix32, `noise1d`), curves (`bezier_quad`), fractals (`mandelbrot_iter`, `julia_iter`), geometry experiments
349
+ - **BigInt** (`bigint.mcrs`): arbitrary precision integers — base 10,000 × 8 limbs = up to 32 decimal digits; `bigint_add/sub/compare/mul/fib` running on MC scoreboard + NBT storage
350
+ - **Compiler fixes**: `isqrt` large-number convergence, optimizer copy propagation alias invalidation, cross-function variable collision, MCRuntime array-index regex
351
+
346
352
  ### Changelog Highlights
347
353
 
354
+ #### v1.2.27 (2026-03-14)
355
+
356
+ - **BigInt real-MC fix**: `storage_set_int` macro now uses `execute store result storage` instead of `data modify set value $(n)` — avoids a Minecraft macro substitution bug with integer values; BigInt confirmed working on Paper 1.21.4
357
+ - **showcase**: `atan2_fixed` returns degrees (0–360), not millidegrees; fixed over-division in examples; `mod_pow` test cases use small safe-range moduli (no INT32 overflow)
358
+
348
359
  #### v1.2.26 (2026-03-14)
349
360
 
350
361
  - Full math/vector/advanced/bigint standard library (see above)
package/README.zh.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  <img src="./logo.png" alt="RedScript Logo" width="64" />
4
4
 
5
- <img src="https://img.shields.io/badge/RedScript-1.2.26-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
5
+ <img src="https://img.shields.io/badge/RedScript-1.2.27-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
6
6
 
7
7
  **一个编译到 Minecraft Datapack 的类型化脚本语言。**
8
8
 
9
9
  写干净的游戏逻辑,把记分板的面条代码交给 RedScript 处理。
10
10
 
11
11
  [![CI](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml)
12
- [![Tests](https://img.shields.io/badge/tests-917%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
12
+ [![Tests](https://img.shields.io/badge/tests-918%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
13
13
  [![npm](https://img.shields.io/npm/v/redscript-mc?color=cb3837)](https://www.npmjs.com/package/redscript-mc)
14
14
  [![npm downloads](https://img.shields.io/npm/dm/redscript-mc?color=cb3837)](https://www.npmjs.com/package/redscript-mc)
15
15
  [![VSCode](https://img.shields.io/badge/VSCode-插件-007ACC?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=bkmashiro.redscript-vscode)
@@ -73,14 +73,20 @@ let running: bool = false;
73
73
 
74
74
  ---
75
75
 
76
+ ### v1.2.27 新增内容
77
+
78
+ - **BigInt 实机验证通过** (`bigint.mcrs`):任意精度整数在 MC 记分板 + NBT 上完整运行 — base 10000 × 8 limbs = 最多 32 位十进制数;`bigint_fib(50)` = 12,586,269,025 在 Paper 1.21.4 实机验证正确
79
+ - **`storage_set_int` 宏修复**:动态 NBT 数组写入改用 `execute store result storage` 而非 `data modify set value $(n)` — 规避 Minecraft 宏机制对整数值的静默替换 bug
80
+ - **完整标准库** (`math.mcrs`、`vec.mcrs`、`advanced.mcrs`、`bigint.mcrs`、`showcase.mcrs`):18 个数学函数、14 个向量几何函数、20+ 数论与分形函数
81
+ - **`module library;` pragma**:库文件零成本树摇 — 标准库永远不会撑大你的数据包
82
+ - **`@require_on_load(fn)` 装饰器**:sin/cos/atan 查找表初始化器的声明式加载依赖跟踪
83
+
76
84
  ### v1.2.26 新增内容
77
85
 
78
86
  - **数学标准库** (`math.mcrs`):18 个定点数函数 — `abs`、`sign`、`min`、`max`、`clamp`、`lerp`、`isqrt`、`sqrt_fixed`、`pow_int`、`gcd`、`lcm`、`sin_fixed`、`cos_fixed`、`map`、`ceil_div`、`log2_int`、`mulfix`、`divfix`、`smoothstep`、`smootherstep`
79
87
  - **向量标准库** (`vec.mcrs`):2D / 3D 几何 — 点积/叉积、`length2d_fixed`、`atan2_fixed`(二分搜索正切表,O(log 46))、`normalize2d`、`rotate2d`、`lerp2d`、完整 3D 叉积
80
88
  - **高级标准库** (`advanced.mcrs`):数论(`fib`、`is_prime`、`collatz_steps`、`mod_pow`)、哈希/噪声(splitmix32 `hash_int`、`noise1d`)、曲线(`bezier_quad`)、分形(`mandelbrot_iter`、`julia_iter`)、几何实验
81
- - **BigInt** (`bigint.mcrs`):任意精度整数 — base 10000 × 8 limbs = 最多 32 位十进制数;`bigint_add/sub/compare/mul/fib` 全部运行在 MC 记分板 + NBT storage 上
82
- - **`module library;` pragma**:将文件声明为库,未被调用的函数会被树摇消除 — 标准库永远不会撑大你的数据包
83
- - **`storage_get_int` / `storage_set_int` 内置函数**:通过 MC 1.20.2 宏子函数实现动态 NBT int 数组读写
89
+ - **BigInt** (`bigint.mcrs`):任意精度整数架构设计 — base 10000 × 8 limbs = 最多 32 位十进制数
84
90
  - **编译器修复**:`isqrt` 大数收敛、优化器拷贝传播别名失效、跨函数变量命名冲突、MCRuntime 数组索引正则
85
91
 
86
92
  ### v1.2.25 新增内容
@@ -337,6 +343,11 @@ fn show_fib() {
337
343
 
338
344
  ### 更新日志亮点
339
345
 
346
+ #### v1.2.27(2026-03-14)
347
+
348
+ - **BigInt 实机修复**:`storage_set_int` 宏改用 `execute store result storage`,规避 MC 宏整数替换 bug;BigInt 在 Paper 1.21.4 实机验证通过
349
+ - showcase 示例修复:`atan2_fixed` 返回度数(0–360),更正不必要的除以 1000;`mod_pow` 测试改用小 modulus 避免 INT32 溢出
350
+
340
351
  #### v1.2.26(2026-03-14)
341
352
 
342
353
  - 完整的数学/向量/高级/BigInt 标准库(详见上方)
@@ -77,7 +77,7 @@ describe('CLI API', () => {
77
77
  const source = fs.readFileSync(mainPath, 'utf-8');
78
78
  const result = (0, index_1.compile)(source, { namespace: 'mygame', filePath: mainPath });
79
79
  const tickTimer = result.files.find(file => file.path.endsWith('/tick_timer.mcfunction'));
80
- expect(tickTimer?.content).toContain('scoreboard players set #rs rs.timer_ticks 1');
80
+ expect(tickTimer?.content).toContain('scoreboard players set #rs mygame.timer_ticks 1');
81
81
  });
82
82
  it('adds a call-site hash for stdlib internal scoreboard objectives', () => {
83
83
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stdlib-hash-'));
@@ -106,7 +106,7 @@ describe('CLI API', () => {
106
106
  const timerFns = result.files.filter(file => /timer_start__callsite_[0-9a-f]{4}\.mcfunction$/.test(file.path));
107
107
  expect(timerFns).toHaveLength(2);
108
108
  const objectives = timerFns
109
- .flatMap(file => [...file.content.matchAll(/rs\._timer_([0-9a-f]{4})/g)].map(match => match[0]));
109
+ .flatMap(file => [...file.content.matchAll(/mygame\._timer_([0-9a-f]{4})/g)].map(match => match[0]));
110
110
  expect(new Set(objectives).size).toBe(2);
111
111
  });
112
112
  it('Timer::new creates timer', () => {
@@ -125,8 +125,8 @@ describe('CLI API', () => {
125
125
  const result = (0, index_1.compile)(source, { namespace: 'timernew', filePath: mainPath });
126
126
  expect(result.typeErrors).toEqual([]);
127
127
  const newFn = result.files.find(file => file.path.endsWith('/Timer_new.mcfunction'));
128
- expect(newFn?.content).toContain('scoreboard players set timer_ticks rs 0');
129
- expect(newFn?.content).toContain('scoreboard players set timer_active rs 0');
128
+ expect(newFn?.content).toContain('scoreboard players set timer_ticks timernew 0');
129
+ expect(newFn?.content).toContain('scoreboard players set timer_active timernew 0');
130
130
  });
131
131
  it('Timer.start/pause/reset', () => {
132
132
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-state-'));
@@ -149,9 +149,9 @@ describe('CLI API', () => {
149
149
  const startFn = result.files.find(file => file.path.endsWith('/Timer_start.mcfunction'));
150
150
  const pauseFn = result.files.find(file => file.path.endsWith('/Timer_pause.mcfunction'));
151
151
  const resetFn = result.files.find(file => file.path.endsWith('/Timer_reset.mcfunction'));
152
- expect(startFn?.content).toContain('scoreboard players set timer_active rs 1');
153
- expect(pauseFn?.content).toContain('scoreboard players set timer_active rs 0');
154
- expect(resetFn?.content).toContain('scoreboard players set timer_ticks rs 0');
152
+ expect(startFn?.content).toContain('scoreboard players set timer_active timerstate 1');
153
+ expect(pauseFn?.content).toContain('scoreboard players set timer_active timerstate 0');
154
+ expect(resetFn?.content).toContain('scoreboard players set timer_ticks timerstate 0');
155
155
  });
156
156
  it('Timer.done returns bool', () => {
157
157
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-done-'));
@@ -174,9 +174,9 @@ describe('CLI API', () => {
174
174
  expect(result.typeErrors).toEqual([]);
175
175
  const doneFn = result.files.find(file => file.path.endsWith('/Timer_done.mcfunction'));
176
176
  const mainFn = result.files.find(file => file.path.endsWith('/main.mcfunction'));
177
- expect(doneFn?.content).toContain('scoreboard players get timer_ticks rs');
177
+ expect(doneFn?.content).toContain('scoreboard players get timer_ticks timerdone');
178
178
  expect(doneFn?.content).toContain('return run scoreboard players get');
179
- expect(mainFn?.content).toContain('execute if score $main_finished rs matches 1..');
179
+ expect(mainFn?.content).toContain('execute if score $main_finished timerdone matches 1..');
180
180
  });
181
181
  it('Timer.tick increments', () => {
182
182
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-tick-'));
@@ -199,10 +199,10 @@ describe('CLI API', () => {
199
199
  .filter(file => file.path.includes('/Timer_tick'))
200
200
  .map(file => file.content)
201
201
  .join('\n');
202
- expect(tickOutput).toContain('scoreboard players get timer_active rs');
203
- expect(tickOutput).toContain('scoreboard players get timer_ticks rs');
204
- expect(tickOutput).toContain(' += $const_1 rs');
205
- expect(tickOutput).toContain('execute store result score timer_ticks rs run scoreboard players get $_');
202
+ expect(tickOutput).toContain('scoreboard players get timer_active timertick');
203
+ expect(tickOutput).toContain('scoreboard players get timer_ticks timertick');
204
+ expect(tickOutput).toContain(' += $const_1 timertick');
205
+ expect(tickOutput).toContain('execute store result score timer_ticks timertick run scoreboard players get $_');
206
206
  });
207
207
  });
208
208
  describe('compile()', () => {
@@ -26,7 +26,7 @@ fn turret_tick() {
26
26
  const result = (0, index_1.compile)(source, { namespace: 'test' });
27
27
  const parent = getFileContent(result.files, 'data/test/function/turret_tick.mcfunction');
28
28
  const loopBody = getFileContent(result.files, 'data/test/function/turret_tick/foreach_0.mcfunction');
29
- const hoistedRead = 'execute store result score $_0 rs run scoreboard players get config test.turret_range';
29
+ const hoistedRead = 'execute store result score $_0 test run scoreboard players get config test.turret_range';
30
30
  const executeCall = 'execute as @e[tag=turret] run function test:turret_tick/foreach_0';
31
31
  expect(parent).toContain(hoistedRead);
32
32
  expect(parent.indexOf(hoistedRead)).toBeLessThan(parent.indexOf(executeCall));
@@ -48,7 +48,7 @@ fn read_twice() {
48
48
  const fn = getFileContent(result.files, 'data/test/function/read_twice.mcfunction');
49
49
  const readMatches = fn.match(/scoreboard players get @s test\.coins/g) ?? [];
50
50
  expect(readMatches).toHaveLength(1);
51
- expect(fn).toContain('scoreboard players operation $_1 rs = $_0 rs');
51
+ expect(fn).toContain('scoreboard players operation $_1 test = $_0 test');
52
52
  });
53
53
  test('reuses duplicate arithmetic sequences', () => {
54
54
  const source = `
@@ -63,9 +63,9 @@ fn math() {
63
63
  `;
64
64
  const result = (0, index_1.compile)(source, { namespace: 'test' });
65
65
  const fn = getFileContent(result.files, 'data/test/function/math.mcfunction');
66
- const addMatches = fn.match(/\+= \$const_2 rs/g) ?? [];
66
+ const addMatches = fn.match(/\+= \$const_2 test/g) ?? [];
67
67
  expect(addMatches).toHaveLength(1);
68
- expect(fn).toContain('scoreboard players operation $_1 rs = $_0 rs');
68
+ expect(fn).toContain('scoreboard players operation $_1 test = $_0 test');
69
69
  });
70
70
  });
71
71
  describe('setblock batching', () => {
package/dist/cli.js CHANGED
@@ -60,7 +60,7 @@ function printUsage() {
60
60
  RedScript Compiler
61
61
 
62
62
  Usage:
63
- redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--target <target>] [--no-dce]
63
+ redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--scoreboard <obj>] [--target <target>] [--no-dce]
64
64
  redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
65
65
  redscript check <file>
66
66
  redscript fmt <file.mcrs> [file2.mcrs ...]
@@ -85,6 +85,9 @@ Options:
85
85
  --target <target> Output target: datapack (default), cmdblock, or structure
86
86
  --no-dce Disable AST dead code elimination
87
87
  --no-mangle Disable variable name mangling (use readable names)
88
+ --scoreboard <obj> Scoreboard objective for variables (default: namespace).
89
+ Each datapack automatically uses its namespace as the objective
90
+ so multiple datapacks can coexist without collisions.
88
91
  --stats Print optimizer statistics
89
92
  --hot-reload <url> After each successful compile, POST to <url>/reload
90
93
  (use with redscript-testharness; e.g. http://localhost:25561)
@@ -218,6 +221,10 @@ function parseArgs(args) {
218
221
  result.mangle = false;
219
222
  i++;
220
223
  }
224
+ else if (arg === '--scoreboard') {
225
+ result.scoreboardObjective = args[++i];
226
+ i++;
227
+ }
221
228
  else if (arg === '--hot-reload') {
222
229
  result.hotReload = args[++i];
223
230
  i++;
@@ -271,7 +278,7 @@ function printOptimizationStats(stats) {
271
278
  console.log(` constant folding: ${stats.constantFolds} constants folded`);
272
279
  console.log(` Total mcfunction commands: ${stats.totalCommandsBefore} -> ${stats.totalCommandsAfter} (${formatReduction(stats.totalCommandsBefore, stats.totalCommandsAfter)} reduction)`);
273
280
  }
274
- function compileCommand(file, output, namespace, target = 'datapack', showStats = false, dce = true, mangle = true) {
281
+ function compileCommand(file, output, namespace, target = 'datapack', showStats = false, dce = true, mangle = true, scoreboardObjective = undefined) {
275
282
  // Read source file
276
283
  if (!fs.existsSync(file)) {
277
284
  console.error(`Error: File not found: ${file}`);
@@ -280,7 +287,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
280
287
  const source = fs.readFileSync(file, 'utf-8');
281
288
  try {
282
289
  if (target === 'cmdblock') {
283
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
290
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle, scoreboardObjective });
284
291
  printWarnings(result.warnings);
285
292
  // Generate command block JSON
286
293
  const hasTick = result.files.some(f => f.path.includes('__tick.mcfunction'));
@@ -309,7 +316,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
309
316
  }
310
317
  }
311
318
  else {
312
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
319
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle, scoreboardObjective });
313
320
  printWarnings(result.warnings);
314
321
  // Default: generate datapack
315
322
  // Create output directory
@@ -483,7 +490,8 @@ async function main() {
483
490
  const output = target === 'structure'
484
491
  ? (parsed.outputNbt ?? parsed.output ?? `./${namespace}.nbt`)
485
492
  : (parsed.output ?? './dist');
486
- compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce, parsed.mangle);
493
+ compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce, parsed.mangle, parsed.scoreboardObjective // undefined = derive from namespace in compile()
494
+ );
487
495
  }
488
496
  break;
489
497
  case 'watch':
@@ -30,6 +30,10 @@ export interface DatapackGenerationResult {
30
30
  export interface DatapackGenerationOptions {
31
31
  optimizeCommands?: boolean;
32
32
  mangle?: boolean;
33
+ /** Scoreboard objective used for all scoreboard variables.
34
+ * Defaults to 'rs'. Override per-datapack to avoid collisions
35
+ * when multiple RedScript datapacks are loaded simultaneously. */
36
+ scoreboardObjective?: string;
33
37
  }
34
38
  export declare function countMcfunctionCommands(files: DatapackFile[]): number;
35
39
  export declare function generateDatapackWithStats(module: IRModule, options?: DatapackGenerationOptions): DatapackGenerationResult;
@@ -26,7 +26,8 @@ const var_allocator_1 = require("../var-allocator");
26
26
  // ---------------------------------------------------------------------------
27
27
  // Utilities
28
28
  // ---------------------------------------------------------------------------
29
- const OBJ = 'rs'; // scoreboard objective name
29
+ // Default scoreboard objective overridden per-compilation via DatapackGenerationOptions.scoreboardObjective
30
+ let OBJ = 'rs';
30
31
  function operandToScore(op, alloc) {
31
32
  if (op.kind === 'var')
32
33
  return `${alloc.alloc(op.name)} ${OBJ}`;
@@ -335,7 +336,11 @@ function preAllocTerm(term, alloc) {
335
336
  }
336
337
  }
337
338
  function generateDatapackWithStats(module, options = {}) {
338
- const { optimizeCommands = true, mangle = false } = options;
339
+ const { optimizeCommands = true, mangle = false, scoreboardObjective = 'rs' } = options;
340
+ // Set module-level OBJ so all helper functions in this module use the correct objective.
341
+ // This is safe because compilation is synchronous.
342
+ OBJ = scoreboardObjective;
343
+ (0, commands_1.setOptimizerObjective)(scoreboardObjective);
339
344
  const alloc = new var_allocator_1.VarAllocator(mangle);
340
345
  const files = [];
341
346
  const advancements = [];
@@ -375,10 +380,10 @@ function generateDatapackWithStats(module, options = {}) {
375
380
  for (const eventType of eventTypes) {
376
381
  const detection = types_1.EVENT_TYPES[eventType].detection;
377
382
  if (eventType === 'PlayerDeath') {
378
- loadLines.push('scoreboard objectives add rs.deaths deathCount');
383
+ loadLines.push(`scoreboard objectives add ${OBJ}.deaths deathCount`);
379
384
  }
380
385
  else if (eventType === 'EntityKill') {
381
- loadLines.push('scoreboard objectives add rs.kills totalKillCount');
386
+ loadLines.push(`scoreboard objectives add ${OBJ}.kills totalKillCount`);
382
387
  }
383
388
  else if (eventType === 'ItemUse') {
384
389
  loadLines.push('# ItemUse detection requires a project-specific objective/tag setup');
package/dist/compile.d.ts CHANGED
@@ -13,6 +13,10 @@ export interface CompileOptions {
13
13
  optimize?: boolean;
14
14
  dce?: boolean;
15
15
  mangle?: boolean;
16
+ /** Scoreboard objective used for all variable slots.
17
+ * Defaults to 'rs'. Set to a unique value (e.g. 'mypack_rs') when loading
18
+ * multiple RedScript datapacks simultaneously to avoid variable collisions. */
19
+ scoreboardObjective?: string;
16
20
  /** Additional source files that should be treated as *library* code.
17
21
  * Functions in these files are DCE-eligible: they are only compiled into
18
22
  * the datapack when actually called from user code. Each string is parsed
package/dist/compile.js CHANGED
@@ -209,6 +209,11 @@ function compile(source, options = {}) {
209
209
  }
210
210
  const dceResult = shouldRunDce ? (0, dce_1.eliminateDeadCode)(parsedAst) : { program: parsedAst, warnings: [] };
211
211
  const ast = dceResult.program;
212
+ // Configure scoreboard objective for this compilation.
213
+ // Default: use the datapack namespace so each datapack gets its own objective
214
+ // automatically, preventing variable collisions when multiple datapacks coexist.
215
+ const scoreboardObj = options.scoreboardObjective ?? namespace;
216
+ (0, lowering_1.setScoreboardObjective)(scoreboardObj);
212
217
  // Lowering
213
218
  const ir = new lowering_1.Lowering(namespace, preprocessed.ranges).lower(ast);
214
219
  // Optimization
@@ -217,7 +222,10 @@ function compile(source, options = {}) {
217
222
  : ir;
218
223
  // Code generation — mangle=true by default to prevent cross-function
219
224
  // scoreboard variable collisions in the global MC scoreboard namespace.
220
- const generated = (0, mcfunction_1.generateDatapackWithStats)(optimized, { mangle: options.mangle ?? true });
225
+ const generated = (0, mcfunction_1.generateDatapackWithStats)(optimized, {
226
+ mangle: options.mangle ?? true,
227
+ scoreboardObjective: scoreboardObj,
228
+ });
221
229
  return {
222
230
  success: true,
223
231
  files: [...generated.files, ...generated.advancements],
package/dist/index.d.ts CHANGED
@@ -17,6 +17,10 @@ export interface CompileOptions {
17
17
  filePath?: string;
18
18
  dce?: boolean;
19
19
  mangle?: boolean;
20
+ /** Scoreboard objective used for all variable slots (default: 'rs').
21
+ * Set a unique value per datapack to avoid collisions when multiple
22
+ * RedScript datapacks are loaded simultaneously. */
23
+ scoreboardObjective?: string;
20
24
  }
21
25
  export interface CompileResult {
22
26
  files: DatapackFile[];
package/dist/index.js CHANGED
@@ -66,11 +66,16 @@ function compile(source, options = {}) {
66
66
  const checker = new typechecker_1.TypeChecker(preprocessedSource, filePath);
67
67
  typeErrors = checker.check(ast);
68
68
  }
69
+ // Configure scoreboard objective for this compilation.
70
+ // Default: use the datapack namespace so each datapack gets its own objective
71
+ // automatically, preventing variable collisions when multiple datapacks coexist.
72
+ const scoreboardObj = options.scoreboardObjective ?? namespace;
73
+ (0, lowering_1.setScoreboardObjective)(scoreboardObj);
69
74
  // Lowering to IR
70
75
  const lowering = new lowering_1.Lowering(namespace, preprocessed.ranges);
71
76
  const ir = lowering.lower(ast);
72
77
  let optimizedIR = ir;
73
- let generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: shouldOptimize, mangle });
78
+ let generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: shouldOptimize, mangle, scoreboardObjective: scoreboardObj });
74
79
  let optimizationStats;
75
80
  if (shouldOptimize) {
76
81
  const stats = (0, commands_1.createEmptyOptimizationStats)();
@@ -86,10 +91,10 @@ function compile(source, options = {}) {
86
91
  }
87
92
  const copyPropagatedIR = { ...ir, functions: copyPropagatedFunctions };
88
93
  optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions };
89
- const baselineGenerated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle });
90
- const beforeDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(copyPropagatedIR, { optimizeCommands: false, mangle });
91
- const afterDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: false, mangle });
92
- generated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: true, mangle });
94
+ const baselineGenerated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj });
95
+ const beforeDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(copyPropagatedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj });
96
+ const afterDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj });
97
+ generated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: true, mangle, scoreboardObjective: scoreboardObj });
93
98
  stats.deadCodeRemoved =
94
99
  (0, mcfunction_1.countMcfunctionCommands)(beforeDceGenerated.files) - (0, mcfunction_1.countMcfunctionCommands)(afterDceGenerated.files);
95
100
  stats.licmHoists = generated.stats.licmHoists;
@@ -105,7 +110,7 @@ function compile(source, options = {}) {
105
110
  }
106
111
  else {
107
112
  optimizedIR = ir;
108
- generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle });
113
+ generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj });
109
114
  }
110
115
  return {
111
116
  files: [...generated.files, ...generated.advancements],
@@ -7,6 +7,9 @@
7
7
  import type { IRModule } from '../ir/types';
8
8
  import type { SourceRange } from '../compile';
9
9
  import type { Program } from '../ast/types';
10
+ /** Current scoreboard objective. Set once per compile() call. */
11
+ export declare let LOWERING_OBJ: string;
12
+ export declare function setScoreboardObjective(obj: string): void;
10
13
  export interface Warning {
11
14
  message: string;
12
15
  code: string;