diff --git a/src/arguments/coords.ts b/src/arguments/coords.ts index 0edc8df3..1c1a3821 100644 --- a/src/arguments/coords.ts +++ b/src/arguments/coords.ts @@ -1,4 +1,5 @@ -import type { MacroString, VectorClass } from 'sandstone/variables' +import type { MacroString } from 'sandstone/core' +import type { VectorClass } from 'sandstone/variables' type AbsoluteFloat = MacroString<`${number}`, MACRO>; type RelativeFloat = `~${MacroString<(number | ''), MACRO>}`; diff --git a/src/arguments/range.ts b/src/arguments/range.ts index ac6a6aff..1a08dc97 100644 --- a/src/arguments/range.ts +++ b/src/arguments/range.ts @@ -1,4 +1,4 @@ -import type { MacroString } from 'sandstone/variables' +import type { MacroString } from 'sandstone/core' export type Range = | MacroString diff --git a/src/commands/implementations/_fake/comment.ts b/src/commands/implementations/_fake/comment.ts index f450f6b1..9a5718aa 100644 --- a/src/commands/implementations/_fake/comment.ts +++ b/src/commands/implementations/_fake/comment.ts @@ -2,7 +2,7 @@ import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class CommentCommandNode extends CommandNode<[unknown[]]> { command = '#' as const diff --git a/src/commands/implementations/_fake/raw.ts b/src/commands/implementations/_fake/raw.ts index c3447f5d..8935878c 100644 --- a/src/commands/implementations/_fake/raw.ts +++ b/src/commands/implementations/_fake/raw.ts @@ -2,7 +2,7 @@ import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class RawCommandNode extends CommandNode { command = '' as const diff --git a/src/commands/implementations/block/clone.ts b/src/commands/implementations/block/clone.ts index ed667b39..df5a8bdb 100644 --- a/src/commands/implementations/block/clone.ts +++ b/src/commands/implementations/block/clone.ts @@ -1,11 +1,11 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser } from 'sandstone/variables' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { BLOCKS, Coordinates } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' export class CloneCommandNode extends CommandNode { command = 'clone' as const diff --git a/src/commands/implementations/block/fill.ts b/src/commands/implementations/block/fill.ts index 1821ffc1..9dc77e8b 100644 --- a/src/commands/implementations/block/fill.ts +++ b/src/commands/implementations/block/fill.ts @@ -1,11 +1,11 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser } from 'sandstone/variables' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { BLOCKS, Coordinates } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' export class FillCommandNode extends CommandNode { command = 'fill' as const diff --git a/src/commands/implementations/block/place.ts b/src/commands/implementations/block/place.ts index 5f9dfb9b..25cbda17 100644 --- a/src/commands/implementations/block/place.ts +++ b/src/commands/implementations/block/place.ts @@ -1,7 +1,7 @@ import { CommandNode } from 'sandstone/core/nodes' import { coordinatesParser, structureMirrorParser, structureRotationParser, -} from 'sandstone/variables' +} from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import { validateIntegerRange } from '../../validators.js' @@ -9,9 +9,9 @@ import { validateIntegerRange } from '../../validators.js' import type { Coordinates, STRUCTURES, WORLDGEN_CONFIGURED_FEATURES, WORLDGEN_STRUCTURES, WORLDGEN_TEMPLATE_POOLS, } from 'sandstone/arguments' -import type { StructureClass } from 'sandstone/core' +import type { Macroable, StructureClass } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable, StructureMirror, StructureRotation } from 'sandstone/variables' +import type { StructureMirror, StructureRotation } from 'sandstone/variables' export class PlaceCommandNode extends CommandNode { command = 'place' as const diff --git a/src/commands/implementations/block/setblock.ts b/src/commands/implementations/block/setblock.ts index b2cf5e71..b9bd68c5 100644 --- a/src/commands/implementations/block/setblock.ts +++ b/src/commands/implementations/block/setblock.ts @@ -1,11 +1,12 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser } from 'sandstone/variables' +import { coordinatesParser, nbtStringifier } from 'sandstone/variables' import { CommandArguments } from '../../helpers.js' -import type { BLOCKS, Coordinates } from 'sandstone/arguments' +import type { BLOCKS, Coordinates, RootNBT } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { FinalCommandOutput } from '../../helpers.js' export class SetBlockCommandNode extends CommandNode { command = 'setblock' as const @@ -28,9 +29,44 @@ export class SetBlockCommand extends CommandArguments { * * If not specified, defaults to `replace`. */ - setblock = ( + setblock( pos: Macroable, MACRO>, block: Macroable, MACRO>, type?: Macroable<'destroy' | 'keep' | 'replace', MACRO>, - ) => this.finalCommand([coordinatesParser(pos), block, type]) + ): FinalCommandOutput + + /** + * Changes a block to another block. + * + * @param pos Specifies the position of the block to be changed. + * + * @param block Specifies the new block. + * + * @param nbt Specifies the nbt of the block to be changed. + * + * @param type Specifies how to handle the block change. Must be one of: + * - `destroy`: The old block drops both itself and its contents (as if destroyed by a player). Plays the appropriate block breaking noise. + * - `keep`: Only air blocks are changed (non-air blocks are unchanged). + * - `replace`: The old block drops neither itself nor any contents. Plays no sound. + * + * If not specified, defaults to `replace`. + */ + setblock( + pos: Macroable, MACRO>, + block: Macroable, MACRO>, + nbt?: Macroable, + type?: Macroable<'destroy' | 'keep' | 'replace', MACRO>, + ): FinalCommandOutput + + setblock( + pos: Macroable, MACRO>, + block: Macroable, MACRO>, + nbtOrType?: Macroable, + type?: Macroable<'destroy' | 'keep' | 'replace', MACRO>, + ) { + if (typeof nbtOrType === 'object') { + return this.finalCommand([coordinatesParser(pos), `${block}${nbtStringifier(nbtOrType)}`, type]) + } + return this.finalCommand([coordinatesParser(pos), block, nbtOrType]) + } } diff --git a/src/commands/implementations/entity/attribute.ts b/src/commands/implementations/entity/attribute.ts index 11606bcc..be3229e5 100644 --- a/src/commands/implementations/entity/attribute.ts +++ b/src/commands/implementations/entity/attribute.ts @@ -1,10 +1,10 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { SingleEntityArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' // Attribute command diff --git a/src/commands/implementations/entity/clear.ts b/src/commands/implementations/entity/clear.ts index 68749ced..52581ca6 100644 --- a/src/commands/implementations/entity/clear.ts +++ b/src/commands/implementations/entity/clear.ts @@ -6,7 +6,7 @@ import { CommandArguments } from '../../helpers.js' import type { ITEMS, MultiplePlayersArgument } from 'sandstone/arguments' import type { TagClass } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class ClearCommandNode extends CommandNode { command = 'clear' as const diff --git a/src/commands/implementations/entity/damage.ts b/src/commands/implementations/entity/damage.ts index 6078065e..e09f67c2 100644 --- a/src/commands/implementations/entity/damage.ts +++ b/src/commands/implementations/entity/damage.ts @@ -7,7 +7,7 @@ import { CommandArguments } from '../../helpers.js' import type { Coordinates, DAMAGE_TYPES, SingleEntityArgument } from 'sandstone/arguments' import type { DamageTypeClass } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class DamageCommandNode extends CommandNode { command = 'damage' as const diff --git a/src/commands/implementations/entity/effect.ts b/src/commands/implementations/entity/effect.ts index 0688f221..558ed41e 100644 --- a/src/commands/implementations/entity/effect.ts +++ b/src/commands/implementations/entity/effect.ts @@ -6,7 +6,7 @@ import { CommandArguments } from '../../helpers.js' import type { MOB_EFFECTS, MultipleEntitiesArgument } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class EffectCommandNode extends CommandNode { command = 'effect' as const diff --git a/src/commands/implementations/entity/enchant.ts b/src/commands/implementations/entity/enchant.ts index ebfaa76f..fe3691a9 100644 --- a/src/commands/implementations/entity/enchant.ts +++ b/src/commands/implementations/entity/enchant.ts @@ -6,7 +6,7 @@ import { CommandArguments } from '../../helpers.js' import type { ENCHANTMENTS, MultipleEntitiesArgument } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class EnchantCommandNode extends CommandNode { command = 'enchant' as const diff --git a/src/commands/implementations/entity/execute.ts b/src/commands/implementations/entity/execute.ts index f1fd6f0c..eaeeeab6 100644 --- a/src/commands/implementations/entity/execute.ts +++ b/src/commands/implementations/entity/execute.ts @@ -1,14 +1,9 @@ -import { - _RawMCFunctionClass, - MCFunctionClass, -} from 'sandstone/core' import { ContainerCommandNode } from 'sandstone/core/nodes' import { makeCallable, toMinecraftResourceName } from 'sandstone/utils' import { - coordinatesParser, ObjectiveClass, rangeParser, rotationParser, - Score, + coordinatesParser, rangeParser, rotationParser, targetParser, -} from 'sandstone/variables' +} from 'sandstone/variables/parsers' import { CommandArguments, FinalCommandOutput } from '../../helpers.js' import { FunctionCommandNode } from '../server/function.js' @@ -21,16 +16,28 @@ import type { Coordinates, DIMENSIONS, ENTITY_TYPES, MultipleEntitiesArgument, ObjectiveArgument, Range, Rotation, SingleEntityArgument, } from 'sandstone/arguments' import type { SandstoneCommands } from 'sandstone/commands' -import type { MCFunctionNode, PredicateClass } from 'sandstone/core' +import type { + Macroable, MacroArgument, MCFunctionNode, PredicateClass, +} from 'sandstone/core' import type { Node } from 'sandstone/core/nodes' +import type { + _RawMCFunctionClass, +} from 'sandstone/core/resources/datapack/mcfunction' import type { SandstonePack } from 'sandstone/pack' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' import type { DataPointClass } from 'sandstone/variables/Data' +import type { ObjectiveClass } from 'sandstone/variables/Objective.js' +import type { Score } from 'sandstone/variables/Score.js' // Execute command export type SubCommand = [subcommand: string, ...args: unknown[]] +// Yes these suck + +const isObjective = (arg: any): arg is ObjectiveClass => typeof arg === 'object' && Object.hasOwn(arg, 'reset') + +const isScore = (arg: any): arg is Score => typeof arg === 'object' && Object.hasOwn(arg, 'unaryOperation') + class ExecuteCommandPart extends CommandArguments { protected nestedExecute = (args: SubCommand, executable = true) => this.subCommand([args], ExecuteCommand, executable) } @@ -93,8 +100,25 @@ export class ExecuteCommandNode extends ContainerCommandNode { // This will be the execute string without "run" const flattenedArgs = this.args.flat(1) - const filteredArgs = flattenedArgs.filter((arg) => arg !== undefined) - const executeString = `${this.command} ${filteredArgs.join(' ')}` + const args = [] + + for (const arg of flattenedArgs) { + if (arg !== undefined && arg !== null) { + // Yes these are cursed, unfortunately, there's not really a better way to do this as visitors only visit the root nodes. + if (typeof arg === 'object') { + if (Object.hasOwn(arg, 'toMacro') && (arg as MacroArgument)['local'].has(this.sandstoneCore.currentNode)) { + this.isMacro = true + + args.push((arg as MacroArgument).toMacro()) + } else { + args.push(arg) + } + } else { + args.push(arg) + } + } + } + const executeString = `${this.command} ${args.join(' ')}` if (this.body.length === 0) { return executeString @@ -105,7 +129,14 @@ export class ExecuteCommandNode extends ContainerCommandNode { return this.body[0].getValue() } - return `${executeString} run ${this.body[0].getValue()}` + let command = this.body[0].getValue() + + if (command.startsWith('/')) { + this.isMacro = true + command = command.slice(1) + } + + return `${this.isMacro ? '/' : ''}${executeString} run ${command}` } createMCFunction = (currentMCFunction: MCFunctionNode | null) => { @@ -116,8 +147,7 @@ export class ExecuteCommandNode extends ContainerCommandNode { const namespace = currentMCFunction.resource.name.includes(':') ? `${currentMCFunction.resource.name.split(':')[0]}:` : '' // Create a new MCFunctionNode with the body of the ExecuteNode. - const mcFunction = new MCFunctionClass(this.sandstoneCore, `${namespace}${currentMCFunction.resource.path.slice(2).join('/')}/${this.callbackName}`, { - addToSandstoneCore: false, + const mcFunction = this.sandstonePack.MCFunction(`${namespace}${currentMCFunction.resource.path.slice(2).join('/')}/${this.callbackName}`, { creator: 'sandstone', onConflict: 'rename', }) @@ -194,7 +224,7 @@ export class ExecuteStoreArgsCommand extends ExecuteComma * @param playerScore The player's score to override. */ score(...args: [targets: Macroable, MACRO>, objective: Macroable] | [playerScore: Macroable]) { - if (args[0] instanceof Score) { + if (isScore(args[0])) { return this.nestedExecute(['score', args[0]]) } return this.nestedExecute(['score', targetParser(args[0]), args[1]]) @@ -307,17 +337,17 @@ export class ExecuteIfUnlessCommand extends ExecuteComman score(...args: any[]) { const finalArgs: string[] = [] - if (args[0] instanceof Score) { + if (isScore(args[0])) { finalArgs.push(args[0].target.toString(), args[0].objective.name, args[1]) - if (args[2] instanceof Score) { + if (isScore(args[2])) { finalArgs.push(args[2].target.toString(), args[2].objective.name) } else { finalArgs.push(rangeParser(args[2])) } } else { - finalArgs.push(targetParser(args[0]), args[1] instanceof ObjectiveClass ? args[1].name : args[1], args[2]) + finalArgs.push(targetParser(args[0]), isObjective(args[1]) ? args[1].name : args[1], args[2]) if (args[4]) { - finalArgs.push(targetParser(args[3]), args[4] instanceof ObjectiveClass ? args[4].name : args[4]) + finalArgs.push(targetParser(args[3]), isObjective(args[4]) ? args[4].name : args[4]) } else { finalArgs.push(rangeParser(args[3])) } @@ -350,8 +380,8 @@ export class ExecuteIfUnlessCommand extends ExecuteComman } function(func: Macroable<_RawMCFunctionClass<[], []> | (() => any) | string, MACRO>) { - if (func instanceof _RawMCFunctionClass) { - return this.nestedExecute(['function', toMinecraftResourceName(func.path)]) + if (typeof func === 'object' && Object.hasOwn(func, 'addToTag')) { + return this.nestedExecute(['function', toMinecraftResourceName((func as _RawMCFunctionClass<[], []>).path)]) } /* @ts-ignore */ if (typeof func === 'string' || Object.hasOwn(func, 'toMacro')) { @@ -359,7 +389,7 @@ export class ExecuteIfUnlessCommand extends ExecuteComman } const name = `${this.sandstoneCore.getCurrentMCFunctionOrThrow().resource.path.slice(2).join('/')}/execute_if_function` - const _func = new MCFunctionClass(this.sandstoneCore, name, { + const _func = this.sandstonePack.MCFunction(name, { addToSandstoneCore: false, creator: 'sandstone', onConflict: 'rename', diff --git a/src/commands/implementations/entity/kill.ts b/src/commands/implementations/entity/kill.ts index 30600de0..6863e5e3 100644 --- a/src/commands/implementations/entity/kill.ts +++ b/src/commands/implementations/entity/kill.ts @@ -4,7 +4,7 @@ import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { MultipleEntitiesArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class KillCommandNode extends CommandNode { command = 'kill' as const diff --git a/src/commands/implementations/entity/ride.ts b/src/commands/implementations/entity/ride.ts index 93566d7e..9c88fe53 100644 --- a/src/commands/implementations/entity/ride.ts +++ b/src/commands/implementations/entity/ride.ts @@ -1,10 +1,10 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { SingleEntityArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class RideCommandNode extends CommandNode { command = 'ride' as const diff --git a/src/commands/implementations/entity/scoreboard.ts b/src/commands/implementations/entity/scoreboard.ts index 8e61afc0..20dcb30f 100644 --- a/src/commands/implementations/entity/scoreboard.ts +++ b/src/commands/implementations/entity/scoreboard.ts @@ -1,8 +1,7 @@ /* eslint-disable max-len */ -import { CommandNode } from 'sandstone/core' -import { - parseJSONText, Score, targetParser, -} from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { parseJSONText } from 'sandstone/variables/JSONTextComponentClass' +import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' @@ -11,13 +10,18 @@ import type { ObjectiveArgument, OPERATORS, } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Score } from 'sandstone/variables' + +// Yes this sucks + +const isScore = (arg: any): arg is Score => typeof arg === 'object' && Object.hasOwn(arg, 'unaryOperation') function scoresParser(...args: unknown[]) { return args.map((_arg, i) => { const arg = _arg as any - if (arg instanceof Score) { + if (isScore(arg)) { return [arg.target, arg.objective] } if (arg?._toSelector) { @@ -144,7 +148,7 @@ export class ScoreboardCommand extends CommandArguments { ...target: [target: Macroable | number, MACRO>, targetObjective: Macroable] | [targetScore: Macroable], name: Macroable, ]) => { - if (args[0] instanceof Score) { + if (isScore(args[1])) { args[1] = parseJSONText(args[1] as JSONTextComponent) as any } else { args[2] = parseJSONText(args[2] as JSONTextComponent) as any diff --git a/src/commands/implementations/entity/spreadplayers.ts b/src/commands/implementations/entity/spreadplayers.ts index b5f44be0..96c783e1 100644 --- a/src/commands/implementations/entity/spreadplayers.ts +++ b/src/commands/implementations/entity/spreadplayers.ts @@ -1,10 +1,10 @@ -import { CommandNode } from 'sandstone/core' -import { coordinatesParser, targetParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { ColumnCoordinates, MultipleEntitiesArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' import type { FinalCommandOutput } from '../../helpers.js' export class SpreadPlayersNode extends CommandNode { diff --git a/src/commands/implementations/entity/summon.ts b/src/commands/implementations/entity/summon.ts index f3fc9669..88c1f7a1 100644 --- a/src/commands/implementations/entity/summon.ts +++ b/src/commands/implementations/entity/summon.ts @@ -1,11 +1,12 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser, nbtStringifier } from 'sandstone/variables' +import { nbtStringifier } from 'sandstone/variables/nbt/NBTs' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { Coordinates, ENTITY_TYPES, RootNBT } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' export class SummonCommandNode extends CommandNode { command = 'summon' as const diff --git a/src/commands/implementations/entity/tag.ts b/src/commands/implementations/entity/tag.ts index d7d2798b..801e26e7 100644 --- a/src/commands/implementations/entity/tag.ts +++ b/src/commands/implementations/entity/tag.ts @@ -1,10 +1,10 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { MultipleEntitiesArgument } from 'sandstone/arguments' -import type { Macroable, MacroArgument } from 'sandstone/variables' +import type { Macroable, MacroArgument } from 'sandstone/core' function checkTagName(tag: string | MacroArgument) { if (typeof tag !== 'string') { diff --git a/src/commands/implementations/entity/team.ts b/src/commands/implementations/entity/team.ts index b0b15c49..9a9d8ebc 100644 --- a/src/commands/implementations/entity/team.ts +++ b/src/commands/implementations/entity/team.ts @@ -1,10 +1,11 @@ -import { CommandNode } from 'sandstone/core' -import { parseJSONText, targetParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { parseJSONText } from 'sandstone/variables/JSONTextComponentClass' +import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { BASIC_COLORS, JSONTextComponent, MultipleEntitiesArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' interface TeamOptions { collisionRule: 'always' | 'never' | 'pushOtherTeams' | 'pushOwnTeam' diff --git a/src/commands/implementations/entity/teleport.ts b/src/commands/implementations/entity/teleport.ts index 008ebd7e..af5fe287 100644 --- a/src/commands/implementations/entity/teleport.ts +++ b/src/commands/implementations/entity/teleport.ts @@ -1,12 +1,13 @@ -import { CommandNode } from 'sandstone/core' -import { coordinatesParser, targetParser, VectorClass } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { VectorClass } from 'sandstone/variables/Coordinates' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { Coordinates, MultipleEntitiesArgument, Rotation, SingleEntityArgument, } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class TeleportCommandNode extends CommandNode { command = 'tp' as const diff --git a/src/commands/implementations/player/advancement.ts b/src/commands/implementations/player/advancement.ts index 9ce2d053..43c3356c 100644 --- a/src/commands/implementations/player/advancement.ts +++ b/src/commands/implementations/player/advancement.ts @@ -5,7 +5,7 @@ import { CommandArguments } from '../../helpers.js' import type { MultiplePlayersArgument } from 'sandstone/arguments' import type { AdvancementClass } from 'sandstone/core' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' // Advancement command diff --git a/src/commands/implementations/player/bossbar.ts b/src/commands/implementations/player/bossbar.ts index f2005d1f..a8df0d05 100644 --- a/src/commands/implementations/player/bossbar.ts +++ b/src/commands/implementations/player/bossbar.ts @@ -1,11 +1,12 @@ import { CommandNode } from 'sandstone/core/nodes' -import { parseJSONText, targetParser } from 'sandstone/variables' +import { parseJSONText } from 'sandstone/variables/JSONTextComponentClass' +import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { BASIC_COLORS, JSONTextComponent, MultiplePlayersArgument } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' // Bossbar command export class BossBarCommandNode extends CommandNode { diff --git a/src/commands/implementations/player/experience.ts b/src/commands/implementations/player/experience.ts index f0524021..5860a7df 100644 --- a/src/commands/implementations/player/experience.ts +++ b/src/commands/implementations/player/experience.ts @@ -5,7 +5,7 @@ import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { MultiplePlayersArgument, SinglePlayerArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class ExperienceCommandNode extends CommandNode { // We always use the shorthand version for compactness purposes diff --git a/src/commands/implementations/player/gamemode.ts b/src/commands/implementations/player/gamemode.ts index 85b8ba9a..557459ab 100644 --- a/src/commands/implementations/player/gamemode.ts +++ b/src/commands/implementations/player/gamemode.ts @@ -4,7 +4,7 @@ import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { GAMEMODES, MultiplePlayersArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' // Gamemode command diff --git a/src/commands/implementations/player/give.ts b/src/commands/implementations/player/give.ts index 2cbc614f..ade78250 100644 --- a/src/commands/implementations/player/give.ts +++ b/src/commands/implementations/player/give.ts @@ -1,11 +1,12 @@ -import { CommandNode } from 'sandstone/core/nodes' -import { targetParser } from 'sandstone/variables/parsers' +import { CommandNode } from 'sandstone/core' +import { nbtStringifier, targetParser } from 'sandstone/variables' import { CommandArguments } from '../../helpers.js' -import type { ITEMS, MultiplePlayersArgument } from 'sandstone/arguments' +import type { ITEMS, MultiplePlayersArgument, RootNBT } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { FinalCommandOutput } from '../../helpers.js' // Give command @@ -25,9 +26,39 @@ export class GiveCommand extends CommandArguments { * * @param count Specifies the number of items to give. If not specified, defaults to `1`. */ - give = ( + give( targets: Macroable, MACRO>, item: Macroable, MACRO>, count?: Macroable, - ) => this.finalCommand([targetParser(targets), item, count]) + ): FinalCommandOutput + + /** + * Gives an item to one or more players with nbt. + * + * @param targets Specifies the target(s) to give item(s) to. + * + * @param item Specifies the item to give. + * + * @param nbt Specifies the nbt of the item to give. + * + * @param count Specifies the number of items to give. If not specified, defaults to `1`. + */ + give( + targets: Macroable, MACRO>, + item: Macroable, MACRO>, + nbt: Macroable, + count?: Macroable, + ): FinalCommandOutput + + give( + targets: Macroable, MACRO>, + item: Macroable, MACRO>, + countOrNBT?: Macroable, + count?: Macroable, + ) { + if (typeof countOrNBT === 'object') { + return this.finalCommand([targetParser(targets), `${item}${nbtStringifier(countOrNBT)}`, count]) + } + return this.finalCommand([targetParser(targets), item, countOrNBT]) + } } diff --git a/src/commands/implementations/player/particle.ts b/src/commands/implementations/player/particle.ts index 6fb24284..e892f4c7 100644 --- a/src/commands/implementations/player/particle.ts +++ b/src/commands/implementations/player/particle.ts @@ -1,5 +1,5 @@ import { CommandNode } from 'sandstone/core/nodes' -import { arrayToArgsParser } from 'sandstone/variables' +import { arrayToArgsParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' @@ -7,8 +7,8 @@ import type { BLOCKS, Coordinates, ITEMS, MultiplePlayersArgument, PARTICLE_TYPES, } from 'sandstone/arguments' +import type { Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' // Particle command diff --git a/src/commands/implementations/player/playsound.ts b/src/commands/implementations/player/playsound.ts index 202f504c..0882a8cd 100644 --- a/src/commands/implementations/player/playsound.ts +++ b/src/commands/implementations/player/playsound.ts @@ -7,7 +7,7 @@ import type { Coordinates, MultiplePlayersArgument, SOUND_EVENTS, SOUND_SOURCES, } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class PlaySoundCommandNode extends CommandNode { command = 'playsound' as const diff --git a/src/commands/implementations/player/recipe.ts b/src/commands/implementations/player/recipe.ts index 3a49dc6f..87f71a79 100644 --- a/src/commands/implementations/player/recipe.ts +++ b/src/commands/implementations/player/recipe.ts @@ -1,4 +1,4 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' @@ -6,7 +6,7 @@ import { CommandArguments } from '../../helpers.js' import type { ITEMS, MultiplePlayersArgument } from 'sandstone/arguments' import type { RecipeClass } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class RecipeCommandNode extends CommandNode { command = 'recipe' as const diff --git a/src/commands/implementations/player/spawnpoint.ts b/src/commands/implementations/player/spawnpoint.ts index 37bc4bd2..70f1a315 100644 --- a/src/commands/implementations/player/spawnpoint.ts +++ b/src/commands/implementations/player/spawnpoint.ts @@ -1,10 +1,10 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser, targetParser } from 'sandstone/variables' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { Coordinates, MultiplePlayersArgument, Rotation } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class SpawnPointCommandNode extends CommandNode { command = 'spawnpoint' as const diff --git a/src/commands/implementations/player/spectate.ts b/src/commands/implementations/player/spectate.ts index d8626954..db7a3337 100644 --- a/src/commands/implementations/player/spectate.ts +++ b/src/commands/implementations/player/spectate.ts @@ -4,7 +4,7 @@ import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { SingleEntityArgument, SinglePlayerArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class SpectateCommandNode extends CommandNode { command = 'spectate' as const diff --git a/src/commands/implementations/player/stopsound.ts b/src/commands/implementations/player/stopsound.ts index e53a92c8..f62d294f 100644 --- a/src/commands/implementations/player/stopsound.ts +++ b/src/commands/implementations/player/stopsound.ts @@ -5,7 +5,7 @@ import { CommandArguments } from '../../helpers.js' import type { MultiplePlayersArgument, SOUND_EVENTS, SOUND_SOURCES } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class StopSoundCommandNode extends CommandNode { command = 'stopsound' as const diff --git a/src/commands/implementations/player/title.ts b/src/commands/implementations/player/title.ts index 14ad3bd2..1632abf5 100644 --- a/src/commands/implementations/player/title.ts +++ b/src/commands/implementations/player/title.ts @@ -1,10 +1,11 @@ -import { CommandNode } from 'sandstone/core' -import { parseJSONText, targetParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { parseJSONText } from 'sandstone/variables/JSONTextComponentClass' +import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { JSONTextComponent, MultiplePlayersArgument, TimeArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class TitleCommandNode extends CommandNode { command = 'title' as const diff --git a/src/commands/implementations/server/function.ts b/src/commands/implementations/server/function.ts index 45013596..735eec7f 100644 --- a/src/commands/implementations/server/function.ts +++ b/src/commands/implementations/server/function.ts @@ -1,12 +1,13 @@ -import { CommandNode, TagClass } from 'sandstone/core' -import { nbtStringifier } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { TagClass } from 'sandstone/core/resources/datapack/tag' +import { nbtStringifier } from 'sandstone/variables/nbt/NBTs' import { CommandArguments } from '../../helpers.js' import type { RootNBT } from 'sandstone/arguments/nbt.js' -import type { MCFunctionClass } from 'sandstone/core' +import type { DataPointPickClass, Macroable, MCFunctionClass } from 'sandstone/core' import type { - DATA_TYPES, DataPointClass, DataPointPickClass, Macroable, + DATA_TYPES, DataPointClass, } from 'sandstone/variables' import type { FinalCommandOutput } from '../../helpers.js' @@ -16,20 +17,18 @@ export class FunctionCommandNode extends CommandNode<[string | MCFunctionClass | string | TagClass<'functions'> +type Func = MCFunctionClass | string | TagClass<'functions'> export class FunctionCommand extends CommandArguments { protected NodeType = FunctionCommandNode - function(mcFunction: Macroable): FinalCommandOutput + function(mcFunction: Macroable): FinalCommandOutput - function(mcFunction: Macroable, params: Macroable): FinalCommandOutput + function(mcFunction: Macroable, params: Macroable): FinalCommandOutput - function(mcFunction: Macroable, _: 'with', type: DATA_TYPES, target: string, path: string): FinalCommandOutput + function(mcFunction: Macroable, _: 'with', type: DATA_TYPES, target: string, path: string): FinalCommandOutput - function(mcFunction: Macroable, _: 'with', dataPoint: DataPointClass | DataPointPickClass): FinalCommandOutput - - function(mcFunction: Macroable, NonNullable>, MACRO>): FinalCommandOutput + function(mcFunction: Macroable, _: 'with', dataPoint: DataPointClass | DataPointPickClass): FinalCommandOutput function( mcFunction: Macroable | TagClass<'functions'>, MACRO>, diff --git a/src/commands/implementations/server/gamerule.ts b/src/commands/implementations/server/gamerule.ts index 21eada38..af5183e6 100644 --- a/src/commands/implementations/server/gamerule.ts +++ b/src/commands/implementations/server/gamerule.ts @@ -5,7 +5,7 @@ import { CommandArguments } from '../../helpers.js' import type { GAMERULES } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' // Gamerule command diff --git a/src/commands/implementations/server/random.ts b/src/commands/implementations/server/random.ts index 56b4d08c..a92dd4d8 100644 --- a/src/commands/implementations/server/random.ts +++ b/src/commands/implementations/server/random.ts @@ -5,7 +5,7 @@ import { CommandArguments } from '../../helpers.js' import type { Range } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils.js' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class RandomCommandNode extends CommandNode { command = 'random' as const diff --git a/src/commands/implementations/server/return.ts b/src/commands/implementations/server/return.ts index 97e55bfb..1aca73b3 100644 --- a/src/commands/implementations/server/return.ts +++ b/src/commands/implementations/server/return.ts @@ -1,15 +1,13 @@ -import { CommandNode, MCFunctionClass } from 'sandstone/core' -import { ContainerCommandNode } from 'sandstone/core/nodes' +import { CommandNode, ContainerCommandNode } from 'sandstone/core/nodes' import { makeCallable } from 'sandstone/utils' import { CommandArguments, FinalCommandOutput } from '../../helpers.js' import { FunctionCommandNode } from './function.js' import type { SandstonePack } from 'sandstone' -import type { SandstoneCommands } from 'sandstone/commands/commands.js' -import type { MCFunctionNode } from 'sandstone/core' +import type { SandstoneCommands } from 'sandstone/commands' +import type { Macroable, MCFunctionNode } from 'sandstone/core' import type { Node } from 'sandstone/core/nodes' -import type { Macroable } from 'sandstone/variables' export class ReturnRunCommandNode extends ContainerCommandNode { command = 'return' as const @@ -31,21 +29,28 @@ export class ReturnRunCommandNode extends ContainerCommandNode { getValue = () => { if (this.body.length > 1) { - throw new Error('Execute nodes can only have one child node when toString is called.') + throw new Error('Return run nodes can only have one child node when toString is called.') } - return `${this.command} run ${this.body[0].getValue()}` + let command = this.body[0].getValue() + + if (command.startsWith('/')) { + this.isMacro = true + command = command.slice(1) + } + + return `${this.isMacro ? '/' : ''}${this.command} run ${command}` } createMCFunction = (currentMCFunction: MCFunctionNode | null) => { - if (!currentMCFunction) { + if (this.isSingleExecute || !currentMCFunction) { return { node: this as ReturnRunCommandNode } } const namespace = currentMCFunction.resource.name.includes(':') ? `${currentMCFunction.resource.name.split(':')[0]}:` : '' // Create a new MCFunctionNode with the body of the ExecuteNode. - const mcFunction = new MCFunctionClass(this.sandstoneCore, `${namespace}${currentMCFunction.resource.path.slice(2).join('/')}/return_run`, { + const mcFunction = this.sandstonePack.MCFunction(`${namespace}${currentMCFunction.resource.path.slice(2).join('/')}/return_run`, { addToSandstoneCore: false, creator: 'sandstone', onConflict: 'rename', @@ -109,6 +114,6 @@ export class ReturnCommand extends CommandArguments { get return() { const run = new ReturnArgumentsCommand(this.sandstonePack) - return makeCallable(run, (value: Macroable) => this.finalCommand([value]), true) + return makeCallable(run, (value?: Macroable) => this.finalCommand([value || 0]), true) } } diff --git a/src/commands/implementations/server/say.ts b/src/commands/implementations/server/say.ts index e1a485f9..c91467b5 100644 --- a/src/commands/implementations/server/say.ts +++ b/src/commands/implementations/server/say.ts @@ -2,7 +2,7 @@ import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' // Say command diff --git a/src/commands/implementations/server/schedule.ts b/src/commands/implementations/server/schedule.ts index ecfa87c9..93581f87 100644 --- a/src/commands/implementations/server/schedule.ts +++ b/src/commands/implementations/server/schedule.ts @@ -1,13 +1,12 @@ -import { ContainerCommandNode } from 'sandstone/core' -import { MCFunctionClass, TagClass } from 'sandstone/core/resources/datapack/index' +import { ContainerCommandNode } from 'sandstone/core/nodes' +import { TagClass } from 'sandstone/core/resources/datapack/tag' import { toMinecraftResourceName } from 'sandstone/utils' import { CommandArguments } from '../../helpers.js' import type { TimeArgument } from 'sandstone/arguments' -import type { Node } from 'sandstone/core' -import type { MCFunctionNode } from 'sandstone/core/resources/datapack/index' -import type { Macroable } from 'sandstone/variables' +import type { Macroable, Node } from 'sandstone/core' +import type { MCFunctionClass, MCFunctionNode } from 'sandstone/core/resources/datapack/index' type ScheduledFunction = string | TagClass<'functions'> | MCFunctionClass | (() => (any | Promise)) @@ -26,7 +25,7 @@ export class ScheduleCommandNode extends ContainerCommandNode { } // Create a new MCFunctionNode with the body of the ExecuteNode. - const mcFunction = new MCFunctionClass(this.sandstoneCore, `${toMinecraftResourceName(currentMCFunction.resource.path)}/${func ?? 'schedule'}`, { + const mcFunction = this.sandstonePack.MCFunction(`${toMinecraftResourceName(currentMCFunction.resource.path)}/${func ?? 'schedule'}`, { addToSandstoneCore: false, creator: 'sandstone', onConflict: 'rename', @@ -78,8 +77,8 @@ export class ScheduleCommand extends CommandArguments, delay: Macroable, type?: Macroable) => { const node = this.getNode() - if (func instanceof MCFunctionClass) { - return this.finalCommand(['function', func.name, delay, type], node) + if (typeof func === 'object' && Object.hasOwn(func, 'addToTag')) { + return this.finalCommand(['function', (func as unknown as MCFunctionClass).name, delay, type], node) } // A callback has been given if (typeof func === 'function') { diff --git a/src/commands/implementations/server/tellraw.ts b/src/commands/implementations/server/tellraw.ts index 39b2464a..8db9b9c6 100644 --- a/src/commands/implementations/server/tellraw.ts +++ b/src/commands/implementations/server/tellraw.ts @@ -1,10 +1,11 @@ import { CommandNode } from 'sandstone/core/nodes' -import { parseJSONText, targetParser } from 'sandstone/variables' +import { parseJSONText } from 'sandstone/variables/JSONTextComponentClass' +import { targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { JSONTextComponent, MultiplePlayersArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class TellRawCommandNode extends CommandNode { command = 'tellraw' as const diff --git a/src/commands/implementations/server/trigger.ts b/src/commands/implementations/server/trigger.ts index b8e9e65f..296f3657 100644 --- a/src/commands/implementations/server/trigger.ts +++ b/src/commands/implementations/server/trigger.ts @@ -1,4 +1,4 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' diff --git a/src/commands/implementations/world/data.ts b/src/commands/implementations/world/data.ts index 9c37e5c8..3292bd5d 100644 --- a/src/commands/implementations/world/data.ts +++ b/src/commands/implementations/world/data.ts @@ -1,10 +1,12 @@ -import { CommandNode } from 'sandstone/core' -import { coordinatesParser, nbtStringifier, targetParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { nbtStringifier } from 'sandstone/variables/nbt/NBTs' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { Coordinates, NBTObject, SingleEntityArgument } from 'sandstone/arguments' -import type { Macroable, MacroArgument, VectorClass } from 'sandstone/variables' +import type { Macroable, MacroArgument } from 'sandstone/core' +import type { VectorClass } from 'sandstone/variables' export class DataCommandNode extends CommandNode { command = 'data' as const diff --git a/src/commands/implementations/world/forceload.ts b/src/commands/implementations/world/forceload.ts index 9fa3ce5d..e21d9220 100644 --- a/src/commands/implementations/world/forceload.ts +++ b/src/commands/implementations/world/forceload.ts @@ -1,10 +1,11 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser } from 'sandstone/variables' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { ColumnCoordinates } from 'sandstone/arguments' -import type { Macroable, MacroArgument, VectorClass } from 'sandstone/variables' +import type { Macroable, MacroArgument } from 'sandstone/core' +import type { VectorClass } from 'sandstone/variables' /** Parses coordinates, and returns numbers. Looses the relative/local/absolute information. */ function coordinatesToNumbers(coords: string[] | VectorClass | string): number[] { diff --git a/src/commands/implementations/world/item.ts b/src/commands/implementations/world/item.ts index 7884a641..6e38e3dc 100644 --- a/src/commands/implementations/world/item.ts +++ b/src/commands/implementations/world/item.ts @@ -1,5 +1,5 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser, targetParser } from 'sandstone/variables' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' @@ -7,9 +7,8 @@ import type { CONTAINER_SLOTS, Coordinates, ENTITY_SLOTS, ITEMS, MultipleEntitiesArgument, } from 'sandstone/arguments' -import type { ItemModifierClass } from 'sandstone/core' +import type { ItemModifierClass, Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' export class ItemCommandNode extends CommandNode { command = 'item' as const diff --git a/src/commands/implementations/world/locate.ts b/src/commands/implementations/world/locate.ts index adc7a5f0..715fdd88 100644 --- a/src/commands/implementations/world/locate.ts +++ b/src/commands/implementations/world/locate.ts @@ -4,7 +4,7 @@ import { CommandArguments } from '../../helpers.js' import type { POINT_OF_INTEREST_TYPES, WORLDGEN_BIOMES, WORLDGEN_STRUCTURES } from 'sandstone/arguments' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class LocateCommandNode extends CommandNode { command = 'locate' as const diff --git a/src/commands/implementations/world/loot.ts b/src/commands/implementations/world/loot.ts index 698cd371..1380e105 100644 --- a/src/commands/implementations/world/loot.ts +++ b/src/commands/implementations/world/loot.ts @@ -1,6 +1,6 @@ import { validateIntegerRange } from 'sandstone/commands/validators' -import { CommandNode } from 'sandstone/core' -import { coordinatesParser, targetParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { coordinatesParser, targetParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' @@ -8,9 +8,8 @@ import type { CONTAINER_SLOTS, Coordinates, ENTITY_SLOTS, ITEMS, MultipleEntitiesArgument, MultiplePlayersArgument, SingleEntityArgument, } from 'sandstone/arguments' -import type { LootTableClass } from 'sandstone/core' +import type { LootTableClass, Macroable } from 'sandstone/core' import type { LiteralUnion } from 'sandstone/utils' -import type { Macroable } from 'sandstone/variables' type LootTableArgument = Macroable diff --git a/src/commands/implementations/world/setworldspawn.ts b/src/commands/implementations/world/setworldspawn.ts index 818eb021..9868afc6 100644 --- a/src/commands/implementations/world/setworldspawn.ts +++ b/src/commands/implementations/world/setworldspawn.ts @@ -1,10 +1,10 @@ import { CommandNode } from 'sandstone/core/nodes' -import { coordinatesParser } from 'sandstone/variables' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { Coordinates, Rotation } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class SetWorldSpawnCommandNode extends CommandNode { command = 'setworldspawn' as const diff --git a/src/commands/implementations/world/time.ts b/src/commands/implementations/world/time.ts index 87d635fd..ab40491e 100644 --- a/src/commands/implementations/world/time.ts +++ b/src/commands/implementations/world/time.ts @@ -1,9 +1,9 @@ -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' import type { TimeArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class TimeCommandNode extends CommandNode { command = 'time' as const diff --git a/src/commands/implementations/world/weather.ts b/src/commands/implementations/world/weather.ts index 7601568f..f670612d 100644 --- a/src/commands/implementations/world/weather.ts +++ b/src/commands/implementations/world/weather.ts @@ -3,7 +3,7 @@ import { CommandNode } from 'sandstone/core/nodes' import { CommandArguments } from '../../helpers.js' import type { TimeArgument } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class WeatherCommandNode extends CommandNode { command = 'weather' as const diff --git a/src/commands/implementations/world/worldborder.ts b/src/commands/implementations/world/worldborder.ts index f7f00af5..9d36596f 100644 --- a/src/commands/implementations/world/worldborder.ts +++ b/src/commands/implementations/world/worldborder.ts @@ -1,10 +1,10 @@ -import { CommandNode } from 'sandstone/core' -import { coordinatesParser } from 'sandstone/variables' +import { CommandNode } from 'sandstone/core/nodes' +import { coordinatesParser } from 'sandstone/variables/parsers' import { CommandArguments } from '../../helpers.js' import type { ColumnCoordinates } from 'sandstone/arguments' -import type { Macroable } from 'sandstone/variables' +import type { Macroable } from 'sandstone/core' export class WorldBorderNode extends CommandNode { command = 'worldborder' as const diff --git a/src/commands/validators.ts b/src/commands/validators.ts index 65dbbd0c..d13b4115 100644 --- a/src/commands/validators.ts +++ b/src/commands/validators.ts @@ -1,4 +1,4 @@ -import { MacroArgument } from 'sandstone/variables' +import type { MacroArgument } from 'sandstone/core' /** Ensure that a number is inside a given range. */ export function validateIntegerRange(integer: number | MacroArgument, name: string, minimum = 0, maximum = 2_147_483_647) { diff --git a/src/variables/Macro.ts b/src/core/Macro.ts similarity index 52% rename from src/variables/Macro.ts rename to src/core/Macro.ts index 3d5ab45e..5e032d92 100644 --- a/src/variables/Macro.ts +++ b/src/core/Macro.ts @@ -1,13 +1,15 @@ -import { makeCallable } from 'sandstone/utils' - -import type { SandstoneCommands } from 'sandstone/commands' +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SandstoneCore } from 'sandstone/core' +import type { ConditionNode } from 'sandstone/flow' +import type { ConditionClass, DataPointClass } from 'sandstone/variables' export class MacroArgument { protected local: Map public toMacro: () => string + readonly Macro = (strings: TemplateStringsArray, ...macros: (string | number | MacroArgument)[]) => new MacroLiteral(this.sandstoneCore, this.local, strings, macros) + constructor(protected sandstoneCore: SandstoneCore) { this.local = new Map() @@ -29,11 +31,9 @@ export function isMacroArgument(core: SandstoneCore, arg: any) { } class MacroLiteral extends MacroArgument { - public local: Map = new Map() - public toMacro: () => string - constructor(public sandstoneCore: SandstoneCore, public strings: TemplateStringsArray, public macros: (MacroArgument | string)[]) { + constructor(public sandstoneCore: SandstoneCore, public local: Map, public strings: TemplateStringsArray, public macros: (MacroArgument | string | number)[]) { super(sandstoneCore) this.toMacro = () => { @@ -44,10 +44,14 @@ class MacroLiteral extends MacroArgument { const macro = this.macros[i] - if (macro) { - if (typeof macro === 'string') { - result += macro + if (macro !== undefined && macro !== null) { + if (typeof macro === 'string' || typeof macro === 'number') { + result += `${macro}` } else { + const current = this.sandstoneCore.currentNode || this.sandstoneCore.getCurrentMCFunctionOrThrow().resource.name + + macro['local'].set(current, this.local.get(current)!) + result += macro.toMacro() } } @@ -58,12 +62,20 @@ class MacroLiteral extends MacroArgument { } } -export class MacroClass { - readonly commands: SandstoneCommands & this['__call__'] - - __call__ = (strings: TemplateStringsArray, ...macros: (string | MacroArgument)[]) => new MacroLiteral(this.sandstoneCore, strings, macros) +export class DataPointPickClass extends MacroArgument { + /** + * @internal + */ + _toDataPoint(): DataPointClass<'storage'> { + throw new Error('Not implemented') + } +} - constructor(protected sandstoneCore: SandstoneCore, commands: SandstoneCommands) { - this.commands = makeCallable(commands, this.__call__) as unknown as SandstoneCommands & this['__call__'] +export class ConditionalDataPointPickClass extends DataPointPickClass implements ConditionClass { + /** + * @internal + */ + _toMinecraftCondition(): ConditionNode { + throw new Error('Not implemented') } } diff --git a/src/core/index.ts b/src/core/index.ts index 624e0a0e..3b0046d5 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,3 +1,4 @@ +export * from './Macro.js' export * from './nodes.js' export * from './resources/index.js' export * from './sandstoneCore.js' diff --git a/src/core/nodes.ts b/src/core/nodes.ts index 1c39bbd6..18ad937c 100644 --- a/src/core/nodes.ts +++ b/src/core/nodes.ts @@ -1,5 +1,6 @@ import type { SandstonePack } from 'sandstone/pack' -import type { LoopArgument, MacroArgument } from 'sandstone/variables' +import type { LoopArgument } from 'sandstone/variables' +import type { MacroArgument } from './Macro.js' import type { MCFunctionClass, MCFunctionNode } from './resources/datapack/index.js' import type { SandstoneCore } from './sandstoneCore.js' @@ -61,14 +62,14 @@ export abstract class CommandNode extends No commited = false + isMacro = false + constructor(public sandstonePack: SandstonePack, ...args: ARGS) { super(sandstonePack.core) this.args = args } getValue() { - let isMacro = false - const filteredArgs: unknown[] = [] for (const arg of this.args) { @@ -76,7 +77,7 @@ export abstract class CommandNode extends No // Yes these are cursed, unfortunately, there's not really a better way to do this as visitors only visit the root nodes. if (typeof arg === 'object') { if (Object.hasOwn(arg, 'toMacro') && (arg as MacroArgument)['local'].has(this.sandstoneCore.currentNode)) { - isMacro = true + this.isMacro = true filteredArgs.push((arg as MacroArgument).toMacro()) } else if (Object.hasOwn(arg, 'toLoop')) { @@ -90,7 +91,7 @@ export abstract class CommandNode extends No } } - return `${isMacro ? '/' : ''}${this.command} ${filteredArgs.join(' ')}` + return `${this.isMacro ? '/' : ''}${this.command} ${filteredArgs.join(' ')}` } /** diff --git a/src/core/resources/datapack/itemModifier.ts b/src/core/resources/datapack/itemModifier.ts index 683bb3e1..a97d076e 100644 --- a/src/core/resources/datapack/itemModifier.ts +++ b/src/core/resources/datapack/itemModifier.ts @@ -1,4 +1,4 @@ -import { targetParser } from 'sandstone/variables' +import { targetParser } from 'sandstone/variables/parsers' import { ContainerNode } from '../../nodes.js' import { ResourceClass } from '../resource.js' diff --git a/src/core/resources/datapack/mcfunction.ts b/src/core/resources/datapack/mcfunction.ts index e964b4a5..3ff2924e 100644 --- a/src/core/resources/datapack/mcfunction.ts +++ b/src/core/resources/datapack/mcfunction.ts @@ -1,6 +1,6 @@ import { makeCallable, makeClassCallable } from 'sandstone/utils' -import { ResolveNBTPart } from 'sandstone/variables' +import { ResolveNBTPart } from '../../../variables/ResolveNBT.js' import { ContainerNode } from '../../nodes.js' import { CallableResourceClass, @@ -12,10 +12,9 @@ import type { NBTObject } from 'sandstone/arguments/nbt.js' import type { ScheduleType } from 'sandstone/commands' import type { FinalCommandOutput } from 'sandstone/commands/helpers' import type { - ContainerCommandNode, Node, ResourceClassArguments, ResourceNode, SandstoneCore, + ContainerCommandNode, MacroArgument, Node, ResourceClassArguments, ResourceNode, SandstoneCore, } from 'sandstone/core' import type { MakeInstanceCallable } from 'sandstone/utils' -import type { MacroArgument } from 'sandstone/variables' const tags: Record> = {} diff --git a/src/core/resources/datapack/trimMaterial.ts b/src/core/resources/datapack/trimMaterial.ts index 63dd45b8..84029b22 100644 --- a/src/core/resources/datapack/trimMaterial.ts +++ b/src/core/resources/datapack/trimMaterial.ts @@ -9,7 +9,7 @@ import type { TagClass } from './tag.js' let trimMaterials: undefined | TagClass<'items'> -type equipmentSlots = 'mainhand' | 'offhand' | 'head' | 'chest' | 'legs' | 'feet' +export type EquipmentSlots = 'mainhand' | 'offhand' | 'head' | 'chest' | 'legs' | 'feet' /** * A node representing a Minecraft trim material. @@ -35,7 +35,7 @@ export type TrimMaterialClassArguments = { /** * Defaults to all equipment slots. Equipment slots to check in predicate condition, `whole_inventory` will use an `if data` check. */ - equipmentCheck?: 'whole_inventory' | equipmentSlots | equipmentSlots[] + equipmentCheck?: 'whole_inventory' | EquipmentSlots | EquipmentSlots[] } export class TrimMaterialClass extends ResourceClass implements ConditionClass { diff --git a/src/core/resources/datapack/trimPattern.ts b/src/core/resources/datapack/trimPattern.ts index 071cc0bc..d49a6f86 100644 --- a/src/core/resources/datapack/trimPattern.ts +++ b/src/core/resources/datapack/trimPattern.ts @@ -5,8 +5,7 @@ import type { TrimPatternJSON } from 'sandstone/arguments' import type { ConditionClass } from 'sandstone/variables' import type { SandstoneCore } from '../../sandstoneCore.js' import type { ResourceClassArguments, ResourceNode } from '../resource.js' - -type equipmentSlots = 'mainhand' | 'offhand' | 'head' | 'chest' | 'legs' | 'feet' +import type { EquipmentSlots } from './trimMaterial.js' /** * A node representing a Minecraft trim pattern. @@ -25,6 +24,10 @@ export type TrimPatternClassArguments = { */ trimPattern?: TrimPatternJSON } & ResourceClassArguments<'default'> & { + /** + * Optional. Defaults to true. Automatically adds trim pattern to #minecraft:trim_templates. + */ + registerPatternTag?: boolean /** * Optional. Defaults to true. Automatically adds armor trim pattern recipe. */ @@ -32,7 +35,7 @@ export type TrimPatternClassArguments = { /** * Defaults to all equipment slots. Equipment slots to check in predicate condition, `whole_inventory` will use an `if data` check. */ - equipmentCheck?: 'whole_inventory' | equipmentSlots | equipmentSlots[] + equipmentCheck?: 'whole_inventory' | EquipmentSlots | EquipmentSlots[] } export class TrimPatternClass extends ResourceClass implements ConditionClass { @@ -47,6 +50,10 @@ export class TrimPatternClass extends ResourceClass implements this.equipmentCheck = args.equipmentCheck + if (args.registerPatternTag !== false) { + sandstoneCore.pack.Tag('items', 'minecraft:trim_templates', [this.template], { onConflict: 'append' }) + } + if (args.registerPatternRecipe !== false) { let assetID = this.trimPatternJSON.asset_id diff --git a/src/core/resources/resourcepack/model.ts b/src/core/resources/resourcepack/model.ts index af685c37..a17128c3 100644 --- a/src/core/resources/resourcepack/model.ts +++ b/src/core/resources/resourcepack/model.ts @@ -147,6 +147,8 @@ export class ModelClass extends ResourceClass { this.handleConflicts() } + toString = () => `${this.path[0]}:${this.path.slice(2)}` + /** * Generates the Minecraft model JSON */ diff --git a/src/core/resources/resourcepack/texture.ts b/src/core/resources/resourcepack/texture.ts index 59c0bc9c..c12ee5c5 100644 --- a/src/core/resources/resourcepack/texture.ts +++ b/src/core/resources/resourcepack/texture.ts @@ -60,6 +60,10 @@ export class TextureClass extends ResourceClass any, arg1[2]) } + return new ForOfStatement(this.sandstoneCore, arg1 as ForOfIterator, arg3 as IterableDataClass, arg4 as ((i: Score, value: DataPointClass) => any)) } + + switch( + value: ValueType, + cases: CaseStatement | DefaultType, + ): void + + switch( + value: ValueType, + cases: ['case', CheckType, () => any][], + _default?: ['default', () => any], + ): void + + switch( + value: ValueType, + _cases: ['case', CheckType, () => any][] | CaseStatement | DefaultType, + _default?: ['default', () => any], + ) { + let cases: (readonly ['case', CheckType, () => any])[] + + if (_cases instanceof CaseStatement) { + cases = _cases['getCases']().map((c) => c['getValue']()) + } else if (Array.isArray(_cases)) { + cases = _cases + } else { + cases = _cases.cases.map((c) => c['getValue']()) + _default = ['default', _cases.default] + } + const { + Data, initMCFunction, MCFunction, Macro, + } = this.sandstoneCore.pack + + // eslint-disable-next-line no-plusplus + const id = switches++ + + const values = Data('storage', `__sandstone:switch_${id}`, 'Values') + + initMCFunction.push(() => values.set(cases.map(([_, v, callback], i) => { + MCFunction(`__sandstone:switch_${id}_case_${i}`, [], () => callback()) + + return { Value: v, Index: i } + }))) + + const flow = this + + MCFunction(`__sandstone:switch_${id}`, [value], () => { + const index = Data('storage', `__sandstone:switch_${id}`, 'Index') + + Macro.data.modify.storage(index.currentTarget, 'Index').set.from.storage(values.currentTarget, value.Macro`Values[{Value:${value}}}].Index`) + + const _if = flow.if(index, () => { + MCFunction(`__sandstone:switch_${id}_inner`, [index], () => { + Macro.functionCmd(index.Macro`__sandstone:switch_${id}_case_${index}`) + })() + }) + if (_default) _if.else(() => _default?.[1]()) + })() + } + + case(value: ValueType, callback: () => any) { + return new CaseStatement(value, callback) + } } diff --git a/src/flow/conditions/variables/dataPoint.ts b/src/flow/conditions/variables/dataPoint.ts index ce5cb539..cbc11cb6 100644 --- a/src/flow/conditions/variables/dataPoint.ts +++ b/src/flow/conditions/variables/dataPoint.ts @@ -1,8 +1,8 @@ import { SingleConditionNode } from '../condition.js' import type { NBTObject } from 'sandstone/arguments/nbt' -import type { SandstoneCore } from 'sandstone/core' -import type { DataPointClass, DataPointPickClass, Score } from 'sandstone/variables' +import type { DataPointPickClass, SandstoneCore } from 'sandstone/core' +import type { DataPointClass, Score } from 'sandstone/variables' export class DataPointExistsConditionNode extends SingleConditionNode { constructor(sandstoneCore: SandstoneCore, readonly dataPoint: DataPointClass) { @@ -15,11 +15,24 @@ export class DataPointExistsConditionNode extends SingleConditionNode { } export class DataPointEqualsConditionNode extends SingleConditionNode { + readonly conditional: Score + constructor(sandstoneCore: SandstoneCore, readonly dataPoint: DataPointClass, readonly value: NBTObject | Score | DataPointClass | DataPointPickClass) { super(sandstoneCore) + + const { DataVariable, Variable, commands } = sandstoneCore.pack + const { execute } = commands + + const anon = DataVariable(this.dataPoint) + + this.conditional = Variable() + + execute.store.result.score(this.conditional).run(() => anon.set(this.value as unknown as DataPointClass)) } + getValue = (negated?: boolean) => (negated ? ['if', ...this.getCondition()] : ['unless', ...this.getCondition()]).join(' ') + getCondition(): unknown[] { - return ['data', 'TODO'] + return ['score', 'matches', '0..'] } } diff --git a/src/flow/old-flow.ts.disable b/src/flow/old-flow.ts.disable deleted file mode 100644 index db48a257..00000000 --- a/src/flow/old-flow.ts.disable +++ /dev/null @@ -1,746 +0,0 @@ -import { isAsyncFunction } from '@/utils' -import { Execute } from '@commands/implementations/Execute' -import { toMCFunctionName } from '@datapack/minecraft' -import { ConditionClass, coordinatesParser } from '@variables' -import { Score } from '@variables/Score' - -import { CombinedConditions, getConditionScore } from './conditions' - -import type { - BLOCKS, Coordinates, SingleEntityArgument, -} from 'src/arguments' -import type { LiteralUnion } from '@/generalTypes' -import type { CommandsRoot } from '@commands' -import type { Datapack } from '@datapack' -import type { CommandArgs } from '@datapack/minecraft' -import type { FunctionResource } from '@datapack/resourcesTree' -import type { ConditionType } from './conditions' - -const ASYNC_CALLBACK_NAME = '__await_flow' - -function valueToCondition(value: unknown[]): ConditionClass { - const condition = new ConditionClass() - condition._toMinecraftCondition = () => ({ value }) - return condition -} - -/** Call a given callback function, and inline it if possible */ -function callOrInlineFunction(datapack: Datapack, callbackFunction: FunctionResource, forceInlineScore?: Score) { - const { commandsRoot } = datapack - - if ( - callbackFunction?.isResource && ( - ( - // Either our command is 1-line-long, then it can be inlined (if it's not an execute!) - callbackFunction.commands.length <= 1 - && callbackFunction.commands?.[0]?.[0] !== 'execute' - ) || ( - /* - * Either it has 2 commands, but the 2nde one is used in an if/else context, - * and can be dropped in favor of an `execute store` - */ - callbackFunction.commands.length === 2 - && forceInlineScore - ) - ) - ) { - /* - * If our callback only has 1 command, inline this command. We CANNOT inline executes, for complicated reasons. - * If you want to understand the reasons, see @vdvman1#9510 explanation => - * https://discordapp.com/channels/154777837382008833/154777837382008833/754985742706409492 - */ - - const { commands } = callbackFunction - - // If our resource has children, just set it as a folder. Else, entirely, destroy it. - if (callbackFunction.children.size > 0) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - callbackFunction.isResource = false - callbackFunction.commands = [] - } else { - datapack.resources.deleteResource(callbackFunction.path, 'functions') - } - - if (commands.length) { - if (commands.length === 2 && forceInlineScore) { - // If we have 2 commands, add the execute store - if (commandsRoot.arguments.length === 0) { - commandsRoot.arguments.push('execute') - } - - commandsRoot.arguments.push('store', 'success', 'score', forceInlineScore) - } - - if (commandsRoot.arguments.length > 0) { - commandsRoot.arguments.push('run') - } - - commandsRoot.addAndRegister(...commands[0]) - } else { - commandsRoot.arguments = [] - } - } else { - // Else, register the function call - commandsRoot.functionCmd(toMCFunctionName(callbackFunction.path)) - } -} - -type FlowStatementConfig = { - callbackName: string - absoluteName?: string - forceInlineScore?: Score -} & ( - { - initialCondition: false, - loopCondition: false, - condition?: undefined - } | { - initialCondition: boolean, - loopCondition: boolean, - condition: ConditionType - } -) - -export class Flow { - private commandsRoot - - private datapack - - arguments: CommandArgs - - executeState: CommandsRoot['executeState'] - - constructor( - datapack: Datapack, - ) { - this.datapack = datapack - this.commandsRoot = datapack.commandsRoot - this.arguments = [] - this.executeState = 'outside' - } - - /** CONDITIONS */ - - /** - * Compares the block at a given position to a given block. Suceeds if both are identical. - * - * @param pos Position of a target block to test. - * - * @param block A block to test against. - */ - block = (coords: Coordinates, block: LiteralUnion): ConditionClass => ( - valueToCondition(['if', 'block', coordinatesParser(coords), block]) - ) - - /** - * Compares the blocks in two equally sized volumes. Suceeds if both are identical. - * - * @param start Positions of the first diagonal corner of the source volume (the comparand; the volume to compare). - * - * @param end Positions of the second diagonal corner of the source volume (the comparand; the volume to compare) - * - * @param destination - * Position of the lower northwest (the smallest X, Y and Z value) corner of the destination volume - * (the comparator; the volume to compare to). Assumed to be of the same size as the source volume. - * - * @param scanMode Specifies whether all blocks in the source volume should be compared, or if air blocks should be masked/ignored. - */ - blocks = (start: Coordinates, end: Coordinates, destination: Coordinates, scanMode: 'all' | 'masked'): ConditionClass => ( - valueToCondition(['if', 'blocks', coordinatesParser(start), coordinatesParser(end), coordinatesParser(destination), scanMode]) - ) - - /** - * Checks if the given target has any data for a given tag. - * - * @example - * // Check whether the current block has an Inventory - * _.if(_.data.block(rel(0, 0, 0), 'Inventory'), () => { - * say('The current block has data in its Inventory tag.') - * }) - * - * // Check whether the player has at least one slot with dirt - * _.if(_.data.entity(`@r`, 'Inventory[{id: "minecraft:dirt"}]'), () => { - * say('The random player has dirt.') - * }) - * - * // Check whether there is data in the "Test" tag of the storage - * _.if(_.data.storage('namespace:mystorage', 'Test'), () => { - * say('There is data in the "Test" tag of mystorage.') - * }) - */ - data = { - /** - * Checks whether the targeted block has any data for a given tag. - * @param pos Position of the block to be tested. - * @param path Data tag to check for. - */ - block: (pos: Coordinates, path: string) => valueToCondition(['if', 'data', 'block', coordinatesParser(pos), path]), - - /** - * Checks whether the targeted entity has any data for a given tag - * @param target One single entity to be tested. - * @param path Data tag to check for. - */ - entity: (target: SingleEntityArgument, path: string) => valueToCondition(['if', 'data', 'entity', target, path]), - - /** - * Checks whether the targeted storage has any data for a given tag - * @param source The storage to check in. - * @param path Data tag to check for. - */ - storage: (source: string, path: string) => valueToCondition(['if', 'data', 'storage', source, path]), - } - - /** Logical operators */ - - /** - * Check if multiple conditions are true at the same time. - * @param conditions The conditions to check. - */ - and = (...conditions: (ConditionType)[]) => new CombinedConditions(this.commandsRoot, conditions, 'and') - - /** - * Check if at least one of the given conditions is true. - * @param conditions The conditions to check. - */ - or = (...conditions: (ConditionType)[]) => new CombinedConditions(this.commandsRoot, conditions, 'or') - - /** - * Check if the given condition is not true. - * @param condition The condition to check. - */ - not = (condition: ConditionType) => new CombinedConditions(this.commandsRoot, [condition], 'not') - - /** Flow statements */ - flowStatementAsync = async (callback: () => Promise, config: FlowStatementConfig) => { - /* - * Sometimes, there are a few arguments left inside the commandsRoot (for execute.run mostly). - * Keep them aside, & register them after. - */ - const previousArguments = this.commandsRoot.arguments - const previousExecuteState = this.commandsRoot.executeState - this.commandsRoot.reset() - - const args = this.arguments.slice(1) - - const { currentFunction } = this.datapack - - const { fullName: asyncCallbackName } = this.datapack.getUniqueChildName(ASYNC_CALLBACK_NAME) - - // First, enter the callback - let callbackFunctionName: string - - if (config.absoluteName) { - callbackFunctionName = this.datapack.createEnterRootFunction(config.absoluteName, 'throw') - } else { - callbackFunctionName = this.datapack.createEnterChildFunction(config.callbackName) - } - - const callbackMCFunction = this.datapack.currentFunction! - - await callback() - - this.commandsRoot.register(true) - - // Add its commands - if (config.initialCondition && !config.loopCondition) { - // If we're in a if/else if/else, call the next lines at the end of each branch - this.commandsRoot.functionCmd(asyncCallbackName) - } - - // At the end of the callback, add the given conditions to call it again - if (config.loopCondition) { - /* - * In an asynchronous flow statement, we need to recursively call the function if the condition is met, else we need to enter a new child function. - * We create a new Flow object to prevent interfering with the current flow object, which might cause problems. - */ - const flow = new Flow(this.datapack) - flow.arguments = this.arguments - flow - .if(config.condition, () => { - this.commandsRoot.functionCmd(callbackFunctionName) - }) - .else(() => { - this.commandsRoot.functionCmd(asyncCallbackName) - }) - } - - // Exit the callback - this.datapack.currentFunction = currentFunction - - // Put back the old arguments - this.commandsRoot.arguments = previousArguments - this.commandsRoot.executeState = previousExecuteState - - // Register the initial condition (in the root function) to enter the callback. - if (config.initialCondition) { - // In while statements, if the condition isn't met the 1st time, directly call the next lines of code - if (config.loopCondition) { - const flow = new Flow(this.datapack) - flow.arguments = this.arguments - flow - .if(config.condition, () => { callOrInlineFunction(this.datapack, callbackMCFunction) }) - .else(() => { this.commandsRoot.functionCmd(asyncCallbackName) }) - } else { - /* - * In if/else/else if, the respective functions have to ensure the next lines of code will be called no matter what. - * Therefore, this function just has to register the initial condition. - */ - registerCondition(this.commandsRoot, config.condition, args) - this.commandsRoot.executeState = 'after' - callOrInlineFunction(this.datapack, callbackMCFunction) - } - } else { - callOrInlineFunction(this.datapack, callbackMCFunction) - } - - // Reset the _.execute.as().at()... arguments. - this.arguments = [] - } - - flowStatement = (callback: () => void, config: FlowStatementConfig) => { - /* - * Sometimes, there are a few arguments left inside the commandsRoot (for execute.run mostly). - * Keep them aside, & register them after. - */ - const previousArguments = this.commandsRoot.arguments - const previousExecuteState = this.commandsRoot.executeState - this.commandsRoot.reset() - - const args = this.arguments.slice(1) - - const { currentFunction } = this.datapack - - // First, enter the callback - let callbackFunctionName: string - - if (config.absoluteName) { - callbackFunctionName = this.datapack.createEnterRootFunction(config.absoluteName, 'throw') - } else { - callbackFunctionName = this.datapack.createEnterChildFunction(config.callbackName) - } - - const callbackMCFunction = this.datapack.currentFunction! - - // Add its commands - callback() - - this.commandsRoot.register(true) - - // At the end of the callback, add the given conditions to call it again - if (config.loopCondition) { - // In a synchronous flow statement, we just have to recursively call the function - registerCondition(this.commandsRoot, config.condition, args) - this.commandsRoot.executeState = 'after' - this.commandsRoot.functionCmd(callbackFunctionName) - } - - // Exit the callback - this.datapack.currentFunction = currentFunction - - // Put back the old arguments - this.commandsRoot.arguments = previousArguments - this.commandsRoot.executeState = previousExecuteState - - // Register the initial condition (in the root function) to enter the callback - if (config.initialCondition) { - registerCondition(this.commandsRoot, config.condition, args) - this.commandsRoot.executeState = 'after' - } - callOrInlineFunction(this.datapack, callbackMCFunction, config.forceInlineScore) - - this.arguments = [] - } - - private if_ = >( - condition: ConditionType, - callback: () => R, - callbackName: string, - ifScore: Score, - forceInlineScore = false, - ): (R extends void ? ElifElseFlow : ElifElseFlow & PromiseLike) => { - function ensureConsistency(nextCallback: () => void) { - if (!isAsyncFunction(callback) && isAsyncFunction(nextCallback)) { - throw new Error('Passed an asynchronous callback in a synchronous if/else if/else. If/else if/else must be all synchronous, or all asynchronous.') - } - if (isAsyncFunction(callback) && !isAsyncFunction(nextCallback)) { - throw new Error('Passed a synchronous callback in an asynchronous if/else if/else. If/else if/else must be all synchronous, or all asynchronous.') - } - } - - if (!isAsyncFunction(callback)) { - // Register the current if - this.flowStatement(callback, { - callbackName, - initialCondition: true, - loopCondition: false, - condition, - forceInlineScore: forceInlineScore ? ifScore : undefined, - }) - - // We know the callback is synchronous. We must prevent the user to pass an asynchronous callback in else/else if. - return { - elseIf: (nextCondition: ConditionType, nextCallback: () => void) => { - // Ensure the callback is synchronous. - ensureConsistency(nextCallback) - - return this.if_(this.and(this.not(ifScore.matches([0, null])), nextCondition), () => { - nextCallback() - ifScore.set(1) - }, 'else_if', ifScore, true) - }, - else: (nextCallback: () => void) => { - // Ensure the callback is synchronous. - ensureConsistency(nextCallback) - - this.if_(this.not(ifScore.matches([0, null])), nextCallback, 'else', ifScore, false) - }, - } as ElifElseFlow as any - } - - const getPreviousPromise = () => this.flowStatementAsync(callback, { - callbackName, - initialCondition: true, - loopCondition: false, - condition, - }) - - const { currentFunction: parentFunction } = this.datapack - - return { - elseIf: (nextCondition: ConditionType, nextCallback: () => Promise) => { - // Ensure the callback is asynchronous. - ensureConsistency(nextCallback) - - return this.if_(this.and(nextCondition, this.not(ifScore.matches([0, null]))), async () => { - // We keep the function where the "else if" is running - const { currentFunction: newCallback } = this.datapack - - // Go back in the parent function - this.datapack.currentFunction = parentFunction - // Run the previous "if/else if" code - await getPreviousPromise() - - // Now, we're going back in the current "else if" - this.datapack.currentFunction = newCallback - - // First, we run all synchronous code (that will end up in the .mcfunction instantly called by the "else if") - const returnedPromise = nextCallback() - - // We notice Sandstone that the condition has successfully passed - ifScore.set(1) - - // Then we run the asynchronous code, that will create other .mcfunction called with /schedule. - await returnedPromise - }, 'else_if', ifScore) - }, - else: (nextCallback: () => Promise) => { - // Ensure the callback is asynchronous. - ensureConsistency(nextCallback) - - /* - * We return the "if" result, which theoritically could allow our users to - * write `.if().else().if()`, however this is forbidden thanks to our TypeScript types. - * We have to return the result for the `then` part. - */ - return this.if_(this.not(ifScore.matches([0, null])), async () => { - // We keep the function where the "else" is running - const { currentFunction: newCallback } = this.datapack - - // Go back in the parent function - this.datapack.currentFunction = parentFunction - // Run the previous "if"/"else if" code - await getPreviousPromise() - - // Now, we're going back in the current "else" - this.datapack.currentFunction = newCallback - - // And we run the "else" code. - await nextCallback() - }, 'else', ifScore) - }, - then: async (onfulfilled: () => void) => { - // In theory, we are already in the parent function so we shouldn't need to go back in it. - - // Run the previous "if/else if/else" code - await getPreviousPromise() - - // Go back in the parent function, because we don't know where the last "if/else if/else" code ended up. - this.datapack.currentFunction = parentFunction - - // Finally enter the callback function - this.datapack.createEnterChildFunction(ASYNC_CALLBACK_NAME) - return onfulfilled?.() - }, - } as any - } - - if = >(condition: ConditionType, callback: () => R): (R extends void ? ElifElseFlow : ElifElseFlow & PromiseLike) => { - const ifScore = getConditionScore(this.commandsRoot.Datapack) - - if (!isAsyncFunction(callback)) { - // /!\ Complicated stuff happening here. - let callbackFunction: FunctionResource - const { elseIf: realElseIf, else: realElse } = this.if_(condition, () => { callbackFunction = this.datapack.currentFunction!; callback() }, 'if', ifScore, false) - const { currentFunction } = this.datapack - - // for Typescript - if (!currentFunction?.isResource) { throw new Error('Impossible') } - - const ifCommandIndex = currentFunction.commands.length - 1 - - const switchToComplicatedIf = () => { - const command = currentFunction.commands[ifCommandIndex] - - try { - // If this doesn't raise an error, it means the function didn't get inlined - this.datapack.resources.getResource(callbackFunction.path, 'functions') - - // The function wasn't inlined - add the '/scoreboard players set' at the end of the function - if (!callbackFunction?.isResource) { throw new Error('Impossible') } - callbackFunction.commands.push(['scoreboard', 'players', 'set', ifScore, 1]) - } catch (e) { - // The function was inlined - add the 'store success' part to the execute - currentFunction.commands[ifCommandIndex] = ['execute', 'store', 'success', 'score', ifScore, ...command.slice(1)] - } - - // Add the reset - currentFunction.commands = [ - ...currentFunction.commands.slice(0, ifCommandIndex), - ['scoreboard', 'players', 'reset', ifScore], - ...currentFunction.commands.slice(ifCommandIndex), - ] - } - - return { - elseIf: (...args: Parameters) => { - switchToComplicatedIf() - return realElseIf(...args) - }, - else: (cb: Parameters['0']) => { - switchToComplicatedIf() - return realElse(cb) - }, - } as ElifElseFlow as any - } - - // First, specify the `if` didn't pass yet (it's in order to chain elif/else) - ifScore.reset() - - // Async function - return this.if_(condition, async () => { - const returnedPromise = callback() - ifScore.set(1) - await returnedPromise - }, 'if', ifScore) as any - } - - binaryMatch = (score: Score, minimum: number, maximum: number, callback: (num: number) => void) => { - // First, specify we didn't find a match yet - const foundMatch = this.datapack.Variable(0) - - const callCallback = (num: number) => { - this.if(this.and(score.equalTo(num), foundMatch.equalTo(0)), () => { - // If we found the correct score, call the callback & specify we found a match - callback(num) - foundMatch.set(1) - }) - } - - // Recursively match the score - const recursiveMatch = (min: number, max: number) => { - const diff = max - min - - if (diff < 0) { - return - } - - if (diff === 3) { - callCallback(min) - callCallback(min + 1) - callCallback(min + 2) - return - } - if (diff === 2) { - callCallback(min) - callCallback(min + 1) - return - } - if (diff === 1) { - callCallback(min) - return - } - - const mean = Math.floor((min + max) / 2) - - this.if(score.lessThan(mean), () => recursiveMatch(min, mean)) - this.if(score.greaterOrEqualThan(mean), () => recursiveMatch(mean, max)) - } - - recursiveMatch(minimum, maximum) - } - - private _while = >(condition: ConditionClass | CombinedConditions, callback: () => R, type: 'while' | 'do_while'): R => { - if (!isAsyncFunction(callback)) { - this.flowStatement(callback, { - callbackName: type, - initialCondition: type === 'while', - loopCondition: true, - condition, - }) - - return undefined as any - } - - const { currentFunction: parentFunction } = this.datapack - - return { - then: async (onfulfilled: () => void) => { - // In theory, we are already in the parent function so we shouldn't need to go back in it. - - // Run the previous code - await this.flowStatementAsync(callback, { - callbackName: type, - initialCondition: type === 'while', - loopCondition: true, - condition, - }) - - // Go back in the parent function, because we don't know where the last code ended up. - this.datapack.currentFunction = parentFunction - - // Finally enter the callback function - this.datapack.createEnterChildFunction(ASYNC_CALLBACK_NAME) - return onfulfilled?.() - }, - } as PromiseLike as any - } - - while = >(condition: ConditionClass | CombinedConditions, callback: () => R): R => this._while(condition, callback, 'while') - - doWhile = >(condition: ConditionClass | CombinedConditions, callback: () => R): R => this._while(condition, callback, 'do_while') - - binaryFor = (from: Score | number, to: Score |number, callback: (amount: number) => void, maximum = 128) => { - if (typeof from === 'number' && typeof to === 'number') { - callback(to - from) - } - - const realStart = from instanceof Score ? from : this.datapack.Variable(from) - const realEnd = to instanceof Score ? to : this.datapack.Variable(to) - - const iterations = realEnd.minus(realStart) - - const _ = this - - /* - * For all iterations above the maximum, - * just do a while loop that calls `maximum` times the callback, - * until there is less than `maximum` iterations - */ - _.while(iterations.lessThan(maximum), () => { - callback(maximum) - iterations.remove(maximum) - }) - - /* - * There is now less iterations than the allowed MAXIMUM - * Start the binary part - */ - for (let i = 1; i < maximum; i *= 2) { - _.if(iterations.moduloBy(2).equalTo(1), () => { - callback(i) - }) - - iterations.dividedBy(2) - } - } - - forRange = >(from: Score | number, to: Score | number, callback: (score: Score) => R) => { - const scoreTracker = from instanceof Score ? from : this.datapack.Variable(from) - - // Small optimization: if we know the loop will run at least once, use a do while - let loop = this.while - if (typeof from === 'number' && typeof to === 'number' && to > from) { - loop = this.doWhile - } - - if (!isAsyncFunction(callback)) { - return loop(scoreTracker.lessThan(to), () => { - callback(scoreTracker) - scoreTracker.add(1) - }) - } - - return loop(scoreTracker.lessThan(to), async () => { - await callback(scoreTracker) - scoreTracker.add(1) - }) - } - - forScore = >( - score: Score | number, - // eslint-disable-next-line no-shadow - condition: ((score: Score) => ConditionType) | ConditionType, - // eslint-disable-next-line no-shadow - modifier: (score: Score) => void, - // eslint-disable-next-line no-shadow - callback: (score: Score) => R, - ): R => { - const realScore = score instanceof Score ? score : this.datapack.Variable(score) - const realCondition = typeof condition === 'function' ? condition(realScore) : condition - - if (!isAsyncFunction(callback)) { - return this.while(realCondition, () => { - callback(realScore) - modifier(realScore) - }) as any - } - - return this.while(realCondition, async () => { - await callback(realScore) - modifier(realScore) - }) as any - } - - private register = (soft?: boolean) => { - throw new Error('Not supposed to happen!') - } - - get execute(): Omit, 'run' | 'runOne'> { - return new Execute(this) - } -} - -function registerCondition(commandsRoot: CommandsRoot, condition: ConditionType, args: unknown[] = []) { - let commands: string[][] - - if (condition instanceof CombinedConditions) { - const realCondition = condition.removeOr().simplify() - - if (realCondition instanceof CombinedConditions) { - const { callableExpression, requiredExpressions } = realCondition.toExecutes() - commands = [...requiredExpressions, callableExpression] - } else { - commands = [['execute', ...args, ...realCondition._toMinecraftCondition().value]] - } - } else { - commands = [['execute', ...args, ...condition._toMinecraftCondition().value]] - } - - // Add & register all required commands - for (const command of commands.slice(0, -1)) { - commandsRoot.addAndRegister(...command) - } - - // Add the callable command, WITHOUT REGISTERING IT. It must be appended with the command to run. - const callableCommand = commands[commands.length - 1] - commandsRoot.arguments.push(...callableCommand) -} - -export type PublicFlow = Omit - -type ElifElseFlow> = { - elseIf: (condition: ConditionType, callback: () => R) => (R extends void ? ElifElseFlow : ElifElseFlow & PromiseLike) - else: (callback: () => R) => void -} diff --git a/src/flow/switch_case.ts b/src/flow/switch_case.ts new file mode 100644 index 00000000..978a6f47 --- /dev/null +++ b/src/flow/switch_case.ts @@ -0,0 +1,27 @@ +import type { NBTObject } from 'sandstone/arguments' + +export type DefaultType = { + cases: CaseStatement[]; + default: () => any; +} + +export class CaseStatement { + constructor(readonly value: ValueType, readonly callback: () => any, readonly previous?: CaseStatement[]) {} + + protected getCases = () => (this.previous ? [...this.previous, this] : [this]) + + protected getValue(): readonly ['case', ValueType, () => any] { + return ['case', this.value, this.callback] + } + + case(value: ValueType, callback: () => any) { + return new CaseStatement(value, callback, this.getCases()) + } + + default(callback: () => any) { + return { + cases: this.getCases(), + default: callback, + } + } +} diff --git a/src/pack/pack.ts b/src/pack/pack.ts index 6e697ef7..ef321f7c 100644 --- a/src/pack/pack.ts +++ b/src/pack/pack.ts @@ -2,7 +2,8 @@ /* eslint-disable no-plusplus */ import { SandstoneCommands } from 'sandstone/commands' import { - AdvancementClass, AtlasClass, BlockStateClass, DamageTypeClass, FontClass, ItemModifierClass, LanguageClass, LootTableClass, MCFunctionClass, ModelClass, PlainTextClass, PredicateClass, RecipeClass, SandstoneCore, SoundEventClass, TagClass, TextureClass, TrimMaterialClass, TrimPatternClass, + AdvancementClass, AtlasClass, BlockStateClass, DamageTypeClass, FontClass, ItemModifierClass, LanguageClass, LootTableClass, + MCFunctionClass, ModelClass, PlainTextClass, PredicateClass, RecipeClass, SandstoneCore, SoundEventClass, TagClass, TextureClass, TrimMaterialClass, TrimPatternClass, } from 'sandstone/core' import { CustomResourceClass } from 'sandstone/core/resources/custom' import { Flow, SandstoneConditions } from 'sandstone/flow' @@ -10,7 +11,7 @@ import { randomUUID } from 'sandstone/utils' import { coordinatesParser, DataArray, - DataClass, DataIndexMap, DataPointClass, LabelClass, LoopArgument, MacroClass, ObjectiveClass, SelectorClass, TargetlessDataClass, TargetlessDataPointClass, VectorClass, + DataClass, DataIndexMap, DataPointClass, LabelClass, LoopArgument, ObjectiveClass, SelectorClass, TargetlessDataClass, TargetlessDataPointClass, VectorClass, } from 'sandstone/variables' import { ResolveNBTClass } from 'sandstone/variables/ResolveNBT' import { Score } from 'sandstone/variables/Score' @@ -34,11 +35,12 @@ import type { StoreType } from 'sandstone/commands' import type { _RawMCFunctionClass, // eslint-disable-next-line max-len - AdvancementClassArguments, AtlasClassArguments, BlockStateArguments, DamageTypeClassArguments, FontArguments, ItemModifierClassArguments, LanguageArguments, LootTableClassArguments, MCFunctionClassArguments, ModelClassArguments, Node, PlainTextArguments, PredicateClassArguments, RecipeClassArguments, SoundEventArguments, TagClassArguments, TextureArguments, TrimMaterialClassArguments, TrimPatternClassArguments, + AdvancementClassArguments, AtlasClassArguments, BlockStateArguments, DamageTypeClassArguments, DataPointPickClass, FontArguments, ItemModifierClassArguments, LanguageArguments, LootTableClassArguments, MacroArgument, + MCFunctionClassArguments, ModelClassArguments, Node, PlainTextArguments, PredicateClassArguments, RecipeClassArguments, SoundEventArguments, TagClassArguments, TextureArguments, TrimMaterialClassArguments, TrimPatternClassArguments, } from 'sandstone/core' import type { LiteralUnion, MakeInstanceCallable } from 'sandstone/utils' import type { - DATA_PATH, DATA_TARGET, DATA_TYPES, DataPointPickClass, MacroArgument, SelectorCreator, SelectorProperties, + DATA_PATH, DATA_TARGET, DATA_TYPES, SelectorCreator, SelectorProperties, } from 'sandstone/variables' import type { UUIDinNumber, UUIDinScore, UUIDOptions, UUIDSource, @@ -55,6 +57,10 @@ let startTickedLoops: MCFunctionClass type MCFunctionArgs = Exclude, MCFunctionClassArguments['callback']> +const foo: MCFunctionArgs = { + addToSandstoneCore: false, +} + export class DataPack extends PackType { // TODO: typing. low priority readonly packMcmeta: any @@ -153,6 +159,8 @@ export class SandstonePack { readonly commands: SandstoneCommands + readonly Macro: SandstoneCommands + readonly conditions = SandstoneConditions objectives: Set @@ -172,6 +180,9 @@ export class SandstonePack { this.commands = new SandstoneCommands(this) + // SandstonePack.Macro is only a type hack + this.Macro = this.commands as unknown as SandstoneCommands + this.flow = new Flow(this.core) this.objectives = new Set() @@ -502,6 +513,11 @@ export class SandstonePack { UUID(source: UUIDSource = randomUUID(), options?: UUIDOptions) { return new UUIDClass(this.core, source, options) } + MCFunction( + name: string, + options?: MCFunctionArgs + ): MCFunctionClass + MCFunction( name: string, callback: (loop: MCFunctionClass) => void, @@ -532,6 +548,9 @@ export class SandstonePack { PARAMS extends readonly MacroArgument[] | undefined, ENV extends readonly MacroArgument[] | undefined>( ...args: [ + name: string, + options?: MCFunctionArgs + ] |[ name: string, callback: (this: MCFunctionClass, ...params: PARAMS extends readonly MacroArgument[] ? PARAMS : []) => void, options?: MCFunctionArgs @@ -548,7 +567,7 @@ export class SandstonePack { addToSandstoneCore: true, onConflict: conflictDefaults('mcfunction') as MCFunctionClassArguments['onConflict'], ...(typeof args[1] === 'function' ? args[2] : args[3]), - }, typeof args[1] === 'function' ? undefined : args[1]) as MCFunctionClass + }, (typeof args[1] !== 'function' && Array.isArray(args[1])) ? args[1] : undefined) as MCFunctionClass } appendNode = (node: Node) => this.core.getCurrentMCFunctionOrThrow().appendNode(node) @@ -590,8 +609,6 @@ export class SandstonePack { sleep = (delay: TimeArgument): PromiseLike => (new SleepClass(this.core, delay)).promise() - readonly Macro = (new MacroClass(this.core, new SandstoneCommands(this))).commands - Loop = () => new LoopArgument(this) __customResourceTypes: string[] = [] @@ -614,6 +631,8 @@ export class SandstonePack { RawResource(pack: PackType, path: string, contents: string | Buffer | Promise): CustomResourceClass RawResource(...args: [path: string, contents: string | Buffer | Promise] | [pack: PackType, path: string, contents: string | Buffer | Promise]) { + const [core, CustomResource] = this.makeCustomResource + if (args[0] instanceof PackType) { const _path = args[1] as string const path = _path.includes('/') ? _path.split('/') : [_path] @@ -621,9 +640,7 @@ export class SandstonePack { path[path.length - 1] = path[path.length - 1].replace(`.${extension}`, '') - const { core } = this - - class RawResource extends this.makeCustomResource[1] { + class RawResource extends CustomResource { constructor() { super(core, path.join('/'), { type: `${Math.random()}`, packType: args[0] as PackType, extension, addToSandstoneCore: true, creator: 'user', @@ -641,12 +658,12 @@ export class SandstonePack { path[path.length - 1] = path[path.length - 1].replace(`.${extension}`, '') - const { core, dataPack } = this + const { dataPack } = this - class RawResource extends this.makeCustomResource[1] { + class RawResource extends CustomResource { constructor() { super(core, path.join('/'), { - type: `${Math.random()}`, packType: dataPack(), extension, addToSandstoneCore: true, creator: 'user', + type: `${Math.random()}`, extension, addToSandstoneCore: true, creator: 'user', }) } diff --git a/src/pack/visitors/simplifyExecuteFunction.ts b/src/pack/visitors/simplifyExecuteFunction.ts index 93820a8a..0dd786f1 100644 --- a/src/pack/visitors/simplifyExecuteFunction.ts +++ b/src/pack/visitors/simplifyExecuteFunction.ts @@ -1,6 +1,6 @@ /* eslint-disable dot-notation */ import { ExecuteCommandNode, FunctionCommandNode } from 'sandstone/commands' -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { GenericSandstoneVisitor } from './visitor.js' diff --git a/src/pack/visitors/simplifyReturnRunFunction.ts b/src/pack/visitors/simplifyReturnRunFunction.ts index 0db75ce2..9625db00 100644 --- a/src/pack/visitors/simplifyReturnRunFunction.ts +++ b/src/pack/visitors/simplifyReturnRunFunction.ts @@ -1,6 +1,6 @@ /* eslint-disable dot-notation */ import { FunctionCommandNode, ReturnRunCommandNode } from 'sandstone/commands' -import { CommandNode } from 'sandstone/core' +import { CommandNode } from 'sandstone/core/nodes' import { GenericSandstoneVisitor } from './visitor.js' diff --git a/src/utils.ts b/src/utils.ts index 442035f4..b5dc3f7f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import fs from 'fs-extra' import { coerce } from 'semver' import * as util from 'util' @@ -6,6 +7,7 @@ import { FormatRegistry, Type } from '@sinclair/typebox' import type FetchType from 'node-fetch' import type { Static } from '@sinclair/typebox' +import type { MultipleEntitiesArgument } from './arguments/selector.js' import type { UUIDinNumber } from './variables/UUID.js' export const fetch = async (...args: Parameters) => (await import('node-fetch')).default(...args) @@ -337,3 +339,7 @@ export async function safeWrite(...args: Partial) } + +export function sanitizeTarget(target: MultipleEntitiesArgument) { + return `${target}`.toLowerCase().replaceAll(/-|=|\[|\]|\{|\}|:|"|'|,|@/g, '_') +} diff --git a/src/variables/Data.ts b/src/variables/Data.ts index 8cc11d8a..7e19b1ba 100644 --- a/src/variables/Data.ts +++ b/src/variables/Data.ts @@ -1,5 +1,4 @@ -import { DataPointPickClass } from './abstractClasses.js' -import { MacroArgument } from './Macro.js' +import { DataPointPickClass, MacroArgument } from '../core/Macro.js' import { nbtStringifier } from './nbt/NBTs.js' import { Score } from './Score.js' diff --git a/src/variables/DataSets.ts b/src/variables/DataSets.ts index 5b77f302..85e0428f 100644 --- a/src/variables/DataSets.ts +++ b/src/variables/DataSets.ts @@ -1,16 +1,16 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable max-len */ -import { ConditionalDataPointPickClass } from './abstractClasses.js' +import { ConditionalDataPointPickClass } from '../core/Macro.js' import { DataPointClass } from './Data.js' import { Score } from './Score.js' import type { NBTObject, RootNBT } from 'sandstone/arguments' +import type { DataPointPickClass } from 'sandstone/core' import type { ConditionNode } from 'sandstone/flow' import type { LiteralUnion } from 'sandstone/utils' +import type { Macroable } from '../core/Macro.js' import type { SandstonePack } from '../pack/index.js' -import type { DataPointPickClass } from './abstractClasses.js' import type { StringDataPointClass } from './Data.js' -import type { Macroable } from './Macro.js' export abstract class IterableDataClass extends ConditionalDataPointPickClass { iterator(callback: (dataPoints: [DataPointClass] | [StringDataPointClass, DataPointClass]) => void): () => void { @@ -155,7 +155,7 @@ export class DataIndexMapClass extends Iter this.pack.MCFunction('__sandstone:variable/index_map/set', [_key!], () => { const indexData = this.pack.DataVariable().set(index) - this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Index[${_key}]`).set.from.storage(indexData.currentTarget, indexData.path) + this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, _key.Macro`${this.dataPoint.path}.Index[${_key}]`).set.from.storage(indexData.currentTarget, indexData.path) })() this.dataPoint.select('Entries').append([]) @@ -190,7 +190,7 @@ export class DataIndexMapClass extends Iter const value = DataVariable() MCFunction('__sandstone:variable/index_map/get', [index], () => { - Macro.data.modify.storage(value.currentTarget, value.path).set.from.storage(this.dataPoint.currentTarget, Macro`Entries[${index}]`) + Macro.data.modify.storage(value.currentTarget, value.path).set.from.storage(this.dataPoint.currentTarget, index.Macro`Entries[${index}]`) })() return value @@ -210,10 +210,10 @@ export class DataIndexMapClass extends Iter this.pack.MCFunction('__sandstone:variable/index_map/get', [_key!], () => { const index = this.pack.DataVariable() - this.pack.Macro.data.modify.storage(index.currentTarget, index.path).set.from.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Index[${_key}]`) + this.pack.Macro.data.modify.storage(index.currentTarget, index.path).set.from.storage(this.dataPoint.currentTarget, _key.Macro`${this.dataPoint.path}.Index[${_key}]`) this.pack.MCFunction('__sandstone:variable/index_map/_get', [index], () => { - this.pack.Macro.data.modify.storage(output.currentTarget, output.path).set.from.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Entries[${index}]`) + this.pack.Macro.data.modify.storage(output.currentTarget, output.path).set.from.storage(this.dataPoint.currentTarget, _key.Macro`${this.dataPoint.path}.Entries[${index}]`) })() })() @@ -227,7 +227,7 @@ export class DataIndexMapClass extends Iter const { MCFunction, Macro } = this.pack MCFunction('__sandstone:variable/index_map/remove', [index], () => { - Macro.data.modify.storage(this.dataPoint.currentTarget, Macro`Entries[${index}]`).set.value(0) + Macro.data.modify.storage(this.dataPoint.currentTarget, index.Macro`Entries[${index}]`).set.value(0) index.remove() })() @@ -245,12 +245,12 @@ export class DataIndexMapClass extends Iter const index = this.pack.DataVariable() this.pack.MCFunction('__sandstone:variable/index_map/remove', [_key], () => { - this.pack.Macro.data.modify.storage(index.currentTarget, index.path).set.from.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Index[${_key}]`) + this.pack.Macro.data.modify.storage(index.currentTarget, index.path).set.from.storage(this.dataPoint.currentTarget, _key.Macro`${this.dataPoint.path}.Index[${_key}]`) - this.pack.Macro.data.remove.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Index[${_key}]`) + this.pack.Macro.data.remove.storage(this.dataPoint.currentTarget, _key.Macro`${this.dataPoint.path}.Index[${_key}]`) this.pack.MCFunction('__sandstone:variable/index_map/_remove', [index], () => { - this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}.Entries[${index}]`).set.value(0) + this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}.Entries[${index}]`).set.value(0) })() })() @@ -327,12 +327,12 @@ export class DataArrayClass extends IterableDa } this.pack.MCFunction('__sandstone:variable/array/set', [index], () => { if (value instanceof DataPointClass) { - this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}[${index}]`).set.from.storage(value.currentTarget, value.path) + this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}[${index}]`).set.from.storage(value.currentTarget, value.path) } else if (typeof value === 'object' && Object.hasOwn(value, '_toDataPoint')) { const point = (value as DataPointPickClass)._toDataPoint() - this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}[${index}]`).set.from.storage(point.currentTarget, point.path) + this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}[${index}]`).set.from.storage(point.currentTarget, point.path) } else { - this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}[${index}]`).set.value(value) + this.pack.Macro.data.modify.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}[${index}]`).set.value(value) } })() return this @@ -357,7 +357,7 @@ export class DataArrayClass extends IterableDa const output = this.pack.DataVariable() this.pack.MCFunction('__sandstone:variable/array/get', [index], () => { - this.pack.Macro.data.modify.storage(output.currentTarget, output.path).set.from.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}[${index}]`) + this.pack.Macro.data.modify.storage(output.currentTarget, output.path).set.from.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}[${index}]`) })() return output @@ -370,7 +370,7 @@ export class DataArrayClass extends IterableDa return this } this.pack.MCFunction('__sandstone:variable/array/remove', [index], () => { - this.pack.Macro.data.remove.storage(this.dataPoint.currentTarget, this.pack.Macro`${this.dataPoint.path}[${index}]`) + this.pack.Macro.data.remove.storage(this.dataPoint.currentTarget, index.Macro`${this.dataPoint.path}[${index}]`) })() return this } diff --git a/src/variables/JSONTextComponentClass.ts b/src/variables/JSONTextComponentClass.ts index 4508bf6f..2f3f2d47 100644 --- a/src/variables/JSONTextComponentClass.ts +++ b/src/variables/JSONTextComponentClass.ts @@ -1,5 +1,5 @@ import type { JSONTextComponent } from 'sandstone/arguments' -import type { MacroArgument } from './Macro.js' +import type { MacroArgument } from '../core/Macro.js' function toComponent(c: any): JSONTextComponent { return c._toSelector?.() ?? c._toChatComponent?.() ?? c.toJSON?.() ?? c diff --git a/src/variables/ResolveNBT.ts b/src/variables/ResolveNBT.ts index 62015c9c..8ca23b69 100644 --- a/src/variables/ResolveNBT.ts +++ b/src/variables/ResolveNBT.ts @@ -1,8 +1,8 @@ /* eslint-disable max-len */ import * as util from 'util' +import { DataPointPickClass } from '../core/Macro.js' import { capitalize } from '../utils.js' -import { DataPointPickClass } from './abstractClasses.js' import { StringDataPointClass } from './Data.js' import { NBTAnyValue, diff --git a/src/variables/Score.ts b/src/variables/Score.ts index 7760cca7..3f1fa9c1 100644 --- a/src/variables/Score.ts +++ b/src/variables/Score.ts @@ -1,11 +1,12 @@ +/* eslint-disable no-plusplus */ +import { MacroArgument } from '../core/Macro.js' import { SelectorPickClass } from './abstractClasses.js' -import { MacroArgument } from './Macro.js' import { rangeParser } from './parsers.js' import type { COMPARISON_OPERATORS, FormattingTags, JSONTextComponent, MultipleEntitiesArgument, ObjectiveArgument, OPERATORS, Range, } from 'sandstone/arguments' -import type { FinalCommandOutput, SandstoneCommands } from 'sandstone/commands' +import type { SandstoneCommands } from 'sandstone/commands' import type { NotNode } from 'sandstone/flow' import type { ConditionClass } from 'sandstone/variables' import type { SandstonePack } from '../pack/index.js' @@ -20,6 +21,8 @@ type OperationArguments = ( [targets: PlayersTarget, objective?: ObjectiveArgument] ) +let matchers = 0 + function createVariable(pack: SandstonePack, amount: number): Score function createVariable(pack: SandstonePack, targets: MultipleEntitiesArgument, objective: ObjectiveArgument): Score @@ -613,70 +616,24 @@ export class Score extends MacroArgument implements ConditionClass, ComponentCla _toMinecraftCondition: () => new this.sandstonePack.conditions.Score(this.sandstonePack.core, [`${this.target}`, `${this.objective}`, 'matches', rangeParser(range)]), }) - match = (minimum: number, maximum: number, callback: (returnCmd: (SandstoneCommands & ((_callback: () => any) => FinalCommandOutput)), num: number) => void) => { - const { _, commands: { execute }, MCFunction } = this.sandstonePack - // First, specify we didn't find a match yet - const callCallback = (num: number) => { - // If we found the correct score, call the callback & specify we found a match - callback(execute.if.score(this, 'matches', num).run.returnCmd.run, num) + match = (minimum: number, maximum: number, callback: (num: number) => void) => { + const { MCFunction, Macro } = this.sandstonePack + + if (maximum > 1000) { + console.warn( + `\nWarning: Score.match() will have to create ${maximum - minimum} mcfunction files, this *will* take a while. ` + + 'Consider only compiling this once and saving it to external resources.', + ) } - // I actually have no idea why this is needed, but it is - const total = maximum - minimum - 1 + const matcher = matchers++ const score = this - // This is my personal hell - - let executeCount = 0 - - function split(currentTotal: number, current: number) { - if (currentTotal > 10) { - const chunks = Math.floor(currentTotal / 10) - - if (chunks > 10) { - const _chunks = Math.floor(chunks / 10) - - const chunkSize = Math.floor(currentTotal / _chunks) - - for (let i = 0; i < _chunks; i += 1) { - const last = i === _chunks - 1 - - executeCount += 1 - - const localCurrent = current + chunkSize * i - - execute.if.score(score, 'matches', `${localCurrent}..${localCurrent + chunkSize - (last ? 0 : 1)}`).run.returnCmd.run(() => split(chunkSize, localCurrent)) - } - - executeCount += 1 - - execute.if.score(score, 'matches', `${(chunkSize * _chunks) + current}..`).run.returnCmd.run(() => split((currentTotal % chunkSize) + 1, (chunkSize * _chunks) + current)) - } else { - const chunkSize = Math.floor(currentTotal / chunks) - - if (chunkSize > 10) { - executeCount += 2 - execute.if.score(score, 'matches', `${current}..${current + 9}`).run.returnCmd.run(() => split(10, current)) - - execute.if.score(score, 'matches', `${10 + current}..`).run.returnCmd.run(() => split(currentTotal - 10, 10 + current)) - } else { - for (let i = 0; i < chunks; i += 1) { - const localCurrent = (chunkSize * i) + current - - executeCount += 1 - - execute.if.score(score, 'matches', `${localCurrent}..${localCurrent + chunkSize - 1}`).run.returnCmd.run(() => split(chunkSize, localCurrent)) - } - } - } - } - if (currentTotal !== 0 && currentTotal <= 10) { - for (let i = 0; i < currentTotal; i += 1) { - callback(execute.if.score(score, 'matches', current + i).run.returnCmd.run, current + i) - } - } + for (let i = minimum; i < maximum; i++) { + MCFunction(`__sandstone:score_match/${matcher}/${i}`, () => callback(i)) } - split(total, minimum) + + return MCFunction(`__sandstone:score_match/${matcher}`, [score], () => Macro.returnCmd.run.functionCmd(score.Macro`__sandstone:score_match/${matcher}/${score}`)) } } diff --git a/src/variables/Selector.ts b/src/variables/Selector.ts index e8ddcc82..b30ad26a 100644 --- a/src/variables/Selector.ts +++ b/src/variables/Selector.ts @@ -11,7 +11,7 @@ import type { SandstonePack } from 'sandstone/pack' import type { LiteralUnion } from '../utils.js' import type { ConditionTextComponentClass, SelectorPickClass } from './abstractClasses.js' import type { LabelClass } from './Label.js' -import type { Macroable } from './Macro.js' +import type { Macroable } from '../core/Macro.js' import type { NotNBT } from './nbt/NBTs.js' type ScoreArgument = Record> diff --git a/src/variables/abstractClasses.ts b/src/variables/abstractClasses.ts index 459dcddf..7e8ed64e 100644 --- a/src/variables/abstractClasses.ts +++ b/src/variables/abstractClasses.ts @@ -1,8 +1,5 @@ -import { MacroArgument } from './Macro.js' - import type { JSONTextComponent } from 'sandstone/arguments/jsonTextComponent' import type { ConditionNode } from '../flow/index.js' -import type { DataPointClass } from './Data.js' import type { SelectorClass } from './Selector.js' export class ComponentClass { @@ -46,21 +43,3 @@ export class ConditionTextComponentClass extends ComponentClass implements Condi throw new Error('Not implemented') } } - -export class DataPointPickClass extends MacroArgument { - /** - * @internal - */ - _toDataPoint(): DataPointClass<'storage'> { - throw new Error('Not implemented') - } -} - -export class ConditionalDataPointPickClass extends DataPointPickClass implements ConditionClass { - /** - * @internal - */ - _toMinecraftCondition(): ConditionNode { - throw new Error('Not implemented') - } -} diff --git a/src/variables/index.ts b/src/variables/index.ts index b54362bf..e891c1ab 100644 --- a/src/variables/index.ts +++ b/src/variables/index.ts @@ -5,7 +5,6 @@ export * from './DataSets.js' export * from './JSONTextComponentClass.js' export * from './Label.js' export * from './Loop.js' -export * from './Macro.js' export * from './nbt/index.js' export * from './Objective.js' export * from './parsers.js' diff --git a/src/variables/nbt/NBTs.ts b/src/variables/nbt/NBTs.ts index b9ff127f..c56ababf 100644 --- a/src/variables/nbt/NBTs.ts +++ b/src/variables/nbt/NBTs.ts @@ -4,7 +4,7 @@ import * as util from 'util' import { parseNBT } from './parser.js' import type { NBTObject, RootNBT } from 'sandstone/arguments' -import type { MacroArgument } from '../index.js' +import type { MacroArgument } from 'sandstone/core' export abstract class NBTClass { abstract [util.inspect.custom]: () => string; @@ -410,7 +410,7 @@ export const nbtStringifier = (nbt: NBTObject | MacroArgument): string => { return nbt[util.inspect.custom]() } - if (nbt.toMacro) { + if (typeof nbt === 'object' && nbt.toMacro) { /* @ts-ignore */ return nbt.toMacro() } diff --git a/src/variables/parsers.ts b/src/variables/parsers.ts index d0aac2c1..8e54c970 100644 --- a/src/variables/parsers.ts +++ b/src/variables/parsers.ts @@ -3,7 +3,7 @@ import { VectorClass } from './Coordinates.js' import type { Coordinates, Range, Rotation, STRUCTURE_MIRROR, STRUCTURE_ROTATION, } from 'sandstone/arguments' -import type { MacroArgument } from './Macro.js' +import type { MacroArgument } from '../core/Macro.js' // PARSERS export function arrayToArgsParser(args: unknown): ( typeof args extends string[] ? VectorClass : typeof args