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.
Files changed (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. 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()