sandstone 1.0.0-beta.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +3 -4
  2. package/dist/_internal/index.d.ts +992 -335
  3. package/dist/_internal/index.js +516 -518
  4. package/dist/_internal/index.js.map +1 -1
  5. package/dist/_internal/types/arguments/coords.d.ts +13 -14
  6. package/dist/_internal/types/arguments/generated/_registry/activities.d.ts +1 -1
  7. package/dist/_internal/types/arguments/generated/_registry/atlases.d.ts +1 -1
  8. package/dist/_internal/types/arguments/generated/_registry/attribute_types.d.ts +1 -1
  9. package/dist/_internal/types/arguments/generated/_registry/attributes.d.ts +1 -1
  10. package/dist/_internal/types/arguments/generated/_registry/banner_patterns.d.ts +1 -1
  11. package/dist/_internal/types/arguments/generated/_registry/block_definitions.d.ts +1 -1
  12. package/dist/_internal/types/arguments/generated/_registry/block_entity_types.d.ts +1 -1
  13. package/dist/_internal/types/arguments/generated/_registry/block_types.d.ts +1 -1
  14. package/dist/_internal/types/arguments/generated/_registry/blocks.d.ts +1 -1
  15. package/dist/_internal/types/arguments/generated/_registry/chunk_statuses.d.ts +1 -1
  16. package/dist/_internal/types/arguments/generated/_registry/command_argument_types.d.ts +1 -1
  17. package/dist/_internal/types/arguments/generated/_registry/creative_mode_tabs.d.ts +1 -1
  18. package/dist/_internal/types/arguments/generated/_registry/custom_stats.d.ts +1 -1
  19. package/dist/_internal/types/arguments/generated/_registry/damage_types.d.ts +1 -1
  20. package/dist/_internal/types/arguments/generated/_registry/data_component_predicate_types.d.ts +1 -1
  21. package/dist/_internal/types/arguments/generated/_registry/data_component_types.d.ts +1 -1
  22. package/dist/_internal/types/arguments/generated/_registry/debug_subscriptions.d.ts +1 -1
  23. package/dist/_internal/types/arguments/generated/_registry/decorated_pot_patterns.d.ts +1 -1
  24. package/dist/_internal/types/arguments/generated/_registry/dimension_types.d.ts +1 -1
  25. package/dist/_internal/types/arguments/generated/_registry/enchantment_effect_component_types.d.ts +1 -1
  26. package/dist/_internal/types/arguments/generated/_registry/enchantment_entity_effect_types.d.ts +1 -1
  27. package/dist/_internal/types/arguments/generated/_registry/enchantment_level_based_value_types.d.ts +1 -1
  28. package/dist/_internal/types/arguments/generated/_registry/enchantment_location_based_effect_types.d.ts +1 -1
  29. package/dist/_internal/types/arguments/generated/_registry/enchantment_provider_types.d.ts +1 -1
  30. package/dist/_internal/types/arguments/generated/_registry/enchantment_value_effect_types.d.ts +1 -1
  31. package/dist/_internal/types/arguments/generated/_registry/enchantments.d.ts +1 -1
  32. package/dist/_internal/types/arguments/generated/_registry/equipments.d.ts +1 -1
  33. package/dist/_internal/types/arguments/generated/_registry/float_provider_types.d.ts +1 -1
  34. package/dist/_internal/types/arguments/generated/_registry/fluids.d.ts +1 -1
  35. package/dist/_internal/types/arguments/generated/_registry/game_events.d.ts +1 -1
  36. package/dist/_internal/types/arguments/generated/_registry/game_rules.d.ts +1 -1
  37. package/dist/_internal/types/arguments/generated/_registry/height_provider_types.d.ts +1 -1
  38. package/dist/_internal/types/arguments/generated/_registry/int_provider_types.d.ts +1 -1
  39. package/dist/_internal/types/arguments/generated/_registry/item_definitions.d.ts +1 -1
  40. package/dist/_internal/types/arguments/generated/_registry/items.d.ts +1 -1
  41. package/dist/_internal/types/arguments/generated/_registry/jukebox_songs.d.ts +1 -1
  42. package/dist/_internal/types/arguments/generated/_registry/loot_condition_types.d.ts +1 -1
  43. package/dist/_internal/types/arguments/generated/_registry/loot_function_types.d.ts +1 -1
  44. package/dist/_internal/types/arguments/generated/_registry/loot_nbt_provider_types.d.ts +1 -1
  45. package/dist/_internal/types/arguments/generated/_registry/loot_number_provider_types.d.ts +1 -1
  46. package/dist/_internal/types/arguments/generated/_registry/memory_module_types.d.ts +1 -1
  47. package/dist/_internal/types/arguments/generated/_registry/menus.d.ts +1 -1
  48. package/dist/_internal/types/arguments/generated/_registry/mob_effects.d.ts +1 -1
  49. package/dist/_internal/types/arguments/generated/_registry/models.d.ts +1 -1
  50. package/dist/_internal/types/arguments/generated/_registry/painting_variants.d.ts +1 -1
  51. package/dist/_internal/types/arguments/generated/_registry/particle_types.d.ts +1 -1
  52. package/dist/_internal/types/arguments/generated/_registry/point_of_interest_types.d.ts +1 -1
  53. package/dist/_internal/types/arguments/generated/_registry/post_effects.d.ts +1 -1
  54. package/dist/_internal/types/arguments/generated/_registry/potions.d.ts +1 -1
  55. package/dist/_internal/types/arguments/generated/_registry/recipe_displays.d.ts +1 -1
  56. package/dist/_internal/types/arguments/generated/_registry/recipe_serializers.d.ts +1 -1
  57. package/dist/_internal/types/arguments/generated/_registry/recipe_types.d.ts +1 -1
  58. package/dist/_internal/types/arguments/generated/_registry/recipes.d.ts +1 -1
  59. package/dist/_internal/types/arguments/generated/_registry/slot_displays.d.ts +1 -1
  60. package/dist/_internal/types/arguments/generated/_registry/slot_source_types.d.ts +1 -1
  61. package/dist/_internal/types/arguments/generated/_registry/structures.d.ts +1 -1
  62. package/dist/_internal/types/arguments/generated/_registry/tag_blocks.d.ts +1 -1
  63. package/dist/_internal/types/arguments/generated/_registry/tag_entity_types.d.ts +1 -1
  64. package/dist/_internal/types/arguments/generated/_registry/tag_fluids.d.ts +1 -1
  65. package/dist/_internal/types/arguments/generated/_registry/tag_items.d.ts +1 -1
  66. package/dist/_internal/types/arguments/generated/_registry/tag_point_of_interest_types.d.ts +1 -1
  67. package/dist/_internal/types/arguments/generated/_registry/tag_timelines.d.ts +1 -1
  68. package/dist/_internal/types/arguments/generated/_registry/tag_villager_trades.d.ts +1 -1
  69. package/dist/_internal/types/arguments/generated/_registry/tag_worldgen_structures.d.ts +1 -1
  70. package/dist/_internal/types/arguments/generated/_registry/textures.d.ts +1 -1
  71. package/dist/_internal/types/arguments/generated/_registry/ticket_types.d.ts +1 -1
  72. package/dist/_internal/types/arguments/generated/_registry/trigger_types.d.ts +1 -1
  73. package/dist/_internal/types/arguments/generated/_registry/trim_patterns.d.ts +1 -1
  74. package/dist/_internal/types/arguments/generated/_registry/villager_professions.d.ts +1 -1
  75. package/dist/_internal/types/arguments/generated/_registry/wolf_variants.d.ts +1 -1
  76. package/dist/_internal/types/arguments/generated/_registry/worldgen_biomes.d.ts +1 -1
  77. package/dist/_internal/types/arguments/generated/_registry/worldgen_density_function_types.d.ts +1 -1
  78. package/dist/_internal/types/arguments/generated/_registry/worldgen_features.d.ts +1 -1
  79. package/dist/_internal/types/arguments/generated/_registry/worldgen_material_conditions.d.ts +1 -1
  80. package/dist/_internal/types/arguments/generated/_registry/worldgen_noise_settings.d.ts +1 -1
  81. package/dist/_internal/types/arguments/generated/_registry/worldgen_noises.d.ts +1 -1
  82. package/dist/_internal/types/arguments/generated/_registry/worldgen_placed_features.d.ts +1 -1
  83. package/dist/_internal/types/arguments/generated/_registry/worldgen_placement_modifier_types.d.ts +1 -1
  84. package/dist/_internal/types/arguments/generated/_registry/worldgen_processor_lists.d.ts +1 -1
  85. package/dist/_internal/types/arguments/generated/_registry/worldgen_structure_sets.d.ts +1 -1
  86. package/dist/_internal/types/arguments/generated/_registry/worldgen_structure_types.d.ts +1 -1
  87. package/dist/_internal/types/arguments/generated/_registry/worldgen_structures.d.ts +1 -1
  88. package/dist/_internal/types/arguments/generated/_registry/worldgen_template_pools.d.ts +1 -1
  89. package/dist/_internal/types/arguments/generated/_registry/worldgen_tree_decorator_types.d.ts +1 -1
  90. package/dist/_internal/types/arguments/objective.d.ts +1 -1
  91. package/dist/_internal/types/arguments/range.d.ts +7 -7
  92. package/dist/_internal/types/commands/commands.d.ts +1 -4
  93. package/dist/_internal/types/commands/implementations/entity/execute.d.ts +9 -3
  94. package/dist/_internal/types/commands/implementations/server/gamerule.d.ts +38 -3
  95. package/dist/_internal/types/commands/implementations/server/random.d.ts +3 -3
  96. package/dist/_internal/types/commands/implementations/world/time.d.ts +6 -6
  97. package/dist/_internal/types/core/Macro.d.ts +0 -1
  98. package/dist/_internal/types/core/sandstoneCore.d.ts +8 -0
  99. package/dist/_internal/types/pack/pack.d.ts +2 -3
  100. package/dist/_internal/types/utils.d.ts +79 -0
  101. package/dist/_internal/types/variables/Score.d.ts +20 -4
  102. package/dist/_internal/types/variables/Sleep.d.ts +0 -3
  103. package/dist/_internal/types/variables/nbt/NBTs.d.ts +28 -1
  104. package/dist/browser/package.json +3 -0
  105. package/dist/browser/sandstone.esm.js +61581 -0
  106. package/dist/browser/sandstone.esm.js.map +1 -0
  107. package/dist/exports/commands/index.d.ts +1 -1
  108. package/dist/exports/core/index.d.ts +1 -1
  109. package/dist/exports/index.d.ts +1 -1
  110. package/dist/exports/index.js +1 -1
  111. package/package.json +22 -18
  112. package/src/arguments/coords.ts +11 -14
  113. package/src/arguments/objective.ts +1 -1
  114. package/src/arguments/range.ts +9 -9
  115. package/src/commands/commands.ts +3 -3
  116. package/src/commands/implementations/block/fill.ts +3 -3
  117. package/src/commands/implementations/block/setblock.ts +2 -2
  118. package/src/commands/implementations/entity/clear.ts +5 -1
  119. package/src/commands/implementations/entity/execute.ts +27 -6
  120. package/src/commands/implementations/entity/summon.ts +2 -2
  121. package/src/commands/implementations/player/dialog.ts +2 -2
  122. package/src/commands/implementations/player/give.ts +2 -2
  123. package/src/commands/implementations/player/particle.ts +2 -2
  124. package/src/commands/implementations/server/debug.ts +6 -1
  125. package/src/commands/implementations/server/function.ts +8 -3
  126. package/src/commands/implementations/server/gamerule.ts +75 -3
  127. package/src/commands/implementations/server/random.ts +3 -3
  128. package/src/commands/implementations/server/schedule.ts +10 -2
  129. package/src/commands/implementations/world/data.ts +5 -5
  130. package/src/commands/implementations/world/item.ts +2 -2
  131. package/src/commands/implementations/world/time.ts +5 -5
  132. package/src/core/Macro.ts +0 -2
  133. package/src/core/nodes.ts +17 -1
  134. package/src/core/resources/datapack/mcfunction.ts +12 -5
  135. package/src/core/resources/datapack/structure.ts +1 -0
  136. package/src/core/resources/resourcepack/sound.ts +2 -4
  137. package/src/core/sandstoneCore.ts +17 -0
  138. package/src/flow/conditions/variables/block.ts +2 -2
  139. package/src/flow/loops/for_i.ts +17 -15
  140. package/src/flow/loops/for_of.ts +0 -1
  141. package/src/pack/pack.ts +13 -6
  142. package/src/sandstone.ts +2 -2
  143. package/src/utils.ts +159 -0
  144. package/src/variables/Data.ts +3 -3
  145. package/src/variables/ItemPredicate.ts +2 -2
  146. package/src/variables/Score.ts +18 -10
  147. package/src/variables/Selector.ts +21 -3
  148. package/src/variables/Sleep.ts +2 -10
  149. package/src/variables/Trigger.ts +3 -5
  150. package/src/variables/nbt/NBTs.ts +70 -19
