redscript-mc 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
  4. package/CHANGELOG.md +112 -0
  5. package/CONTRIBUTING.md +140 -0
  6. package/README.md +28 -19
  7. package/README.zh.md +28 -19
  8. package/dist/__tests__/cli.test.js +148 -10
  9. package/dist/__tests__/codegen.test.js +26 -1
  10. package/dist/__tests__/diagnostics.test.js +5 -5
  11. package/dist/__tests__/e2e.test.js +336 -17
  12. package/dist/__tests__/formatter.test.d.ts +1 -0
  13. package/dist/__tests__/formatter.test.js +40 -0
  14. package/dist/__tests__/lexer.test.js +12 -2
  15. package/dist/__tests__/lowering.test.js +200 -12
  16. package/dist/__tests__/mc-integration.test.js +370 -31
  17. package/dist/__tests__/mc-syntax.test.js +3 -3
  18. package/dist/__tests__/nbt.test.js +2 -2
  19. package/dist/__tests__/optimizer-advanced.test.js +5 -5
  20. package/dist/__tests__/parser.test.js +80 -0
  21. package/dist/__tests__/runtime.test.js +9 -9
  22. package/dist/__tests__/typechecker.test.js +158 -0
  23. package/dist/ast/types.d.ts +40 -3
  24. package/dist/cli.js +25 -7
  25. package/dist/codegen/mcfunction/index.d.ts +1 -1
  26. package/dist/codegen/mcfunction/index.js +38 -3
  27. package/dist/codegen/structure/index.js +32 -1
  28. package/dist/compile.d.ts +10 -0
  29. package/dist/compile.js +36 -5
  30. package/dist/events/types.d.ts +35 -0
  31. package/dist/events/types.js +59 -0
  32. package/dist/formatter/index.d.ts +1 -0
  33. package/dist/formatter/index.js +26 -0
  34. package/dist/index.js +3 -2
  35. package/dist/ir/builder.d.ts +2 -1
  36. package/dist/ir/types.d.ts +11 -2
  37. package/dist/ir/types.js +1 -1
  38. package/dist/lexer/index.d.ts +1 -1
  39. package/dist/lexer/index.js +2 -0
  40. package/dist/lowering/index.d.ts +34 -1
  41. package/dist/lowering/index.js +622 -23
  42. package/dist/mc-test/runner.d.ts +2 -2
  43. package/dist/mc-test/runner.js +3 -3
  44. package/dist/mc-test/setup.js +2 -2
  45. package/dist/parser/index.d.ts +4 -0
  46. package/dist/parser/index.js +153 -16
  47. package/dist/typechecker/index.d.ts +17 -0
  48. package/dist/typechecker/index.js +343 -17
  49. package/docs/COMPILATION_STATS.md +24 -24
  50. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  51. package/docs/IMPLEMENTATION_GUIDE.md +1 -1
  52. package/docs/STRUCTURE_TARGET.md +1 -1
  53. package/editors/vscode/.vscodeignore +1 -0
  54. package/editors/vscode/CHANGELOG.md +9 -0
  55. package/editors/vscode/icons/mcrs.svg +7 -0
  56. package/editors/vscode/icons/redscript-icons.json +10 -0
  57. package/editors/vscode/out/extension.js +1295 -80
  58. package/editors/vscode/package-lock.json +2 -2
  59. package/editors/vscode/package.json +10 -3
  60. package/editors/vscode/src/hover.ts +55 -2
  61. package/editors/vscode/src/symbols.ts +42 -0
  62. package/package.json +1 -1
  63. package/src/__tests__/cli.test.ts +176 -10
  64. package/src/__tests__/codegen.test.ts +28 -1
  65. package/src/__tests__/diagnostics.test.ts +5 -5
  66. package/src/__tests__/e2e.test.ts +335 -17
  67. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  68. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  69. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  70. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  71. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  72. package/src/__tests__/lexer.test.ts +14 -2
  73. package/src/__tests__/lowering.test.ts +226 -12
  74. package/src/__tests__/mc-integration.test.ts +421 -31
  75. package/src/__tests__/mc-syntax.test.ts +3 -3
  76. package/src/__tests__/nbt.test.ts +2 -2
  77. package/src/__tests__/optimizer-advanced.test.ts +5 -5
  78. package/src/__tests__/parser.test.ts +91 -5
  79. package/src/__tests__/runtime.test.ts +9 -9
  80. package/src/__tests__/typechecker.test.ts +171 -0
  81. package/src/ast/types.ts +44 -3
  82. package/src/cli.ts +10 -10
  83. package/src/codegen/mcfunction/index.ts +40 -3
  84. package/src/codegen/structure/index.ts +35 -1
  85. package/src/compile.ts +54 -6
  86. package/src/events/types.ts +69 -0
  87. package/src/examples/capture_the_flag.mcrs +208 -0
  88. package/src/examples/{counter.rs → counter.mcrs} +1 -1
  89. package/src/examples/hunger_games.mcrs +301 -0
  90. package/src/examples/new_features_demo.mcrs +193 -0
  91. package/src/examples/parkour_race.mcrs +233 -0
  92. package/src/examples/rpg.mcrs +13 -0
  93. package/src/examples/{shop.rs → shop.mcrs} +1 -1
  94. package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
  95. package/src/examples/{turret.rs → turret.mcrs} +1 -1
  96. package/src/examples/zombie_survival.mcrs +314 -0
  97. package/src/index.ts +4 -3
  98. package/src/ir/builder.ts +3 -1
  99. package/src/ir/types.ts +12 -2
  100. package/src/lexer/index.ts +3 -1
  101. package/src/lowering/index.ts +684 -24
  102. package/src/mc-test/runner.ts +3 -3
  103. package/src/mc-test/setup.ts +2 -2
  104. package/src/parser/index.ts +170 -19
  105. package/src/stdlib/README.md +178 -140
  106. package/src/stdlib/bossbar.mcrs +68 -0
  107. package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
  108. package/src/stdlib/effects.mcrs +64 -0
  109. package/src/stdlib/interactions.mcrs +195 -0
  110. package/src/stdlib/inventory.mcrs +38 -0
  111. package/src/stdlib/mobs.mcrs +99 -0
  112. package/src/stdlib/particles.mcrs +52 -0
  113. package/src/stdlib/sets.mcrs +20 -0
  114. package/src/stdlib/spawn.mcrs +41 -0
  115. package/src/stdlib/tags.mcrs +951 -0
  116. package/src/stdlib/teams.mcrs +68 -0
  117. package/src/stdlib/timer.mcrs +72 -0
  118. package/src/stdlib/world.mcrs +92 -0
  119. package/src/typechecker/index.ts +404 -18
  120. package/src/examples/rpg.rs +0 -13
  121. package/src/stdlib/mobs.rs +0 -99
  122. package/src/stdlib/timer.rs +0 -51
  123. /package/src/examples/{arena.rs → arena.mcrs} +0 -0
  124. /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
  125. /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
  126. /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
  127. /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
  128. /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
  129. /package/src/stdlib/{math.rs → math.mcrs} +0 -0
  130. /package/src/stdlib/{player.rs → player.mcrs} +0 -0
  131. /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
  132. /package/src/templates/{combat.rs → combat.mcrs} +0 -0
  133. /package/src/templates/{economy.rs → economy.mcrs} +0 -0
  134. /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
  135. /package/src/templates/{quest.rs → quest.mcrs} +0 -0
  136. /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * RedScript MC Integration Test Runner
