redscript-mc 1.1.0 → 1.2.1
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/CHANGELOG.md +59 -0
- package/README.md +53 -10
- package/README.zh.md +53 -10
- package/dist/__tests__/cli.test.js +138 -0
- package/dist/__tests__/codegen.test.js +25 -0
- package/dist/__tests__/dce.test.d.ts +1 -0
- package/dist/__tests__/dce.test.js +137 -0
- package/dist/__tests__/e2e.test.js +190 -12
- package/dist/__tests__/lexer.test.js +31 -4
- package/dist/__tests__/lowering.test.js +172 -9
- package/dist/__tests__/mc-integration.test.js +145 -51
- package/dist/__tests__/mc-syntax.test.js +12 -0
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/parser.test.js +90 -0
- package/dist/__tests__/runtime.test.js +21 -8
- package/dist/__tests__/typechecker.test.js +188 -0
- package/dist/ast/types.d.ts +42 -3
- package/dist/cli.js +15 -10
- package/dist/codegen/mcfunction/index.js +30 -1
- package/dist/codegen/structure/index.d.ts +4 -1
- package/dist/codegen/structure/index.js +29 -2
- package/dist/compile.d.ts +11 -0
- package/dist/compile.js +40 -6
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -3
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +2 -1
- package/dist/lexer/index.js +91 -1
- package/dist/lowering/index.d.ts +32 -1
- package/dist/lowering/index.js +476 -16
- package/dist/optimizer/dce.d.ts +23 -0
- package/dist/optimizer/dce.js +591 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +160 -26
- package/dist/typechecker/index.d.ts +19 -0
- package/dist/typechecker/index.js +392 -17
- package/docs/ARCHITECTURE.zh.md +1088 -0
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/editors/vscode/.vscodeignore +3 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/out/extension.js +1144 -72
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +6 -2
- package/examples/spiral.mcrs +79 -0
- package/logo.png +0 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +166 -0
- package/src/__tests__/codegen.test.ts +27 -0
- package/src/__tests__/dce.test.ts +129 -0
- package/src/__tests__/e2e.test.ts +201 -12
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +35 -4
- package/src/__tests__/lowering.test.ts +187 -9
- package/src/__tests__/mc-integration.test.ts +166 -51
- package/src/__tests__/mc-syntax.test.ts +14 -0
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/parser.test.ts +102 -5
- package/src/__tests__/runtime.test.ts +24 -8
- package/src/__tests__/typechecker.test.ts +204 -0
- package/src/ast/types.ts +39 -2
- package/src/cli.ts +24 -10
- package/src/codegen/mcfunction/index.ts +31 -1
- package/src/codegen/structure/index.ts +40 -2
- package/src/compile.ts +59 -7
- package/src/events/types.ts +69 -0
- package/src/index.ts +9 -4
- package/src/ir/types.ts +4 -0
- package/src/lexer/index.ts +105 -2
- package/src/lowering/index.ts +566 -18
- package/src/optimizer/dce.ts +618 -0
- package/src/parser/index.ts +187 -29
- package/src/stdlib/README.md +34 -4
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/timer.mcrs +54 -33
- package/src/typechecker/index.ts +469 -18
package/src/parser/index.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { Lexer, type Token, type TokenKind } from '../lexer'
|
|
|
9
9
|
import type {
|
|
10
10
|
Block, ConstDecl, Decorator, EntitySelector, Expr, FnDecl, GlobalDecl, LiteralExpr, Param,
|
|
11
11
|
Program, RangeExpr, SelectorFilter, SelectorKind, Span, Stmt, TypeNode, AssignOp,
|
|
12
|
-
StructDecl, StructField, ExecuteSubcommand, EnumDecl, EnumVariant, BlockPosExpr,
|
|
13
|
-
CoordComponent, LambdaParam
|
|
12
|
+
StructDecl, StructField, ExecuteSubcommand, EnumDecl, EnumVariant, BlockPosExpr, ImplBlock,
|
|
13
|
+
CoordComponent, LambdaParam, EntityTypeName
|
|
14
14
|
} from '../ast/types'
|
|
15
15
|
import type { BinOp, CmpOp } from '../ir/types'
|
|
16
16
|
import { DiagnosticError } from '../diagnostics'
|
|
@@ -23,12 +23,33 @@ const PRECEDENCE: Record<string, number> = {
|
|
|
23
23
|
'||': 1,
|
|
24
24
|
'&&': 2,
|
|
25
25
|
'==': 3, '!=': 3,
|
|
26
|
-
'<': 4, '<=': 4, '>': 4, '>=': 4,
|
|
26
|
+
'<': 4, '<=': 4, '>': 4, '>=': 4, 'is': 4,
|
|
27
27
|
'+': 5, '-': 5,
|
|
28
28
|
'*': 6, '/': 6, '%': 6,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', '+', '-', '*', '/', '%'])
|
|
31
|
+
const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', 'is', '+', '-', '*', '/', '%'])
|
|
32
|
+
|
|
33
|
+
const ENTITY_TYPE_NAMES = new Set<EntityTypeName>([
|
|
34
|
+
'entity',
|
|
35
|
+
'Player',
|
|
36
|
+
'Mob',
|
|
37
|
+
'HostileMob',
|
|
38
|
+
'PassiveMob',
|
|
39
|
+
'Zombie',
|
|
40
|
+
'Skeleton',
|
|
41
|
+
'Creeper',
|
|
42
|
+
'Spider',
|
|
43
|
+
'Enderman',
|
|
44
|
+
'Pig',
|
|
45
|
+
'Cow',
|
|
46
|
+
'Sheep',
|
|
47
|
+
'Chicken',
|
|
48
|
+
'Villager',
|
|
49
|
+
'ArmorStand',
|
|
50
|
+
'Item',
|
|
51
|
+
'Arrow',
|
|
52
|
+
])
|
|
32
53
|
|
|
33
54
|
function computeIsSingle(raw: string): boolean {
|
|
34
55
|
if (/^@[spr](\[|$)/.test(raw)) return true
|
|
@@ -135,6 +156,7 @@ export class Parser {
|
|
|
135
156
|
const globals: GlobalDecl[] = []
|
|
136
157
|
const declarations: FnDecl[] = []
|
|
137
158
|
const structs: StructDecl[] = []
|
|
159
|
+
const implBlocks: ImplBlock[] = []
|
|
138
160
|
const enums: EnumDecl[] = []
|
|
139
161
|
const consts: ConstDecl[] = []
|
|
140
162
|
|
|
@@ -152,6 +174,8 @@ export class Parser {
|
|
|
152
174
|
globals.push(this.parseGlobalDecl(true))
|
|
153
175
|
} else if (this.check('struct')) {
|
|
154
176
|
structs.push(this.parseStructDecl())
|
|
177
|
+
} else if (this.check('impl')) {
|
|
178
|
+
implBlocks.push(this.parseImplBlock())
|
|
155
179
|
} else if (this.check('enum')) {
|
|
156
180
|
enums.push(this.parseEnumDecl())
|
|
157
181
|
} else if (this.check('const')) {
|
|
@@ -161,7 +185,7 @@ export class Parser {
|
|
|
161
185
|
}
|
|
162
186
|
}
|
|
163
187
|
|
|
164
|
-
return { namespace, globals, declarations, structs, enums, consts }
|
|
188
|
+
return { namespace, globals, declarations, structs, implBlocks, enums, consts }
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
// -------------------------------------------------------------------------
|
|
@@ -219,6 +243,20 @@ export class Parser {
|
|
|
219
243
|
return this.withLoc({ name, variants }, enumToken)
|
|
220
244
|
}
|
|
221
245
|
|
|
246
|
+
private parseImplBlock(): ImplBlock {
|
|
247
|
+
const implToken = this.expect('impl')
|
|
248
|
+
const typeName = this.expect('ident').value
|
|
249
|
+
this.expect('{')
|
|
250
|
+
|
|
251
|
+
const methods: FnDecl[] = []
|
|
252
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
253
|
+
methods.push(this.parseFnDecl(typeName))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.expect('}')
|
|
257
|
+
return this.withLoc({ kind: 'impl_block', typeName, methods }, implToken)
|
|
258
|
+
}
|
|
259
|
+
|
|
222
260
|
private parseConstDecl(): ConstDecl {
|
|
223
261
|
const constToken = this.expect('const')
|
|
224
262
|
const name = this.expect('ident').value
|
|
@@ -245,17 +283,17 @@ export class Parser {
|
|
|
245
283
|
// Function Declaration
|
|
246
284
|
// -------------------------------------------------------------------------
|
|
247
285
|
|
|
248
|
-
private parseFnDecl(): FnDecl {
|
|
286
|
+
private parseFnDecl(implTypeName?: string): FnDecl {
|
|
249
287
|
const decorators = this.parseDecorators()
|
|
250
288
|
|
|
251
289
|
const fnToken = this.expect('fn')
|
|
252
290
|
const name = this.expect('ident').value
|
|
253
291
|
this.expect('(')
|
|
254
|
-
const params = this.parseParams()
|
|
292
|
+
const params = this.parseParams(implTypeName)
|
|
255
293
|
this.expect(')')
|
|
256
294
|
|
|
257
295
|
let returnType: TypeNode = { kind: 'named', name: 'void' }
|
|
258
|
-
if (this.match('->')) {
|
|
296
|
+
if (this.match('->') || this.match(':')) {
|
|
259
297
|
returnType = this.parseType()
|
|
260
298
|
}
|
|
261
299
|
|
|
@@ -277,7 +315,7 @@ export class Parser {
|
|
|
277
315
|
}
|
|
278
316
|
|
|
279
317
|
private parseDecoratorValue(value: string): Decorator {
|
|
280
|
-
// Parse @tick
|
|
318
|
+
// Parse @tick, @on(PlayerDeath), or @on_trigger("name")
|
|
281
319
|
const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/)
|
|
282
320
|
if (!match) {
|
|
283
321
|
this.error(`Invalid decorator: ${value}`)
|
|
@@ -292,6 +330,14 @@ export class Parser {
|
|
|
292
330
|
|
|
293
331
|
const args: Decorator['args'] = {}
|
|
294
332
|
|
|
333
|
+
if (name === 'on') {
|
|
334
|
+
const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/)
|
|
335
|
+
if (eventTypeMatch) {
|
|
336
|
+
args.eventType = eventTypeMatch[1]
|
|
337
|
+
return { name, args }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
295
341
|
// Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
|
|
296
342
|
if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
|
|
297
343
|
const strMatch = argsStr.match(/^"([^"]*)"$/)
|
|
@@ -328,15 +374,20 @@ export class Parser {
|
|
|
328
374
|
return { name, args }
|
|
329
375
|
}
|
|
330
376
|
|
|
331
|
-
private parseParams(): Param[] {
|
|
377
|
+
private parseParams(implTypeName?: string): Param[] {
|
|
332
378
|
const params: Param[] = []
|
|
333
379
|
|
|
334
380
|
if (!this.check(')')) {
|
|
335
381
|
do {
|
|
336
382
|
const paramToken = this.expect('ident')
|
|
337
383
|
const name = paramToken.value
|
|
338
|
-
|
|
339
|
-
|
|
384
|
+
let type: TypeNode
|
|
385
|
+
if (implTypeName && params.length === 0 && name === 'self' && !this.check(':')) {
|
|
386
|
+
type = { kind: 'struct', name: implTypeName }
|
|
387
|
+
} else {
|
|
388
|
+
this.expect(':')
|
|
389
|
+
type = this.parseType()
|
|
390
|
+
}
|
|
340
391
|
let defaultValue: Expr | undefined
|
|
341
392
|
if (this.match('=')) {
|
|
342
393
|
defaultValue = this.parseExpr()
|
|
@@ -740,6 +791,15 @@ export class Parser {
|
|
|
740
791
|
if (prec < minPrec) break
|
|
741
792
|
|
|
742
793
|
const opToken = this.advance()
|
|
794
|
+
if (op === 'is') {
|
|
795
|
+
const entityType = this.parseEntityTypeName()
|
|
796
|
+
left = this.withLoc(
|
|
797
|
+
{ kind: 'is_check', expr: left, entityType },
|
|
798
|
+
this.getLocToken(left) ?? opToken
|
|
799
|
+
)
|
|
800
|
+
continue
|
|
801
|
+
}
|
|
802
|
+
|
|
743
803
|
const right = this.parseBinaryExpr(prec + 1) // left associative
|
|
744
804
|
left = this.withLoc(
|
|
745
805
|
{ kind: 'binary', op: op as BinOp | CmpOp | '&&' | '||', left, right },
|
|
@@ -766,6 +826,14 @@ export class Parser {
|
|
|
766
826
|
return this.parsePostfixExpr()
|
|
767
827
|
}
|
|
768
828
|
|
|
829
|
+
private parseEntityTypeName(): EntityTypeName {
|
|
830
|
+
const token = this.expect('ident')
|
|
831
|
+
if (ENTITY_TYPE_NAMES.has(token.value as EntityTypeName)) {
|
|
832
|
+
return token.value as EntityTypeName
|
|
833
|
+
}
|
|
834
|
+
this.error(`Unknown entity type '${token.value}'`)
|
|
835
|
+
}
|
|
836
|
+
|
|
769
837
|
private isSubtraction(): boolean {
|
|
770
838
|
// Check if this minus is binary (subtraction) by looking at previous token
|
|
771
839
|
// If previous was a value (literal, ident, ), ]) it's subtraction
|
|
@@ -871,6 +939,16 @@ export class Parser {
|
|
|
871
939
|
private parsePrimaryExpr(): Expr {
|
|
872
940
|
const token = this.peek()
|
|
873
941
|
|
|
942
|
+
if (token.kind === 'ident' && this.peek(1).kind === '::') {
|
|
943
|
+
const typeToken = this.advance()
|
|
944
|
+
this.expect('::')
|
|
945
|
+
const methodToken = this.expect('ident')
|
|
946
|
+
this.expect('(')
|
|
947
|
+
const args = this.parseArgs()
|
|
948
|
+
this.expect(')')
|
|
949
|
+
return this.withLoc({ kind: 'static_call', type: typeToken.value, method: methodToken.value, args }, typeToken)
|
|
950
|
+
}
|
|
951
|
+
|
|
874
952
|
if (token.kind === 'ident' && this.peek(1).kind === '=>') {
|
|
875
953
|
return this.parseSingleParamLambda()
|
|
876
954
|
}
|
|
@@ -887,6 +965,18 @@ export class Parser {
|
|
|
887
965
|
return this.withLoc({ kind: 'float_lit', value: parseFloat(token.value) }, token)
|
|
888
966
|
}
|
|
889
967
|
|
|
968
|
+
// Relative coordinate: ~ ~5 ~-3 ~0.5
|
|
969
|
+
if (token.kind === 'rel_coord') {
|
|
970
|
+
this.advance()
|
|
971
|
+
return this.withLoc({ kind: 'rel_coord', value: token.value }, token)
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Local coordinate: ^ ^5 ^-3 ^0.5
|
|
975
|
+
if (token.kind === 'local_coord') {
|
|
976
|
+
this.advance()
|
|
977
|
+
return this.withLoc({ kind: 'local_coord', value: token.value }, token)
|
|
978
|
+
}
|
|
979
|
+
|
|
890
980
|
// NBT suffix literals
|
|
891
981
|
if (token.kind === 'byte_lit') {
|
|
892
982
|
this.advance()
|
|
@@ -911,6 +1001,11 @@ export class Parser {
|
|
|
911
1001
|
return this.parseStringExpr(token)
|
|
912
1002
|
}
|
|
913
1003
|
|
|
1004
|
+
if (token.kind === 'f_string') {
|
|
1005
|
+
this.advance()
|
|
1006
|
+
return this.parseFStringExpr(token)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
914
1009
|
// MC name literal: #health → mc_name node (value = "health", without #)
|
|
915
1010
|
if (token.kind === 'mc_name') {
|
|
916
1011
|
this.advance()
|
|
@@ -1091,6 +1186,67 @@ export class Parser {
|
|
|
1091
1186
|
return this.withLoc({ kind: 'str_interp', parts }, token)
|
|
1092
1187
|
}
|
|
1093
1188
|
|
|
1189
|
+
private parseFStringExpr(token: Token): Expr {
|
|
1190
|
+
const parts: Array<{ kind: 'text'; value: string } | { kind: 'expr'; expr: Expr }> = []
|
|
1191
|
+
let current = ''
|
|
1192
|
+
let index = 0
|
|
1193
|
+
|
|
1194
|
+
while (index < token.value.length) {
|
|
1195
|
+
if (token.value[index] === '{') {
|
|
1196
|
+
if (current) {
|
|
1197
|
+
parts.push({ kind: 'text', value: current })
|
|
1198
|
+
current = ''
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
index++
|
|
1202
|
+
let depth = 1
|
|
1203
|
+
let exprSource = ''
|
|
1204
|
+
let inString = false
|
|
1205
|
+
|
|
1206
|
+
while (index < token.value.length && depth > 0) {
|
|
1207
|
+
const char = token.value[index]
|
|
1208
|
+
|
|
1209
|
+
if (char === '"' && token.value[index - 1] !== '\\') {
|
|
1210
|
+
inString = !inString
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (!inString) {
|
|
1214
|
+
if (char === '{') {
|
|
1215
|
+
depth++
|
|
1216
|
+
} else if (char === '}') {
|
|
1217
|
+
depth--
|
|
1218
|
+
if (depth === 0) {
|
|
1219
|
+
index++
|
|
1220
|
+
break
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (depth > 0) {
|
|
1226
|
+
exprSource += char
|
|
1227
|
+
}
|
|
1228
|
+
index++
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (depth !== 0) {
|
|
1232
|
+
this.error('Unterminated f-string interpolation')
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
parts.push({ kind: 'expr', expr: this.parseEmbeddedExpr(exprSource) })
|
|
1236
|
+
continue
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
current += token.value[index]
|
|
1240
|
+
index++
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (current) {
|
|
1244
|
+
parts.push({ kind: 'text', value: current })
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return this.withLoc({ kind: 'f_string', parts }, token)
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1094
1250
|
private parseEmbeddedExpr(source: string): Expr {
|
|
1095
1251
|
const tokens = new Lexer(source, this.filePath).tokenize()
|
|
1096
1252
|
const parser = new Parser(tokens, source, this.filePath)
|
|
@@ -1265,20 +1421,11 @@ export class Parser {
|
|
|
1265
1421
|
return this.peek(offset + 1).kind === 'int_lit' ? 2 : 0
|
|
1266
1422
|
}
|
|
1267
1423
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const next = this.peek(offset + 1)
|
|
1273
|
-
if (next.kind === ',' || next.kind === ')') {
|
|
1424
|
+
// rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) are single tokens now
|
|
1425
|
+
if (token.kind === 'rel_coord' || token.kind === 'local_coord') {
|
|
1274
1426
|
return 1
|
|
1275
1427
|
}
|
|
1276
|
-
|
|
1277
|
-
return 2
|
|
1278
|
-
}
|
|
1279
|
-
if (next.kind === '-' && this.peek(offset + 2).kind === 'int_lit') {
|
|
1280
|
-
return 3
|
|
1281
|
-
}
|
|
1428
|
+
|
|
1282
1429
|
return 0
|
|
1283
1430
|
}
|
|
1284
1431
|
|
|
@@ -1296,17 +1443,28 @@ export class Parser {
|
|
|
1296
1443
|
private parseCoordComponent(): CoordComponent {
|
|
1297
1444
|
const token = this.peek()
|
|
1298
1445
|
|
|
1299
|
-
|
|
1446
|
+
// Handle rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) tokens
|
|
1447
|
+
if (token.kind === 'rel_coord') {
|
|
1448
|
+
this.advance()
|
|
1449
|
+
// Parse the offset from the token value (e.g., "~5" -> 5, "~" -> 0, "~-3" -> -3)
|
|
1450
|
+
const offset = this.parseCoordOffsetFromValue(token.value.slice(1))
|
|
1451
|
+
return { kind: 'relative', offset }
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (token.kind === 'local_coord') {
|
|
1300
1455
|
this.advance()
|
|
1301
|
-
const offset = this.
|
|
1302
|
-
return
|
|
1303
|
-
? { kind: 'relative', offset }
|
|
1304
|
-
: { kind: 'local', offset }
|
|
1456
|
+
const offset = this.parseCoordOffsetFromValue(token.value.slice(1))
|
|
1457
|
+
return { kind: 'local', offset }
|
|
1305
1458
|
}
|
|
1306
1459
|
|
|
1307
1460
|
return { kind: 'absolute', value: this.parseSignedCoordOffset(true) }
|
|
1308
1461
|
}
|
|
1309
1462
|
|
|
1463
|
+
private parseCoordOffsetFromValue(value: string): number {
|
|
1464
|
+
if (value === '' || value === undefined) return 0
|
|
1465
|
+
return parseFloat(value)
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1310
1468
|
private parseSignedCoordOffset(requireValue = false): number {
|
|
1311
1469
|
let sign = 1
|
|
1312
1470
|
if (this.match('-')) {
|
package/src/stdlib/README.md
CHANGED
|
@@ -70,10 +70,13 @@ Cooldown system using scoreboards.
|
|
|
70
70
|
- `is_on_cooldown(target)` → int
|
|
71
71
|
|
|
72
72
|
### timer.mcrs
|
|
73
|
-
Timer utilities.
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
76
|
-
- `
|
|
73
|
+
Timer utilities with an OOP API.
|
|
74
|
+
- `Timer::new(ticks)` → `Timer`
|
|
75
|
+
- `timer.start()`, `timer.pause()`, `timer.reset()`
|
|
76
|
+
- `timer.done()` → bool
|
|
77
|
+
- `timer.elapsed()` → int
|
|
78
|
+
- `timer.remaining()` → int
|
|
79
|
+
- `timer.tick()` — manual tick update; current runtime uses one shared timer slot
|
|
77
80
|
|
|
78
81
|
### combat.mcrs
|
|
79
82
|
Combat helpers.
|
|
@@ -162,3 +165,30 @@ Player input detection (right click, sneak, look direction).
|
|
|
162
165
|
// Position ranges
|
|
163
166
|
@a[x=-5..5, y=62..68, z=-5..5] // In specific area
|
|
164
167
|
```
|
|
168
|
+
|
|
169
|
+
### tags.mcrs
|
|
170
|
+
Minecraft Java Edition tag constants generated from the Minecraft Fandom tag list.
|
|
171
|
+
|
|
172
|
+
#### Coverage
|
|
173
|
+
- 171 `BLOCK_*` constants for Java block tags
|
|
174
|
+
- 14 `ENTITY_*` constants for Java entity type tags
|
|
175
|
+
- 99 `ITEM_*` constants for Java item tags
|
|
176
|
+
- 2 `FLUID_*` constants for Java fluid tags
|
|
177
|
+
- 27 `DAMAGE_*` constants for Java damage type tags
|
|
178
|
+
|
|
179
|
+
#### Naming
|
|
180
|
+
- Constants use `SCREAMING_SNAKE_CASE`
|
|
181
|
+
- Each constant is prefixed by category: `BLOCK_`, `ENTITY_`, `ITEM_`, `FLUID_`, `DAMAGE_`
|
|
182
|
+
- Each value is the full tag selector string, for example `#minecraft:mineable/axe`
|
|
183
|
+
|
|
184
|
+
#### Usage
|
|
185
|
+
```mcrs
|
|
186
|
+
import "stdlib/tags.mcrs"
|
|
187
|
+
|
|
188
|
+
// Select skeleton variants
|
|
189
|
+
kill(@e[type=ENTITY_SKELETONS]);
|
|
190
|
+
|
|
191
|
+
// Use block and item tags in your own helpers
|
|
192
|
+
const LOGS: string = BLOCK_LOGS;
|
|
193
|
+
const SWORDS: string = ITEM_SWORDS;
|
|
194
|
+
```
|