@@ -13,7 +13,7 @@ import type {
13
13
  } from 'sandstone/arguments'
14
14
  import type { ExecuteCommand, SandstoneCommands } from 'sandstone/commands'
15
15
  import type { NotNode } from 'sandstone/flow'
16
- import { nbtStringifier, type ConditionClass } from 'sandstone/variables'
16
+ import { nbtResolver, type ConditionClass } from 'sandstone/variables'
17
17
  import * as util from 'util'
18
18
  import { MacroArgument } from '../core/Macro'
19
19
  import type { SandstonePack } from '../pack'
@@ -96,7 +96,7 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
96
96
  }
97
97
 
98
98
  toNBT() {
99
- return nbtStringifier(this._toChatComponent() as NBTObject)
99
+ return nbtResolver(this._toChatComponent() as NBTObject).toString()
100
100
  }
101
101
 
102
102
  toJSON() {
@@ -353,6 +353,8 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
353
353
  return this.binaryOperation('%=', ...args)
354
354
  }
355
355
 
356
+ mod = this.modulo
357
+
356
358
  '%=' = this.modulo
357
359
 
358
360
  /**
@@ -443,6 +445,8 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
443
445
  return createVariable(this.sandstonePack, this).binaryOperation('*=', ...args)
444
446
  }
445
447
 
448
+ times = this.multipliedBy
449
+
446
450
  '*' = this.multipliedBy
447
451
 
448
452
  /**
@@ -487,6 +491,8 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
487
491
  return createVariable(this.sandstonePack, this).binaryOperation('%=', ...args)
488
492
  }
489
493
 
494
+ modBy = this.moduloBy
495
+
490
496
  '%' = this.moduloBy
491
497
 
492
498
  /** COMPARISONS OPERATORS */
