redscript-mc 1.2.28 → 1.2.30

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.27-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
5
+ <img src="https://img.shields.io/badge/RedScript-1.2.29-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-918%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
12
+ [![Tests](https://img.shields.io/badge/tests-920%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)
@@ -20,9 +20,11 @@ Write clean game logic. RedScript handles the scoreboard spaghetti.
20
20
 
21
21
  ### 🚀 [Try it online — no install needed!](https://redscript-ide.pages.dev)
22
22
 
23
- <img src="./demo.gif" alt="RedScript Demo" width="400" />
23
+ <img src="./demo.gif" alt="RedScript Demo — math curves drawn with particles in Minecraft" width="520" />
24
24
 
25
- *↑ Particles spawning at player position every tick — 100% vanilla, no mods! Just 30 lines of RedScript with full control flow: `if`, `foreach`, `@tick`, f-strings, and more.*
25
+ *↑ Five mathematical curves rendered in real-time with particles — 100% vanilla, no mods!*
26
+ *`y = x·sin(x)` · `y = sin(x) + ½sin(2x)` · `y = e⁻ˣsin(4x)` · `y = tanh(2x)` · `r = cos(2θ)` rose curve*
27
+ *Each curve is computed tick-by-tick using RedScript's fixed-point math stdlib.*
26
28
 
27
29
  </div>
28
30
 
@@ -32,34 +34,33 @@ Write clean game logic. RedScript handles the scoreboard spaghetti.
32
34
 
33
35
  RedScript is a typed scripting language that compiles to vanilla Minecraft datapacks. Write clean code with variables, functions, loops, and events — RedScript handles the scoreboard commands and `.mcfunction` files for you.
34
36
 
35
- **The demo above?** Just 30 lines:
37
+ **The demo above?** Five math curves drawn with 64 sample points each. The core logic:
36
38
 
37
39
  ```rs
38
- let counter: int = 0;
39
- let running: bool = false;
40
-
41
- @tick fn demo_tick() {
42
- if (!running) { return; }
43
- counter = counter + 1;
44
-
45
- foreach (p in @a) at @s {
46
- particle("minecraft:end_rod", ~0, ~1, ~0, 0.5, 0.5, 0.5, 0.1, 5);
47
- }
48
-
49
- if (counter % 20 == 0) {
50
- say(f"Running for {counter} ticks");
51
- }
52
- }
40
+ import "stdlib/math.mcrs"
53
41
 
54
- fn start() {
55
- running = true;
56
- counter = 0;
57
- say(f"Demo started!");
58
- }
42
+ let phase: int = 0;
43
+ let frame: int = 0;
44
+
45
+ // 5 curves cycle every 128 ticks (~6.5 s each)
46
+ @tick fn _wave_tick() {
47
+ phase = (phase + 4) % 360;
48
+ frame = frame + 1;
49
+
50
+ let curve_id: int = (frame / 128) % 5;
51
+
52
+ // Compute sin at 9 column offsets (40° apart = full sine wave span)
53
+ let s0: int = sin_fixed((phase + 0) % 360);
54
+ let s1: int = sin_fixed((phase + 40) % 360);
55
+ // ... s2 through s8 ...
56
+
57
+ // Draw bar chart: each column height = sin value
58
+ // (64 fixed particle positions per curve, all respawned each tick)
59
+ if (curve_id == 0) { _draw_xsinx(); }
60
+ if (curve_id == 1) { _draw_harmonic(); }
61
+ // ...
59
62
 
60
- fn stop() {
61
- running = false;
62
- say(f"Demo stopped at {counter} ticks.");
63
+ actionbar(@a, f"§e y = x·sin(x) phase: {phase}° center: {s0}‰");
63
64
  }
64
65
  ```
65
66
 
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.27-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
5
+ <img src="https://img.shields.io/badge/RedScript-1.2.29-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-918%20passing-brightgreen)](https://github.com/bkmashiro/redscript)
12
+ [![Tests](https://img.shields.io/badge/tests-920%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)
@@ -20,9 +20,11 @@
20
20
 
21
21
  ### 🚀 [在线试用 — 无需安装!](https://redscript-ide.pages.dev)
22
22
 
23
- <img src="./demo.gif" alt="RedScript Demo" width="400" />
23
+ <img src="./demo.gif" alt="RedScript Demo — 用粒子在 Minecraft 中绘制数学曲线" width="520" />
24
24
 
25
- *↑ tick 在玩家位置生成粒子 — 纯原版,无需 MOD!仅 30 行 RedScript,包含完整控制流:`if`、`foreach`、`@tick`、f-strings 等。*
25
+ *↑ 五条数学函数图像实时粒子渲染 — 纯原版,无需 MOD!*
26
+ *`y = x·sin(x)` · `y = sin(x) + ½sin(2x)` · `y = e⁻ˣsin(4x)` · `y = tanh(2x)` · 玫瑰曲线 `r = cos(2θ)`*
27
+ *每条曲线由 RedScript 定点数学库逐 tick 计算,64 个采样点动态绘制。*
26
28
 
27
29
  </div>
28
30
 
@@ -32,34 +34,32 @@
32
34
 
33
35
  RedScript 是一门编译到原版 Minecraft 数据包的脚本语言。用变量、函数、循环、事件写代码,RedScript 帮你生成记分板命令和 `.mcfunction` 文件。
34
36
 
35
- **上面的演示?** 只有 30 行:
37
+ **上面的演示?** 五条数学曲线,每条 64 个采样点,核心逻辑:
36
38
 
37
39
  ```rs
38
- let counter: int = 0;
39
- let running: bool = false;
40
-
41
- @tick fn demo_tick() {
42
- if (!running) { return; }
43
- counter = counter + 1;
44
-
45
- foreach (p in @a) at @s {
46
- particle("minecraft:end_rod", ~0, ~1, ~0, 0.5, 0.5, 0.5, 0.1, 5);
47
- }
48
-
49
- if (counter % 20 == 0) {
50
- say(f"已运行 {counter} ticks");
51
- }
52
- }
40
+ import "stdlib/math.mcrs"
53
41
 
54
- @keep fn start() {
55
- running = true;
56
- counter = 0;
57
- say(f"Demo 已启动!");
58
- }
42
+ let phase: int = 0;
43
+ let frame: int = 0;
44
+
45
+ // 5 条曲线每 128 tick (~6.5 ) 自动切换
46
+ @tick fn _wave_tick() {
47
+ phase = (phase + 4) % 360;
48
+ frame = frame + 1;
49
+
50
+ let curve_id: int = (frame / 128) % 5;
51
+
52
+ // 计算 9 列的 sin 值(每列相差 40°,刚好覆盖完整周期)
53
+ let s0: int = sin_fixed((phase + 0) % 360);
54
+ let s1: int = sin_fixed((phase + 40) % 360);
55
+ // ... s2 到 s8 ...
56
+
57
+ // 绘制图像:每条曲线有 64 个固定坐标粒子,每 tick 全部重绘
58
+ if (curve_id == 0) { _draw_xsinx(); }
59
+ if (curve_id == 1) { _draw_harmonic(); }
60
+ // ...
59
61
 
60
- @keep fn stop() {
61
- running = false;
62
- say(f"Demo 已停止,共运行 {counter} ticks。");
62
+ actionbar(@a, f"§e y = x·sin(x) phase: {phase}° center: {s0}‰");
63
63
  }
64
64
  ```
65
65
 
package/demo.gif CHANGED
Binary file
@@ -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 mygame.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-'));
@@ -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 timernew 0');
129
- expect(newFn?.content).toContain('scoreboard players set timer_active timernew 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 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');
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 timerdone');
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 timerdone 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 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 $_');
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 test 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 test = $_0 test');
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 test/g) ?? [];
66
+ const addMatches = fn.match(/\+= \$const_2 __test/g) ?? [];
67
67
  expect(addMatches).toHaveLength(1);
68
- expect(fn).toContain('scoreboard players operation $_1 test = $_0 test');
68
+ expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test');
69
69
  });
70
70
  });
