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,1288 @@
1
+ "use strict";
2
+ /**
3
+ * MCRuntime - Minecraft Command Runtime Simulator
4
+ *
5
+ * A TypeScript interpreter that simulates the subset of MC commands that
6
+ * RedScript generates, allowing behavioral testing without a real server.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.MCRuntime = void 0;
43
+ exports.parseRange = parseRange;
44
+ exports.matchesRange = matchesRange;
45
+ exports.parseSelector = parseSelector;
46
+ const compile_1 = require("../compile");
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ // ---------------------------------------------------------------------------
50
+ // Selector & Range Parsing
51
+ // ---------------------------------------------------------------------------
52
+ function parseRange(s) {
53
+ if (s.includes('..')) {
54
+ const [left, right] = s.split('..');
55
+ return {
56
+ min: left === '' ? -Infinity : parseInt(left, 10),
57
+ max: right === '' ? Infinity : parseInt(right, 10),
58
+ };
59
+ }
60
+ const val = parseInt(s, 10);
61
+ return { min: val, max: val };
62
+ }
63
+ function matchesRange(value, range) {
64
+ return value >= range.min && value <= range.max;
65
+ }
66
+ function canonicalEntityType(entityType) {
67
+ return entityType.includes(':') ? entityType : `minecraft:${entityType}`;
68
+ }
69
+ function parseFilters(content) {
70
+ const filters = {
71
+ tag: [],
72
+ notTag: [],
73
+ type: [],
74
+ notType: [],
75
+ };
76
+ if (!content)
77
+ return filters;
78
+ // Handle scores={...} separately
79
+ let processed = content;
80
+ const scoresMatch = content.match(/scores=\{([^}]*)\}/);
81
+ if (scoresMatch) {
82
+ filters.scores = new Map();
83
+ const scoresPart = scoresMatch[1];
84
+ const scoreEntries = scoresPart.split(',');
85
+ for (const entry of scoreEntries) {
86
+ const [obj, range] = entry.split('=');
87
+ if (obj && range) {
88
+ filters.scores.set(obj.trim(), parseRange(range.trim()));
89
+ }
90
+ }
91
+ processed = content.replace(/,?scores=\{[^}]*\},?/, ',').replace(/^,|,$/g, '');
92
+ }
93
+ // Parse remaining filters
94
+ const parts = processed.split(',').filter(p => p.trim());
95
+ for (const part of parts) {
96
+ const [key, value] = part.split('=').map(s => s.trim());
97
+ if (key === 'tag') {
98
+ if (value.startsWith('!')) {
99
+ filters.notTag.push(value.slice(1));
100
+ }
101
+ else {
102
+ filters.tag.push(value);
103
+ }
104
+ }
105
+ else if (key === 'type') {
106
+ if (value.startsWith('!')) {
107
+ filters.notType.push(value.slice(1));
108
+ }
109
+ else {
110
+ filters.type.push(value);
111
+ }
112
+ }
113
+ else if (key === 'limit') {
114
+ filters.limit = parseInt(value, 10);
115
+ }
116
+ }
117
+ return filters;
118
+ }
119
+ function matchesFilters(entity, filters, objective = 'rs') {
120
+ // Check required tags
121
+ for (const tag of filters.tag || []) {
122
+ if (!entity.tags.has(tag))
123
+ return false;
124
+ }
125
+ // Check excluded tags
126
+ for (const notTag of filters.notTag || []) {
127
+ if (entity.tags.has(notTag))
128
+ return false;
129
+ }
130
+ // Check types
131
+ if ((filters.type?.length ?? 0) > 0) {
132
+ const entityType = canonicalEntityType(entity.type ?? 'minecraft:armor_stand');
133
+ const allowedTypes = filters.type.map(canonicalEntityType);
134
+ if (!allowedTypes.includes(entityType)) {
135
+ return false;
136
+ }
137
+ }
138
+ for (const notType of filters.notType || []) {
139
+ const entityType = canonicalEntityType(entity.type ?? 'minecraft:armor_stand');
140
+ if (canonicalEntityType(notType) === entityType) {
141
+ return false;
142
+ }
143
+ }
144
+ // Check scores
145
+ if (filters.scores) {
146
+ for (const [obj, range] of filters.scores) {
147
+ const score = entity.scores.get(obj) ?? 0;
148
+ if (!matchesRange(score, range))
149
+ return false;
150
+ }
151
+ }
152
+ return true;
153
+ }
154
+ function parseSelector(sel, entities, executor) {
155
+ // Handle @s
156
+ if (sel === '@s') {
157
+ return executor ? [executor] : [];
158
+ }
159
+ // Handle bare selectors
160
+ if (sel === '@e' || sel === '@a') {
161
+ return [...entities];
162
+ }
163
+ // Parse selector with brackets
164
+ const match = sel.match(/^(@[eaps])(?:\[(.*)\])?$/);
165
+ if (!match) {
166
+ return [];
167
+ }
168
+ const [, selectorType, bracketContent] = match;
169
+ // @s with filters
170
+ if (selectorType === '@s') {
171
+ if (!executor)
172
+ return [];
173
+ const filters = parseFilters(bracketContent || '');
174
+ if (matchesFilters(executor, filters)) {
175
+ return [executor];
176
+ }
177
+ return [];
178
+ }
179
+ // @e/@a with filters
180
+ const filters = parseFilters(bracketContent || '');
181
+ let result = entities.filter(e => matchesFilters(e, filters));
182
+ // Apply limit
183
+ if (filters.limit !== undefined) {
184
+ result = result.slice(0, filters.limit);
185
+ }
186
+ return result;
187
+ }
188
+ // ---------------------------------------------------------------------------
189
+ // JSON Component Parsing
190
+ // ---------------------------------------------------------------------------
191
+ // ---------------------------------------------------------------------------
192
+ // NBT Parsing
193
+ // ---------------------------------------------------------------------------
194
+ function parseNBT(nbt) {
195
+ // Simple NBT parser for Tags array
196
+ const result = {};
197
+ const tagsMatch = nbt.match(/Tags:\s*\[(.*?)\]/);
198
+ if (tagsMatch) {
199
+ const tagsStr = tagsMatch[1];
200
+ result.Tags = tagsStr
201
+ .split(',')
202
+ .map(s => s.trim().replace(/^["']|["']$/g, ''))
203
+ .filter(s => s.length > 0);
204
+ }
205
+ return result;
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // MCRuntime Class
209
+ // ---------------------------------------------------------------------------
210
+ class MCRuntime {
211
+ constructor(namespace) {
212
+ // Scoreboard state: objective → (player → score)
213
+ this.scoreboard = new Map();
214
+ // NBT storage: "namespace:path" → JSON value
215
+ this.storage = new Map();
216
+ // Entities in world
217
+ this.entities = [];
218
+ // Loaded functions: "ns:name" → lines of mcfunction
219
+ this.functions = new Map();
220
+ // Log of say/tellraw/title output
221
+ this.chatLog = [];
222
+ // Simple world state: "x,y,z" -> block id
223
+ this.world = new Map();
224
+ // Current weather
225
+ this.weather = 'clear';
226
+ // Current world time
227
+ this.worldTime = 0;
228
+ // Active potion effects by entity id
229
+ this.effects = new Map();
230
+ // XP values by player/entity id
231
+ this.xp = new Map();
232
+ // Tick counter
233
+ this.tickCount = 0;
234
+ // Entity ID counter
235
+ this.entityIdCounter = 0;
236
+ // Flag to stop function execution (for return)
237
+ this.shouldReturn = false;
238
+ this.namespace = namespace;
239
+ // Initialize default objective
240
+ this.scoreboard.set('rs', new Map());
241
+ }
242
+ // -------------------------------------------------------------------------
243
+ // Datapack Loading
244
+ // -------------------------------------------------------------------------
245
+ loadDatapack(dir) {
246
+ const functionsDir = path.join(dir, 'data', this.namespace, 'function');
247
+ if (!fs.existsSync(functionsDir))
248
+ return;
249
+ const loadFunctions = (base, prefix) => {
250
+ const entries = fs.readdirSync(base, { withFileTypes: true });
251
+ for (const entry of entries) {
252
+ const fullPath = path.join(base, entry.name);
253
+ if (entry.isDirectory()) {
254
+ loadFunctions(fullPath, `${prefix}${entry.name}/`);
255
+ }
256
+ else if (entry.name.endsWith('.mcfunction')) {
257
+ const fnName = `${prefix}${entry.name.replace('.mcfunction', '')}`;
258
+ const content = fs.readFileSync(fullPath, 'utf-8');
259
+ this.loadFunction(`${this.namespace}:${fnName}`, content.split('\n'));
260
+ }
261
+ }
262
+ };
263
+ loadFunctions(functionsDir, '');
264
+ }
265
+ loadFunction(name, lines) {
266
+ // Filter out comments and empty lines, but keep all commands
267
+ const cleaned = lines
268
+ .map(l => l.trim())
269
+ .filter(l => l && !l.startsWith('#'));
270
+ this.functions.set(name, cleaned);
271
+ }
272
+ // -------------------------------------------------------------------------
273
+ // Lifecycle Methods
274
+ // -------------------------------------------------------------------------
275
+ load() {
276
+ const loadFn = `${this.namespace}:__load`;
277
+ if (this.functions.has(loadFn)) {
278
+ this.execFunction(loadFn);
279
+ }
280
+ }
281
+ tick() {
282
+ this.tickCount++;
283
+ const tickFn = `${this.namespace}:__tick`;
284
+ if (this.functions.has(tickFn)) {
285
+ this.execFunction(tickFn);
286
+ }
287
+ }
288
+ ticks(n) {
289
+ for (let i = 0; i < n; i++) {
290
+ this.tick();
291
+ }
292
+ }
293
+ // -------------------------------------------------------------------------
294
+ // Function Execution
295
+ // -------------------------------------------------------------------------
296
+ execFunction(name, executor) {
297
+ const lines = this.functions.get(name);
298
+ if (!lines) {
299
+ // Try with namespace prefix
300
+ const prefixedName = name.includes(':') ? name : `${this.namespace}:${name}`;
301
+ const prefixedLines = this.functions.get(prefixedName);
302
+ if (!prefixedLines)
303
+ return;
304
+ this.execFunctionLines(prefixedLines, executor);
305
+ return;
306
+ }
307
+ this.execFunctionLines(lines, executor);
308
+ }
309
+ execFunctionLines(lines, executor) {
310
+ this.shouldReturn = false;
311
+ for (const line of lines) {
312
+ if (this.shouldReturn)
313
+ break;
314
+ this.execCommand(line, executor);
315
+ }
316
+ }
317
+ // -------------------------------------------------------------------------
318
+ // Command Execution
319
+ // -------------------------------------------------------------------------
320
+ execCommand(cmd, executor) {
321
+ cmd = cmd.trim();
322
+ if (!cmd || cmd.startsWith('#'))
323
+ return true;
324
+ // Parse command
325
+ if (cmd.startsWith('scoreboard ')) {
326
+ return this.execScoreboard(cmd);
327
+ }
328
+ if (cmd.startsWith('execute ')) {
329
+ return this.execExecute(cmd, executor);
330
+ }
331
+ if (cmd.startsWith('function ')) {
332
+ return this.execFunctionCmd(cmd, executor);
333
+ }
334
+ if (cmd.startsWith('data ')) {
335
+ return this.execData(cmd);
336
+ }
337
+ if (cmd.startsWith('tag ')) {
338
+ return this.execTag(cmd, executor);
339
+ }
340
+ if (cmd.startsWith('say ')) {
341
+ return this.execSay(cmd, executor);
342
+ }
343
+ if (cmd.startsWith('tellraw ')) {
344
+ return this.execTellraw(cmd);
345
+ }
346
+ if (cmd.startsWith('title ')) {
347
+ return this.execTitle(cmd);
348
+ }
349
+ if (cmd.startsWith('setblock ')) {
350
+ return this.execSetblock(cmd);
351
+ }
352
+ if (cmd.startsWith('fill ')) {
353
+ return this.execFill(cmd);
354
+ }
355
+ if (cmd.startsWith('tp ')) {
356
+ return this.execTp(cmd, executor);
357
+ }
358
+ if (cmd.startsWith('weather ')) {
359
+ return this.execWeather(cmd);
360
+ }
361
+ if (cmd.startsWith('time ')) {
362
+ return this.execTime(cmd);
363
+ }
364
+ if (cmd.startsWith('kill ')) {
365
+ return this.execKill(cmd, executor);
366
+ }
367
+ if (cmd.startsWith('effect ')) {
368
+ return this.execEffect(cmd, executor);
369
+ }
370
+ if (cmd.startsWith('xp ')) {
371
+ return this.execXp(cmd, executor);
372
+ }
373
+ if (cmd.startsWith('summon ')) {
374
+ return this.execSummon(cmd);
375
+ }
376
+ if (cmd.startsWith('return ')) {
377
+ return this.execReturn(cmd, executor);
378
+ }
379
+ if (cmd === 'return') {
380
+ this.shouldReturn = true;
381
+ return true;
382
+ }
383
+ // Unknown command - succeed silently
384
+ return true;
385
+ }
386
+ // -------------------------------------------------------------------------
387
+ // Scoreboard Commands
388
+ // -------------------------------------------------------------------------
389
+ execScoreboard(cmd) {
390
+ const parts = cmd.split(/\s+/);
391
+ // scoreboard objectives add <name> <criteria>
392
+ if (parts[1] === 'objectives' && parts[2] === 'add') {
393
+ const name = parts[3];
394
+ if (!this.scoreboard.has(name)) {
395
+ this.scoreboard.set(name, new Map());
396
+ }
397
+ return true;
398
+ }
399
+ // scoreboard players ...
400
+ if (parts[1] === 'players') {
401
+ const action = parts[2];
402
+ const player = parts[3];
403
+ const objective = parts[4];
404
+ switch (action) {
405
+ case 'set': {
406
+ const value = parseInt(parts[5], 10);
407
+ this.setScore(player, objective, value);
408
+ return true;
409
+ }
410
+ case 'add': {
411
+ const delta = parseInt(parts[5], 10);
412
+ this.addScore(player, objective, delta);
413
+ return true;
414
+ }
415
+ case 'remove': {
416
+ const delta = parseInt(parts[5], 10);
417
+ this.addScore(player, objective, -delta);
418
+ return true;
419
+ }
420
+ case 'get': {
421
+ this.returnValue = this.getScore(player, objective);
422
+ return true;
423
+ }
424
+ case 'reset': {
425
+ const obj = this.scoreboard.get(objective);
426
+ if (obj)
427
+ obj.delete(player);
428
+ return true;
429
+ }
430
+ case 'enable': {
431
+ // No-op for trigger enabling
432
+ return true;
433
+ }
434
+ case 'operation': {
435
+ // scoreboard players operation <target> <targetObj> <op> <source> <sourceObj>
436
+ const targetObj = objective;
437
+ const op = parts[5];
438
+ const source = parts[6];
439
+ const sourceObj = parts[7];
440
+ const targetVal = this.getScore(player, targetObj);
441
+ const sourceVal = this.getScore(source, sourceObj);
442
+ let result;
443
+ switch (op) {
444
+ case '=':
445
+ result = sourceVal;
446
+ break;
447
+ case '+=':
448
+ result = targetVal + sourceVal;
449
+ break;
450
+ case '-=':
451
+ result = targetVal - sourceVal;
452
+ break;
453
+ case '*=':
454
+ result = targetVal * sourceVal;
455
+ break;
456
+ case '/=':
457
+ result = Math.trunc(targetVal / sourceVal);
458
+ break;
459
+ case '%=':
460
+ result = targetVal % sourceVal; // Java modulo: sign follows dividend
461
+ break;
462
+ case '<':
463
+ result = Math.min(targetVal, sourceVal);
464
+ break;
465
+ case '>':
466
+ result = Math.max(targetVal, sourceVal);
467
+ break;
468
+ case '><':
469
+ // Swap
470
+ this.setScore(player, targetObj, sourceVal);
471
+ this.setScore(source, sourceObj, targetVal);
472
+ return true;
473
+ default:
474
+ return false;
475
+ }
476
+ this.setScore(player, targetObj, result);
477
+ return true;
478
+ }
479
+ }
480
+ }
481
+ return false;
482
+ }
483
+ // -------------------------------------------------------------------------
484
+ // Execute Commands
485
+ // -------------------------------------------------------------------------
486
+ execExecute(cmd, executor) {
487
+ // Remove 'execute ' prefix
488
+ let rest = cmd.slice(8);
489
+ // Track execute state
490
+ let currentExecutor = executor;
491
+ let condition = true;
492
+ let storeTarget = null;
493
+ while (rest.length > 0) {
494
+ rest = rest.trimStart();
495
+ // Handle 'run' - execute the final command
496
+ if (rest.startsWith('run ')) {
497
+ if (!condition)
498
+ return false;
499
+ const innerCmd = rest.slice(4);
500
+ const result = this.execCommand(innerCmd, currentExecutor);
501
+ if (storeTarget) {
502
+ const value = storeTarget.type === 'result'
503
+ ? (this.returnValue ?? (result ? 1 : 0))
504
+ : (result ? 1 : 0);
505
+ this.setScore(storeTarget.player, storeTarget.objective, value);
506
+ }
507
+ return result;
508
+ }
509
+ // Handle 'as <selector>'
510
+ if (rest.startsWith('as ')) {
511
+ rest = rest.slice(3);
512
+ const { selector, remaining } = this.parseNextSelector(rest);
513
+ rest = remaining;
514
+ const entities = parseSelector(selector, this.entities, currentExecutor);
515
+ if (entities.length === 0)
516
+ return false;
517
+ // For multiple entities, execute as each
518
+ if (entities.length > 1) {
519
+ let success = false;
520
+ for (const entity of entities) {
521
+ const result = this.execCommand('execute ' + rest, entity);
522
+ success = success || result;
523
+ }
524
+ return success;
525
+ }
526
+ currentExecutor = entities[0];
527
+ continue;
528
+ }
529
+ // Handle 'at <selector>' - no-op for position, just continue
530
+ if (rest.startsWith('at ')) {
531
+ rest = rest.slice(3);
532
+ const { remaining } = this.parseNextSelector(rest);
533
+ rest = remaining;
534
+ continue;
535
+ }
536
+ // Handle 'if score <player> <obj> matches <range>'
537
+ if (rest.startsWith('if score ')) {
538
+ rest = rest.slice(9);
539
+ const scoreParts = rest.match(/^(\S+)\s+(\S+)\s+matches\s+(\S+)(.*)$/);
540
+ if (scoreParts) {
541
+ const [, player, obj, rangeStr, remaining] = scoreParts;
542
+ const range = parseRange(rangeStr);
543
+ const score = this.getScore(player, obj);
544
+ condition = condition && matchesRange(score, range);
545
+ rest = remaining.trim();
546
+ continue;
547
+ }
548
+ // if score <p1> <o1> <op> <p2> <o2>
549
+ const compareMatch = rest.match(/^(\S+)\s+(\S+)\s+([<>=]+)\s+(\S+)\s+(\S+)(.*)$/);
550
+ if (compareMatch) {
551
+ const [, p1, o1, op, p2, o2, remaining] = compareMatch;
552
+ const v1 = this.getScore(p1, o1);
553
+ const v2 = this.getScore(p2, o2);
554
+ let matches = false;
555
+ switch (op) {
556
+ case '=':
557
+ matches = v1 === v2;
558
+ break;
559
+ case '<':
560
+ matches = v1 < v2;
561
+ break;
562
+ case '<=':
563
+ matches = v1 <= v2;
564
+ break;
565
+ case '>':
566
+ matches = v1 > v2;
567
+ break;
568
+ case '>=':
569
+ matches = v1 >= v2;
570
+ break;
571
+ }
572
+ condition = condition && matches;
573
+ rest = remaining.trim();
574
+ continue;
575
+ }
576
+ }
577
+ // Handle 'unless score ...'
578
+ if (rest.startsWith('unless score ')) {
579
+ rest = rest.slice(13);
580
+ const scoreParts = rest.match(/^(\S+)\s+(\S+)\s+matches\s+(\S+)(.*)$/);
581
+ if (scoreParts) {
582
+ const [, player, obj, rangeStr, remaining] = scoreParts;
583
+ const range = parseRange(rangeStr);
584
+ const score = this.getScore(player, obj);
585
+ condition = condition && !matchesRange(score, range);
586
+ rest = remaining.trim();
587
+ continue;
588
+ }
589
+ }
590
+ // Handle 'if entity <selector>'
591
+ if (rest.startsWith('if entity ')) {
592
+ rest = rest.slice(10);
593
+ const { selector, remaining } = this.parseNextSelector(rest);
594
+ rest = remaining;
595
+ const entities = parseSelector(selector, this.entities, currentExecutor);
596
+ condition = condition && entities.length > 0;
597
+ continue;
598
+ }
599
+ // Handle 'unless entity <selector>'
600
+ if (rest.startsWith('unless entity ')) {
601
+ rest = rest.slice(14);
602
+ const { selector, remaining } = this.parseNextSelector(rest);
603
+ rest = remaining;
604
+ const entities = parseSelector(selector, this.entities, currentExecutor);
605
+ condition = condition && entities.length === 0;
606
+ continue;
607
+ }
608
+ // Handle 'store result score <player> <obj>'
609
+ if (rest.startsWith('store result score ')) {
610
+ rest = rest.slice(19);
611
+ const storeParts = rest.match(/^(\S+)\s+(\S+)(.*)$/);
612
+ if (storeParts) {
613
+ const [, player, obj, remaining] = storeParts;
614
+ storeTarget = { player, objective: obj, type: 'result' };
615
+ rest = remaining.trim();
616
+ continue;
617
+ }
618
+ }
619
+ // Handle 'store success score <player> <obj>'
620
+ if (rest.startsWith('store success score ')) {
621
+ rest = rest.slice(20);
622
+ const storeParts = rest.match(/^(\S+)\s+(\S+)(.*)$/);
623
+ if (storeParts) {
624
+ const [, player, obj, remaining] = storeParts;
625
+ storeTarget = { player, objective: obj, type: 'success' };
626
+ rest = remaining.trim();
627
+ continue;
628
+ }
629
+ }
630
+ // Unknown subcommand - skip to next space or 'run'
631
+ const nextSpace = rest.indexOf(' ');
632
+ if (nextSpace === -1)
633
+ break;
634
+ rest = rest.slice(nextSpace + 1);
635
+ }
636
+ if (storeTarget) {
637
+ const value = storeTarget.type === 'result'
638
+ ? (this.returnValue ?? (condition ? 1 : 0))
639
+ : (condition ? 1 : 0);
640
+ this.setScore(storeTarget.player, storeTarget.objective, value);
641
+ }
642
+ return condition;
643
+ }
644
+ parseNextSelector(input) {
645
+ input = input.trimStart();
646
+ const match = input.match(/^(@[eaps])(\[[^\]]*\])?/);
647
+ if (match) {
648
+ const selector = match[0];
649
+ return { selector, remaining: input.slice(selector.length).trim() };
650
+ }
651
+ // Non-selector target
652
+ const spaceIdx = input.indexOf(' ');
653
+ if (spaceIdx === -1) {
654
+ return { selector: input, remaining: '' };
655
+ }
656
+ return { selector: input.slice(0, spaceIdx), remaining: input.slice(spaceIdx + 1) };
657
+ }
658
+ // -------------------------------------------------------------------------
659
+ // Function Command
660
+ // -------------------------------------------------------------------------
661
+ execFunctionCmd(cmd, executor) {
662
+ const fnName = cmd.slice(9).trim(); // remove 'function '
663
+ const outerShouldReturn = this.shouldReturn;
664
+ this.execFunction(fnName, executor);
665
+ this.shouldReturn = outerShouldReturn;
666
+ return true;
667
+ }
668
+ // -------------------------------------------------------------------------
669
+ // Data Commands
670
+ // -------------------------------------------------------------------------
671
+ execData(cmd) {
672
+ // data modify storage <ns:path> <field> set value <val>
673
+ const setMatch = cmd.match(/^data modify storage (\S+) (\S+) set value (.+)$/);
674
+ if (setMatch) {
675
+ const [, storagePath, field, valueStr] = setMatch;
676
+ const value = this.parseDataValue(valueStr);
677
+ this.setStorageField(storagePath, field, value);
678
+ return true;
679
+ }
680
+ // data modify storage <ns:path> <field> append value <val>
681
+ const appendMatch = cmd.match(/^data modify storage (\S+) (\S+) append value (.+)$/);
682
+ if (appendMatch) {
683
+ const [, storagePath, field, valueStr] = appendMatch;
684
+ const value = this.parseDataValue(valueStr);
685
+ const current = this.getStorageField(storagePath, field) ?? [];
686
+ if (Array.isArray(current)) {
687
+ current.push(value);
688
+ this.setStorageField(storagePath, field, current);
689
+ }
690
+ return true;
691
+ }
692
+ // data get storage <ns:path> <field>
693
+ const getMatch = cmd.match(/^data get storage (\S+) (\S+)$/);
694
+ if (getMatch) {
695
+ const [, storagePath, field] = getMatch;
696
+ const value = this.getStorageField(storagePath, field);
697
+ if (typeof value === 'number') {
698
+ this.returnValue = value;
699
+ }
700
+ else if (Array.isArray(value)) {
701
+ this.returnValue = value.length;
702
+ }
703
+ else {
704
+ this.returnValue = value ? 1 : 0;
705
+ }
706
+ return true;
707
+ }
708
+ // data modify storage <ns:path> <field> set from storage <src> <srcpath>
709
+ const copyMatch = cmd.match(/^data modify storage (\S+) (\S+) set from storage (\S+) (\S+)$/);
710
+ if (copyMatch) {
711
+ const [, dstPath, dstField, srcPath, srcField] = copyMatch;
712
+ const value = this.getStorageField(srcPath, srcField);
713
+ this.setStorageField(dstPath, dstField, value);
714
+ return true;
715
+ }
716
+ // data remove storage <ns:path> <field>
717
+ const removeMatch = cmd.match(/^data remove storage (\S+) (\S+)$/);
718
+ if (removeMatch) {
719
+ const [, storagePath, field] = removeMatch;
720
+ return this.removeStorageField(storagePath, field);
721
+ }
722
+ return false;
723
+ }
724
+ parseDataValue(str) {
725
+ str = str.trim();
726
+ // Try JSON parse
727
+ try {
728
+ return JSON.parse(str);
729
+ }
730
+ catch {
731
+ // Try numeric
732
+ const num = parseFloat(str);
733
+ if (!isNaN(num))
734
+ return num;
735
+ // Return as string
736
+ return str;
737
+ }
738
+ }
739
+ getStorageField(storagePath, field) {
740
+ const data = this.storage.get(storagePath) ?? {};
741
+ const segments = this.parseStoragePath(field);
742
+ let current = data;
743
+ for (const segment of segments) {
744
+ if (typeof segment === 'number') {
745
+ if (!Array.isArray(current))
746
+ return undefined;
747
+ const index = segment < 0 ? current.length + segment : segment;
748
+ current = current[index];
749
+ continue;
750
+ }
751
+ if (current == null || typeof current !== 'object')
752
+ return undefined;
753
+ current = current[segment];
754
+ }
755
+ return current;
756
+ }
757
+ setStorageField(storagePath, field, value) {
758
+ let data = this.storage.get(storagePath);
759
+ if (!data) {
760
+ data = {};
761
+ this.storage.set(storagePath, data);
762
+ }
763
+ const segments = this.parseStoragePath(field);
764
+ let current = data;
765
+ for (let i = 0; i < segments.length - 1; i++) {
766
+ const segment = segments[i];
767
+ const next = segments[i + 1];
768
+ if (typeof segment === 'number') {
769
+ if (!Array.isArray(current))
770
+ return;
771
+ const index = segment < 0 ? current.length + segment : segment;
772
+ if (current[index] === undefined) {
773
+ current[index] = typeof next === 'number' ? [] : {};
774
+ }
775
+ current = current[index];
776
+ continue;
777
+ }
778
+ if (!(segment in current)) {
779
+ current[segment] = typeof next === 'number' ? [] : {};
780
+ }
781
+ current = current[segment];
782
+ }
783
+ const last = segments[segments.length - 1];
784
+ if (typeof last === 'number') {
785
+ if (!Array.isArray(current))
786
+ return;
787
+ const index = last < 0 ? current.length + last : last;
788
+ current[index] = value;
789
+ return;
790
+ }
791
+ current[last] = value;
792
+ }
793
+ removeStorageField(storagePath, field) {
794
+ const data = this.storage.get(storagePath);
795
+ if (!data)
796
+ return false;
797
+ const segments = this.parseStoragePath(field);
798
+ let current = data;
799
+ for (let i = 0; i < segments.length - 1; i++) {
800
+ const segment = segments[i];
801
+ if (typeof segment === 'number') {
802
+ if (!Array.isArray(current))
803
+ return false;
804
+ const index = segment < 0 ? current.length + segment : segment;
805
+ current = current[index];
806
+ }
807
+ else {
808
+ current = current?.[segment];
809
+ }
810
+ if (current === undefined)
811
+ return false;
812
+ }
813
+ const last = segments[segments.length - 1];
814
+ if (typeof last === 'number') {
815
+ if (!Array.isArray(current))
816
+ return false;
817
+ const index = last < 0 ? current.length + last : last;
818
+ if (index < 0 || index >= current.length)
819
+ return false;
820
+ current.splice(index, 1);
821
+ return true;
822
+ }
823
+ if (current == null || typeof current !== 'object' || !(last in current))
824
+ return false;
825
+ delete current[last];
826
+ return true;
827
+ }
828
+ parseStoragePath(field) {
829
+ return field
830
+ .split('.')
831
+ .flatMap(part => {
832
+ const segments = [];
833
+ const regex = /([^\[\]]+)|\[(-?\d+)\]/g;
834
+ for (const match of part.matchAll(regex)) {
835
+ if (match[1])
836
+ segments.push(match[1]);
837
+ if (match[2])
838
+ segments.push(parseInt(match[2], 10));
839
+ }
840
+ return segments;
841
+ });
842
+ }
843
+ // -------------------------------------------------------------------------
844
+ // Tag Commands
845
+ // -------------------------------------------------------------------------
846
+ execTag(cmd, executor) {
847
+ // tag <selector> add <name>
848
+ const addMatch = cmd.match(/^tag (\S+) add (\S+)$/);
849
+ if (addMatch) {
850
+ const [, selStr, tagName] = addMatch;
851
+ const entities = selStr === '@s' && executor
852
+ ? [executor]
853
+ : parseSelector(selStr, this.entities, executor);
854
+ for (const entity of entities) {
855
+ entity.tags.add(tagName);
856
+ }
857
+ return entities.length > 0;
858
+ }
859
+ // tag <selector> remove <name>
860
+ const removeMatch = cmd.match(/^tag (\S+) remove (\S+)$/);
861
+ if (removeMatch) {
862
+ const [, selStr, tagName] = removeMatch;
863
+ const entities = selStr === '@s' && executor
864
+ ? [executor]
865
+ : parseSelector(selStr, this.entities, executor);
866
+ for (const entity of entities) {
867
+ entity.tags.delete(tagName);
868
+ }
869
+ return entities.length > 0;
870
+ }
871
+ return false;
872
+ }
873
+ // -------------------------------------------------------------------------
874
+ // Say/Tellraw/Title Commands
875
+ // -------------------------------------------------------------------------
876
+ execSay(cmd, executor) {
877
+ const message = cmd.slice(4);
878
+ this.chatLog.push(`[${executor?.id ?? 'Server'}] ${message}`);
879
+ return true;
880
+ }
881
+ execTellraw(cmd) {
882
+ // tellraw <selector> <json>
883
+ const match = cmd.match(/^tellraw \S+ (.+)$/);
884
+ if (match) {
885
+ const jsonStr = match[1];
886
+ const text = this.extractJsonText(jsonStr);
887
+ this.chatLog.push(text);
888
+ return true;
889
+ }
890
+ return false;
891
+ }
892
+ execTitle(cmd) {
893
+ // title <selector> <kind> <json>
894
+ const match = cmd.match(/^title \S+ (actionbar|title|subtitle) (.+)$/);
895
+ if (match) {
896
+ const [, kind, jsonStr] = match;
897
+ const text = this.extractJsonText(jsonStr);
898
+ this.chatLog.push(`[${kind.toUpperCase()}] ${text}`);
899
+ return true;
900
+ }
901
+ return false;
902
+ }
903
+ extractJsonText(json) {
904
+ if (typeof json === 'string') {
905
+ try {
906
+ json = JSON.parse(json);
907
+ }
908
+ catch {
909
+ return json;
910
+ }
911
+ }
912
+ if (typeof json === 'string')
913
+ return json;
914
+ if (Array.isArray(json)) {
915
+ return json.map(part => this.extractJsonText(part)).join('');
916
+ }
917
+ if (typeof json === 'object' && json !== null) {
918
+ if ('text' in json)
919
+ return String(json.text);
920
+ if ('score' in json && typeof json.score === 'object' && json.score !== null) {
921
+ const name = 'name' in json.score ? String(json.score.name) : '';
922
+ const objective = 'objective' in json.score ? String(json.score.objective) : 'rs';
923
+ return String(this.getScore(name, objective));
924
+ }
925
+ if ('extra' in json && Array.isArray(json.extra)) {
926
+ return json.extra.map((part) => this.extractJsonText(part)).join('');
927
+ }
928
+ }
929
+ return '';
930
+ }
931
+ // -------------------------------------------------------------------------
932
+ // World Commands
933
+ // -------------------------------------------------------------------------
934
+ execSetblock(cmd) {
935
+ const match = cmd.match(/^setblock (\S+) (\S+) (\S+) (\S+)$/);
936
+ if (!match)
937
+ return false;
938
+ const [, x, y, z, block] = match;
939
+ const key = this.positionKey(x, y, z);
940
+ if (!key)
941
+ return false;
942
+ this.world.set(key, block);
943
+ return true;
944
+ }
945
+ execFill(cmd) {
946
+ const match = cmd.match(/^fill (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+)$/);
947
+ if (!match)
948
+ return false;
949
+ const [, x1, y1, z1, x2, y2, z2, block] = match;
950
+ const start = this.parseAbsolutePosition(x1, y1, z1);
951
+ const end = this.parseAbsolutePosition(x2, y2, z2);
952
+ if (!start || !end)
953
+ return false;
954
+ const [minX, maxX] = [Math.min(start.x, end.x), Math.max(start.x, end.x)];
955
+ const [minY, maxY] = [Math.min(start.y, end.y), Math.max(start.y, end.y)];
956
+ const [minZ, maxZ] = [Math.min(start.z, end.z), Math.max(start.z, end.z)];
957
+ for (let x = minX; x <= maxX; x++) {
958
+ for (let y = minY; y <= maxY; y++) {
959
+ for (let z = minZ; z <= maxZ; z++) {
960
+ this.world.set(`${x},${y},${z}`, block);
961
+ }
962
+ }
963
+ }
964
+ return true;
965
+ }
966
+ execTp(cmd, executor) {
967
+ const selfCoordsMatch = cmd.match(/^tp (\S+) (\S+) (\S+)$/);
968
+ if (selfCoordsMatch && executor) {
969
+ const [, x, y, z] = selfCoordsMatch;
970
+ const next = this.resolvePosition(executor.position ?? { x: 0, y: 0, z: 0 }, x, y, z);
971
+ if (!next)
972
+ return false;
973
+ executor.position = next;
974
+ return true;
975
+ }
976
+ const coordsMatch = cmd.match(/^tp (\S+) (\S+) (\S+) (\S+)$/);
977
+ if (coordsMatch) {
978
+ const [, selStr, x, y, z] = coordsMatch;
979
+ const entities = selStr === '@s' && executor
980
+ ? [executor]
981
+ : parseSelector(selStr, this.entities, executor);
982
+ for (const entity of entities) {
983
+ const next = this.resolvePosition(entity.position ?? { x: 0, y: 0, z: 0 }, x, y, z);
984
+ if (next) {
985
+ entity.position = next;
986
+ }
987
+ }
988
+ return entities.length > 0;
989
+ }
990
+ const entityMatch = cmd.match(/^tp (\S+) (\S+)$/);
991
+ if (entityMatch) {
992
+ const [, selStr, targetStr] = entityMatch;
993
+ const entities = selStr === '@s' && executor
994
+ ? [executor]
995
+ : parseSelector(selStr, this.entities, executor);
996
+ const target = targetStr === '@s' && executor
997
+ ? executor
998
+ : parseSelector(targetStr, this.entities, executor)[0];
999
+ if (!target?.position)
1000
+ return false;
1001
+ for (const entity of entities) {
1002
+ entity.position = { ...target.position };
1003
+ }
1004
+ return entities.length > 0;
1005
+ }
1006
+ return false;
1007
+ }
1008
+ execWeather(cmd) {
1009
+ const match = cmd.match(/^weather (\S+)$/);
1010
+ if (!match)
1011
+ return false;
1012
+ this.weather = match[1];
1013
+ return true;
1014
+ }
1015
+ execTime(cmd) {
1016
+ const match = cmd.match(/^time (set|add) (\S+)$/);
1017
+ if (!match)
1018
+ return false;
1019
+ const [, action, valueStr] = match;
1020
+ const value = this.parseTimeValue(valueStr);
1021
+ if (value === null)
1022
+ return false;
1023
+ if (action === 'set') {
1024
+ this.worldTime = value;
1025
+ }
1026
+ else {
1027
+ this.worldTime += value;
1028
+ }
1029
+ return true;
1030
+ }
1031
+ // -------------------------------------------------------------------------
1032
+ // Kill Command
1033
+ // -------------------------------------------------------------------------
1034
+ execKill(cmd, executor) {
1035
+ const selStr = cmd.slice(5).trim();
1036
+ if (selStr === '@s' && executor) {
1037
+ this.entities = this.entities.filter(e => e !== executor);
1038
+ return true;
1039
+ }
1040
+ const entities = parseSelector(selStr, this.entities, executor);
1041
+ for (const entity of entities) {
1042
+ this.entities = this.entities.filter(e => e !== entity);
1043
+ }
1044
+ return entities.length > 0;
1045
+ }
1046
+ // -------------------------------------------------------------------------
1047
+ // Effect / XP Commands
1048
+ // -------------------------------------------------------------------------
1049
+ execEffect(cmd, executor) {
1050
+ const match = cmd.match(/^effect give (\S+) (\S+)(?: (\S+))?(?: (\S+))?(?: \S+)?$/);
1051
+ if (!match)
1052
+ return false;
1053
+ const [, selStr, effect, durationStr, amplifierStr] = match;
1054
+ const entities = selStr === '@s' && executor
1055
+ ? [executor]
1056
+ : parseSelector(selStr, this.entities, executor);
1057
+ const duration = durationStr ? parseInt(durationStr, 10) : 30;
1058
+ const amplifier = amplifierStr ? parseInt(amplifierStr, 10) : 0;
1059
+ for (const entity of entities) {
1060
+ const current = this.effects.get(entity.id) ?? [];
1061
+ current.push({ effect, duration: isNaN(duration) ? 30 : duration, amplifier: isNaN(amplifier) ? 0 : amplifier });
1062
+ this.effects.set(entity.id, current);
1063
+ }
1064
+ return entities.length > 0;
1065
+ }
1066
+ execXp(cmd, executor) {
1067
+ const match = cmd.match(/^xp (add|set) (\S+) (-?\d+)(?: (\S+))?$/);
1068
+ if (!match)
1069
+ return false;
1070
+ const [, action, target, amountStr] = match;
1071
+ const amount = parseInt(amountStr, 10);
1072
+ const keys = this.resolveTargetKeys(target, executor);
1073
+ if (keys.length === 0)
1074
+ return false;
1075
+ for (const key of keys) {
1076
+ const current = this.xp.get(key) ?? 0;
1077
+ this.xp.set(key, action === 'set' ? amount : current + amount);
1078
+ }
1079
+ return true;
1080
+ }
1081
+ // -------------------------------------------------------------------------
1082
+ // Summon Command
1083
+ // -------------------------------------------------------------------------
1084
+ execSummon(cmd) {
1085
+ // summon minecraft:armor_stand <x> <y> <z> {Tags:["tag1","tag2"]}
1086
+ const match = cmd.match(/^summon (\S+) (\S+) (\S+) (\S+) ({.+})$/);
1087
+ if (match) {
1088
+ const [, type, x, y, z, nbtStr] = match;
1089
+ const nbt = parseNBT(nbtStr);
1090
+ const position = this.parseAbsolutePosition(x, y, z) ?? { x: 0, y: 0, z: 0 };
1091
+ this.spawnEntity(nbt.Tags || [], type, position);
1092
+ return true;
1093
+ }
1094
+ // Simple summon without NBT
1095
+ const simpleMatch = cmd.match(/^summon (\S+)(?: (\S+) (\S+) (\S+))?$/);
1096
+ if (simpleMatch) {
1097
+ const [, type, x, y, z] = simpleMatch;
1098
+ const position = x && y && z
1099
+ ? (this.parseAbsolutePosition(x, y, z) ?? { x: 0, y: 0, z: 0 })
1100
+ : { x: 0, y: 0, z: 0 };
1101
+ this.spawnEntity([], type, position);
1102
+ return true;
1103
+ }
1104
+ return false;
1105
+ }
1106
+ // -------------------------------------------------------------------------
1107
+ // Return Command
1108
+ // -------------------------------------------------------------------------
1109
+ execReturn(cmd, executor) {
1110
+ const rest = cmd.slice(7).trim();
1111
+ // return run <cmd>
1112
+ if (rest.startsWith('run ')) {
1113
+ const innerCmd = rest.slice(4);
1114
+ this.execCommand(innerCmd, executor);
1115
+ this.shouldReturn = true;
1116
+ return true;
1117
+ }
1118
+ // return <value>
1119
+ const value = parseInt(rest, 10);
1120
+ if (!isNaN(value)) {
1121
+ this.returnValue = value;
1122
+ this.shouldReturn = true;
1123
+ return true;
1124
+ }
1125
+ return false;
1126
+ }
1127
+ // -------------------------------------------------------------------------
1128
+ // Scoreboard Helpers
1129
+ // -------------------------------------------------------------------------
1130
+ getScore(player, objective) {
1131
+ const obj = this.scoreboard.get(objective);
1132
+ if (!obj)
1133
+ return 0;
1134
+ return obj.get(player) ?? 0;
1135
+ }
1136
+ setScore(player, objective, value) {
1137
+ let obj = this.scoreboard.get(objective);
1138
+ if (!obj) {
1139
+ obj = new Map();
1140
+ this.scoreboard.set(objective, obj);
1141
+ }
1142
+ obj.set(player, value);
1143
+ }
1144
+ addScore(player, objective, delta) {
1145
+ const current = this.getScore(player, objective);
1146
+ this.setScore(player, objective, current + delta);
1147
+ }
1148
+ // -------------------------------------------------------------------------
1149
+ // Storage Helpers
1150
+ // -------------------------------------------------------------------------
1151
+ getStorage(path) {
1152
+ // "ns:path.field" → parse namespace and nested fields
1153
+ const colonIdx = path.indexOf(':');
1154
+ if (colonIdx === -1)
1155
+ return this.storage.get(path);
1156
+ const nsPath = path.slice(0, colonIdx + 1) + path.slice(colonIdx + 1).split('.')[0];
1157
+ const field = path.slice(colonIdx + 1).includes('.')
1158
+ ? path.slice(path.indexOf('.', colonIdx) + 1)
1159
+ : undefined;
1160
+ if (!field)
1161
+ return this.storage.get(nsPath);
1162
+ return this.getStorageField(nsPath, field);
1163
+ }
1164
+ setStorage(path, value) {
1165
+ const colonIdx = path.indexOf(':');
1166
+ if (colonIdx === -1) {
1167
+ this.storage.set(path, value);
1168
+ return;
1169
+ }
1170
+ const basePath = path.slice(0, colonIdx + 1) + path.slice(colonIdx + 1).split('.')[0];
1171
+ const field = path.slice(colonIdx + 1).includes('.')
1172
+ ? path.slice(path.indexOf('.', colonIdx) + 1)
1173
+ : undefined;
1174
+ if (!field) {
1175
+ this.storage.set(basePath, value);
1176
+ return;
1177
+ }
1178
+ this.setStorageField(basePath, field, value);
1179
+ }
1180
+ // -------------------------------------------------------------------------
1181
+ // Entity Helpers
1182
+ // -------------------------------------------------------------------------
1183
+ spawnEntity(tags, type = 'minecraft:armor_stand', position = { x: 0, y: 0, z: 0 }) {
1184
+ const id = `entity_${this.entityIdCounter++}`;
1185
+ const entity = {
1186
+ id,
1187
+ tags: new Set(tags),
1188
+ scores: new Map(),
1189
+ selector: `@e[tag=${tags[0] ?? id},limit=1]`,
1190
+ type,
1191
+ position,
1192
+ };
1193
+ this.entities.push(entity);
1194
+ return entity;
1195
+ }
1196
+ killEntity(tag) {
1197
+ this.entities = this.entities.filter(e => !e.tags.has(tag));
1198
+ }
1199
+ getEntities(selector) {
1200
+ return parseSelector(selector, this.entities);
1201
+ }
1202
+ positionKey(x, y, z) {
1203
+ const pos = this.parseAbsolutePosition(x, y, z);
1204
+ return pos ? `${pos.x},${pos.y},${pos.z}` : null;
1205
+ }
1206
+ parseAbsolutePosition(x, y, z) {
1207
+ const coords = [x, y, z].map(coord => {
1208
+ if (coord.startsWith('~') || coord.startsWith('^')) {
1209
+ const offset = coord.slice(1);
1210
+ return offset === '' ? 0 : parseInt(offset, 10);
1211
+ }
1212
+ return parseInt(coord, 10);
1213
+ });
1214
+ if (coords.some(Number.isNaN))
1215
+ return null;
1216
+ return { x: coords[0], y: coords[1], z: coords[2] };
1217
+ }
1218
+ resolvePosition(base, x, y, z) {
1219
+ const values = [x, y, z].map((coord, index) => {
1220
+ if (coord.startsWith('~') || coord.startsWith('^')) {
1221
+ const offset = coord.slice(1);
1222
+ const delta = offset === '' ? 0 : parseInt(offset, 10);
1223
+ return [base.x, base.y, base.z][index] + delta;
1224
+ }
1225
+ return parseInt(coord, 10);
1226
+ });
1227
+ if (values.some(Number.isNaN))
1228
+ return null;
1229
+ return { x: values[0], y: values[1], z: values[2] };
1230
+ }
1231
+ parseTimeValue(value) {
1232
+ if (/^-?\d+$/.test(value)) {
1233
+ return parseInt(value, 10);
1234
+ }
1235
+ const aliases = {
1236
+ day: 1000,
1237
+ noon: 6000,
1238
+ night: 13000,
1239
+ midnight: 18000,
1240
+ sunrise: 23000,
1241
+ };
1242
+ return aliases[value] ?? null;
1243
+ }
1244
+ resolveTargetKeys(target, executor) {
1245
+ if (target.startsWith('@')) {
1246
+ const entities = target === '@s' && executor
1247
+ ? [executor]
1248
+ : parseSelector(target, this.entities, executor);
1249
+ return entities.map(entity => entity.id);
1250
+ }
1251
+ return [target];
1252
+ }
1253
+ // -------------------------------------------------------------------------
1254
+ // Output Helpers
1255
+ // -------------------------------------------------------------------------
1256
+ getLastSaid() {
1257
+ return this.chatLog[this.chatLog.length - 1] ?? '';
1258
+ }
1259
+ getChatLog() {
1260
+ return [...this.chatLog];
1261
+ }
1262
+ // -------------------------------------------------------------------------
1263
+ // Convenience: Compile and Load
1264
+ // -------------------------------------------------------------------------
1265
+ compileAndLoad(source) {
1266
+ const result = (0, compile_1.compile)(source, { namespace: this.namespace });
1267
+ if (!result.success || !result.files) {
1268
+ throw new Error('Compilation failed');
1269
+ }
1270
+ // Load all .mcfunction files
1271
+ for (const file of result.files) {
1272
+ if (file.path.endsWith('.mcfunction')) {
1273
+ // Extract function name from path
1274
+ // e.g., "data/test/function/increment.mcfunction" → "test:increment"
1275
+ const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/);
1276
+ if (match) {
1277
+ const [, ns, fnPath] = match;
1278
+ const fnName = `${ns}:${fnPath.replace(/\//g, '/')}`;
1279
+ this.loadFunction(fnName, file.content.split('\n'));
1280
+ }
1281
+ }
1282
+ }
1283
+ // Run load function
1284
+ this.load();
1285
+ }
1286
+ }
1287
+ exports.MCRuntime = MCRuntime;
1288
+ //# sourceMappingURL=index.js.map