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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "redscript-vscode",
3
- "version": "1.0.35",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "redscript-vscode",
9
- "version": "1.0.35",
9
+ "version": "1.2.0",
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.27",
26
+ "version": "1.2.29",
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.35",
5
+ "version": "1.2.0",
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
- "name": "variable.other.mc-name.redscript",
312
- "match": "#[a-zA-Z_][a-zA-Z0-9_]*"
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": {
@@ -1,92 +1,70 @@
1
- // readme-demo.mcrs — RedScript v1.2.27 README visual showcase
2
- // What 55 lines of RedScript can do on a vanilla Minecraft server.
1
+ // readme-demo.mcrs — real-time 2D sine wave
2
+ // Uses sin_fixed() to compute y = sin(x + t) at runtime.
3
+ // _draw(px, py) is a macro function: float params are stored as double×0.01
4
+ // so an integer value N → coordinate N/100 blocks (e.g. 975 → 9.75 blocks).
5
+ // No lookup tables. No precomputed positions. Pure runtime computation.
3
6
  //
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
+ // Compile:
8
+ // node dist/cli.js compile examples/readme-demo.mcrs \
9
+ // -o <out> --namespace rsdemo
10
+ // In-game:
11
+ // /function rsdemo:start (face forward, stand ~10 blocks back)
12
+ // /function rsdemo:stop
7
13
 
8
14
  import "../src/stdlib/math.mcrs"
9
15
 
10
- let t: int = 0; // 0..359 degrees, 4° per tick = full cycle every 1.5 s
11
- let frame: int = 0;
16
+ let phase: int = 0;
17
+ let t: int = 0;
12
18
  let on: bool = false;
13
19
 
14
- @load fn _init() {
15
- t = 0;
16
- frame = 0;
20
+ // Macro function: place one particle at (^px/100, ^py/100, ^5).
21
+ // RedScript stores float params as `double 0.01` in rs:macro_args,
22
+ // so integer values become sub-block coordinates at call time.
23
+ fn _draw(px: float, py: float) {
24
+ particle("minecraft:end_rod", ^px, ^py, ^5, 0.02, 0.02, 0.02, 0.0, 10);
17
25
  }
18
26
 
27
+ @load fn _init() { phase = 0; t = 0; }
28
+
19
29
  @tick fn _wave_tick() {
20
30
  if (!on) { return; }
21
31
 
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
32
+ // Advance: 4 new points this tick, phase scrolls at 1°/tick
33
+ t = (t + 4) % 40;
34
+ phase = (phase + 1) % 360;
28
35
 
29
- // ── Particle rings: spread values scale with sin(t) ─────────────────
30
- // Inner ring — always visible, small tight shimmer
31
36
  foreach (p in @a) at @s {
32
- particle("minecraft:end_rod", ~0, ~1, ~0, 0.25, 0.08, 0.25, 0.02, 6);
33
- }
37
+ // Draw 4 consecutive points (loop-unrolled)
38
+ // px: index 0..39 → -975..975 (÷100 = -9.75..9.75 blocks)
39
+ // py: sin_fixed → -1000..1000 → ÷4 → -250..250 (÷100 = -2.5..2.5 blocks)
34
40
 
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
+ let i0: int = t;
42
+ let s0: int = sin_fixed((i0 * 9 + phase) % 360);
43
+ _draw(i0 * 50 - 975, s0 / 4);
41
44
 
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
- }
45
+ let i1: int = (t + 1) % 40;
46
+ let s1: int = sin_fixed((i1 * 9 + phase) % 360);
47
+ _draw(i1 * 50 - 975, s1 / 4);
49
48
 
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
- }
49
+ let i2: int = (t + 2) % 40;
50
+ let s2: int = sin_fixed((i2 * 9 + phase) % 360);
51
+ _draw(i2 * 50 - 975, s2 / 4);
56
52
 
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)}");
53
+ let i3: int = (t + 3) % 40;
54
+ let s3: int = sin_fixed((i3 * 9 + phase) % 360);
55
+ _draw(i3 * 50 - 975, s3 / 4);
77
56
  }
78
57
  }
79
58
 
80
59
  fn start() {
81
- on = true;
82
- t = 0;
83
- frame = 0;
84
- title(@a, f"§e §bRedScript§e ", f7trig-driven particle rings");
85
- say("§a▶ readme-demo started /function rsdemo:stop to end");
60
+ on = true;
61
+ phase = 0;
62
+ t = 0;
63
+ title(@a, "§e y = sin(x + t)", "§7sin_fixed · macro · real-time");
64
+ say("§a▶ sine wave face forward, stand back ~10 blocks");
86
65
  }
87
66
 
88
67
  fn stop() {
89
68
  on = false;
90
- actionbar(@a, f7[ demo stopped ]");
91
- say("§c■ readme-demo stopped");
69
+ say("§c■ stopped");
92
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.28",
3
+ "version": "1.2.30",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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 mygame.timer_ticks 1')
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', () => {
@@ -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 timernew 0')
117
- expect(newFn?.content).toContain('scoreboard players set timer_active timernew 0')
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 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')
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 timerdone')
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 timerdone matches 1..')
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 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 $_')
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 test run scoreboard players get config test.turret_range'
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 test = $_0 test')
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 test/g) ?? []
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 test = $_0 test')
77
+ expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test')
78
78
  })
