redscript-mc 1.2.19 → 1.2.21
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/dist/__tests__/compile-all.test.d.ts +2 -1
- package/dist/__tests__/compile-all.test.js +26 -8
- package/dist/cli.js +99 -4
- package/dist/compile.d.ts +8 -0
- package/dist/compile.js +14 -0
- package/dist/index.js +1 -1
- package/dist/optimizer/dce.d.ts +2 -1
- package/dist/optimizer/dce.js +13 -2
- package/editors/vscode/out/extension.js +29 -4
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/compile-all.test.ts +29 -8
- package/src/cli.ts +107 -5
- package/src/compile.ts +18 -0
- package/src/examples/hunger_games.mcrs +1 -1
- package/src/examples/zombie_survival.mcrs +73 -73
- package/src/index.ts +1 -1
- package/src/optimizer/dce.ts +18 -2
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Compile-all smoke test
|
|
3
3
|
*
|
|
4
4
|
* Finds every .mcrs file in the repo (excluding declaration files and node_modules)
|
|
5
|
-
* and verifies that each one compiles without
|
|
5
|
+
* and verifies that each one compiles without errors via the CLI (which handles
|
|
6
|
+
* `import` statements, unlike the bare `compile()` function).
|
|
6
7
|
*
|
|
7
8
|
* This catches regressions where a language change breaks existing source files.
|
|
8
9
|
*/
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Compile-all smoke test
|
|
4
4
|
*
|
|
5
5
|
* Finds every .mcrs file in the repo (excluding declaration files and node_modules)
|
|
6
|
-
* and verifies that each one compiles without
|
|
6
|
+
* and verifies that each one compiles without errors via the CLI (which handles
|
|
7
|
+
* `import` statements, unlike the bare `compile()` function).
|
|
7
8
|
*
|
|
8
9
|
* This catches regressions where a language change breaks existing source files.
|
|
9
10
|
*/
|
|
@@ -43,8 +44,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
45
46
|
const path = __importStar(require("path"));
|
|
46
|
-
const
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const os = __importStar(require("os"));
|
|
47
49
|
const REPO_ROOT = path.resolve(__dirname, '../../');
|
|
50
|
+
const CLI = path.join(REPO_ROOT, 'dist', 'cli.js');
|
|
51
|
+
// Ensure dist/cli.js exists — build first if not (e.g. in CI)
|
|
52
|
+
if (!fs.existsSync(CLI)) {
|
|
53
|
+
console.log('[compile-all] dist/cli.js not found, running npm run build...');
|
|
54
|
+
(0, child_process_1.execSync)('npm run build', { cwd: REPO_ROOT, stdio: 'inherit' });
|
|
55
|
+
}
|
|
48
56
|
/** Patterns to skip */
|
|
49
57
|
const SKIP_GLOBS = [
|
|
50
58
|
'node_modules',
|
|
@@ -72,18 +80,28 @@ function findMcrsFiles(dir) {
|
|
|
72
80
|
return results;
|
|
73
81
|
}
|
|
74
82
|
const mcrsFiles = findMcrsFiles(REPO_ROOT);
|
|
75
|
-
|
|
83
|
+
const TMP_OUT = path.join(os.tmpdir(), 'redscript-compile-all');
|
|
84
|
+
describe('compile-all: every .mcrs file should compile without errors (CLI)', () => {
|
|
76
85
|
test('found at least one .mcrs file', () => {
|
|
77
86
|
expect(mcrsFiles.length).toBeGreaterThan(0);
|
|
78
87
|
});
|
|
79
88
|
for (const filePath of mcrsFiles) {
|
|
80
89
|
const label = path.relative(REPO_ROOT, filePath);
|
|
81
90
|
test(label, () => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const outDir = path.join(TMP_OUT, label.replace(/[^a-zA-Z0-9]/g, '_'));
|
|
92
|
+
let stdout = '';
|
|
93
|
+
let stderr = '';
|
|
94
|
+
try {
|
|
95
|
+
const result = (0, child_process_1.execSync)(`node "${CLI}" compile "${filePath}" -o "${outDir}"`, { encoding: 'utf8', stdio: 'pipe' });
|
|
96
|
+
stdout = result;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
stdout = err.stdout ?? '';
|
|
100
|
+
stderr = err.stderr ?? '';
|
|
101
|
+
const output = (stdout + stderr).trim();
|
|
102
|
+
// Fail with the compiler error message
|
|
103
|
+
throw new Error(`Compile failed for ${label}:\n${output}`);
|
|
104
|
+
}
|
|
87
105
|
});
|
|
88
106
|
}
|
|
89
107
|
});
|
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,8 @@ const repl_1 = require("./repl");
|
|
|
51
51
|
const metadata_1 = require("./builtins/metadata");
|
|
52
52
|
const fs = __importStar(require("fs"));
|
|
53
53
|
const path = __importStar(require("path"));
|
|
54
|
+
const https = __importStar(require("https"));
|
|
55
|
+
const child_process_1 = require("child_process");
|
|
54
56
|
// Parse command line arguments
|
|
55
57
|
const args = process.argv.slice(2);
|
|
56
58
|
function printUsage() {
|
|
@@ -74,6 +76,7 @@ Commands:
|
|
|
74
76
|
generate-dts Generate builtin function declaration file (builtins.d.mcrs)
|
|
75
77
|
repl Start an interactive RedScript REPL
|
|
76
78
|
version Print the RedScript version
|
|
79
|
+
upgrade Upgrade to the latest version (npm install -g redscript-mc@latest)
|
|
77
80
|
|
|
78
81
|
Options:
|
|
79
82
|
-o, --output <path> Output directory or file path, depending on target
|
|
@@ -92,15 +95,90 @@ Targets:
|
|
|
92
95
|
structure Generate a Minecraft structure .nbt file with command blocks
|
|
93
96
|
`);
|
|
94
97
|
}
|
|
95
|
-
function
|
|
98
|
+
function getLocalVersion() {
|
|
96
99
|
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
97
100
|
try {
|
|
98
101
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
99
|
-
|
|
102
|
+
return pkg.version ?? '0.0.0';
|
|
100
103
|
}
|
|
101
104
|
catch {
|
|
102
|
-
|
|
105
|
+
return '0.0.0';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function printVersion() {
|
|
109
|
+
console.log(`RedScript v${getLocalVersion()}`);
|
|
110
|
+
}
|
|
111
|
+
/** Fetch latest version from npm registry (non-blocking, best-effort). */
|
|
112
|
+
function fetchLatestVersion() {
|
|
113
|
+
return new Promise(resolve => {
|
|
114
|
+
const req = https.get('https://registry.npmjs.org/redscript-mc/latest', { timeout: 3000 }, res => {
|
|
115
|
+
let data = '';
|
|
116
|
+
res.on('data', chunk => { data += chunk; });
|
|
117
|
+
res.on('end', () => {
|
|
118
|
+
try {
|
|
119
|
+
const json = JSON.parse(data);
|
|
120
|
+
resolve(json.version ?? null);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
resolve(null);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
req.on('error', () => resolve(null));
|
|
128
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Compare semver strings. Returns true if b > a. */
|
|
132
|
+
function isNewer(current, latest) {
|
|
133
|
+
const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
|
|
134
|
+
const [ca, cb, cc] = parse(current);
|
|
135
|
+
const [la, lb, lc] = parse(latest);
|
|
136
|
+
if (la !== ca)
|
|
137
|
+
return la > ca;
|
|
138
|
+
if (lb !== cb)
|
|
139
|
+
return lb > cb;
|
|
140
|
+
return lc > cc;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check for a newer version and print a notice if one exists.
|
|
144
|
+
* Runs in background — does NOT block normal CLI operation.
|
|
145
|
+
*/
|
|
146
|
+
async function checkForUpdates(silent = false) {
|
|
147
|
+
const current = getLocalVersion();
|
|
148
|
+
const latest = await fetchLatestVersion();
|
|
149
|
+
if (latest && isNewer(current, latest)) {
|
|
150
|
+
console.log(`\n💡 New version available: v${current} → v${latest}`);
|
|
151
|
+
console.log(` Run: redscript upgrade\n`);
|
|
103
152
|
}
|
|
153
|
+
else if (!silent && latest) {
|
|
154
|
+
// Only print when explicitly running 'version' or 'upgrade'
|
|
155
|
+
// No output for normal commands — keep startup noise-free
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Run npm install -g to upgrade to latest. */
|
|
159
|
+
function upgradeCommand() {
|
|
160
|
+
const current = getLocalVersion();
|
|
161
|
+
console.log(`Current version: v${current}`);
|
|
162
|
+
console.log('Checking latest version...');
|
|
163
|
+
fetchLatestVersion().then(latest => {
|
|
164
|
+
if (!latest) {
|
|
165
|
+
console.error('Could not fetch latest version from npm.');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
if (!isNewer(current, latest)) {
|
|
169
|
+
console.log(`✅ Already up to date (v${current})`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(`Upgrading v${current} → v${latest}...`);
|
|
173
|
+
try {
|
|
174
|
+
(0, child_process_1.execSync)('npm install -g redscript-mc@latest', { stdio: 'inherit' });
|
|
175
|
+
console.log(`✅ Upgraded to v${latest}`);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
console.error('Upgrade failed. Try manually: npm install -g redscript-mc@latest');
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
104
182
|
}
|
|
105
183
|
function parseArgs(args) {
|
|
106
184
|
const result = { dce: true };
|
|
@@ -163,7 +241,13 @@ function printWarnings(warnings) {
|
|
|
163
241
|
return;
|
|
164
242
|
}
|
|
165
243
|
for (const warning of warnings) {
|
|
166
|
-
|
|
244
|
+
const loc = warning.filePath
|
|
245
|
+
? `${warning.filePath}:${warning.line ?? '?'}`
|
|
246
|
+
: warning.line != null
|
|
247
|
+
? `line ${warning.line}`
|
|
248
|
+
: null;
|
|
249
|
+
const locStr = loc ? ` (${loc})` : '';
|
|
250
|
+
console.error(`Warning [${warning.code}]: ${warning.message}${locStr}`);
|
|
167
251
|
}
|
|
168
252
|
}
|
|
169
253
|
function formatReduction(before, after) {
|
|
@@ -369,6 +453,12 @@ async function main() {
|
|
|
369
453
|
printUsage();
|
|
370
454
|
process.exit(parsed.help ? 0 : 1);
|
|
371
455
|
}
|
|
456
|
+
// Background update check — non-blocking, only shows notice if newer version exists
|
|
457
|
+
// Skip for repl/upgrade/version to avoid double-printing
|
|
458
|
+
const noCheckCmds = new Set(['upgrade', 'update', 'version', 'repl']);
|
|
459
|
+
if (!noCheckCmds.has(parsed.command ?? '')) {
|
|
460
|
+
checkForUpdates().catch(() => { });
|
|
461
|
+
}
|
|
372
462
|
switch (parsed.command) {
|
|
373
463
|
case 'compile':
|
|
374
464
|
if (!parsed.file) {
|
|
@@ -429,6 +519,11 @@ async function main() {
|
|
|
429
519
|
break;
|
|
430
520
|
case 'version':
|
|
431
521
|
printVersion();
|
|
522
|
+
await checkForUpdates();
|
|
523
|
+
break;
|
|
524
|
+
case 'upgrade':
|
|
525
|
+
case 'update':
|
|
526
|
+
upgradeCommand();
|
|
432
527
|
break;
|
|
433
528
|
default:
|
|
434
529
|
console.error(`Error: Unknown command '${parsed.command}'`);
|
package/dist/compile.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface PreprocessedSource {
|
|
|
30
30
|
source: string;
|
|
31
31
|
ranges: SourceRange[];
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a combined-source line number back to the original file and line.
|
|
35
|
+
* Returns { filePath, line } if a mapping is found, otherwise returns the input unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveSourceLine(combinedLine: number, ranges: SourceRange[], fallbackFile?: string): {
|
|
38
|
+
filePath?: string;
|
|
39
|
+
line: number;
|
|
40
|
+
};
|
|
33
41
|
interface PreprocessOptions {
|
|
34
42
|
filePath?: string;
|
|
35
43
|
seen?: Set<string>;
|
package/dist/compile.js
CHANGED
|
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.resolveSourceLine = resolveSourceLine;
|
|
41
42
|
exports.preprocessSourceWithMetadata = preprocessSourceWithMetadata;
|
|
42
43
|
exports.preprocessSource = preprocessSource;
|
|
43
44
|
exports.compile = compile;
|
|
@@ -51,6 +52,19 @@ const passes_1 = require("./optimizer/passes");
|
|
|
51
52
|
const dce_1 = require("./optimizer/dce");
|
|
52
53
|
const mcfunction_1 = require("./codegen/mcfunction");
|
|
53
54
|
const diagnostics_1 = require("./diagnostics");
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a combined-source line number back to the original file and line.
|
|
57
|
+
* Returns { filePath, line } if a mapping is found, otherwise returns the input unchanged.
|
|
58
|
+
*/
|
|
59
|
+
function resolveSourceLine(combinedLine, ranges, fallbackFile) {
|
|
60
|
+
for (const range of ranges) {
|
|
61
|
+
if (combinedLine >= range.startLine && combinedLine <= range.endLine) {
|
|
62
|
+
const localLine = combinedLine - range.startLine + 1;
|
|
63
|
+
return { filePath: range.filePath, line: localLine };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { filePath: fallbackFile, line: combinedLine };
|
|
67
|
+
}
|
|
54
68
|
const IMPORT_RE = /^\s*import\s+"([^"]+)"\s*;?\s*$/;
|
|
55
69
|
function countLines(source) {
|
|
56
70
|
return source === '' ? 0 : source.split('\n').length;
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function compile(source, options = {}) {
|
|
|
38
38
|
const tokens = new lexer_1.Lexer(preprocessedSource, filePath).tokenize();
|
|
39
39
|
// Parsing
|
|
40
40
|
const parsedAst = new parser_1.Parser(tokens, preprocessedSource, filePath).parse(namespace);
|
|
41
|
-
const dceResult = shouldRunDce ? (0, dce_1.eliminateDeadCode)(parsedAst) : { program: parsedAst, warnings: [] };
|
|
41
|
+
const dceResult = shouldRunDce ? (0, dce_1.eliminateDeadCode)(parsedAst, preprocessed.ranges) : { program: parsedAst, warnings: [] };
|
|
42
42
|
const ast = dceResult.program;
|
|
43
43
|
// Type checking (warn mode - collect errors but don't block)
|
|
44
44
|
let typeErrors;
|
package/dist/optimizer/dce.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface DCEWarning {
|
|
|
4
4
|
code: string;
|
|
5
5
|
line?: number;
|
|
6
6
|
col?: number;
|
|
7
|
+
filePath?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare class DeadCodeEliminator {
|
|
9
10
|
private readonly functionMap;
|
|
@@ -27,7 +28,7 @@ export declare class DeadCodeEliminator {
|
|
|
27
28
|
private transformStmt;
|
|
28
29
|
private transformExpr;
|
|
29
30
|
}
|
|
30
|
-
export declare function eliminateDeadCode(program: Program): {
|
|
31
|
+
export declare function eliminateDeadCode(program: Program, sourceRanges?: import('../compile').SourceRange[]): {
|
|
31
32
|
program: Program;
|
|
32
33
|
warnings: DCEWarning[];
|
|
33
34
|
};
|
package/dist/optimizer/dce.js
CHANGED
|
@@ -604,9 +604,20 @@ class DeadCodeEliminator {
|
|
|
604
604
|
}
|
|
605
605
|
}
|
|
606
606
|
exports.DeadCodeEliminator = DeadCodeEliminator;
|
|
607
|
-
function eliminateDeadCode(program) {
|
|
607
|
+
function eliminateDeadCode(program, sourceRanges) {
|
|
608
608
|
const eliminator = new DeadCodeEliminator();
|
|
609
609
|
const result = eliminator.eliminate(program);
|
|
610
|
-
|
|
610
|
+
let warnings = eliminator.warnings;
|
|
611
|
+
// Resolve combined-source line numbers back to original file + line
|
|
612
|
+
if (sourceRanges && sourceRanges.length > 0) {
|
|
613
|
+
const { resolveSourceLine } = require('../compile');
|
|
614
|
+
warnings = warnings.map(w => {
|
|
615
|
+
if (w.line == null)
|
|
616
|
+
return w;
|
|
617
|
+
const resolved = resolveSourceLine(w.line, sourceRanges);
|
|
618
|
+
return { ...w, line: resolved.line, filePath: resolved.filePath ?? w.filePath };
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return { program: result, warnings };
|
|
611
622
|
}
|
|
612
623
|
//# sourceMappingURL=dce.js.map
|
|
@@ -1193,10 +1193,22 @@ var require_parser = __commonJS({
|
|
|
1193
1193
|
parseForRangeStmt(forToken) {
|
|
1194
1194
|
const varName = this.expect("ident").value;
|
|
1195
1195
|
this.expect("in");
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1196
|
+
let start;
|
|
1197
|
+
let end;
|
|
1198
|
+
if (this.check("range_lit")) {
|
|
1199
|
+
const rangeToken = this.advance();
|
|
1200
|
+
const range = this.parseRangeValue(rangeToken.value);
|
|
1201
|
+
start = this.withLoc({ kind: "int_lit", value: range.min ?? 0 }, rangeToken);
|
|
1202
|
+
if (range.max !== null && range.max !== void 0) {
|
|
1203
|
+
end = this.withLoc({ kind: "int_lit", value: range.max }, rangeToken);
|
|
1204
|
+
} else {
|
|
1205
|
+
end = this.parseUnaryExpr();
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
start = this.withLoc({ kind: "int_lit", value: 0 }, this.peek());
|
|
1209
|
+
end = this.withLoc({ kind: "int_lit", value: 0 }, this.peek());
|
|
1210
|
+
this.error("Dynamic range start requires a literal integer (e.g. 0..count)");
|
|
1211
|
+
}
|
|
1200
1212
|
const body = this.parseBlock();
|
|
1201
1213
|
return this.withLoc({ kind: "for_range", varName, start, end, body }, forToken);
|
|
1202
1214
|
}
|
|
@@ -1656,6 +1668,19 @@ var require_parser = __commonJS({
|
|
|
1656
1668
|
this.error(`Unexpected token '${token.kind}'`);
|
|
1657
1669
|
}
|
|
1658
1670
|
parseLiteralExpr() {
|
|
1671
|
+
if (this.check("-")) {
|
|
1672
|
+
this.advance();
|
|
1673
|
+
const token = this.peek();
|
|
1674
|
+
if (token.kind === "int_lit") {
|
|
1675
|
+
this.advance();
|
|
1676
|
+
return this.withLoc({ kind: "int_lit", value: -Number(token.value) }, token);
|
|
1677
|
+
}
|
|
1678
|
+
if (token.kind === "float_lit") {
|
|
1679
|
+
this.advance();
|
|
1680
|
+
return this.withLoc({ kind: "float_lit", value: -Number(token.value) }, token);
|
|
1681
|
+
}
|
|
1682
|
+
this.error("Expected number after unary -");
|
|
1683
|
+
}
|
|
1659
1684
|
const expr = this.parsePrimaryExpr();
|
|
1660
1685
|
if (expr.kind === "int_lit" || expr.kind === "float_lit" || expr.kind === "bool_lit" || expr.kind === "str_lit") {
|
|
1661
1686
|
return expr;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "redscript-vscode",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.12",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"redscript": "file:../../"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
3
|
"displayName": "RedScript for Minecraft",
|
|
4
4
|
"description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.12",
|
|
6
6
|
"publisher": "bkmashiro",
|
|
7
7
|
"icon": "icon.png",
|
|
8
8
|
"license": "MIT",
|
package/package.json
CHANGED
|
@@ -2,16 +2,25 @@
|
|
|
2
2
|
* Compile-all smoke test
|
|
3
3
|
*
|
|
4
4
|
* Finds every .mcrs file in the repo (excluding declaration files and node_modules)
|
|
5
|
-
* and verifies that each one compiles without
|
|
5
|
+
* and verifies that each one compiles without errors via the CLI (which handles
|
|
6
|
+
* `import` statements, unlike the bare `compile()` function).
|
|
6
7
|
*
|
|
7
8
|
* This catches regressions where a language change breaks existing source files.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import * as fs from 'fs'
|
|
11
12
|
import * as path from 'path'
|
|
12
|
-
import {
|
|
13
|
+
import { execSync } from 'child_process'
|
|
14
|
+
import * as os from 'os'
|
|
13
15
|
|
|
14
16
|
const REPO_ROOT = path.resolve(__dirname, '../../')
|
|
17
|
+
const CLI = path.join(REPO_ROOT, 'dist', 'cli.js')
|
|
18
|
+
|
|
19
|
+
// Ensure dist/cli.js exists — build first if not (e.g. in CI)
|
|
20
|
+
if (!fs.existsSync(CLI)) {
|
|
21
|
+
console.log('[compile-all] dist/cli.js not found, running npm run build...')
|
|
22
|
+
execSync('npm run build', { cwd: REPO_ROOT, stdio: 'inherit' })
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
/** Patterns to skip */
|
|
17
26
|
const SKIP_GLOBS = [
|
|
@@ -41,8 +50,9 @@ function findMcrsFiles(dir: string): string[] {
|
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
const mcrsFiles = findMcrsFiles(REPO_ROOT)
|
|
53
|
+
const TMP_OUT = path.join(os.tmpdir(), 'redscript-compile-all')
|
|
44
54
|
|
|
45
|
-
describe('compile-all: every .mcrs file should compile without errors', () => {
|
|
55
|
+
describe('compile-all: every .mcrs file should compile without errors (CLI)', () => {
|
|
46
56
|
test('found at least one .mcrs file', () => {
|
|
47
57
|
expect(mcrsFiles.length).toBeGreaterThan(0)
|
|
48
58
|
})
|
|
@@ -50,11 +60,22 @@ describe('compile-all: every .mcrs file should compile without errors', () => {
|
|
|
50
60
|
for (const filePath of mcrsFiles) {
|
|
51
61
|
const label = path.relative(REPO_ROOT, filePath)
|
|
52
62
|
test(label, () => {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
const outDir = path.join(TMP_OUT, label.replace(/[^a-zA-Z0-9]/g, '_'))
|
|
64
|
+
let stdout = ''
|
|
65
|
+
let stderr = ''
|
|
66
|
+
try {
|
|
67
|
+
const result = execSync(
|
|
68
|
+
`node "${CLI}" compile "${filePath}" -o "${outDir}"`,
|
|
69
|
+
{ encoding: 'utf8', stdio: 'pipe' }
|
|
70
|
+
)
|
|
71
|
+
stdout = result
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
stdout = err.stdout ?? ''
|
|
74
|
+
stderr = err.stderr ?? ''
|
|
75
|
+
const output = (stdout + stderr).trim()
|
|
76
|
+
// Fail with the compiler error message
|
|
77
|
+
throw new Error(`Compile failed for ${label}:\n${output}`)
|
|
78
|
+
}
|
|
58
79
|
})
|
|
59
80
|
}
|
|
60
81
|
})
|
package/src/cli.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { generateDts } from './builtins/metadata'
|
|
|
18
18
|
import type { OptimizationStats } from './optimizer/commands'
|
|
19
19
|
import * as fs from 'fs'
|
|
20
20
|
import * as path from 'path'
|
|
21
|
+
import * as https from 'https'
|
|
22
|
+
import { execSync } from 'child_process'
|
|
21
23
|
|
|
22
24
|
// Parse command line arguments
|
|
23
25
|
const args = process.argv.slice(2)
|
|
@@ -43,6 +45,7 @@ Commands:
|
|
|
43
45
|
generate-dts Generate builtin function declaration file (builtins.d.mcrs)
|
|
44
46
|
repl Start an interactive RedScript REPL
|
|
45
47
|
version Print the RedScript version
|
|
48
|
+
upgrade Upgrade to the latest version (npm install -g redscript-mc@latest)
|
|
46
49
|
|
|
47
50
|
Options:
|
|
48
51
|
-o, --output <path> Output directory or file path, depending on target
|
|
@@ -62,16 +65,96 @@ Targets:
|
|
|
62
65
|
`)
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
function
|
|
68
|
+
function getLocalVersion(): string {
|
|
66
69
|
const packagePath = path.join(__dirname, '..', 'package.json')
|
|
67
70
|
try {
|
|
68
71
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
|
|
69
|
-
|
|
72
|
+
return pkg.version ?? '0.0.0'
|
|
70
73
|
} catch {
|
|
71
|
-
|
|
74
|
+
return '0.0.0'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printVersion(): void {
|
|
79
|
+
console.log(`RedScript v${getLocalVersion()}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Fetch latest version from npm registry (non-blocking, best-effort). */
|
|
83
|
+
function fetchLatestVersion(): Promise<string | null> {
|
|
84
|
+
return new Promise(resolve => {
|
|
85
|
+
const req = https.get(
|
|
86
|
+
'https://registry.npmjs.org/redscript-mc/latest',
|
|
87
|
+
{ timeout: 3000 },
|
|
88
|
+
res => {
|
|
89
|
+
let data = ''
|
|
90
|
+
res.on('data', chunk => { data += chunk })
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
try {
|
|
93
|
+
const json = JSON.parse(data)
|
|
94
|
+
resolve(json.version ?? null)
|
|
95
|
+
} catch {
|
|
96
|
+
resolve(null)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
req.on('error', () => resolve(null))
|
|
102
|
+
req.on('timeout', () => { req.destroy(); resolve(null) })
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Compare semver strings. Returns true if b > a. */
|
|
107
|
+
function isNewer(current: string, latest: string): boolean {
|
|
108
|
+
const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number)
|
|
109
|
+
const [ca, cb, cc] = parse(current)
|
|
110
|
+
const [la, lb, lc] = parse(latest)
|
|
111
|
+
if (la !== ca) return la > ca
|
|
112
|
+
if (lb !== cb) return lb > cb
|
|
113
|
+
return lc > cc
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check for a newer version and print a notice if one exists.
|
|
118
|
+
* Runs in background — does NOT block normal CLI operation.
|
|
119
|
+
*/
|
|
120
|
+
async function checkForUpdates(silent = false): Promise<void> {
|
|
121
|
+
const current = getLocalVersion()
|
|
122
|
+
const latest = await fetchLatestVersion()
|
|
123
|
+
if (latest && isNewer(current, latest)) {
|
|
124
|
+
console.log(`\n💡 New version available: v${current} → v${latest}`)
|
|
125
|
+
console.log(` Run: redscript upgrade\n`)
|
|
126
|
+
} else if (!silent && latest) {
|
|
127
|
+
// Only print when explicitly running 'version' or 'upgrade'
|
|
128
|
+
// No output for normal commands — keep startup noise-free
|
|
72
129
|
}
|
|
73
130
|
}
|
|
74
131
|
|
|
132
|
+
/** Run npm install -g to upgrade to latest. */
|
|
133
|
+
function upgradeCommand(): void {
|
|
134
|
+
const current = getLocalVersion()
|
|
135
|
+
console.log(`Current version: v${current}`)
|
|
136
|
+
console.log('Checking latest version...')
|
|
137
|
+
|
|
138
|
+
fetchLatestVersion().then(latest => {
|
|
139
|
+
if (!latest) {
|
|
140
|
+
console.error('Could not fetch latest version from npm.')
|
|
141
|
+
process.exit(1)
|
|
142
|
+
}
|
|
143
|
+
if (!isNewer(current, latest)) {
|
|
144
|
+
console.log(`✅ Already up to date (v${current})`)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
console.log(`Upgrading v${current} → v${latest}...`)
|
|
148
|
+
try {
|
|
149
|
+
execSync('npm install -g redscript-mc@latest', { stdio: 'inherit' })
|
|
150
|
+
console.log(`✅ Upgraded to v${latest}`)
|
|
151
|
+
} catch {
|
|
152
|
+
console.error('Upgrade failed. Try manually: npm install -g redscript-mc@latest')
|
|
153
|
+
process.exit(1)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
75
158
|
function parseArgs(args: string[]): {
|
|
76
159
|
command?: string
|
|
77
160
|
file?: string
|
|
@@ -134,13 +217,19 @@ function deriveNamespace(filePath: string): string {
|
|
|
134
217
|
return basename.toLowerCase().replace(/[^a-z0-9]/g, '_')
|
|
135
218
|
}
|
|
136
219
|
|
|
137
|
-
function printWarnings(warnings: Array<{ code: string; message: string }> | undefined): void {
|
|
220
|
+
function printWarnings(warnings: Array<{ code: string; message: string; line?: number; col?: number; filePath?: string }> | undefined): void {
|
|
138
221
|
if (!warnings || warnings.length === 0) {
|
|
139
222
|
return
|
|
140
223
|
}
|
|
141
224
|
|
|
142
225
|
for (const warning of warnings) {
|
|
143
|
-
|
|
226
|
+
const loc = warning.filePath
|
|
227
|
+
? `${warning.filePath}:${warning.line ?? '?'}`
|
|
228
|
+
: warning.line != null
|
|
229
|
+
? `line ${warning.line}`
|
|
230
|
+
: null
|
|
231
|
+
const locStr = loc ? ` (${loc})` : ''
|
|
232
|
+
console.error(`Warning [${warning.code}]: ${warning.message}${locStr}`)
|
|
144
233
|
}
|
|
145
234
|
}
|
|
146
235
|
|
|
@@ -378,6 +467,13 @@ async function main(): Promise<void> {
|
|
|
378
467
|
process.exit(parsed.help ? 0 : 1)
|
|
379
468
|
}
|
|
380
469
|
|
|
470
|
+
// Background update check — non-blocking, only shows notice if newer version exists
|
|
471
|
+
// Skip for repl/upgrade/version to avoid double-printing
|
|
472
|
+
const noCheckCmds = new Set(['upgrade', 'update', 'version', 'repl'])
|
|
473
|
+
if (!noCheckCmds.has(parsed.command ?? '')) {
|
|
474
|
+
checkForUpdates().catch(() => { /* ignore */ })
|
|
475
|
+
}
|
|
476
|
+
|
|
381
477
|
switch (parsed.command) {
|
|
382
478
|
case 'compile':
|
|
383
479
|
if (!parsed.file) {
|
|
@@ -458,6 +554,12 @@ async function main(): Promise<void> {
|
|
|
458
554
|
|
|
459
555
|
case 'version':
|
|
460
556
|
printVersion()
|
|
557
|
+
await checkForUpdates()
|
|
558
|
+
break
|
|
559
|
+
|
|
560
|
+
case 'upgrade':
|
|
561
|
+
case 'update':
|
|
562
|
+
upgradeCommand()
|
|
461
563
|
break
|
|
462
564
|
|
|
463
565
|
default:
|
package/src/compile.ts
CHANGED
|
@@ -52,6 +52,24 @@ export interface PreprocessedSource {
|
|
|
52
52
|
ranges: SourceRange[]
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a combined-source line number back to the original file and line.
|
|
57
|
+
* Returns { filePath, line } if a mapping is found, otherwise returns the input unchanged.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveSourceLine(
|
|
60
|
+
combinedLine: number,
|
|
61
|
+
ranges: SourceRange[],
|
|
62
|
+
fallbackFile?: string
|
|
63
|
+
): { filePath?: string; line: number } {
|
|
64
|
+
for (const range of ranges) {
|
|
65
|
+
if (combinedLine >= range.startLine && combinedLine <= range.endLine) {
|
|
66
|
+
const localLine = combinedLine - range.startLine + 1
|
|
67
|
+
return { filePath: range.filePath, line: localLine }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { filePath: fallbackFile, line: combinedLine }
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
const IMPORT_RE = /^\s*import\s+"([^"]+)"\s*;?\s*$/
|
|
56
74
|
|
|
57
75
|
interface PreprocessOptions {
|
|
@@ -204,7 +204,7 @@ fn check_border_damage() {
|
|
|
204
204
|
// 需要检查玩家是否在边界外
|
|
205
205
|
foreach (p in @a) {
|
|
206
206
|
// 简化:使用 execute positioned
|
|
207
|
-
let
|
|
207
|
+
let _dist: int = 0; // TODO: calculate distance from center
|
|
208
208
|
// 如果超出边界,造成伤害
|
|
209
209
|
// effect(p, "minecraft:wither", 1, 0);
|
|
210
210
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ============================================
|
|
2
|
-
// Zombie Survival -
|
|
2
|
+
// Zombie Survival - Zombie Survival Mode
|
|
3
3
|
// ============================================
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Scenario: Players defend against waves of zombies in the arena center
|
|
5
|
+
// Players can purchase equipment upgrades between waves
|
|
6
6
|
// Scenario: Survive zombie waves, buy upgrades between rounds
|
|
7
7
|
// ============================================
|
|
8
8
|
|
|
@@ -12,19 +12,19 @@ import "../stdlib/inventory.mcrs"
|
|
|
12
12
|
import "../stdlib/bossbar.mcrs"
|
|
13
13
|
import "../stdlib/particles.mcrs"
|
|
14
14
|
|
|
15
|
-
// =====
|
|
15
|
+
// ===== Configuration =====
|
|
16
16
|
const ARENA_X: int = 0;
|
|
17
17
|
const ARENA_Y: int = 64;
|
|
18
18
|
const ARENA_Z: int = 0;
|
|
19
19
|
const ARENA_RADIUS: int = 30;
|
|
20
|
-
const WAVE_DELAY: int = 200; // 10
|
|
20
|
+
const WAVE_DELAY: int = 200; // 10-second preparation time
|
|
21
21
|
|
|
22
|
-
// =====
|
|
22
|
+
// ===== Game State =====
|
|
23
23
|
struct SurvivalState {
|
|
24
24
|
running: int,
|
|
25
25
|
wave: int,
|
|
26
26
|
zombies_left: int,
|
|
27
|
-
phase: int, // 0
|
|
27
|
+
phase: int, // 0=prep, 1=combat
|
|
28
28
|
prep_timer: int,
|
|
29
29
|
total_kills: int
|
|
30
30
|
}
|
|
@@ -38,33 +38,33 @@ let state: SurvivalState = {
|
|
|
38
38
|
total_kills: 0
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
// =====
|
|
42
|
-
//
|
|
43
|
-
//
|
|
41
|
+
// ===== Player Data =====
|
|
42
|
+
// Coin scoreboard: zs_coins
|
|
43
|
+
// Kill count: zs_kills
|
|
44
44
|
|
|
45
|
-
// =====
|
|
45
|
+
// ===== Initialization =====
|
|
46
46
|
@load
|
|
47
47
|
fn init() {
|
|
48
48
|
scoreboard_add_objective("zs_coins", "dummy");
|
|
49
49
|
scoreboard_add_objective("zs_kills", "dummy");
|
|
50
50
|
scoreboard_add_objective("zs_display", "dummy");
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// Display scoreboard
|
|
53
53
|
scoreboard_display("sidebar", "zs_display");
|
|
54
54
|
|
|
55
55
|
set_night();
|
|
56
56
|
disable_mob_griefing();
|
|
57
57
|
|
|
58
|
-
announce("§4[
|
|
58
|
+
announce("§4[Zombie Survival] §fLoaded! Type /trigger start to begin");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// =====
|
|
61
|
+
// ===== Start Game =====
|
|
62
62
|
fn start_game() {
|
|
63
63
|
state.running = 1;
|
|
64
64
|
state.wave = 0;
|
|
65
65
|
state.total_kills = 0;
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Initialize players
|
|
68
68
|
foreach (p in @a) {
|
|
69
69
|
scoreboard_set(p, "zs_coins", 0);
|
|
70
70
|
scoreboard_set(p, "zs_kills", 0);
|
|
@@ -74,74 +74,74 @@ fn start_game() {
|
|
|
74
74
|
tp(p, ARENA_X, ARENA_Y, ARENA_Z);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
create_progress_bar("zs_wave", "§
|
|
77
|
+
// Create bossbar
|
|
78
|
+
create_progress_bar("zs_wave", "§cZombies Remaining", 10);
|
|
79
79
|
|
|
80
|
-
title(@a, "§
|
|
81
|
-
subtitle(@a, "§
|
|
80
|
+
title(@a, "§4Zombie Survival");
|
|
81
|
+
subtitle(@a, "§7Prepare for battle...");
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// Start first wave
|
|
84
84
|
start_prep_phase();
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// =====
|
|
87
|
+
// ===== Phase Control =====
|
|
88
88
|
fn start_prep_phase() {
|
|
89
89
|
state.phase = 0;
|
|
90
90
|
state.prep_timer = WAVE_DELAY;
|
|
91
91
|
state.wave = state.wave + 1;
|
|
92
92
|
|
|
93
|
-
announce("§4[
|
|
94
|
-
announce("§
|
|
93
|
+
announce("§4[Zombie Survival] §eWave " + state.wave + " incoming!");
|
|
94
|
+
announce("§7Preparation time: 10 seconds...");
|
|
95
95
|
|
|
96
|
-
//
|
|
96
|
+
// Shop hint
|
|
97
97
|
if (state.wave > 1) {
|
|
98
|
-
announce("§a[
|
|
98
|
+
announce("§a[Shop] §fType /trigger buy to purchase equipment");
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
fn start_combat_phase() {
|
|
103
103
|
state.phase = 1;
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// Calculate zombie count (increases each wave)
|
|
106
106
|
let zombie_count: int = 3 + (state.wave * 2);
|
|
107
107
|
state.zombies_left = zombie_count;
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// Update bossbar
|
|
110
110
|
bossbar_set_max("zs_wave", zombie_count);
|
|
111
111
|
bossbar_set_value("zs_wave", zombie_count);
|
|
112
112
|
|
|
113
|
-
title(@a, "§
|
|
113
|
+
title(@a, "§cWave " + state.wave);
|
|
114
114
|
|
|
115
|
-
//
|
|
115
|
+
// Spawn zombies
|
|
116
116
|
spawn_zombies(zombie_count);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
fn spawn_zombies(count: int) {
|
|
120
120
|
for i in 0..count {
|
|
121
|
-
//
|
|
122
|
-
let
|
|
121
|
+
// Spawn randomly at arena edge
|
|
122
|
+
let _angle: int = i * 30; // spread spawning
|
|
123
123
|
let spawn_x: int = ARENA_X + (ARENA_RADIUS - 5);
|
|
124
124
|
let spawn_z: int = ARENA_Z;
|
|
125
125
|
|
|
126
|
-
//
|
|
126
|
+
// Increase zombie difficulty based on wave number
|
|
127
127
|
if (state.wave < 3) {
|
|
128
128
|
summon("minecraft:zombie", spawn_x, ARENA_Y, spawn_z);
|
|
129
129
|
} else {
|
|
130
130
|
if (state.wave < 5) {
|
|
131
|
-
//
|
|
131
|
+
// Armored zombies
|
|
132
132
|
summon("minecraft:zombie", spawn_x, ARENA_Y, spawn_z,
|
|
133
133
|
{ArmorItems: [{}, {}, {id: "iron_chestplate", Count: 1}, {}]});
|
|
134
134
|
} else {
|
|
135
|
-
//
|
|
135
|
+
// Fast zombies
|
|
136
136
|
summon("minecraft:husk", spawn_x, ARENA_Y, spawn_z);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
announce("§c" + count + "
|
|
141
|
+
announce("§c" + count + " zombies have appeared!");
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// =====
|
|
144
|
+
// ===== Every Tick =====
|
|
145
145
|
@tick
|
|
146
146
|
fn game_tick() {
|
|
147
147
|
if (state.running == 0) {
|
|
@@ -149,10 +149,10 @@ fn game_tick() {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (state.phase == 0) {
|
|
152
|
-
//
|
|
152
|
+
// Preparation phase
|
|
153
153
|
prep_tick();
|
|
154
154
|
} else {
|
|
155
|
-
//
|
|
155
|
+
// Combat phase
|
|
156
156
|
combat_tick();
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -162,15 +162,15 @@ fn game_tick() {
|
|
|
162
162
|
fn prep_tick() {
|
|
163
163
|
state.prep_timer = state.prep_timer - 1;
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Countdown hints
|
|
166
166
|
if (state.prep_timer == 100) {
|
|
167
|
-
actionbar(@a, "§e5
|
|
167
|
+
actionbar(@a, "§e5 seconds...");
|
|
168
168
|
}
|
|
169
169
|
if (state.prep_timer == 60) {
|
|
170
|
-
actionbar(@a, "§e3
|
|
170
|
+
actionbar(@a, "§e3 seconds...");
|
|
171
171
|
}
|
|
172
172
|
if (state.prep_timer == 20) {
|
|
173
|
-
actionbar(@a, "§c1
|
|
173
|
+
actionbar(@a, "§c1 second...");
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
if (state.prep_timer <= 0) {
|
|
@@ -179,20 +179,20 @@ fn prep_tick() {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
fn combat_tick() {
|
|
182
|
-
//
|
|
182
|
+
// Check zombie count
|
|
183
183
|
let zombies: int = count_entities(@e[type=zombie, distance=..50]);
|
|
184
184
|
let husks: int = count_entities(@e[type=husk, distance=..50]);
|
|
185
185
|
state.zombies_left = zombies + husks;
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// Update bossbar
|
|
188
188
|
update_bar("zs_wave", state.zombies_left);
|
|
189
189
|
|
|
190
|
-
//
|
|
190
|
+
// Check wave completion
|
|
191
191
|
if (state.zombies_left <= 0) {
|
|
192
192
|
wave_complete();
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
//
|
|
195
|
+
// Check player survival
|
|
196
196
|
let alive: int = count_entities(@a[gamemode=survival]);
|
|
197
197
|
if (alive <= 0) {
|
|
198
198
|
game_over();
|
|
@@ -200,17 +200,17 @@ fn combat_tick() {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
fn wave_complete() {
|
|
203
|
-
announce("§4[
|
|
203
|
+
announce("§4[Zombie Survival] §aWave " + state.wave + " complete!");
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// Reward coins
|
|
206
206
|
let reward: int = 50 + (state.wave * 25);
|
|
207
207
|
foreach (p in @a) {
|
|
208
208
|
scoreboard_add(p, "zs_coins", reward);
|
|
209
209
|
happy(p);
|
|
210
210
|
}
|
|
211
|
-
announce("§6+" + reward + "
|
|
211
|
+
announce("§6+" + reward + " coins");
|
|
212
212
|
|
|
213
|
-
//
|
|
213
|
+
// Check special wave
|
|
214
214
|
if (state.wave == 10) {
|
|
215
215
|
victory();
|
|
216
216
|
return;
|
|
@@ -219,65 +219,65 @@ fn wave_complete() {
|
|
|
219
219
|
start_prep_phase();
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
// =====
|
|
222
|
+
// ===== Shop System =====
|
|
223
223
|
fn buy_item(player: selector, item_id: int) {
|
|
224
224
|
let coins: int = scoreboard_get(player, "zs_coins");
|
|
225
225
|
|
|
226
226
|
if (item_id == 1) {
|
|
227
|
-
//
|
|
227
|
+
// Iron sword - 100 coins
|
|
228
228
|
if (coins >= 100) {
|
|
229
229
|
scoreboard_add(player, "zs_coins", -100);
|
|
230
230
|
give(player, "minecraft:iron_sword", 1);
|
|
231
|
-
tell(player, "§
|
|
231
|
+
tell(player, "§aPurchased: Iron Sword");
|
|
232
232
|
} else {
|
|
233
|
-
tell(player, "§
|
|
233
|
+
tell(player, "§cNot enough coins! Need 100");
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
if (item_id == 2) {
|
|
238
|
-
//
|
|
238
|
+
// Iron armor - 200 coins
|
|
239
239
|
if (coins >= 200) {
|
|
240
240
|
scoreboard_add(player, "zs_coins", -200);
|
|
241
241
|
give(player, "minecraft:iron_chestplate", 1);
|
|
242
242
|
give(player, "minecraft:iron_leggings", 1);
|
|
243
243
|
give(player, "minecraft:iron_boots", 1);
|
|
244
|
-
tell(player, "§
|
|
244
|
+
tell(player, "§aPurchased: Iron Armor Set");
|
|
245
245
|
} else {
|
|
246
|
-
tell(player, "§
|
|
246
|
+
tell(player, "§cNot enough coins! Need 200");
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
if (item_id == 3) {
|
|
251
|
-
//
|
|
251
|
+
// Bow and arrows - 150 coins
|
|
252
252
|
if (coins >= 150) {
|
|
253
253
|
scoreboard_add(player, "zs_coins", -150);
|
|
254
254
|
give(player, "minecraft:bow", 1);
|
|
255
255
|
give(player, "minecraft:arrow", 32);
|
|
256
|
-
tell(player, "§
|
|
256
|
+
tell(player, "§aPurchased: Bow + 32 Arrows");
|
|
257
257
|
} else {
|
|
258
|
-
tell(player, "§
|
|
258
|
+
tell(player, "§cNot enough coins! Need 150");
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
if (item_id == 4) {
|
|
263
|
-
//
|
|
263
|
+
// Golden apple - 75 coins
|
|
264
264
|
if (coins >= 75) {
|
|
265
265
|
scoreboard_add(player, "zs_coins", -75);
|
|
266
266
|
give(player, "minecraft:golden_apple", 2);
|
|
267
|
-
tell(player, "§
|
|
267
|
+
tell(player, "§aPurchased: Golden Apple x2");
|
|
268
268
|
} else {
|
|
269
|
-
tell(player, "§
|
|
269
|
+
tell(player, "§cNot enough coins! Need 75");
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
// =====
|
|
274
|
+
// ===== End =====
|
|
275
275
|
fn victory() {
|
|
276
276
|
state.running = 0;
|
|
277
277
|
|
|
278
|
-
title(@a, "§
|
|
279
|
-
subtitle(@a, "§
|
|
280
|
-
announce("§4[
|
|
278
|
+
title(@a, "§6Victory!");
|
|
279
|
+
subtitle(@a, "§aYou survived 10 waves!");
|
|
280
|
+
announce("§4[Zombie Survival] §6Congratulations! Completed all 10 waves!");
|
|
281
281
|
|
|
282
282
|
foreach (p in @a) {
|
|
283
283
|
totem_effect(p);
|
|
@@ -290,11 +290,11 @@ fn victory() {
|
|
|
290
290
|
fn game_over() {
|
|
291
291
|
state.running = 0;
|
|
292
292
|
|
|
293
|
-
title(@a, "§
|
|
294
|
-
subtitle(@a, "§
|
|
295
|
-
announce("§4[
|
|
293
|
+
title(@a, "§cGame Over");
|
|
294
|
+
subtitle(@a, "§7Survived " + state.wave + " waves");
|
|
295
|
+
announce("§4[Zombie Survival] §cAll players eliminated! Highest wave: " + state.wave);
|
|
296
296
|
|
|
297
|
-
//
|
|
297
|
+
// Clean up zombies
|
|
298
298
|
kill(@e[type=zombie]);
|
|
299
299
|
kill(@e[type=husk]);
|
|
300
300
|
|
|
@@ -307,8 +307,8 @@ fn update_scoreboard() {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
fn count_entities(sel: selector) -> int {
|
|
310
|
-
//
|
|
310
|
+
// Use execute store to count entities
|
|
311
311
|
let count: int = 0;
|
|
312
|
-
//
|
|
312
|
+
// Actual implementation requires execute store
|
|
313
313
|
return count;
|
|
314
314
|
}
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
68
68
|
|
|
69
69
|
// Parsing
|
|
70
70
|
const parsedAst = new Parser(tokens, preprocessedSource, filePath).parse(namespace)
|
|
71
|
-
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst) : { program: parsedAst, warnings: [] }
|
|
71
|
+
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst, preprocessed.ranges) : { program: parsedAst, warnings: [] }
|
|
72
72
|
const ast = dceResult.program
|
|
73
73
|
|
|
74
74
|
// Type checking (warn mode - collect errors but don't block)
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -72,6 +72,7 @@ export interface DCEWarning {
|
|
|
72
72
|
code: string
|
|
73
73
|
line?: number
|
|
74
74
|
col?: number
|
|
75
|
+
filePath?: string
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export class DeadCodeEliminator {
|
|
@@ -639,8 +640,23 @@ export class DeadCodeEliminator {
|
|
|
639
640
|
}
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
export function eliminateDeadCode(
|
|
643
|
+
export function eliminateDeadCode(
|
|
644
|
+
program: Program,
|
|
645
|
+
sourceRanges?: import('../compile').SourceRange[]
|
|
646
|
+
): { program: Program; warnings: DCEWarning[] } {
|
|
643
647
|
const eliminator = new DeadCodeEliminator()
|
|
644
648
|
const result = eliminator.eliminate(program)
|
|
645
|
-
|
|
649
|
+
let warnings = eliminator.warnings
|
|
650
|
+
|
|
651
|
+
// Resolve combined-source line numbers back to original file + line
|
|
652
|
+
if (sourceRanges && sourceRanges.length > 0) {
|
|
653
|
+
const { resolveSourceLine } = require('../compile') as typeof import('../compile')
|
|
654
|
+
warnings = warnings.map(w => {
|
|
655
|
+
if (w.line == null) return w
|
|
656
|
+
const resolved = resolveSourceLine(w.line, sourceRanges)
|
|
657
|
+
return { ...w, line: resolved.line, filePath: resolved.filePath ?? w.filePath }
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return { program: result, warnings }
|
|
646
662
|
}
|