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.
- package/.claude/commands/build-test.md +10 -0
- package/.claude/commands/deploy-demo.md +12 -0
- package/.claude/commands/stage-status.md +13 -0
- package/.claude/settings.json +12 -0
- package/.github/workflows/ci.yml +1 -0
- package/CLAUDE.md +231 -0
- package/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/lowering/index.js +5 -3
- package/dist/src/__tests__/cli.test.d.ts +1 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/src/__tests__/codegen.test.d.ts +1 -0
- package/dist/src/__tests__/codegen.test.js +152 -0
- package/dist/src/__tests__/compile-all.test.d.ts +10 -0
- package/dist/src/__tests__/compile-all.test.js +108 -0
- package/dist/src/__tests__/dce.test.d.ts +1 -0
- package/dist/src/__tests__/dce.test.js +102 -0
- package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/src/__tests__/diagnostics.test.js +177 -0
- package/dist/src/__tests__/e2e.test.d.ts +6 -0
- package/dist/src/__tests__/e2e.test.js +1789 -0
- package/dist/src/__tests__/entity-types.test.d.ts +1 -0
- package/dist/src/__tests__/entity-types.test.js +203 -0
- package/dist/src/__tests__/formatter.test.d.ts +1 -0
- package/dist/src/__tests__/formatter.test.js +40 -0
- package/dist/src/__tests__/lexer.test.d.ts +1 -0
- package/dist/src/__tests__/lexer.test.js +343 -0
- package/dist/src/__tests__/lowering.test.d.ts +1 -0
- package/dist/src/__tests__/lowering.test.js +1015 -0
- package/dist/src/__tests__/macro.test.d.ts +8 -0
- package/dist/src/__tests__/macro.test.js +306 -0
- package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/src/__tests__/mc-integration.test.js +817 -0
- package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/src/__tests__/mc-syntax.test.js +124 -0
- package/dist/src/__tests__/nbt.test.d.ts +1 -0
- package/dist/src/__tests__/nbt.test.js +82 -0
- package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/src/__tests__/optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer.test.js +149 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/parser.test.js +807 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.js +27 -0
- package/dist/src/__tests__/runtime.test.d.ts +1 -0
- package/dist/src/__tests__/runtime.test.js +289 -0
- package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
- package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
- package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-math.test.js +351 -0
- package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-vec.test.js +263 -0
- package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/structure-optimizer.test.js +33 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/src/__tests__/typechecker.test.js +552 -0
- package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/src/__tests__/var-allocator.test.js +69 -0
- package/dist/src/ast/types.d.ts +515 -0
- package/dist/src/ast/types.js +9 -0
- package/dist/src/builtins/metadata.d.ts +36 -0
- package/dist/src/builtins/metadata.js +1014 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +443 -0
- package/dist/src/codegen/cmdblock/index.d.ts +26 -0
- package/dist/src/codegen/cmdblock/index.js +45 -0
- package/dist/src/codegen/mcfunction/index.d.ts +40 -0
- package/dist/src/codegen/mcfunction/index.js +606 -0
- package/dist/src/codegen/structure/index.d.ts +24 -0
- package/dist/src/codegen/structure/index.js +279 -0
- package/dist/src/codegen/var-allocator.d.ts +45 -0
- package/dist/src/codegen/var-allocator.js +104 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/src/diagnostics/index.d.ts +44 -0
- package/dist/src/diagnostics/index.js +140 -0
- package/dist/src/events/types.d.ts +35 -0
- package/dist/src/events/types.js +59 -0
- package/dist/src/formatter/index.d.ts +1 -0
- package/dist/src/formatter/index.js +26 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +45 -0
- package/dist/src/ir/builder.d.ts +33 -0
- package/dist/src/ir/builder.js +99 -0
- package/dist/src/ir/types.d.ts +132 -0
- package/dist/src/ir/types.js +15 -0
- package/dist/src/lexer/index.d.ts +37 -0
- package/dist/src/lexer/index.js +569 -0
- package/dist/src/lowering/index.d.ts +188 -0
- package/dist/src/lowering/index.js +3405 -0
- package/dist/src/mc-test/client.d.ts +128 -0
- package/dist/src/mc-test/client.js +174 -0
- package/dist/src/mc-test/runner.d.ts +28 -0
- package/dist/src/mc-test/runner.js +151 -0
- package/dist/src/mc-test/setup.d.ts +11 -0
- package/dist/src/mc-test/setup.js +98 -0
- package/dist/src/mc-validator/index.d.ts +17 -0
- package/dist/src/mc-validator/index.js +322 -0
- package/dist/src/nbt/index.d.ts +86 -0
- package/dist/src/nbt/index.js +250 -0
- package/dist/src/optimizer/commands.d.ts +38 -0
- package/dist/src/optimizer/commands.js +451 -0
- package/dist/src/optimizer/dce.d.ts +34 -0
- package/dist/src/optimizer/dce.js +639 -0
- package/dist/src/optimizer/passes.d.ts +34 -0
- package/dist/src/optimizer/passes.js +243 -0
- package/dist/src/optimizer/structure.d.ts +9 -0
- package/dist/src/optimizer/structure.js +356 -0
- package/dist/src/parser/index.d.ts +93 -0
- package/dist/src/parser/index.js +1687 -0
- package/dist/src/repl.d.ts +16 -0
- package/dist/src/repl.js +165 -0
- package/dist/src/runtime/index.d.ts +107 -0
- package/dist/src/runtime/index.js +1409 -0
- package/dist/src/typechecker/index.d.ts +61 -0
- package/dist/src/typechecker/index.js +1034 -0
- package/dist/src/types/entity-hierarchy.d.ts +29 -0
- package/dist/src/types/entity-hierarchy.js +107 -0
- package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src2/__tests__/e2e/basic.test.js +140 -0
- package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src2/__tests__/e2e/macros.test.js +182 -0
- package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
- package/dist/src2/__tests__/hir/desugar.test.js +234 -0
- package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/lower.test.js +559 -0
- package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/types.test.js +185 -0
- package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/verify.test.js +221 -0
- package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/verify.test.js +223 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src2/emit/compile.d.ts +19 -0
- package/dist/src2/emit/compile.js +80 -0
- package/dist/src2/emit/index.d.ts +17 -0
- package/dist/src2/emit/index.js +172 -0
- package/dist/src2/hir/lower.d.ts +15 -0
- package/dist/src2/hir/lower.js +378 -0
- package/dist/src2/hir/types.d.ts +373 -0
- package/dist/src2/hir/types.js +16 -0
- package/dist/src2/lir/lower.d.ts +15 -0
- package/dist/src2/lir/lower.js +453 -0
- package/dist/src2/lir/types.d.ts +136 -0
- package/dist/src2/lir/types.js +11 -0
- package/dist/src2/lir/verify.d.ts +14 -0
- package/dist/src2/lir/verify.js +113 -0
- package/dist/src2/mir/lower.d.ts +9 -0
- package/dist/src2/mir/lower.js +1030 -0
- package/dist/src2/mir/macro.d.ts +22 -0
- package/dist/src2/mir/macro.js +168 -0
- package/dist/src2/mir/types.d.ts +183 -0
- package/dist/src2/mir/types.js +11 -0
- package/dist/src2/mir/verify.d.ts +16 -0
- package/dist/src2/mir/verify.js +216 -0
- package/dist/src2/optimizer/block_merge.d.ts +12 -0
- package/dist/src2/optimizer/block_merge.js +84 -0
- package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src2/optimizer/branch_simplify.js +28 -0
- package/dist/src2/optimizer/constant_fold.d.ts +10 -0
- package/dist/src2/optimizer/constant_fold.js +85 -0
- package/dist/src2/optimizer/copy_prop.d.ts +9 -0
- package/dist/src2/optimizer/copy_prop.js +113 -0
- package/dist/src2/optimizer/dce.d.ts +8 -0
- package/dist/src2/optimizer/dce.js +155 -0
- package/dist/src2/optimizer/pipeline.d.ts +10 -0
- package/dist/src2/optimizer/pipeline.js +42 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/compiler-pipeline-redesign.md +2243 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/jest.config.js +1 -1
- package/package.json +6 -5
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +59 -38
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/ast/types.ts +6 -1
- package/src/cli.ts +29 -156
- package/src/compile.ts +6 -162
- package/src/index.ts +14 -178
- package/src/mc-test/runner.ts +4 -3
- package/src/parser/index.ts +1 -1
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src2/__tests__/e2e/basic.test.ts +154 -0
- package/src2/__tests__/e2e/macros.test.ts +199 -0
- package/src2/__tests__/e2e/migrate.test.ts +3008 -0
- package/src2/__tests__/hir/desugar.test.ts +263 -0
- package/src2/__tests__/lir/lower.test.ts +619 -0
- package/src2/__tests__/lir/types.test.ts +207 -0
- package/src2/__tests__/lir/verify.test.ts +249 -0
- package/src2/__tests__/mir/arithmetic.test.ts +156 -0
- package/src2/__tests__/mir/control-flow.test.ts +242 -0
- package/src2/__tests__/mir/verify.test.ts +254 -0
- package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src2/__tests__/optimizer/dce.test.ts +83 -0
- package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src2/emit/compile.ts +99 -0
- package/src2/emit/index.ts +222 -0
- package/src2/hir/lower.ts +428 -0
- package/src2/hir/types.ts +216 -0
- package/src2/lir/lower.ts +556 -0
- package/src2/lir/types.ts +109 -0
- package/src2/lir/verify.ts +129 -0
- package/src2/mir/lower.ts +1160 -0
- package/src2/mir/macro.ts +167 -0
- package/src2/mir/types.ts +106 -0
- package/src2/mir/verify.ts +218 -0
- package/src2/optimizer/block_merge.ts +93 -0
- package/src2/optimizer/branch_simplify.ts +27 -0
- package/src2/optimizer/constant_fold.ts +88 -0
- package/src2/optimizer/copy_prop.ts +106 -0
- package/src2/optimizer/dce.ts +133 -0
- package/src2/optimizer/pipeline.ts +44 -0
- package/tsconfig.json +2 -2
- package/src/__tests__/codegen.test.ts +0 -161
- package/src/__tests__/e2e.test.ts +0 -2039
- package/src/__tests__/entity-types.test.ts +0 -236
- package/src/__tests__/lowering.test.ts +0 -1185
- package/src/__tests__/macro.test.ts +0 -343
- package/src/__tests__/nbt.test.ts +0 -58
- package/src/__tests__/optimizer-advanced.test.ts +0 -144
- package/src/__tests__/optimizer.test.ts +0 -162
- package/src/__tests__/runtime.test.ts +0 -305
- package/src/__tests__/stdlib-advanced.test.ts +0 -379
- package/src/__tests__/stdlib-bigint.test.ts +0 -427
- package/src/__tests__/stdlib-math.test.ts +0 -374
- package/src/__tests__/stdlib-vec.test.ts +0 -259
- package/src/__tests__/structure-optimizer.test.ts +0 -38
- package/src/__tests__/var-allocator.test.ts +0 -75
- package/src/codegen/cmdblock/index.ts +0 -63
- package/src/codegen/mcfunction/index.ts +0 -662
- package/src/codegen/structure/index.ts +0 -346
- package/src/codegen/var-allocator.ts +0 -104
- package/src/ir/builder.ts +0 -116
- package/src/ir/types.ts +0 -134
- package/src/lowering/index.ts +0 -3876
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/dce.ts +0 -679
- package/src/optimizer/passes.ts +0 -250
- 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
|