79
79
  })
80
80
 
package/src/compile.ts CHANGED
@@ -28,8 +28,9 @@ export interface CompileOptions {
28
28
  dce?: boolean
29
29
  mangle?: boolean
30
30
  /** Scoreboard objective used for all variable slots.
31
- * Defaults to 'rs'. Set to a unique value (e.g. 'mypack_rs') when loading
32
- * multiple RedScript datapacks simultaneously to avoid variable collisions. */
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. */
33
34
  scoreboardObjective?: string
34
35
  /** Additional source files that should be treated as *library* code.
35
36
  * Functions in these files are DCE-eligible: they are only compiled into
@@ -269,7 +270,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
269
270
  // Configure scoreboard objective for this compilation.
270
271
  // Default: use the datapack namespace so each datapack gets its own objective
271
272
  // automatically, preventing variable collisions when multiple datapacks coexist.
272
- const scoreboardObj = options.scoreboardObjective ?? namespace
273
+ const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`
273
274
  setScoreboardObjective(scoreboardObj)
274
275
 
275
276
  // Lowering
package/src/index.ts CHANGED
@@ -36,9 +36,10 @@ export interface CompileOptions {
36
36
  filePath?: string
37
37
  dce?: boolean
38
38
  mangle?: boolean
39
- /** Scoreboard objective used for all variable slots (default: 'rs').
40
- * Set a unique value per datapack to avoid collisions when multiple
41
- * RedScript datapacks are loaded simultaneously. */
39
+ /** Scoreboard objective used for all variable slots.
40
+ * Defaults to '__<namespace>' (e.g. '__mathshow') to avoid collisions when
41
+ * multiple RedScript datapacks are loaded simultaneously, without occupying
42
+ * the user's own namespace. Override only if you need a specific name. */
42
43
  scoreboardObjective?: string
43
44
  }
44
45
 
@@ -108,7 +109,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
108
109
  // Configure scoreboard objective for this compilation.
109
110
  // Default: use the datapack namespace so each datapack gets its own objective
110
111
  // automatically, preventing variable collisions when multiple datapacks coexist.
111
- const scoreboardObj = options.scoreboardObjective ?? namespace
112
+ const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`
112
113
  setScoreboardObjective(scoreboardObj)
113
114
 
114
115
  // Lowering to IR
@@ -313,9 +313,17 @@ export class Lexer {
313
313
  return
314
314
  }
315
315
 
316
- // Local coordinate: ^ or ^5 or ^-3 or ^0.5
316
+ // Local coordinate: ^ or ^5 or ^-3 or ^0.5 or ^varname (macro variable)
317
317
  if (char === '^') {
318
318
  let value = '^'
319
+ // Check for identifier (variable name for macro substitution, e.g. ^px, ^height)
320
+ if (/[a-zA-Z_]/.test(this.peek())) {
321
+ while (/[a-zA-Z0-9_]/.test(this.peek())) {
322
+ value += this.advance()
323
+ }
324
+ this.addToken('local_coord', value, startLine, startCol)
325
+ return
326
+ }
319
327
  // Check for optional sign
320
328
  if (this.peek() === '-' || this.peek() === '+') {
321
329
  value += this.advance()
@@ -515,17 +515,33 @@ export class Lowering {
515
515
  }
516
516
 
517
517
  // Set up NBT storage for each macro param
518
+ // float-typed params are stored as `double 0.01` so that an integer value N
519
+ // becomes N/100.0 in the command (e.g. scoreboard value 975 → NBT 9.75d → ^9.75)
518
520
  for (const macroParam of macroParamNames) {
519
521
  const paramIdx = params.findIndex(p => p.name === macroParam)
520
522
  if (paramIdx < 0 || paramIdx >= loweredArgs.length) continue
521
523
 
522
524
  const operand = loweredArgs[paramIdx]
525
+ const paramType = params[paramIdx]?.type
526
+ const isFloat = paramType?.kind === 'named' && (paramType as any).name === 'float'
527
+
523
528
  if (operand.kind === 'const') {
524
- this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`)
529
+ if (isFloat) {
530
+ const floatVal = (operand.value / 100).toFixed(6)
531
+ this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${floatVal}d`)
532
+ } else {
533
+ this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`)
534
+ }
525
535
  } else if (operand.kind === 'var') {
526
- this.builder.emitRaw(
527
- `execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${LOWERING_OBJ}`
528
- )
536
+ if (isFloat) {
537
+ this.builder.emitRaw(
538
+ `execute store result storage rs:macro_args ${macroParam} double 0.01 run scoreboard players get ${operand.name} ${LOWERING_OBJ}`
539
+ )
540
+ } else {
541
+ this.builder.emitRaw(
542
+ `execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${LOWERING_OBJ}`
543
+ )
544
+ }
529
545
  }
530
546
  }
531
547