71
71
  describe('setblock batching', () => {
package/dist/compile.d.ts CHANGED
@@ -14,8 +14,9 @@ export interface CompileOptions {
14
14
  dce?: boolean;
15
15
  mangle?: boolean;
16
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. */
17
+ * Defaults to '__<namespace>' (e.g. '__mathshow') the double-underscore
18
+ * prefix signals compiler-internal and avoids occupying the user's namespace.
19
+ * Each datapack gets a unique objective automatically; no manual setup needed. */
19
20
  scoreboardObjective?: string;
20
21
  /** Additional source files that should be treated as *library* code.
21
22
  * Functions in these files are DCE-eligible: they are only compiled into
package/dist/compile.js CHANGED
@@ -212,7 +212,7 @@ function compile(source, options = {}) {
212
212
  // Configure scoreboard objective for this compilation.
213
213
  // Default: use the datapack namespace so each datapack gets its own objective
214
214
  // automatically, preventing variable collisions when multiple datapacks coexist.
215
- const scoreboardObj = options.scoreboardObjective ?? namespace;
215
+ const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`;
216
216
  (0, lowering_1.setScoreboardObjective)(scoreboardObj);
217
217
  // Lowering
218
218
  const ir = new lowering_1.Lowering(namespace, preprocessed.ranges).lower(ast);
package/dist/index.d.ts CHANGED
@@ -17,9 +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. */
20
+ /** Scoreboard objective used for all variable slots.
21
+ * Defaults to '__<namespace>' (e.g. '__mathshow') to avoid collisions when
22
+ * multiple RedScript datapacks are loaded simultaneously, without occupying
23
+ * the user's own namespace. Override only if you need a specific name. */
23
24
  scoreboardObjective?: string;
24
25
  }
25
26
  export interface CompileResult {
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ function compile(source, options = {}) {
69
69
  // Configure scoreboard objective for this compilation.
70
70
  // Default: use the datapack namespace so each datapack gets its own objective
71
71
  // automatically, preventing variable collisions when multiple datapacks coexist.
72
- const scoreboardObj = options.scoreboardObjective ?? namespace;
72
+ const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`;
73
73
  (0, lowering_1.setScoreboardObjective)(scoreboardObj);
74
74
  // Lowering to IR
75
75
  const lowering = new lowering_1.Lowering(namespace, preprocessed.ranges);
@@ -236,9 +236,17 @@ class Lexer {
236
236
  this.addToken('rel_coord', value, startLine, startCol);
237
237
  return;
238
238
  }
239
- // Local coordinate: ^ or ^5 or ^-3 or ^0.5
239
+ // Local coordinate: ^ or ^5 or ^-3 or ^0.5 or ^varname (macro variable)
240
240
  if (char === '^') {
241
241
  let value = '^';
242
+ // Check for identifier (variable name for macro substitution, e.g. ^px, ^height)
243
+ if (/[a-zA-Z_]/.test(this.peek())) {
244
+ while (/[a-zA-Z0-9_]/.test(this.peek())) {
245
+ value += this.advance();
246
+ }
247
+ this.addToken('local_coord', value, startLine, startCol);
248
+ return;
249
+ }
242
250
  // Check for optional sign
243
251
  if (this.peek() === '-' || this.peek() === '+') {
244
252
  value += this.advance();
@@ -495,16 +495,31 @@ class Lowering {
495
495
  }
496
496
  }
497
497
  // Set up NBT storage for each macro param
498
+ // float-typed params are stored as `double 0.01` so that an integer value N
499
+ // becomes N/100.0 in the command (e.g. scoreboard value 975 → NBT 9.75d → ^9.75)
498
500
  for (const macroParam of macroParamNames) {
499
501
  const paramIdx = params.findIndex(p => p.name === macroParam);
500
502
  if (paramIdx < 0 || paramIdx >= loweredArgs.length)
501
503
  continue;
502
504
  const operand = loweredArgs[paramIdx];
505
+ const paramType = params[paramIdx]?.type;
506
+ const isFloat = paramType?.kind === 'named' && paramType.name === 'float';
503
507
  if (operand.kind === 'const') {
504
- this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`);
508
+ if (isFloat) {
509
+ const floatVal = (operand.value / 100).toFixed(6);
510
+ this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${floatVal}d`);
511
+ }
512
+ else {
513
+ this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`);
514
+ }
505
515
  }
506
516
  else if (operand.kind === 'var') {
507
- this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${exports.LOWERING_OBJ}`);
517
+ if (isFloat) {
518
+ this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} double 0.01 run scoreboard players get ${operand.name} ${exports.LOWERING_OBJ}`);
519
+ }
520
+ else {
521
+ this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${exports.LOWERING_OBJ}`);
522
+ }
508
523
  }
509
524
  }
510
525
  // Call with macro storage