@@ -548,20 +554,20 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
548
554
  *
549
555
  * @param objective The related objective. If not specified, default to the same objective as the current target.
550
556
  */
551
- greaterOrEqualThan(targets: MultipleEntitiesArgument<false>, objective?: ObjectiveArgument): ConditionClass
557
+ greaterThanOrEqualTo(targets: MultipleEntitiesArgument<false>, objective?: ObjectiveArgument): ConditionClass
552
558
 
553
559
  /**
554
560
  * Check if the current score is greater or equal than the given amount or score.
555
561
  *
556
562
  * @param amountOrTargetScore The amount or score compare the current score against.
557
563
  */
558
- greaterOrEqualThan(amountOrTargetScore: number | Score): ConditionClass
564
+ greaterThanOrEqualTo(amountOrTargetScore: number | Score): ConditionClass
559
565
 
560
- greaterOrEqualThan(...args: OperationArguments) {
566
+ greaterThanOrEqualTo(...args: OperationArguments) {
561
567
  return this.comparison('>=', `${args[0]}..`, args)
562
568
  }
563
569
 
564
- '>=' = this.greaterOrEqualThan
570
+ '>=' = this.greaterThanOrEqualTo
565
571
 
566
572
  /**
567
573
  * Check if the current score is strictly lower than the given score.
@@ -592,20 +598,20 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
592
598
  *
593
599
  * @param objective The related objective. If not specified, default to the same objective as the current target.
594
600
  */
595
- lessOrEqualThan(targets: MultipleEntitiesArgument<false>, objective?: ObjectiveArgument): ConditionClass
601
+ lessThanOrEqualTo(targets: MultipleEntitiesArgument<false>, objective?: ObjectiveArgument): ConditionClass
596
602
 
597
603
  /**
598
604
  * Check if the current score is lower or equal than the given amount or score.
599
605
  *
600
606
  * @param amountOrTargetScore The amount or score target to compare the current score against.
601
607
  */
602
- lessOrEqualThan(amountOrTargetScore: number | Score): ConditionClass
608
+ lessThanOrEqualTo(amountOrTargetScore: number | Score): ConditionClass
603
609
 
604
- lessOrEqualThan(...args: OperationArguments) {
610
+ lessThanOrEqualTo(...args: OperationArguments) {
605
611
  return this.comparison('<=', `..${args[0]}`, args)
606
612
  }
607
613
 
608
- '<=' = this.lessOrEqualThan
614
+ '<=' = this.lessThanOrEqualTo
609
615
 
610
616
  /**
611
617
  * Check if the current score is equal to than the given score.
@@ -627,6 +633,8 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla
627
633
  return this.comparison('=', args[0].toString(), args)
628
634
  }
629
635
 
636
+ equals = this.equalTo
637
+
630
638
  '==' = this.equalTo
631
639
 
632
640
  /**
@@ -4,7 +4,7 @@
4
4
  import type { GAMEMODES, JSONTextComponent, NBTSerializable, Range, RootNBT, SymbolEntity } from 'sandstone/arguments'
5
5
  import type { PredicateClass, SandstoneCore, TagClass } from 'sandstone/core'
6
6
  import type { SandstonePack } from 'sandstone/pack'
7
- import { nbtStringifier } from 'sandstone/variables/nbt/NBTs'
7
+ import { nbtResolver, ResolvedNBT } from 'sandstone/variables/nbt/NBTs'
8
8
  import { rangeParser } from 'sandstone/variables/parsers'
9
9
  import * as util from 'util'
10
10
  import type { Macroable } from '../core/Macro'
@@ -316,6 +316,17 @@ export class SelectorClass<
316
316
 
317
317
  arguments: SelectorProperties<IsSingle, IsPlayer, SelectorEntityType, MACRO>
318
318
 
319
+ /**
320
+ * Whether the most recent `toString()` produced a selector string containing
321
+ * any `$(...)` macro substitution. `CommandNode.getValue()` reads this so it
322
+ * can flag the surrounding command as a macro command (prefixed with `$`),
323
+ * even though `toString()` itself returns a plain string (the `$(...)` is
324
+ * baked in via template literal coercion).
325
+ *
326
+ * @internal
327
+ */
328
+ _hasMacro = false
329
+
319
330
  constructor(
320
331
  protected sandstonePack: SandstonePack,
321
332
  public target: '@s' | '@p' | '@a' | '@e' | '@n' | '@r',
@@ -335,10 +346,11 @@ export class SelectorClass<
335
346
 
336
347
  toString() {
337
348
  if (!this.arguments || !Object.keys(this.arguments).length) {
349
+ this._hasMacro = false
338
350
  return this.target
339
351
  }
340
352
 
341
- const result: (readonly [string, string])[] = []
353
+ const result: (readonly [string, string | ResolvedNBT])[] = []
342
354
 
343
355
  const args = { ...this.arguments }
344
356
 
@@ -349,7 +361,13 @@ export class SelectorClass<
349
361
  // Parse potentially multiple nbt
350
362
  nbt: (nbt: RootNBT | RootNBT[]) => {
351
363
  const nbts = Array.isArray(nbt) ? nbt : [nbt]
352
- result.push(...nbts.map((nbt_) => ['nbt', nbtStringifier(nbt_)] as const))
364
+ for (const nbt_ of nbts) {
365
+ const resolved = nbtResolver(nbt_)
366
+ if (resolved.containsMacro) {
367
+ this._hasMacro = true
368
+ }
369
+ result.push(['nbt', resolved] as const)
370
+ }
353
371
  },
354
372
 
355
373
  // Parse advancements
@@ -69,6 +69,8 @@ export class SleepClass extends AwaitNode {
69
69
 
70
70
  const timer = new ObjectiveClass(core.pack, name.replace(':', '.'), 'dummy', undefined, { creator: 'sandstone' })
71
71
 
72
+ core.pack.registerNewObjective(timer)
73
+
72
74
  execute.store.result(timer('@s')).run.time.query('gametime')
73
75
 
74
76
  timer('@s').add(Duration)
@@ -102,14 +104,4 @@ export class SleepClass extends AwaitNode {
102
104
 
103
105
  core.awaitNodes.add(this)
104
106
  }
105
-
106
- promise() {
107
- return {
108
- // oxlint-disable-next-line no-thenable
109
- then: (async (onfullfilled?: () => void | Promise<void>) => {
110
- await onfullfilled?.()
111
- return this
112
- }) as any,
113
- }
114
- }
115
107
  }
@@ -3,8 +3,6 @@ import { _RawMCFunctionClass, type MCFunctionClass } from 'sandstone/core/resour
3
3
  import { ObjectiveClass } from './Objective'
4
4
  import type { Score } from './Score'
5
5
 
6
- const checkTriggers: Record<number, MCFunctionClass<undefined, undefined>> = {}
7
-
8
6
  export type TriggerHandler =
9
7
  | (() => any)
10
8
  | MCFunctionClass<undefined, undefined>
@@ -26,14 +24,14 @@ export class TriggerClass<HANDLE extends TriggerHandler> {
26
24
 
27
25
  registerNewObjective(this.objective)
28
26
 
29
- if (!checkTriggers[pollingEvery]) {
30
- checkTriggers[pollingEvery] = MCFunction(`triggers/check/${pollingEvery}`, {
27
+ if (!sandstoneCore.checkTriggers[pollingEvery]) {
28
+ sandstoneCore.checkTriggers[pollingEvery] = MCFunction(`triggers/check/${pollingEvery}`, {
31
29
  creator: 'sandstone',
32
30
  runEvery: pollingEvery,
33
31
  })
34
32
  }
35
33
 
36
- checkTriggers[pollingEvery].push(() => {
34
+ sandstoneCore.checkTriggers[pollingEvery].push(() => {
37
35
  commands.execute.as(`@a[scores={${name}=1..}]`).run(() => {
38
36
  const score = this.objective('@s')
39
37
 
@@ -128,7 +128,7 @@ export class NotNBT<VALUE extends RootNBT = RootNBT> extends NBTClass {
128
128
  this.nbt = nbt
129
129
  }
130
130
 
131
- [util.inspect.custom] = () => `!${nbtStringifier(this.nbt)}`
131
+ [util.inspect.custom] = () => `!${nbtResolver(this.nbt)}`
132
132
  }
133
133
 
134
134
  export type NBTAllNumberClasses<Range extends NBTRange = {}> = NBTLong<Range> | NBTShort<Range> | NBTByte<Range> | NBTFloat<Range> | NBTDouble<Range> | NBTInt<Range>
@@ -161,7 +161,7 @@ function customNumber<T extends number | number[], C extends NBTSimpleClasses>(
161
161
  function dynamicNBT(template: TemplateStringsArray, ...args: unknown[]): NBTObject {
162
162
  const mixedArgs = template.flatMap((s, i) => [s, args[i]]).slice(0, -1) // Remove the last argument, which will always be undefined
163
163
  const result = mixedArgs
164
- .map((element: any) => (element instanceof NBTClass ? nbtStringifier(element) : element.toString()))
164
+ .map((element: any) => (element instanceof NBTClass ? nbtResolver(element).toString() : element.toString()))
165
165
  .join('')
166
166
 
167
167
  return parseNBT(NBT, result)
@@ -440,25 +440,45 @@ export const NBT: NBTInterface = makeCallable(
440
440
 
441
441
  not: (nbt: RootNBT) => new NotNBT(nbt),
442
442
 
443
- stringify: (nbt: NBTObject) => nbtStringifier(nbt),
443
+ stringify: (nbt: NBTObject) => nbtResolver(nbt).toString(),
444
444
 
445
445
  parse: (nbt: string) => parseNBT(NBT, nbt),
446
446
  },
447
447
  dynamicNBT,
448
448
  )
449
449
 
450
- export const nbtStringifier = (nbt: NBTObject | MacroArgument): string => {
450
+ /**
451
+ * The resolved result of converting a Sandstone NBT value (which may contain
452
+ * live `MacroArgument` references) into the final Minecraft SNBT string.
453
+ *
454
+ * Carries macro information up to `CommandNode.getValue()` so commands with
455
+ * `$(...)` substitutions can be flagged as macro commands (prefixed with `$`)
456
+ * even when the macro originated from inside a nested value rather than as
457
+ * a top-level arg object.
458
+ */
459
+ export class ResolvedNBT {
460
+ constructor(
461
+ public readonly value: string,
462
+ public readonly containsMacro: boolean,
463
+ ) {}
464
+
465
+ toString(): string {
466
+ return this.value
467
+ }
468
+ }
469
+
470
+ const resolve = (nbt: NBTObject | MacroArgument): ResolvedNBT => {
451
471
  if (nbt === null || nbt === undefined) {
452
472
  throw new Error('Nullish nbt values are not allowed')
453
473
  }
454
474
  if (typeof nbt === 'number') {
455
475
  // We have a double, integers are now required to be defined with NBT.int
456
- return `${nbt}d`
476
+ return new ResolvedNBT(`${nbt}d`, false)
457
477
  }
458
478
 
459
479
  if (typeof nbt === 'boolean') {
460
480
  // We have a boolean
461
- return nbt.toString()
481
+ return new ResolvedNBT(nbt.toString(), false)
462
482
  }
463
483
 
464
484
  if (typeof nbt === 'string') {
@@ -476,22 +496,29 @@ export const nbtStringifier = (nbt: NBTObject | MacroArgument): string => {
476
496
  })
477
497
 
478
498
  if (inspectedStr[0] === '`') {
479
- return JSON.stringify(nbt)
499
+ return new ResolvedNBT(JSON.stringify(nbt), false)
480
500
  }
481
501
 
482
- return inspectedStr
502
+ return new ResolvedNBT(inspectedStr, false)
483
503
  }
484
504
 
485
505
  if (Array.isArray(nbt)) {
486
506
  // We have an array
487
- const itemsStr = nbt.map(nbtStringifier).join(',')
488
- return `[${itemsStr}]`
507
+ let containsMacro = false
508
+ const itemsStr = nbt
509
+ .map((item) => {
510
+ const resolved = resolve(item)
511
+ if (resolved.containsMacro) containsMacro = true
512
+ return resolved.value
513
+ })
514
+ .join(',')
515
+ return new ResolvedNBT(`[${itemsStr}]`, containsMacro)
489
516
  }
490
517
 
491
518
  // We have an object
492
519
  if (nbt instanceof NBTClass) {
493
520
  // It's actually a "Minecraft Primitive", like 3b, and not an object
494
- return nbt[util.inspect.custom]()
521
+ return new ResolvedNBT(nbt[util.inspect.custom](), false)
495
522
  }
496
523
 
497
524
  if (typeof nbt === 'object' || typeof nbt === 'function') {
@@ -503,32 +530,56 @@ export const nbtStringifier = (nbt: NBTObject | MacroArgument): string => {
503
530
  if (macro === '$()') {
504
531
  failedMacro = true
505
532
  } else {
506
- return macro
533
+ return new ResolvedNBT(macro, true)
507
534
  }
508
535
  }
509
536
  if ('toNBT' in nbt) {
537
+ // toNBT itself may recurse through nbtResolver, so reuse its result.
510
538
  // @ts-ignore
511
- return nbt.toNBT()
539
+ const toNbtResult = nbt.toNBT()
540
+ if (toNbtResult instanceof ResolvedNBT) {
541
+ return toNbtResult
542
+ }
543
+ return new ResolvedNBT(String(toNbtResult), false)
512
544
  } else if (failedMacro) {
513
- throw new Error('[nbtStringifier] A macro variable NBT insertion was attempted in an mcfunction without macros defined.')
545
+ throw new Error('[nbtResolver] A macro variable NBT insertion was attempted in an mcfunction without macros defined.')
514
546
  }
515
547
  if (typeof nbt === 'function') {
516
548
  /* @ts-ignore */
517
- throw new Error(`[nbtStringifier] This should never happen. A function or callable instance (${nbt.constructor?.name}) without toNBT was passed in as an NBT value.`)
549
+ throw new Error(`[nbtResolver] This should never happen. A function or callable instance (${nbt.constructor?.name}) without toNBT was passed in as an NBT value.`)
518
550
  }
519
551
  if (nbt.constructor.name !== 'Object') {
520
552
  if ('toString' in nbt) {
521
- return `${toString}`
553
+ return new ResolvedNBT(`${toString}`, false)
522
554
  } else {
523
555
  /* @ts-ignore */
524
- throw new Error(`[nbtStringifier] A ${nbt.constructor?.name} was passed in as an NBT value. Hint: define toNBT or toString if this was intentional`)
556
+ throw new Error(`[nbtResolver] A ${nbt.constructor?.name} was passed in as an NBT value. Hint: define toNBT or toString if this was intentional`)
525
557
  }
526
558
  }
527
559
  }
528
560
 
529
561
  // It's a real object.
562
+ let containsMacro = false
530
563
  const objectStr = Object.entries(nbt)
531
- .map(([key, value]) => `${key}:${nbtStringifier(value as NBTObject)}`)
564
+ .map(([key, value]) => {
565
+ const resolved = resolve(value as NBTObject)
566
+ if (resolved.containsMacro) containsMacro = true
567
+ return `${key}:${resolved.value}`
568
+ })
532
569
  .join(',')
533
- return `{${objectStr}}`
570
+ return new ResolvedNBT(`{${objectStr}}`, containsMacro)
534
571
  }
572
+
573
+ /**
574
+ * Resolves a Sandstone NBT value into its Minecraft SNBT string form.
575
+ *
576
+ * Unlike the previous `nbtStringifier`, the result is a `ResolvedNBT` object
577
+ * that preserves whether the value contained `$(...)` macro substitutions.
578
+ * `CommandNode.getValue()` uses `containsMacro` to decide whether to emit the
579
+ * command as a macro command (prefixed with `$`).
580
+ *
581
+ * Callers that need a plain string (template literals, string concatenation)
582
+ * can rely on `ResolvedNBT`'s `toString()`, which is called automatically by
583
+ * JS coercion. To be explicit, call `.toString()` on the result.
584
+ */
585
+ export const nbtResolver = (nbt: NBTObject | MacroArgument): ResolvedNBT => resolve(nbt)