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