redscript-mc 1.2.12 → 1.2.14

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.
@@ -45,6 +45,16 @@ const diagnostics_1 = require("../diagnostics");
45
45
  const path = __importStar(require("path"));
46
46
  const types_1 = require("../events/types");
47
47
  // ---------------------------------------------------------------------------
48
+ // Macro-aware builtins (MC 1.20.2+)
49
+ // These builtins generate commands where parameter variables cannot appear
50
+ // as literal values (coordinates, entity types, block types), so they
51
+ // require MC macro syntax when called with runtime variables.
52
+ // ---------------------------------------------------------------------------
53
+ const MACRO_AWARE_BUILTINS = new Set([
54
+ 'summon', 'particle', 'setblock', 'fill', 'clone',
55
+ 'playsound', 'tp', 'tp_to', 'effect', 'effect_clear', 'give',
56
+ ]);
57
+ // ---------------------------------------------------------------------------
48
58
  // Builtin Functions
49
59
  // ---------------------------------------------------------------------------
50
60
  const BUILTINS = {
@@ -213,12 +223,194 @@ class Lowering {
213
223
  this.worldObjCounter = 0;
214
224
  // Loop context stack for break/continue
215
225
  this.loopStack = [];
226
+ // MC 1.20.2+ macro function support
227
+ // Names of params in the current function being lowered
228
+ this.currentFnParamNames = new Set();
229
+ // Params in the current function that need macro treatment (used in literal positions)
230
+ this.currentFnMacroParams = new Set();
231
+ // Global registry: fnName → macroParamNames (populated by pre-scan + lowering)
232
+ this.macroFunctionInfo = new Map();
216
233
  this.namespace = namespace;
217
234
  this.sourceRanges = sourceRanges;
218
235
  LoweringBuilder.resetTempCounter();
219
236
  }
237
+ // ---------------------------------------------------------------------------
238
+ // MC Macro pre-scan: identify which function params need macro treatment
239
+ // ---------------------------------------------------------------------------
240
+ preScanMacroFunctions(program) {
241
+ for (const fn of program.declarations) {
242
+ const paramNames = new Set(fn.params.map(p => p.name));
243
+ const macroParams = new Set();
244
+ this.preScanStmts(fn.body, paramNames, macroParams);
245
+ if (macroParams.size > 0) {
246
+ this.macroFunctionInfo.set(fn.name, [...macroParams]);
247
+ }
248
+ }
249
+ for (const implBlock of program.implBlocks ?? []) {
250
+ for (const method of implBlock.methods) {
251
+ const paramNames = new Set(method.params.map(p => p.name));
252
+ const macroParams = new Set();
253
+ this.preScanStmts(method.body, paramNames, macroParams);
254
+ if (macroParams.size > 0) {
255
+ this.macroFunctionInfo.set(`${implBlock.typeName}_${method.name}`, [...macroParams]);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ preScanStmts(stmts, paramNames, macroParams) {
261
+ for (const stmt of stmts) {
262
+ this.preScanStmt(stmt, paramNames, macroParams);
263
+ }
264
+ }
265
+ preScanStmt(stmt, paramNames, macroParams) {
266
+ switch (stmt.kind) {
267
+ case 'expr':
268
+ this.preScanExpr(stmt.expr, paramNames, macroParams);
269
+ break;
270
+ case 'let':
271
+ this.preScanExpr(stmt.init, paramNames, macroParams);
272
+ break;
273
+ case 'return':
274
+ if (stmt.value)
275
+ this.preScanExpr(stmt.value, paramNames, macroParams);
276
+ break;
277
+ case 'if':
278
+ this.preScanExpr(stmt.cond, paramNames, macroParams);
279
+ this.preScanStmts(stmt.then, paramNames, macroParams);
280
+ if (stmt.else_)
281
+ this.preScanStmts(stmt.else_, paramNames, macroParams);
282
+ break;
283
+ case 'while':
284
+ this.preScanExpr(stmt.cond, paramNames, macroParams);
285
+ this.preScanStmts(stmt.body, paramNames, macroParams);
286
+ break;
287
+ case 'for':
288
+ if (stmt.init)
289
+ this.preScanStmt(stmt.init, paramNames, macroParams);
290
+ this.preScanExpr(stmt.cond, paramNames, macroParams);
291
+ this.preScanStmts(stmt.body, paramNames, macroParams);
292
+ break;
293
+ case 'for_range':
294
+ this.preScanStmts(stmt.body, paramNames, macroParams);
295
+ break;
296
+ case 'foreach':
297
+ this.preScanStmts(stmt.body, paramNames, macroParams);
298
+ break;
299
+ case 'match':
300
+ this.preScanExpr(stmt.expr, paramNames, macroParams);
301
+ for (const arm of stmt.arms) {
302
+ this.preScanStmts(arm.body, paramNames, macroParams);
303
+ }
304
+ break;
305
+ case 'as_block':
306
+ case 'at_block':
307
+ this.preScanStmts(stmt.body, paramNames, macroParams);
308
+ break;
309
+ case 'execute':
310
+ this.preScanStmts(stmt.body, paramNames, macroParams);
311
+ break;
312
+ // raw, break, continue have no nested exprs of interest
313
+ }
314
+ }
315
+ preScanExpr(expr, paramNames, macroParams) {
316
+ if (expr.kind === 'call' && MACRO_AWARE_BUILTINS.has(expr.fn)) {
317
+ // All ident args to macro-aware builtins that are params → macro params
318
+ for (const arg of expr.args) {
319
+ if (arg.kind === 'ident' && paramNames.has(arg.name)) {
320
+ macroParams.add(arg.name);
321
+ }
322
+ }
323
+ return;
324
+ }
325
+ // Recurse into sub-expressions for other call types
326
+ if (expr.kind === 'call') {
327
+ for (const arg of expr.args)
328
+ this.preScanExpr(arg, paramNames, macroParams);
329
+ }
330
+ else if (expr.kind === 'binary') {
331
+ this.preScanExpr(expr.left, paramNames, macroParams);
332
+ this.preScanExpr(expr.right, paramNames, macroParams);
333
+ }
334
+ else if (expr.kind === 'unary') {
335
+ this.preScanExpr(expr.operand, paramNames, macroParams);
336
+ }
337
+ else if (expr.kind === 'assign') {
338
+ this.preScanExpr(expr.value, paramNames, macroParams);
339
+ }
340
+ }
341
+ // ---------------------------------------------------------------------------
342
+ // Macro helpers
343
+ // ---------------------------------------------------------------------------
344
+ /**
345
+ * If `expr` is a function parameter that needs macro treatment (runtime value
346
+ * used in a literal position), returns the param name; otherwise null.
347
+ */
348
+ tryGetMacroParam(expr) {
349
+ if (expr.kind !== 'ident')
350
+ return null;
351
+ if (!this.currentFnParamNames.has(expr.name))
352
+ return null;
353
+ if (this.constValues.has(expr.name))
354
+ return null;
355
+ if (this.stringValues.has(expr.name))
356
+ return null;
357
+ return expr.name;
358
+ }
359
+ /**
360
+ * Converts an expression to a string for use as a builtin arg.
361
+ * If the expression is a macro param, returns `$(name)` and sets macroParam.
362
+ */
363
+ exprToBuiltinArg(expr) {
364
+ const macroParam = this.tryGetMacroParam(expr);
365
+ if (macroParam) {
366
+ return { str: `$(${macroParam})`, macroParam };
367
+ }
368
+ if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
369
+ return { str: this.exprToSnbt(expr) };
370
+ }
371
+ return { str: this.exprToString(expr) };
372
+ }
373
+ /**
374
+ * Emits a call to a macro function, setting up both scoreboard params
375
+ * (for arithmetic use) and NBT macro args (for coordinate/literal use).
376
+ */
377
+ emitMacroFunctionCall(fnName, args, macroParamNames, fnDecl) {
378
+ const params = fnDecl?.params ?? [];
379
+ const loweredArgs = args.map(arg => this.lowerExpr(arg));
380
+ // Set up regular scoreboard params (for arithmetic within the function)
381
+ for (let i = 0; i < loweredArgs.length; i++) {
382
+ const operand = loweredArgs[i];
383
+ if (operand.kind === 'const') {
384
+ this.builder.emitRaw(`scoreboard players set $p${i} rs ${operand.value}`);
385
+ }
386
+ else if (operand.kind === 'var') {
387
+ this.builder.emitRaw(`scoreboard players operation $p${i} rs = ${operand.name} rs`);
388
+ }
389
+ }
390
+ // Set up NBT storage for each macro param
391
+ for (const macroParam of macroParamNames) {
392
+ const paramIdx = params.findIndex(p => p.name === macroParam);
393
+ if (paramIdx < 0 || paramIdx >= loweredArgs.length)
394
+ continue;
395
+ const operand = loweredArgs[paramIdx];
396
+ if (operand.kind === 'const') {
397
+ this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`);
398
+ }
399
+ else if (operand.kind === 'var') {
400
+ this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} rs`);
401
+ }
402
+ }
403
+ // Call with macro storage
404
+ this.builder.emitRaw(`function ${this.namespace}:${fnName} with storage rs:macro_args`);
405
+ // Copy return value (callers may use it)
406
+ const dst = this.builder.freshTemp();
407
+ this.builder.emitRaw(`scoreboard players operation ${dst} rs = $ret rs`);
408
+ return { kind: 'var', name: dst };
409
+ }
220
410
  lower(program) {
221
411
  this.namespace = program.namespace;
412
+ // Pre-scan for macro functions before main lowering (so call sites can detect them)
413
+ this.preScanMacroFunctions(program);
222
414
  // Load struct definitions
223
415
  for (const struct of program.structs ?? []) {
224
416
  const fields = new Map();
@@ -297,6 +489,9 @@ class Lowering {
297
489
  this.blockPosVars = new Map();
298
490
  this.stringValues = new Map();
299
491
  this.builder = new LoweringBuilder();
492
+ // Initialize macro tracking for this function
493
+ this.currentFnParamNames = new Set(runtimeParams.map(p => p.name));
494
+ this.currentFnMacroParams = new Set();
300
495
  // Map parameters
301
496
  if (staticEventDec) {
302
497
  for (let i = 0; i < fn.params.length; i++) {
@@ -401,6 +596,13 @@ class Lowering {
401
596
  if (tickRate && tickRate > 1) {
402
597
  this.wrapWithTickRate(irFn, tickRate);
403
598
  }
599
+ // Set macro metadata if this function uses MC macro syntax
600
+ if (this.currentFnMacroParams.size > 0) {
601
+ irFn.isMacroFunction = true;
602
+ irFn.macroParamNames = [...this.currentFnMacroParams];
603
+ // Update registry (may refine the pre-scan result)
604
+ this.macroFunctionInfo.set(loweredName, irFn.macroParamNames);
605
+ }
404
606
  this.functions.push(irFn);
405
607
  }
406
608
  getTickRate(decorators) {
@@ -1588,8 +1790,18 @@ class Lowering {
1588
1790
  const targetFn = callbackBindings.size > 0 || stdlibCallSite
1589
1791
  ? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
1590
1792
  : expr.fn;
1793
+ // Check if this is a call to a known macro function
1794
+ const macroParams = this.macroFunctionInfo.get(targetFn);
1795
+ if (macroParams && macroParams.length > 0) {
1796
+ return this.emitMacroFunctionCall(targetFn, runtimeArgs, macroParams, fnDecl);
1797
+ }
1591
1798
  return this.emitDirectFunctionCall(targetFn, runtimeArgs);
1592
1799
  }
1800
+ // Check for macro function (forward-declared or external)
1801
+ const macroParamsForUnknown = this.macroFunctionInfo.get(expr.fn);
1802
+ if (macroParamsForUnknown && macroParamsForUnknown.length > 0) {
1803
+ return this.emitMacroFunctionCall(expr.fn, fullArgs, macroParamsForUnknown, undefined);
1804
+ }
1593
1805
  return this.emitDirectFunctionCall(expr.fn, fullArgs);
1594
1806
  }
1595
1807
  lowerStaticCallExpr(expr) {
@@ -1718,6 +1930,9 @@ class Lowering {
1718
1930
  const savedBlockPosVars = new Map(this.blockPosVars);
1719
1931
  const savedStringValues = new Map(this.stringValues);
1720
1932
  const savedVarTypes = new Map(this.varTypes);
1933
+ // Macro tracking state
1934
+ const savedCurrentFnParamNames = new Set(this.currentFnParamNames);
1935
+ const savedCurrentFnMacroParams = new Set(this.currentFnMacroParams);
1721
1936
  try {
1722
1937
  return callback();
1723
1938
  }
@@ -1734,6 +1949,8 @@ class Lowering {
1734
1949
  this.blockPosVars = savedBlockPosVars;
1735
1950
  this.stringValues = savedStringValues;
1736
1951
  this.varTypes = savedVarTypes;
1952
+ this.currentFnParamNames = savedCurrentFnParamNames;
1953
+ this.currentFnMacroParams = savedCurrentFnMacroParams;
1737
1954
  }
1738
1955
  }
1739
1956
  lowerBuiltinCall(name, args, callSpan) {
@@ -1967,16 +2184,31 @@ class Lowering {
1967
2184
  code: 'W_DEPRECATED',
1968
2185
  ...(callSpan ? { line: callSpan.line, col: callSpan.col } : {}),
1969
2186
  });
1970
- const tpCommand = this.lowerTpCommand(args);
1971
- if (tpCommand) {
1972
- this.builder.emitRaw(tpCommand);
2187
+ const tpResult = this.lowerTpCommandMacroAware(args);
2188
+ if (tpResult) {
2189
+ this.builder.emitRaw(tpResult.cmd);
1973
2190
  }
1974
2191
  return { kind: 'const', value: 0 };
1975
2192
  }
1976
2193
  if (name === 'tp') {
1977
- const tpCommand = this.lowerTpCommand(args);
1978
- if (tpCommand) {
1979
- this.builder.emitRaw(tpCommand);
2194
+ const tpResult = this.lowerTpCommandMacroAware(args);
2195
+ if (tpResult) {
2196
+ this.builder.emitRaw(tpResult.cmd);
2197
+ }
2198
+ return { kind: 'const', value: 0 };
2199
+ }
2200
+ // For macro-aware builtins, check if any arg is a param needing macro treatment
2201
+ if (MACRO_AWARE_BUILTINS.has(name)) {
2202
+ const argResults = args.map(arg => this.exprToBuiltinArg(arg));
2203
+ const hasMacroArg = argResults.some(r => r.macroParam !== undefined);
2204
+ if (hasMacroArg) {
2205
+ argResults.forEach(r => { if (r.macroParam)
2206
+ this.currentFnMacroParams.add(r.macroParam); });
2207
+ }
2208
+ const strArgs = argResults.map(r => r.str);
2209
+ const cmd = BUILTINS[name]?.(strArgs);
2210
+ if (cmd) {
2211
+ this.builder.emitRaw(hasMacroArg ? `$${cmd}` : cmd);
1980
2212
  }
1981
2213
  return { kind: 'const', value: 0 };
1982
2214
  }
@@ -2488,6 +2720,36 @@ class Lowering {
2488
2720
  }
2489
2721
  return null;
2490
2722
  }
2723
+ lowerTpCommandMacroAware(args) {
2724
+ const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
2725
+ const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
2726
+ // If blockpos args are used, no macro needed (coords are already resolved)
2727
+ if (args.length === 1 && pos0) {
2728
+ return { cmd: `tp ${emitBlockPos(pos0)}` };
2729
+ }
2730
+ if (args.length === 2 && pos1) {
2731
+ return { cmd: `tp ${this.exprToString(args[0])} ${emitBlockPos(pos1)}` };
2732
+ }
2733
+ // Check for macro args (int params used as coordinates)
2734
+ if (args.length >= 2) {
2735
+ const argResults = args.map(a => this.exprToBuiltinArg(a));
2736
+ const hasMacro = argResults.some(r => r.macroParam !== undefined);
2737
+ if (hasMacro) {
2738
+ argResults.forEach(r => { if (r.macroParam)
2739
+ this.currentFnMacroParams.add(r.macroParam); });
2740
+ const strs = argResults.map(r => r.str);
2741
+ if (args.length === 2) {
2742
+ return { cmd: `$tp ${strs[0]} ${strs[1]}` };
2743
+ }
2744
+ if (args.length === 4) {
2745
+ return { cmd: `$tp ${strs[0]} ${strs[1]} ${strs[2]} ${strs[3]}` };
2746
+ }
2747
+ }
2748
+ }
2749
+ // Fallback to non-macro
2750
+ const plain = this.lowerTpCommand(args);
2751
+ return plain ? { cmd: plain } : null;
2752
+ }
2491
2753
  resolveBlockPosExpr(expr) {
2492
2754
  if (expr.kind === 'blockpos') {
2493
2755
  return expr;
@@ -9,6 +9,7 @@ export interface OptimizationStats {
9
9
  setblockSavedCommands: number;
10
10
  deadCodeRemoved: number;
11
11
  constantFolds: number;
12
+ inlinedTrivialFunctions: number;
12
13
  totalCommandsBefore: number;
13
14
  totalCommandsAfter: number;
14
15
  }
@@ -23,6 +23,7 @@ function createEmptyOptimizationStats() {
23
23
  setblockSavedCommands: 0,
24
24
  deadCodeRemoved: 0,
25
25
  constantFolds: 0,
26
+ inlinedTrivialFunctions: 0,
26
27
  totalCommandsBefore: 0,
27
28
  totalCommandsAfter: 0,
28
29
  };
@@ -330,11 +331,105 @@ function batchSetblocks(functions) {
330
331
  stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0);
331
332
  return { functions: optimized, stats };
332
333
  }
334
+ /**
335
+ * Inline trivial functions:
336
+ * 1. Functions that only contain a single `function` call → inline the call
337
+ * 2. Empty functions (no commands) → remove and eliminate all calls to them
338
+ */
339
+ function inlineTrivialFunctions(functions) {
340
+ const FUNCTION_CMD_RE = /^function ([^:]+):(.+)$/;
341
+ // Find trivial functions (only a single function call, no other commands)
342
+ const trivialMap = new Map(); // fn name -> target fn name
343
+ const emptyFunctions = new Set(); // functions with no commands
344
+ // System functions that should never be removed
345
+ const SYSTEM_FUNCTIONS = new Set(['__tick', '__load']);
346
+ for (const fn of functions) {
347
+ // Never remove system functions
348
+ if (SYSTEM_FUNCTIONS.has(fn.name) || fn.name.startsWith('__trigger_')) {
349
+ continue;
350
+ }
351
+ const nonCommentCmds = fn.commands.filter(cmd => !cmd.cmd.startsWith('#'));
352
+ if (nonCommentCmds.length === 0 && fn.name.includes('/')) {
353
+ // Empty control-flow block (e.g., main/merge_5) - mark for removal
354
+ // Only remove if it's a sub-block (contains /), not a top-level function
355
+ emptyFunctions.add(fn.name);
356
+ }
357
+ else if (nonCommentCmds.length === 1 && fn.name.includes('/')) {
358
+ const match = nonCommentCmds[0].cmd.match(FUNCTION_CMD_RE);
359
+ if (match) {
360
+ // This function only calls another function
361
+ trivialMap.set(fn.name, match[2]);
362
+ }
363
+ }
364
+ }
365
+ // Resolve chains: if A -> B -> C, then A -> C
366
+ // Also handle: A -> B where B is empty → A is effectively empty
367
+ let changed = true;
368
+ while (changed) {
369
+ changed = false;
370
+ for (const [from, to] of trivialMap) {
371
+ if (emptyFunctions.has(to)) {
372
+ // Target is empty, so this function is effectively empty too
373
+ trivialMap.delete(from);
374
+ emptyFunctions.add(from);
375
+ changed = true;
376
+ }
377
+ else {
378
+ const finalTarget = trivialMap.get(to);
379
+ if (finalTarget && finalTarget !== to) {
380
+ trivialMap.set(from, finalTarget);
381
+ changed = true;
382
+ }
383
+ }
384
+ }
385
+ }
386
+ const totalRemoved = trivialMap.size + emptyFunctions.size;
387
+ if (totalRemoved === 0) {
388
+ return { functions, stats: {} };
389
+ }
390
+ // Set of all functions to remove
391
+ const removedNames = new Set([...trivialMap.keys(), ...emptyFunctions]);
392
+ // Rewrite all function calls to skip trivial wrappers or remove empty calls
393
+ const result = [];
394
+ for (const fn of functions) {
395
+ // Skip removed functions
396
+ if (removedNames.has(fn.name)) {
397
+ continue;
398
+ }
399
+ // Rewrite function calls in this function
400
+ const rewrittenCmds = [];
401
+ for (const cmd of fn.commands) {
402
+ // Check if this is a call to an empty function
403
+ const emptyCallMatch = cmd.cmd.match(/^(?:execute .* run )?function ([^:]+):([^\s]+)$/);
404
+ if (emptyCallMatch) {
405
+ const targetFn = emptyCallMatch[2];
406
+ if (emptyFunctions.has(targetFn)) {
407
+ // Skip calls to empty functions entirely
408
+ continue;
409
+ }
410
+ }
411
+ // Rewrite calls to trivial wrapper functions
412
+ const rewritten = cmd.cmd.replace(/function ([^:]+):([^\s]+)/g, (match, ns, fnPath) => {
413
+ const target = trivialMap.get(fnPath);
414
+ return target ? `function ${ns}:${target}` : match;
415
+ });
416
+ rewrittenCmds.push({ ...cmd, cmd: rewritten });
417
+ }
418
+ result.push({ name: fn.name, commands: rewrittenCmds });
419
+ }
420
+ return {
421
+ functions: result,
422
+ stats: { inlinedTrivialFunctions: totalRemoved }
423
+ };
424
+ }
333
425
  function optimizeCommandFunctions(functions) {
334
426
  const initial = cloneFunctions(functions);
335
427
  const stats = createEmptyOptimizationStats();
336
428
  stats.totalCommandsBefore = initial.reduce((sum, fn) => sum + fn.commands.length, 0);
337
- const licm = applyLICM(initial);
429
+ // First pass: inline trivial functions
430
+ const inlined = inlineTrivialFunctions(initial);
431
+ mergeOptimizationStats(stats, inlined.stats);
432
+ const licm = applyLICM(inlined.functions);
338
433
  mergeOptimizationStats(stats, licm.stats);
339
434
  const cse = applyCSE(licm.functions);
340
435
  mergeOptimizationStats(stats, cse.stats);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.12",
3
+ "version": "1.2.14",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,35 @@
1
+ // MC 1.20.2+ macro function test
2
+ // Tests that functions using parameters in coordinate/type positions
3
+ // generate macro syntax ($-prefixed commands with $(param) substitution)
4
+ //
5
+ // Compilation: redscript compile macro-test.mcrs -o output/ -n test
6
+
7
+ // Basic macro function: parameters used as summon coordinates
8
+ fn spawn_zombie(x: int, y: int, z: int) {
9
+ summon("minecraft:zombie", x, y, z);
10
+ }
11
+
12
+ // Macro function: particle effect at coordinates
13
+ fn show_particle(x: int, y: int, z: int) {
14
+ particle("minecraft:flame", x, y, z);
15
+ }
16
+
17
+ // Macro function: setblock at variable position
18
+ fn place_block(x: int, y: int, z: int) {
19
+ setblock(x, y, z, "minecraft:stone");
20
+ }
21
+
22
+ // Non-macro function: all args are compile-time constants in the body
23
+ fn summon_fixed() {
24
+ summon("minecraft:zombie", 100, 64, 200);
25
+ }
26
+
27
+ // Caller with constant args (calls macro function with inline NBT data)
28
+ fn call_with_constants() {
29
+ spawn_zombie(100, 64, 200);
30
+ }
31
+
32
+ // Caller with variable args (calls macro function via 'with storage rs:macro_args')
33
+ fn call_with_vars(px: int, pz: int) {
34
+ spawn_zombie(px, 64, pz);
35
+ }