redscript-mc 2.1.0 → 2.2.1
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/CHANGELOG.md +11 -0
- package/README.md +86 -21
- package/README.zh.md +61 -61
- package/dist/src/__tests__/e2e/basic.test.js +25 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
- package/dist/src/__tests__/lsp.test.js +76 -0
- package/dist/src/__tests__/mc-integration.test.js +25 -13
- package/dist/src/__tests__/mc-syntax.test.js +1 -6
- package/dist/src/__tests__/schedule.test.js +105 -0
- package/dist/src/__tests__/stdlib-include.test.d.ts +1 -0
- package/dist/src/__tests__/stdlib-include.test.js +86 -0
- package/dist/src/__tests__/typechecker.test.js +63 -0
- package/dist/src/cli.js +10 -3
- package/dist/src/compile.d.ts +1 -0
- package/dist/src/compile.js +33 -10
- package/dist/src/emit/compile.d.ts +2 -0
- package/dist/src/emit/compile.js +3 -2
- package/dist/src/emit/index.js +3 -1
- package/dist/src/lir/lower.js +26 -0
- package/dist/src/lsp/server.js +51 -0
- package/dist/src/mir/lower.js +341 -12
- package/dist/src/mir/types.d.ts +10 -0
- package/dist/src/optimizer/copy_prop.js +4 -0
- package/dist/src/optimizer/coroutine.d.ts +2 -0
- package/dist/src/optimizer/coroutine.js +33 -1
- package/dist/src/optimizer/dce.js +7 -1
- package/dist/src/optimizer/lir/const_imm.js +1 -1
- package/dist/src/optimizer/lir/dead_slot.js +1 -1
- package/dist/src/typechecker/index.d.ts +2 -0
- package/dist/src/typechecker/index.js +29 -0
- package/docs/ROADMAP.md +35 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +34 -0
- package/examples/coroutine-demo.mcrs +51 -0
- package/examples/enum-demo.mcrs +95 -0
- package/examples/scheduler-demo.mcrs +59 -0
- package/jest.config.js +19 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/basic.test.ts +27 -0
- package/src/__tests__/e2e/coroutine.test.ts +23 -0
- package/src/__tests__/fixtures/array-test.mcrs +21 -22
- package/src/__tests__/fixtures/counter.mcrs +17 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
- package/src/__tests__/lsp.test.ts +89 -0
- package/src/__tests__/mc-integration.test.ts +25 -13
- package/src/__tests__/mc-syntax.test.ts +1 -7
- package/src/__tests__/schedule.test.ts +112 -0
- package/src/__tests__/stdlib-include.test.ts +61 -0
- package/src/__tests__/typechecker.test.ts +68 -0
- package/src/cli.ts +9 -1
- package/src/compile.ts +44 -15
- package/src/emit/compile.ts +5 -2
- package/src/emit/index.ts +3 -1
- package/src/lir/lower.ts +27 -0
- package/src/lsp/server.ts +55 -0
- package/src/mir/lower.ts +355 -9
- package/src/mir/types.ts +4 -0
- package/src/optimizer/copy_prop.ts +4 -0
- package/src/optimizer/coroutine.ts +37 -1
- package/src/optimizer/dce.ts +6 -1
- package/src/optimizer/lir/const_imm.ts +1 -1
- package/src/optimizer/lir/dead_slot.ts +1 -1
- package/src/stdlib/timer.mcrs +10 -5
- package/src/typechecker/index.ts +39 -0
- package/examples/spiral.mcrs +0 -43
- package/src/examples/arena.mcrs +0 -44
- package/src/examples/counter.mcrs +0 -12
- package/src/examples/new_features_demo.mcrs +0 -193
- package/src/examples/rpg.mcrs +0 -13
- package/src/examples/stdlib_demo.mcrs +0 -181
|
@@ -40,6 +40,8 @@ export interface CoroutineResult {
|
|
|
40
40
|
module: MIRModule
|
|
41
41
|
/** Names of generated @tick dispatcher functions (caller must add to tick list). */
|
|
42
42
|
generatedTickFunctions: string[]
|
|
43
|
+
/** Warning messages for skipped transforms. */
|
|
44
|
+
warnings: string[]
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -51,11 +53,12 @@ export function coroutineTransform(
|
|
|
51
53
|
mod: MIRModule,
|
|
52
54
|
infos: CoroutineInfo[],
|
|
53
55
|
): CoroutineResult {
|
|
54
|
-
if (infos.length === 0) return { module: mod, generatedTickFunctions: [] }
|
|
56
|
+
if (infos.length === 0) return { module: mod, generatedTickFunctions: [], warnings: [] }
|
|
55
57
|
|
|
56
58
|
const infoMap = new Map(infos.map(i => [i.fnName, i]))
|
|
57
59
|
const newFunctions: MIRFunction[] = []
|
|
58
60
|
const tickFns: string[] = []
|
|
61
|
+
const warnings: string[] = []
|
|
59
62
|
|
|
60
63
|
for (const fn of mod.functions) {
|
|
61
64
|
const info = infoMap.get(fn.name)
|
|
@@ -64,6 +67,17 @@ export function coroutineTransform(
|
|
|
64
67
|
continue
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
// Skip transform if function contains macro calls — continuations are called
|
|
71
|
+
// directly (not via `function ... with storage`) so macro variables like
|
|
72
|
+
// ${px} would not be substituted, causing MC parse errors.
|
|
73
|
+
if (fnContainsMacroCalls(fn)) {
|
|
74
|
+
warnings.push(
|
|
75
|
+
`@coroutine cannot be applied to functions containing macro calls (skipped: ${fn.name})`,
|
|
76
|
+
)
|
|
77
|
+
newFunctions.push(fn)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
const transformed = transformCoroutine(fn, info, mod.objective)
|
|
68
82
|
newFunctions.push(transformed.initFn)
|
|
69
83
|
newFunctions.push(...transformed.continuations)
|
|
@@ -74,7 +88,29 @@ export function coroutineTransform(
|
|
|
74
88
|
return {
|
|
75
89
|
module: { ...mod, functions: newFunctions },
|
|
76
90
|
generatedTickFunctions: tickFns,
|
|
91
|
+
warnings,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns true if any instruction in the function requires macro processing.
|
|
97
|
+
* This includes:
|
|
98
|
+
* - call_macro: explicit macro function invocations
|
|
99
|
+
* - call with fn = '__raw:\x01...': builtin calls (particle, summon, etc.) with macro params
|
|
100
|
+
* - call with fn = '__raw:<cmd>' where cmd contains '${': raw() commands with variable interpolation
|
|
101
|
+
*/
|
|
102
|
+
function fnContainsMacroCalls(fn: MIRFunction): boolean {
|
|
103
|
+
for (const block of fn.blocks) {
|
|
104
|
+
for (const instr of [...block.instrs, block.term]) {
|
|
105
|
+
if (instr.kind === 'call_macro') return true
|
|
106
|
+
if (instr.kind === 'call' && instr.fn.startsWith('__raw:')) {
|
|
107
|
+
const cmd = instr.fn.slice(6)
|
|
108
|
+
// \x01 sentinel: builtin with macro params; '${': raw() with variable interpolation
|
|
109
|
+
if (cmd.startsWith('\x01') || cmd.includes('${')) return true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
77
112
|
}
|
|
113
|
+
return false
|
|
78
114
|
}
|
|
79
115
|
|
|
80
116
|
// ---------------------------------------------------------------------------
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -78,7 +78,8 @@ function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
|
|
|
78
78
|
|
|
79
79
|
function hasSideEffects(instr: MIRInstr): boolean {
|
|
80
80
|
if (instr.kind === 'call' || instr.kind === 'call_macro' ||
|
|
81
|
-
instr.kind === 'call_context' || instr.kind === 'nbt_write'
|
|
81
|
+
instr.kind === 'call_context' || instr.kind === 'nbt_write' ||
|
|
82
|
+
instr.kind === 'score_write') return true
|
|
82
83
|
// Return field temps (__rf_) write to global return slots — not dead even if unused locally
|
|
83
84
|
// Option slot temps (__opt_) write observable scoreboard state — preserve even if var unused
|
|
84
85
|
const dst = getDst(instr)
|
|
@@ -104,6 +105,8 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
104
105
|
return instr.dst
|
|
105
106
|
case 'call': case 'call_macro':
|
|
106
107
|
return instr.dst
|
|
108
|
+
case 'score_read':
|
|
109
|
+
return instr.dst
|
|
107
110
|
default:
|
|
108
111
|
return null
|
|
109
112
|
}
|
|
@@ -129,6 +132,8 @@ function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
|
129
132
|
addOp(instr.cond); break
|
|
130
133
|
case 'return':
|
|
131
134
|
if (instr.value) addOp(instr.value); break
|
|
135
|
+
case 'score_write':
|
|
136
|
+
addOp(instr.src); break
|
|
132
137
|
}
|
|
133
138
|
return temps
|
|
134
139
|
}
|
|
@@ -28,7 +28,7 @@ function countSlotUses(instrs: LIRInstr[], target: string): number {
|
|
|
28
28
|
|
|
29
29
|
function extractSlotsFromRaw(cmd: string): Slot[] {
|
|
30
30
|
const slots: Slot[] = []
|
|
31
|
-
const re = /(\$[\w
|
|
31
|
+
const re = /(\$[\w.:]+)\s+(\S+)/g
|
|
32
32
|
let m
|
|
33
33
|
while ((m = re.exec(cmd)) !== null) {
|
|
34
34
|
slots.push({ player: m[1], obj: m[2] })
|
|
@@ -24,7 +24,7 @@ function slotKey(s: Slot): string {
|
|
|
24
24
|
function extractSlotsFromRaw(cmd: string): Slot[] {
|
|
25
25
|
const slots: Slot[] = []
|
|
26
26
|
// Match $<player> <obj> patterns (scoreboard slot references)
|
|
27
|
-
const re = /(\$[\w
|
|
27
|
+
const re = /(\$[\w.:]+)\s+(\S+)/g
|
|
28
28
|
let m
|
|
29
29
|
while ((m = re.exec(cmd)) !== null) {
|
|
30
30
|
slots.push({ player: m[1], obj: m[2] })
|
package/src/stdlib/timer.mcrs
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// Timer utilities with an OOP-style API.
|
|
2
2
|
//
|
|
3
|
-
// Timer
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Each Timer::new() call is statically allocated a unique compile-time ID
|
|
4
|
+
// (0, 1, 2, ...). The compiler intercepts Timer method calls and inlines
|
|
5
|
+
// them as direct scoreboard operations on per-instance slots:
|
|
6
|
+
// __timer_N_ticks, __timer_N_active (stored in the namespace objective)
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// Restriction: Timer::new() must be called at the top level of a function,
|
|
9
|
+
// not inside loops or if/else bodies (compile error enforced by TypeChecker).
|
|
10
|
+
//
|
|
11
|
+
// This file provides the struct definition and method stubs. The actual
|
|
12
|
+
// scoreboard operations are generated by the MIR lowering pass and never
|
|
13
|
+
// call these method bodies directly when the _id is statically known.
|
|
9
14
|
|
|
10
15
|
struct Timer {
|
|
11
16
|
_id: int,
|
package/src/typechecker/index.ts
CHANGED
|
@@ -138,6 +138,9 @@ export class TypeChecker {
|
|
|
138
138
|
private scope: Map<string, ScopeSymbol> = new Map()
|
|
139
139
|
// Stack for tracking @s type in different contexts
|
|
140
140
|
private selfTypeStack: EntityTypeName[] = ['entity']
|
|
141
|
+
// Depth of loop/conditional nesting (for static-allocation enforcement)
|
|
142
|
+
private loopDepth = 0
|
|
143
|
+
private condDepth = 0
|
|
141
144
|
|
|
142
145
|
private readonly richTextBuiltins = new Map<string, { messageIndex: number }>([
|
|
143
146
|
['say', { messageIndex: 0 }],
|
|
@@ -352,17 +355,23 @@ export class TypeChecker {
|
|
|
352
355
|
break
|
|
353
356
|
case 'if':
|
|
354
357
|
this.checkExpr(stmt.cond)
|
|
358
|
+
this.condDepth++
|
|
355
359
|
this.checkIfBranches(stmt)
|
|
360
|
+
this.condDepth--
|
|
356
361
|
break
|
|
357
362
|
case 'while':
|
|
358
363
|
this.checkExpr(stmt.cond)
|
|
364
|
+
this.loopDepth++
|
|
359
365
|
this.checkBlock(stmt.body)
|
|
366
|
+
this.loopDepth--
|
|
360
367
|
break
|
|
361
368
|
case 'for':
|
|
362
369
|
if (stmt.init) this.checkStmt(stmt.init)
|
|
363
370
|
this.checkExpr(stmt.cond)
|
|
364
371
|
this.checkExpr(stmt.step)
|
|
372
|
+
this.loopDepth++
|
|
365
373
|
this.checkBlock(stmt.body)
|
|
374
|
+
this.loopDepth--
|
|
366
375
|
break
|
|
367
376
|
case 'foreach':
|
|
368
377
|
this.checkExpr(stmt.iterable)
|
|
@@ -375,7 +384,9 @@ export class TypeChecker {
|
|
|
375
384
|
})
|
|
376
385
|
// Push self type context for @s inside the loop
|
|
377
386
|
this.pushSelfType(entityType)
|
|
387
|
+
this.loopDepth++
|
|
378
388
|
this.checkBlock(stmt.body)
|
|
389
|
+
this.loopDepth--
|
|
379
390
|
this.popSelfType()
|
|
380
391
|
} else {
|
|
381
392
|
const iterableType = this.inferType(stmt.iterable)
|
|
@@ -384,7 +395,9 @@ export class TypeChecker {
|
|
|
384
395
|
} else {
|
|
385
396
|
this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true })
|
|
386
397
|
}
|
|
398
|
+
this.loopDepth++
|
|
387
399
|
this.checkBlock(stmt.body)
|
|
400
|
+
this.loopDepth--
|
|
388
401
|
}
|
|
389
402
|
break
|
|
390
403
|
case 'match':
|
|
@@ -712,6 +725,19 @@ export class TypeChecker {
|
|
|
712
725
|
|
|
713
726
|
const builtin = BUILTIN_SIGNATURES[expr.fn]
|
|
714
727
|
if (builtin) {
|
|
728
|
+
if (expr.fn === 'setTimeout' || expr.fn === 'setInterval') {
|
|
729
|
+
if (this.loopDepth > 0) {
|
|
730
|
+
this.report(
|
|
731
|
+
`${expr.fn}() cannot be called inside a loop. Declare timers at the top level.`,
|
|
732
|
+
expr
|
|
733
|
+
)
|
|
734
|
+
} else if (this.condDepth > 0) {
|
|
735
|
+
this.report(
|
|
736
|
+
`${expr.fn}() cannot be called inside an if/else body. Declare timers at the top level.`,
|
|
737
|
+
expr
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
715
741
|
this.checkFunctionCallArgs(expr.args, builtin.params, expr.fn, expr)
|
|
716
742
|
return
|
|
717
743
|
}
|
|
@@ -907,6 +933,19 @@ export class TypeChecker {
|
|
|
907
933
|
}
|
|
908
934
|
|
|
909
935
|
private checkStaticCallExpr(expr: Extract<Expr, { kind: 'static_call' }>): void {
|
|
936
|
+
if (expr.type === 'Timer' && expr.method === 'new') {
|
|
937
|
+
if (this.loopDepth > 0) {
|
|
938
|
+
this.report(
|
|
939
|
+
`Timer::new() cannot be called inside a loop. Declare timers at the top level.`,
|
|
940
|
+
expr
|
|
941
|
+
)
|
|
942
|
+
} else if (this.condDepth > 0) {
|
|
943
|
+
this.report(
|
|
944
|
+
`Timer::new() cannot be called inside an if/else body. Declare timers at the top level.`,
|
|
945
|
+
expr
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
910
949
|
const method = this.implMethods.get(expr.type)?.get(expr.method)
|
|
911
950
|
if (!method) {
|
|
912
951
|
this.report(`Type '${expr.type}' has no static method '${expr.method}'`, expr)
|
package/examples/spiral.mcrs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// ===== Simple Particle Demo =====
|
|
2
|
-
// 展示: @tick, 状态管理, f-strings, 控制命令
|
|
3
|
-
|
|
4
|
-
// 状态
|
|
5
|
-
let counter: int = 0;
|
|
6
|
-
let running: bool = false;
|
|
7
|
-
|
|
8
|
-
// ===== 主循环 =====
|
|
9
|
-
@tick fn demo_tick() {
|
|
10
|
-
if (!running) { return; }
|
|
11
|
-
|
|
12
|
-
// 每 tick 增加计数器
|
|
13
|
-
counter = counter + 1;
|
|
14
|
-
|
|
15
|
-
// 在每个玩家位置生成粒子
|
|
16
|
-
foreach (p in @a) at @s {
|
|
17
|
-
particle("minecraft:end_rod", ~0, ~1, ~0, 0.5, 0.5, 0.5, 0.1, 5);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 每 20 ticks (1秒) 报告一次
|
|
21
|
-
if (counter % 20 == 0) {
|
|
22
|
-
say(f"Running for {counter} ticks");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ===== 控制命令 =====
|
|
27
|
-
// @keep 防止 DCE 删除
|
|
28
|
-
@keep fn start() {
|
|
29
|
-
running = true;
|
|
30
|
-
counter = 0;
|
|
31
|
-
say(f"Demo started!");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@keep fn stop() {
|
|
35
|
-
running = false;
|
|
36
|
-
say(f"Demo stopped at {counter} ticks.");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@keep fn reset() {
|
|
40
|
-
running = false;
|
|
41
|
-
counter = 0;
|
|
42
|
-
say(f"Demo reset.");
|
|
43
|
-
}
|
package/src/examples/arena.mcrs
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// PvP arena scoreboard tracker.
|
|
2
|
-
// Reads the vanilla kills objective, announces the top score every 200 ticks,
|
|
3
|
-
// and tells the current leader(s) directly.
|
|
4
|
-
|
|
5
|
-
@tick
|
|
6
|
-
fn arena_tick() {
|
|
7
|
-
let ticks: int = scoreboard_get("arena", #ticks);
|
|
8
|
-
ticks = ticks + 1;
|
|
9
|
-
scoreboard_set("arena", #ticks, ticks);
|
|
10
|
-
|
|
11
|
-
if (ticks % 200 == 0) {
|
|
12
|
-
announce_leaders();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
fn announce_leaders() {
|
|
17
|
-
let top_kills: int = 0;
|
|
18
|
-
|
|
19
|
-
foreach (player in @a) {
|
|
20
|
-
let kills: int = scoreboard_get(player, #kills);
|
|
21
|
-
if (kills > top_kills) {
|
|
22
|
-
top_kills = kills;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (top_kills > 0) {
|
|
27
|
-
announce("Arena update: leader check complete.");
|
|
28
|
-
title_times(@a, 10, 40, 10);
|
|
29
|
-
actionbar(@a, "Top kills updated");
|
|
30
|
-
|
|
31
|
-
foreach (player in @a) {
|
|
32
|
-
let kills: int = scoreboard_get(player, #kills);
|
|
33
|
-
if (kills == top_kills) {
|
|
34
|
-
tell(player, "You are leading the arena right now.");
|
|
35
|
-
title(player, "Arena Leader");
|
|
36
|
-
subtitle(player, "Hold the top score");
|
|
37
|
-
actionbar(player, "Stay alive to keep the lead");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
announce("Arena update: no PvP kills yet.");
|
|
42
|
-
actionbar(@a, "No arena leader yet");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Tick counter that announces every 100 ticks.
|
|
2
|
-
|
|
3
|
-
@tick
|
|
4
|
-
fn counter_tick() {
|
|
5
|
-
let ticks = scoreboard_get("counter", #ticks);
|
|
6
|
-
ticks = ticks + 1;
|
|
7
|
-
scoreboard_set("counter", #ticks, ticks);
|
|
8
|
-
|
|
9
|
-
if (ticks % 100 == 0) {
|
|
10
|
-
say("Counter reached another 100 ticks");
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RedScript New Features Demo
|
|
3
|
-
* Showcasing language features added on 2026-03-12
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ============================================
|
|
7
|
-
// 1. Type Inference
|
|
8
|
-
// ============================================
|
|
9
|
-
fn type_inference_demo() {
|
|
10
|
-
// No need to write `: int`, compiler infers automatically
|
|
11
|
-
let health = 100;
|
|
12
|
-
let name = "Steve";
|
|
13
|
-
let alive = true;
|
|
14
|
-
let speed = 1.5;
|
|
15
|
-
|
|
16
|
-
// NBT suffixes can also be inferred
|
|
17
|
-
let damage = 20b; // byte
|
|
18
|
-
let distance = 1000s; // short
|
|
19
|
-
let bignum = 999999L; // long
|
|
20
|
-
let precise = 3.14d; // double
|
|
21
|
-
|
|
22
|
-
say("Health: ${health}, Name: ${name}");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ============================================
|
|
26
|
-
// 2. For-Range Loops
|
|
27
|
-
// ============================================
|
|
28
|
-
fn for_range_demo() {
|
|
29
|
-
// Loop from 0 to 9
|
|
30
|
-
for i in 0..10 {
|
|
31
|
-
say("Count: ${i}");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Can be used for countdown
|
|
35
|
-
for sec in 0..5 {
|
|
36
|
-
title(@a, "Starting in ${sec}...");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// 3. NBT Structured Params
|
|
42
|
-
// ============================================
|
|
43
|
-
fn nbt_params_demo() {
|
|
44
|
-
// Give item with NBT
|
|
45
|
-
give(@s, "minecraft:diamond_sword", 1, {
|
|
46
|
-
display: { Name: "Excalibur" },
|
|
47
|
-
Enchantments: [
|
|
48
|
-
{ id: "minecraft:sharpness", lvl: 5 },
|
|
49
|
-
{ id: "minecraft:unbreaking", lvl: 3 }
|
|
50
|
-
]
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Summon entity with attributes
|
|
54
|
-
summon("minecraft:zombie", @s, {
|
|
55
|
-
CustomName: "Boss Zombie",
|
|
56
|
-
Health: 100.0,
|
|
57
|
-
Attributes: [
|
|
58
|
-
{ Name: "generic.max_health", Base: 100.0 }
|
|
59
|
-
]
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ============================================
|
|
64
|
-
// 4. Set Data Structure (Runtime Set)
|
|
65
|
-
// ============================================
|
|
66
|
-
fn set_demo() {
|
|
67
|
-
// Create set
|
|
68
|
-
let visited = set_new();
|
|
69
|
-
|
|
70
|
-
// Add elements using method syntax
|
|
71
|
-
visited.add("spawn");
|
|
72
|
-
visited.add("castle");
|
|
73
|
-
visited.add("dungeon");
|
|
74
|
-
|
|
75
|
-
// Check if exists
|
|
76
|
-
if (visited.contains("castle")) {
|
|
77
|
-
say("You've been to the castle!");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Remove element
|
|
81
|
-
visited.remove("spawn");
|
|
82
|
-
|
|
83
|
-
// Clear set
|
|
84
|
-
visited.clear();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ============================================
|
|
88
|
-
// 5. Method Syntax Sugar
|
|
89
|
-
// ============================================
|
|
90
|
-
fn method_syntax_demo() {
|
|
91
|
-
let items: string[] = [];
|
|
92
|
-
|
|
93
|
-
// obj.method(args) → method(obj, args)
|
|
94
|
-
items.push("sword");
|
|
95
|
-
items.push("shield");
|
|
96
|
-
items.push("potion");
|
|
97
|
-
|
|
98
|
-
let count = items.len();
|
|
99
|
-
say("You have ${count} items");
|
|
100
|
-
|
|
101
|
-
// Sets can also use method syntax
|
|
102
|
-
let tags = set_new();
|
|
103
|
-
tags.add("vip");
|
|
104
|
-
tags.add("admin");
|
|
105
|
-
|
|
106
|
-
if (tags.contains("admin")) {
|
|
107
|
-
say("Welcome, admin!");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ============================================
|
|
112
|
-
// 6. #mc_name Syntax (MC Identifier Syntax)
|
|
113
|
-
// ============================================
|
|
114
|
-
fn mc_name_demo() {
|
|
115
|
-
// #name compiles to bare MC name (without quotes)
|
|
116
|
-
scoreboard_set(@s, #kills, 0);
|
|
117
|
-
scoreboard_add(@s, #deaths, 1);
|
|
118
|
-
|
|
119
|
-
let score = scoreboard_get(@s, #points);
|
|
120
|
-
|
|
121
|
-
// For tag
|
|
122
|
-
tag_add(@s, #vip);
|
|
123
|
-
|
|
124
|
-
// For team
|
|
125
|
-
team_join(@s, #red);
|
|
126
|
-
|
|
127
|
-
// Comparison: strings still need quotes
|
|
128
|
-
give(@s, "minecraft:diamond", 1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ============================================
|
|
132
|
-
// 7. Block Comments
|
|
133
|
-
// ============================================
|
|
134
|
-
|
|
135
|
-
/*
|
|
136
|
-
* This is a block comment
|
|
137
|
-
* Can span multiple lines
|
|
138
|
-
*/
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* This is a doc comment
|
|
142
|
-
* @param player Target player
|
|
143
|
-
*/
|
|
144
|
-
fn documented_function() {
|
|
145
|
-
say("Hello!");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ============================================
|
|
149
|
-
// Combined Example: Simple Game
|
|
150
|
-
// ============================================
|
|
151
|
-
|
|
152
|
-
struct Player {
|
|
153
|
-
score: int,
|
|
154
|
-
level: int,
|
|
155
|
-
visited: string // set ID
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
fn init_player() {
|
|
159
|
-
let visited_set = set_new();
|
|
160
|
-
let p: Player = {
|
|
161
|
-
score: 0,
|
|
162
|
-
level: 1,
|
|
163
|
-
visited: visited_set
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Record spawn point (using set_add since visited is a string ID)
|
|
167
|
-
set_add(p.visited, "spawn");
|
|
168
|
-
|
|
169
|
-
scoreboard_set(@s, #score, p.score);
|
|
170
|
-
scoreboard_set(@s, #level, p.level);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
@tick(rate=20)
|
|
174
|
-
fn game_tick() {
|
|
175
|
-
// Check once per second
|
|
176
|
-
for i in 0..1 {
|
|
177
|
-
let score = scoreboard_get(@s, #score);
|
|
178
|
-
if (score >= 100) {
|
|
179
|
-
title(@s, "Level Up!");
|
|
180
|
-
scoreboard_set(@s, #level, 2);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
fn reward_player(amount: int) {
|
|
186
|
-
// Type inference + NBT params
|
|
187
|
-
let bonus = amount * 2;
|
|
188
|
-
scoreboard_add(@s, #score, bonus);
|
|
189
|
-
|
|
190
|
-
give(@s, "minecraft:gold_ingot", amount, {
|
|
191
|
-
display: { Name: "Reward Gold" }
|
|
192
|
-
});
|
|
193
|
-
}
|
package/src/examples/rpg.mcrs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import "../stdlib/math.mcrs"
|
|
2
|
-
import "../stdlib/combat.mcrs"
|
|
3
|
-
|
|
4
|
-
fn attack(enemy: string, base: int, bonus: int) {
|
|
5
|
-
let raw_damage = weapon_damage(base, bonus);
|
|
6
|
-
let damage = clamp(raw_damage, 1, 20);
|
|
7
|
-
apply_damage(enemy, damage);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
@tick
|
|
11
|
-
fn battle_tick() {
|
|
12
|
-
attack("goblin", 4, 2);
|
|
13
|
-
}
|