3
3
  *
4
- * Compiles a .rs file, installs it to a running Paper server,
4
+ * Compiles a .mcrs file, installs it to a running Paper server,
5
5
  * runs test scenarios, and reports results.
6
6
  *
7
7
  * Usage:
8
- * npx ts-node src/mc-test/runner.ts src/examples/counter.rs
8
+ * npx ts-node src/mc-test/runner.ts src/examples/counter.mcrs
9
9
  *
10
10
  * Requires:
11
11
  * - Paper server running with TestHarnessPlugin
@@ -105,7 +105,7 @@ export async function runMCTests(
105
105
  if (require.main === module) {
106
106
  const sourceFile = process.argv[2]
107
107
  if (!sourceFile) {
108
- console.error('Usage: ts-node runner.ts <source.rs>')
108
+ console.error('Usage: ts-node runner.ts <source.mcrs>')
109
109
  process.exit(1)
110
110
  }
111
111
 
@@ -36,11 +36,11 @@ function main() {
36
36
  // Example files
37
37
  const exampleNamespaces = ['counter', 'world_manager']
38
38
  for (const ns of exampleNamespaces) {
39
- const file = path.join(EXAMPLES_DIR, `${ns}.rs`)
39
+ const file = path.join(EXAMPLES_DIR, `${ns}.mcrs`)
40
40
  if (fs.existsSync(file)) {
41
41
  writeFixture(fs.readFileSync(file, 'utf-8'), ns)
42
42
  } else {
43
- console.log(` ⚠ ${ns}.rs not found, skipping`)
43
+ console.log(` ⚠ ${ns}.mcrs not found, skipping`)
44
44
  }
45
45
  }
46
46
 
@@ -7,10 +7,10 @@
7
7
 
8
8
  import { Lexer, type Token, type TokenKind } from '../lexer'
9
9
  import type {
10
- Block, ConstDecl, Decorator, EntitySelector, Expr, FnDecl, LiteralExpr, Param,
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
@@ -132,8 +153,10 @@ export class Parser {
132
153
 
133
154
  parse(defaultNamespace = 'redscript'): Program {
134
155
  let namespace = defaultNamespace
156
+ const globals: GlobalDecl[] = []
135
157
  const declarations: FnDecl[] = []
136
158
  const structs: StructDecl[] = []
159
+ const implBlocks: ImplBlock[] = []
137
160
  const enums: EnumDecl[] = []
138
161
  const consts: ConstDecl[] = []
139
162
 
@@ -147,8 +170,12 @@ export class Parser {
147
170
 
148
171
  // Parse struct and function declarations
149
172
  while (!this.check('eof')) {
150
- if (this.check('struct')) {
173
+ if (this.check('let')) {
174
+ globals.push(this.parseGlobalDecl(true))
175
+ } else if (this.check('struct')) {
151
176
  structs.push(this.parseStructDecl())
177
+ } else if (this.check('impl')) {
178
+ implBlocks.push(this.parseImplBlock())
152
179
  } else if (this.check('enum')) {
153
180
  enums.push(this.parseEnumDecl())
154
181
  } else if (this.check('const')) {
@@ -158,7 +185,7 @@ export class Parser {
158
185
  }
159
186
  }
160
187
 
161
- return { namespace, declarations, structs, enums, consts }
188
+ return { namespace, globals, declarations, structs, implBlocks, enums, consts }
162
189
  }
163
190
 
164
191
  // -------------------------------------------------------------------------
@@ -216,6 +243,20 @@ export class Parser {
216
243
  return this.withLoc({ name, variants }, enumToken)
217
244
  }
218
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
+
219
260
  private parseConstDecl(): ConstDecl {
220
261
  const constToken = this.expect('const')
221
262
  const name = this.expect('ident').value
@@ -227,21 +268,32 @@ export class Parser {
227
268
  return this.withLoc({ name, type, value }, constToken)
228
269
  }
229
270
 
271
+ private parseGlobalDecl(mutable: boolean): GlobalDecl {
272
+ const token = this.advance() // consume 'let'
273
+ const name = this.expect('ident').value
274
+ this.expect(':')
275
+ const type = this.parseType()
276
+ this.expect('=')
277
+ const init = this.parseExpr()
278
+ this.expect(';')
279
+ return this.withLoc({ kind: 'global', name, type, init, mutable }, token)
280
+ }
281
+
230
282
  // -------------------------------------------------------------------------
231
283
  // Function Declaration
232
284
  // -------------------------------------------------------------------------
233
285
 
234
- private parseFnDecl(): FnDecl {
286
+ private parseFnDecl(implTypeName?: string): FnDecl {
235
287
  const decorators = this.parseDecorators()
236
288
 
237
289
  const fnToken = this.expect('fn')
238
290
  const name = this.expect('ident').value
239
291
  this.expect('(')
240
- const params = this.parseParams()
292
+ const params = this.parseParams(implTypeName)
241
293
  this.expect(')')
242
294
 
243
295
  let returnType: TypeNode = { kind: 'named', name: 'void' }
244
- if (this.match('->')) {
296
+ if (this.match('->') || this.match(':')) {
245
297
  returnType = this.parseType()
246
298
  }
247
299
 
@@ -263,7 +315,7 @@ export class Parser {
263
315
  }
264
316
 
265
317
  private parseDecoratorValue(value: string): Decorator {
266
- // Parse @tick or @on_trigger("name") or @on_advancement("story/mine_diamond")
318
+ // Parse @tick, @on(PlayerDeath), or @on_trigger("name")
267
319
  const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/)
268
320
  if (!match) {
269
321
  this.error(`Invalid decorator: ${value}`)
@@ -278,6 +330,14 @@ export class Parser {
278
330
 
279
331
  const args: Decorator['args'] = {}
280
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
+
281
341
  // Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
282
342
  if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
283
343
  const strMatch = argsStr.match(/^"([^"]*)"$/)
@@ -314,15 +374,20 @@ export class Parser {
314
374
  return { name, args }
315
375
  }
316
376
 
317
- private parseParams(): Param[] {
377
+ private parseParams(implTypeName?: string): Param[] {
318
378
  const params: Param[] = []
319
379
 
320
380
  if (!this.check(')')) {
321
381
  do {
322
382
  const paramToken = this.expect('ident')
323
383
  const name = paramToken.value
324
- this.expect(':')
325
- const type = this.parseType()
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
+ }
326
391
  let defaultValue: Expr | undefined
327
392
  if (this.match('=')) {
328
393
  defaultValue = this.parseExpr()
@@ -650,15 +715,15 @@ export class Parser {
650
715
  if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
651
716
  this.advance() // consume 'entity'
652
717
  }
653
- const selector = this.parseSelector()
654
- subcommands.push({ kind: 'if_entity', selector })
718
+ const selectorOrVar = this.parseSelectorOrVarSelector()
719
+ subcommands.push({ kind: 'if_entity', ...selectorOrVar })
655
720
  } else if (this.match('unless')) {
656
721
  // Expect 'entity' keyword (as ident) or just parse selector directly
657
722
  if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
658
723
  this.advance() // consume 'entity'
659
724
  }
660
- const selector = this.parseSelector()
661
- subcommands.push({ kind: 'unless_entity', selector })
725
+ const selectorOrVar = this.parseSelectorOrVarSelector()
726
+ subcommands.push({ kind: 'unless_entity', ...selectorOrVar })
662
727
  } else if (this.match('in')) {
663
728
  const dim = this.expect('ident').value
664
729
  subcommands.push({ kind: 'in', dimension: dim })
@@ -726,6 +791,15 @@ export class Parser {
726
791
  if (prec < minPrec) break
727
792
 
728
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
+
729
803
  const right = this.parseBinaryExpr(prec + 1) // left associative
730
804
  left = this.withLoc(
731
805
  { kind: 'binary', op: op as BinOp | CmpOp | '&&' | '||', left, right },
@@ -752,6 +826,14 @@ export class Parser {
752
826
  return this.parsePostfixExpr()
753
827
  }
754
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
+
755
837
  private isSubtraction(): boolean {
756
838
  // Check if this minus is binary (subtraction) by looking at previous token
757
839
  // If previous was a value (literal, ident, ), ]) it's subtraction
@@ -782,6 +864,10 @@ export class Parser {
782
864
  'has_tag': '__entity_has_tag',
783
865
  'push': '__array_push',
784
866
  'pop': '__array_pop',
867
+ 'add': 'set_add',
868
+ 'contains': 'set_contains',
869
+ 'remove': 'set_remove',
870
+ 'clear': 'set_clear',
785
871
  }
786
872
  const internalFn = methodMap[expr.field]
787
873
  if (internalFn) {
@@ -793,7 +879,14 @@ export class Parser {
793
879
  )
794
880
  continue
795
881
  }
796
- this.error(`Unknown method '${expr.field}'`)
882
+ // Generic method sugar: obj.method(args) method(obj, args)
883
+ const args = this.parseArgs()
884
+ this.expect(')')
885
+ expr = this.withLoc(
886
+ { kind: 'call', fn: expr.field, args: [expr.obj, ...args] },
887
+ this.getLocToken(expr) ?? openParenToken
888
+ )
889
+ continue
797
890
  }
798
891
  const args = this.parseArgs()
799
892
  this.expect(')')
@@ -846,6 +939,16 @@ export class Parser {
846
939
  private parsePrimaryExpr(): Expr {
847
940
  const token = this.peek()
848
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
+
849
952
  if (token.kind === 'ident' && this.peek(1).kind === '=>') {
850
953
  return this.parseSingleParamLambda()
851
954
  }
@@ -1308,6 +1411,39 @@ export class Parser {
1308
1411
  return this.parseSelectorValue(token.value)
1309
1412
  }
1310
1413
 
1414
+ // Parse either a selector (@a[...]) or a variable with filters (p[...])
1415
+ // Returns { selector } for selectors or { varName, filters } for variables
1416
+ private parseSelectorOrVarSelector(): { selector?: EntitySelector, varName?: string, filters?: SelectorFilter } {
1417
+ if (this.check('selector')) {
1418
+ return { selector: this.parseSelector() }
1419
+ }
1420
+
1421
+ // Must be an identifier (variable) possibly with filters
1422
+ const varToken = this.expect('ident')
1423
+ const varName = varToken.value
1424
+
1425
+ // Check for optional filters [...]
1426
+ if (this.check('[')) {
1427
+ this.advance() // consume '['
1428
+ // Collect everything until ']'
1429
+ let filterStr = ''
1430
+ let depth = 1
1431
+ while (depth > 0 && !this.check('eof')) {
1432
+ if (this.check('[')) depth++
1433
+ else if (this.check(']')) depth--
1434
+ if (depth > 0) {
1435
+ filterStr += this.peek().value ?? this.peek().kind
1436
+ this.advance()
1437
+ }
1438
+ }
1439
+ this.expect(']')
1440
+ const filters = this.parseSelectorFilters(filterStr)
1441
+ return { varName, filters }
1442
+ }
1443
+
1444
+ return { varName }
1445
+ }
1446
+
1311
1447
  private parseSelectorValue(value: string): EntitySelector {
1312
1448
  // Parse @e[type=zombie, distance=..5]
1313
1449
  const bracketIndex = value.indexOf('[')
@@ -1364,6 +1500,21 @@ export class Parser {
1364
1500
  case 'scores':
1365
1501
  filters.scores = this.parseScoresFilter(val)
1366
1502
  break
1503
+ case 'x':
1504
+ filters.x = this.parseRangeValue(val)
1505
+ break
1506
+ case 'y':
1507
+ filters.y = this.parseRangeValue(val)
1508
+ break
1509
+ case 'z':
1510
+ filters.z = this.parseRangeValue(val)
1511
+ break
1512
+ case 'x_rotation':
1513
+ filters.x_rotation = this.parseRangeValue(val)
1514
+ break
1515
+ case 'y_rotation':
1516
+ filters.y_rotation = this.parseRangeValue(val)
1517
+ break
1367
1518
  }
1368
1519
  }
1369
1520