redscript-mc 1.0.0
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/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish-extension.yml +35 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh.md +261 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +140 -0
- package/dist/__tests__/codegen.test.d.ts +1 -0
- package/dist/__tests__/codegen.test.js +121 -0
- package/dist/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/__tests__/diagnostics.test.js +149 -0
- package/dist/__tests__/e2e.test.d.ts +6 -0
- package/dist/__tests__/e2e.test.js +1528 -0
- package/dist/__tests__/lexer.test.d.ts +1 -0
- package/dist/__tests__/lexer.test.js +316 -0
- package/dist/__tests__/lowering.test.d.ts +1 -0
- package/dist/__tests__/lowering.test.js +819 -0
- package/dist/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/__tests__/mc-integration.test.js +395 -0
- package/dist/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/__tests__/mc-syntax.test.js +112 -0
- package/dist/__tests__/nbt.test.d.ts +1 -0
- package/dist/__tests__/nbt.test.js +82 -0
- package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/__tests__/optimizer.test.d.ts +1 -0
- package/dist/__tests__/optimizer.test.js +118 -0
- package/dist/__tests__/parser.test.d.ts +1 -0
- package/dist/__tests__/parser.test.js +717 -0
- package/dist/__tests__/repl.test.d.ts +1 -0
- package/dist/__tests__/repl.test.js +27 -0
- package/dist/__tests__/runtime.test.d.ts +1 -0
- package/dist/__tests__/runtime.test.js +276 -0
- package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/__tests__/structure-optimizer.test.js +33 -0
- package/dist/__tests__/typechecker.test.d.ts +1 -0
- package/dist/__tests__/typechecker.test.js +364 -0
- package/dist/ast/types.d.ts +357 -0
- package/dist/ast/types.js +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +407 -0
- package/dist/codegen/cmdblock/index.d.ts +26 -0
- package/dist/codegen/cmdblock/index.js +45 -0
- package/dist/codegen/mcfunction/index.d.ts +34 -0
- package/dist/codegen/mcfunction/index.js +413 -0
- package/dist/codegen/structure/index.d.ts +18 -0
- package/dist/codegen/structure/index.js +249 -0
- package/dist/compile.d.ts +30 -0
- package/dist/compile.js +152 -0
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/diagnostics/index.d.ts +44 -0
- package/dist/diagnostics/index.js +140 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +126 -0
- package/dist/ir/builder.d.ts +32 -0
- package/dist/ir/builder.js +99 -0
- package/dist/ir/types.d.ts +117 -0
- package/dist/ir/types.js +15 -0
- package/dist/lexer/index.d.ts +36 -0
- package/dist/lexer/index.js +458 -0
- package/dist/lowering/index.d.ts +106 -0
- package/dist/lowering/index.js +2041 -0
- package/dist/mc-test/client.d.ts +128 -0
- package/dist/mc-test/client.js +174 -0
- package/dist/mc-test/runner.d.ts +28 -0
- package/dist/mc-test/runner.js +150 -0
- package/dist/mc-test/setup.d.ts +11 -0
- package/dist/mc-test/setup.js +98 -0
- package/dist/mc-validator/index.d.ts +17 -0
- package/dist/mc-validator/index.js +322 -0
- package/dist/nbt/index.d.ts +86 -0
- package/dist/nbt/index.js +250 -0
- package/dist/optimizer/commands.d.ts +36 -0
- package/dist/optimizer/commands.js +349 -0
- package/dist/optimizer/passes.d.ts +34 -0
- package/dist/optimizer/passes.js +227 -0
- package/dist/optimizer/structure.d.ts +8 -0
- package/dist/optimizer/structure.js +344 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +76 -0
- package/dist/parser/index.js +1193 -0
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +165 -0
- package/dist/runtime/index.d.ts +101 -0
- package/dist/runtime/index.js +1288 -0
- package/dist/typechecker/index.d.ts +42 -0
- package/dist/typechecker/index.js +629 -0
- package/docs/COMPILATION_STATS.md +142 -0
- package/docs/IMPLEMENTATION_GUIDE.md +512 -0
- package/docs/LANGUAGE_REFERENCE.md +415 -0
- package/docs/MC_MAPPING.md +280 -0
- package/docs/STRUCTURE_TARGET.md +80 -0
- package/docs/mc-reference/commands.md +259 -0
- package/editors/vscode/.vscodeignore +10 -0
- package/editors/vscode/LICENSE +21 -0
- package/editors/vscode/README.md +78 -0
- package/editors/vscode/build.mjs +28 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/mcfunction-language-configuration.json +28 -0
- package/editors/vscode/out/extension.js +7236 -0
- package/editors/vscode/package-lock.json +566 -0
- package/editors/vscode/package.json +137 -0
- package/editors/vscode/redscript-language-configuration.json +28 -0
- package/editors/vscode/snippets/redscript.json +114 -0
- package/editors/vscode/src/codeactions.ts +89 -0
- package/editors/vscode/src/completion.ts +130 -0
- package/editors/vscode/src/extension.ts +239 -0
- package/editors/vscode/src/hover.ts +1120 -0
- package/editors/vscode/src/symbols.ts +207 -0
- package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
- package/editors/vscode/tsconfig.json +13 -0
- package/jest.config.js +5 -0
- package/package.json +38 -0
- package/src/__tests__/cli.test.ts +130 -0
- package/src/__tests__/codegen.test.ts +128 -0
- package/src/__tests__/diagnostics.test.ts +195 -0
- package/src/__tests__/e2e.test.ts +1721 -0
- package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
- package/src/__tests__/formatter.test.ts +46 -0
- package/src/__tests__/lexer.test.ts +356 -0
- package/src/__tests__/lowering.test.ts +962 -0
- package/src/__tests__/mc-integration.test.ts +409 -0
- package/src/__tests__/mc-syntax.test.ts +96 -0
- package/src/__tests__/nbt.test.ts +58 -0
- package/src/__tests__/optimizer-advanced.test.ts +144 -0
- package/src/__tests__/optimizer.test.ts +129 -0
- package/src/__tests__/parser.test.ts +800 -0
- package/src/__tests__/repl.test.ts +33 -0
- package/src/__tests__/runtime.test.ts +289 -0
- package/src/__tests__/structure-optimizer.test.ts +38 -0
- package/src/__tests__/typechecker.test.ts +395 -0
- package/src/ast/types.ts +248 -0
- package/src/cli.ts +445 -0
- package/src/codegen/cmdblock/index.ts +63 -0
- package/src/codegen/mcfunction/index.ts +471 -0
- package/src/codegen/structure/index.ts +305 -0
- package/src/compile.ts +188 -0
- package/src/diagnostics/index.ts +186 -0
- package/src/examples/README.md +77 -0
- package/src/examples/SHOWCASE_GAME.md +43 -0
- package/src/examples/arena.rs +44 -0
- package/src/examples/counter.rs +12 -0
- package/src/examples/pvp_arena.rs +131 -0
- package/src/examples/quiz.rs +90 -0
- package/src/examples/rpg.rs +13 -0
- package/src/examples/shop.rs +30 -0
- package/src/examples/showcase_game.rs +552 -0
- package/src/examples/stdlib_demo.rs +181 -0
- package/src/examples/turret.rs +27 -0
- package/src/examples/world_manager.rs +23 -0
- package/src/formatter/index.ts +22 -0
- package/src/index.ts +161 -0
- package/src/ir/builder.ts +114 -0
- package/src/ir/types.ts +119 -0
- package/src/lexer/index.ts +555 -0
- package/src/lowering/index.ts +2406 -0
- package/src/mc-test/client.ts +259 -0
- package/src/mc-test/runner.ts +140 -0
- package/src/mc-test/setup.ts +70 -0
- package/src/mc-validator/index.ts +367 -0
- package/src/nbt/index.ts +321 -0
- package/src/optimizer/commands.ts +416 -0
- package/src/optimizer/passes.ts +233 -0
- package/src/optimizer/structure.ts +441 -0
- package/src/parser/index.ts +1437 -0
- package/src/repl.ts +165 -0
- package/src/runtime/index.ts +1403 -0
- package/src/stdlib/README.md +156 -0
- package/src/stdlib/combat.rs +20 -0
- package/src/stdlib/cooldown.rs +45 -0
- package/src/stdlib/math.rs +49 -0
- package/src/stdlib/mobs.rs +99 -0
- package/src/stdlib/player.rs +29 -0
- package/src/stdlib/strings.rs +7 -0
- package/src/stdlib/timer.rs +51 -0
- package/src/templates/README.md +126 -0
- package/src/templates/combat.rs +96 -0
- package/src/templates/economy.rs +40 -0
- package/src/templates/mini-game-framework.rs +117 -0
- package/src/templates/quest.rs +78 -0
- package/src/test_programs/zombie_game.rs +25 -0
- package/src/typechecker/index.ts +737 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedScript MC Test Client
|
|
3
|
+
*
|
|
4
|
+
* Connects to a Paper server running TestHarnessPlugin and provides
|
|
5
|
+
* a fluent API for integration testing compiled datapacks.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const mc = new MCTestClient('localhost', 25561)
|
|
9
|
+
* await mc.command('/function arena:start')
|
|
10
|
+
* await mc.ticks(100)
|
|
11
|
+
* const score = await mc.scoreboard('Alice', 'kills')
|
|
12
|
+
* expect(score).toBe(3)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ScoreResult {
|
|
16
|
+
player: string
|
|
17
|
+
obj: string
|
|
18
|
+
value: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BlockResult {
|
|
22
|
+
x: number
|
|
23
|
+
y: number
|
|
24
|
+
z: number
|
|
25
|
+
world: string
|
|
26
|
+
type: string
|
|
27
|
+
blockData: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EntityResult {
|
|
31
|
+
uuid: string
|
|
32
|
+
name: string
|
|
33
|
+
type: string
|
|
34
|
+
x: number
|
|
35
|
+
y: number
|
|
36
|
+
z: number
|
|
37
|
+
world: string
|
|
38
|
+
tags: string[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ChatMessage {
|
|
42
|
+
tick: number
|
|
43
|
+
type: string
|
|
44
|
+
sender?: string
|
|
45
|
+
message: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface GameEvent {
|
|
49
|
+
tick: number
|
|
50
|
+
type: string
|
|
51
|
+
player?: string
|
|
52
|
+
advancement?: string
|
|
53
|
+
cause?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ServerStatus {
|
|
57
|
+
online: boolean
|
|
58
|
+
tps_1m: number
|
|
59
|
+
tps_5m: number
|
|
60
|
+
tps_15m: number
|
|
61
|
+
players: number
|
|
62
|
+
playerNames: string[]
|
|
63
|
+
worlds: string[]
|
|
64
|
+
version: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class MCTestClient {
|
|
68
|
+
private baseUrl: string
|
|
69
|
+
|
|
70
|
+
constructor(host = 'localhost', port = 25561) {
|
|
71
|
+
this.baseUrl = `http://${host}:${port}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async get<T>(path: string, params: Record<string, string | number> = {}): Promise<T> {
|
|
75
|
+
const qs = Object.entries(params)
|
|
76
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)
|
|
77
|
+
.join('&')
|
|
78
|
+
const url = `${this.baseUrl}${path}${qs ? '?' + qs : ''}`
|
|
79
|
+
const res = await fetch(url)
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const body = await res.text()
|
|
82
|
+
throw new Error(`GET ${path} failed ${res.status}: ${body}`)
|
|
83
|
+
}
|
|
84
|
+
return res.json() as Promise<T>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async post<T>(path: string, body: Record<string, unknown> = {}): Promise<T> {
|
|
88
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify(body)
|
|
92
|
+
})
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const text = await res.text()
|
|
95
|
+
throw new Error(`POST ${path} failed ${res.status}: ${text}`)
|
|
96
|
+
}
|
|
97
|
+
return res.json() as Promise<T>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Check if server is reachable */
|
|
101
|
+
async isOnline(): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
const status = await this.get<ServerStatus>('/status')
|
|
104
|
+
return status.online
|
|
105
|
+
} catch {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Get server status */
|
|
111
|
+
async status(): Promise<ServerStatus> {
|
|
112
|
+
return this.get('/status')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Run a command on the server (as console sender) */
|
|
116
|
+
async command(cmd: string): Promise<{ ok: boolean; cmd: string }> {
|
|
117
|
+
return this.post('/command', { cmd })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Wait for N server ticks (50ms each) */
|
|
121
|
+
async ticks(count: number): Promise<void> {
|
|
122
|
+
await this.post('/tick', { count })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Wait for 1 second = 20 ticks */
|
|
126
|
+
async seconds(s: number): Promise<void> {
|
|
127
|
+
await this.ticks(s * 20)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Get a scoreboard value */
|
|
131
|
+
async scoreboard(player: string, obj: string): Promise<number> {
|
|
132
|
+
const result = await this.get<ScoreResult>('/scoreboard', { player, obj })
|
|
133
|
+
return result.value
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Get all scoreboard values for a selector */
|
|
137
|
+
async scoreboardAll(selector: string, obj: string): Promise<ScoreResult[]> {
|
|
138
|
+
return this.get('/scoreboard', { player: selector, obj })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Get block at position */
|
|
142
|
+
async block(x: number, y: number, z: number, world = 'world'): Promise<BlockResult> {
|
|
143
|
+
return this.get('/block', { x, y, z, world })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Get entities matching selector */
|
|
147
|
+
async entities(selector = '@e'): Promise<EntityResult[]> {
|
|
148
|
+
return this.get('/entity', { sel: selector })
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Get chat log since a tick */
|
|
152
|
+
async chat(since = 0): Promise<ChatMessage[]> {
|
|
153
|
+
return this.get('/chat', { since })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Get last N chat messages */
|
|
157
|
+
async chatLast(n: number): Promise<ChatMessage[]> {
|
|
158
|
+
return this.get('/chat', { last: n })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Get events since a tick, optionally filtered by type */
|
|
162
|
+
async events(since = 0, type?: string): Promise<GameEvent[]> {
|
|
163
|
+
const params: Record<string, string | number> = { since }
|
|
164
|
+
if (type) params.type = type
|
|
165
|
+
return this.get('/events', params)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Clear chat and event logs */
|
|
169
|
+
async reset(): Promise<void> {
|
|
170
|
+
await this.post('/reset')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Full test reset: clear logs + fill test area with air + kill entities + reset scoreboards.
|
|
175
|
+
* Call this at the start of each integration test.
|
|
176
|
+
*/
|
|
177
|
+
async fullReset(options?: {
|
|
178
|
+
x1?: number; y1?: number; z1?: number
|
|
179
|
+
x2?: number; y2?: number; z2?: number
|
|
180
|
+
clearArea?: boolean
|
|
181
|
+
killEntities?: boolean
|
|
182
|
+
resetScoreboards?: boolean
|
|
183
|
+
}): Promise<void> {
|
|
184
|
+
await this.post('/reset', {
|
|
185
|
+
clearArea: options?.clearArea ?? true,
|
|
186
|
+
killEntities: options?.killEntities ?? true,
|
|
187
|
+
resetScoreboards: options?.resetScoreboards ?? true,
|
|
188
|
+
x1: options?.x1 ?? -50, y1: options?.y1 ?? 0, z1: options?.z1 ?? -50,
|
|
189
|
+
x2: options?.x2 ?? 50, y2: options?.y2 ?? 100, z2: options?.z2 ?? 50,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Reload datapacks */
|
|
194
|
+
async reload(): Promise<void> {
|
|
195
|
+
await this.post('/reload')
|
|
196
|
+
await this.ticks(40) // wait 2s for reload
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Assert a scoreboard value equals expected.
|
|
201
|
+
* Throws with a descriptive error if it doesn't match.
|
|
202
|
+
*/
|
|
203
|
+
async assertScore(player: string, obj: string, expected: number, msg?: string): Promise<void> {
|
|
204
|
+
const actual = await this.scoreboard(player, obj)
|
|
205
|
+
if (actual !== expected) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
msg ?? `assertScore failed: ${player}/${obj} expected ${expected}, got ${actual}`
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Assert a block type at position.
|
|
214
|
+
*/
|
|
215
|
+
async assertBlock(x: number, y: number, z: number, expectedType: string, world = 'world'): Promise<void> {
|
|
216
|
+
const block = await this.block(x, y, z, world)
|
|
217
|
+
if (block.type !== expectedType) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`assertBlock failed: (${x},${y},${z}) expected ${expectedType}, got ${block.type}`
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Assert chat log contains a message matching substring.
|
|
226
|
+
*/
|
|
227
|
+
async assertChatContains(substring: string, since = 0): Promise<void> {
|
|
228
|
+
const msgs = await this.chat(since)
|
|
229
|
+
const found = msgs.some(m => m.message.includes(substring))
|
|
230
|
+
if (!found) {
|
|
231
|
+
const recent = msgs.map(m => m.message).slice(-5).join(', ')
|
|
232
|
+
throw new Error(
|
|
233
|
+
`assertChatContains: "${substring}" not found in chat. Recent: [${recent}]`
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Wait until a scoreboard value equals expected, up to timeout ms.
|
|
240
|
+
*/
|
|
241
|
+
async waitForScore(
|
|
242
|
+
player: string,
|
|
243
|
+
obj: string,
|
|
244
|
+
expected: number,
|
|
245
|
+
timeoutMs = 5000,
|
|
246
|
+
pollMs = 100
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const start = Date.now()
|
|
249
|
+
while (Date.now() - start < timeoutMs) {
|
|
250
|
+
try {
|
|
251
|
+
const val = await this.scoreboard(player, obj)
|
|
252
|
+
if (val === expected) return
|
|
253
|
+
} catch { /* ignore transient errors */ }
|
|
254
|
+
await new Promise(r => setTimeout(r, pollMs))
|
|
255
|
+
}
|
|
256
|
+
const final = await this.scoreboard(player, obj)
|
|
257
|
+
throw new Error(`waitForScore: ${player}/${obj} never reached ${expected} (last: ${final})`)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedScript MC Integration Test Runner
|
|
3
|
+
*
|
|
4
|
+
* Compiles a .rs file, installs it to a running Paper server,
|
|
5
|
+
* runs test scenarios, and reports results.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx ts-node src/mc-test/runner.ts src/examples/counter.rs
|
|
9
|
+
*
|
|
10
|
+
* Requires:
|
|
11
|
+
* - Paper server running with TestHarnessPlugin
|
|
12
|
+
* - MC_SERVER_DIR env var pointing to server directory
|
|
13
|
+
* - MC_HOST and MC_PORT env vars (default: localhost:25561)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as fs from 'fs'
|
|
17
|
+
import * as path from 'path'
|
|
18
|
+
import { execSync } from 'child_process'
|
|
19
|
+
import { MCTestClient } from './client'
|
|
20
|
+
import { compile } from '../compile'
|
|
21
|
+
|
|
22
|
+
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
23
|
+
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
24
|
+
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
25
|
+
|
|
26
|
+
export interface TestCase {
|
|
27
|
+
name: string
|
|
28
|
+
run: (mc: MCTestClient) => Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TestResult {
|
|
32
|
+
name: string
|
|
33
|
+
passed: boolean
|
|
34
|
+
error?: string
|
|
35
|
+
durationMs: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runMCTests(
|
|
39
|
+
sourceFile: string,
|
|
40
|
+
tests: TestCase[],
|
|
41
|
+
options: { skipInstall?: boolean } = {}
|
|
42
|
+
): Promise<TestResult[]> {
|
|
43
|
+
const mc = new MCTestClient(MC_HOST, MC_PORT)
|
|
44
|
+
|
|
45
|
+
// Check server is online
|
|
46
|
+
console.log(`Connecting to MC server at ${MC_HOST}:${MC_PORT}...`)
|
|
47
|
+
const online = await mc.isOnline()
|
|
48
|
+
if (!online) {
|
|
49
|
+
throw new Error(`MC server not reachable at ${MC_HOST}:${MC_PORT}. Start Paper server first.`)
|
|
50
|
+
}
|
|
51
|
+
console.log('✓ Server online')
|
|
52
|
+
|
|
53
|
+
if (!options.skipInstall) {
|
|
54
|
+
// Compile and install datapack
|
|
55
|
+
const outDir = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
56
|
+
console.log(`Compiling ${sourceFile}...`)
|
|
57
|
+
const result = compile(fs.readFileSync(sourceFile, 'utf-8'))
|
|
58
|
+
if (!result.success || !result.files) {
|
|
59
|
+
throw result.error ?? new Error('Compilation failed')
|
|
60
|
+
}
|
|
61
|
+
// Write files
|
|
62
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
63
|
+
for (const file of result.files) {
|
|
64
|
+
const filePath = path.join(outDir, file.path)
|
|
65
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
66
|
+
fs.writeFileSync(filePath, file.content)
|
|
67
|
+
}
|
|
68
|
+
console.log(`✓ Datapack installed to ${outDir}`)
|
|
69
|
+
|
|
70
|
+
// Reload datapacks
|
|
71
|
+
await mc.command('/reload')
|
|
72
|
+
await mc.ticks(40) // wait 2s for reload
|
|
73
|
+
console.log('✓ Datapacks reloaded')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Run tests
|
|
77
|
+
const results: TestResult[] = []
|
|
78
|
+
for (const test of tests) {
|
|
79
|
+
await mc.reset() // clear logs before each test
|
|
80
|
+
const start = Date.now()
|
|
81
|
+
try {
|
|
82
|
+
await test.run(mc)
|
|
83
|
+
results.push({ name: test.name, passed: true, durationMs: Date.now() - start })
|
|
84
|
+
console.log(` ✓ ${test.name} (${Date.now() - start}ms)`)
|
|
85
|
+
} catch (err: any) {
|
|
86
|
+
results.push({
|
|
87
|
+
name: test.name,
|
|
88
|
+
passed: false,
|
|
89
|
+
error: err.message,
|
|
90
|
+
durationMs: Date.now() - start
|
|
91
|
+
})
|
|
92
|
+
console.log(` ✗ ${test.name}: ${err.message}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Summary
|
|
97
|
+
const passed = results.filter(r => r.passed).length
|
|
98
|
+
const failed = results.length - passed
|
|
99
|
+
console.log(`\nResults: ${passed}/${results.length} passed${failed > 0 ? ` (${failed} FAILED)` : ''}`)
|
|
100
|
+
|
|
101
|
+
return results
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// CLI entry point
|
|
105
|
+
if (require.main === module) {
|
|
106
|
+
const sourceFile = process.argv[2]
|
|
107
|
+
if (!sourceFile) {
|
|
108
|
+
console.error('Usage: ts-node runner.ts <source.rs>')
|
|
109
|
+
process.exit(1)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Example test suite (replace with actual tests)
|
|
113
|
+
const exampleTests: TestCase[] = [
|
|
114
|
+
{
|
|
115
|
+
name: 'server is online',
|
|
116
|
+
run: async (mc) => {
|
|
117
|
+
const status = await mc.status()
|
|
118
|
+
if (!status.online) throw new Error('Server not online')
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'datapack loads without errors',
|
|
123
|
+
run: async (mc) => {
|
|
124
|
+
await mc.command('/reload')
|
|
125
|
+
await mc.ticks(20)
|
|
126
|
+
// If reload didn't crash, we're good
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
runMCTests(sourceFile, exampleTests)
|
|
132
|
+
.then(results => {
|
|
133
|
+
const failed = results.filter(r => !r.passed)
|
|
134
|
+
process.exit(failed.length > 0 ? 1 : 0)
|
|
135
|
+
})
|
|
136
|
+
.catch(err => {
|
|
137
|
+
console.error(err.message)
|
|
138
|
+
process.exit(1)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MC Test Setup Script
|
|
3
|
+
*
|
|
4
|
+
* Pre-compiles all RedScript test fixtures into the datapack directory.
|
|
5
|
+
* Run this ONCE before starting the Paper server, or before running tests
|
|
6
|
+
* with a fresh server.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* MC_SERVER_DIR=~/mc-test-server npx ts-node src/mc-test/setup.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs'
|
|
13
|
+
import * as path from 'path'
|
|
14
|
+
import { compile } from '../compile'
|
|
15
|
+
|
|
16
|
+
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
17
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
18
|
+
const EXAMPLES_DIR = path.join(__dirname, '../examples')
|
|
19
|
+
|
|
20
|
+
function writeFixture(source: string, namespace: string): void {
|
|
21
|
+
const result = compile(source, { namespace })
|
|
22
|
+
let fileCount = 0
|
|
23
|
+
for (const file of result.files ?? []) {
|
|
24
|
+
const filePath = path.join(DATAPACK_DIR, file.path)
|
|
25
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
26
|
+
fs.writeFileSync(filePath, file.content)
|
|
27
|
+
fileCount++
|
|
28
|
+
}
|
|
29
|
+
console.log(` ✓ ${namespace} (${fileCount} files)`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
console.log(`Setting up MC test fixtures in:\n ${DATAPACK_DIR}\n`)
|
|
34
|
+
fs.mkdirSync(DATAPACK_DIR, { recursive: true })
|
|
35
|
+
|
|
36
|
+
// Example files
|
|
37
|
+
const exampleNamespaces = ['counter', 'world_manager']
|
|
38
|
+
for (const ns of exampleNamespaces) {
|
|
39
|
+
const file = path.join(EXAMPLES_DIR, `${ns}.rs`)
|
|
40
|
+
if (fs.existsSync(file)) {
|
|
41
|
+
writeFixture(fs.readFileSync(file, 'utf-8'), ns)
|
|
42
|
+
} else {
|
|
43
|
+
console.log(` ⚠ ${ns}.rs not found, skipping`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Inline test fixtures
|
|
48
|
+
writeFixture(`
|
|
49
|
+
@tick
|
|
50
|
+
fn on_tick() {
|
|
51
|
+
scoreboard_set("#tick_counter", "ticks", scoreboard_get("#tick_counter", "ticks") + 1)
|
|
52
|
+
}
|
|
53
|
+
`, 'tick_test')
|
|
54
|
+
|
|
55
|
+
writeFixture(`
|
|
56
|
+
fn check_score() {
|
|
57
|
+
let x: int = scoreboard_get("#check_x", "test_score")
|
|
58
|
+
if (x > 5) {
|
|
59
|
+
scoreboard_set("#check_x", "result", 1)
|
|
60
|
+
} else {
|
|
61
|
+
scoreboard_set("#check_x", "result", 0)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`, 'inline_test')
|
|
65
|
+
|
|
66
|
+
console.log('\n✅ All fixtures written. Restart the MC server to load them.')
|
|
67
|
+
console.log(' Then run: MC_SERVER_DIR=... npx jest mc-integration --testTimeout=60000')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main()
|