diff --git a/src/server/modules/mask.ts b/src/server/modules/mask.ts index 5fca3d290..87535bef0 100644 --- a/src/server/modules/mask.ts +++ b/src/server/modules/mask.ts @@ -1,4 +1,4 @@ -import { Vector3, BlockPermutation } from "@minecraft/server"; +import { Vector3, BlockPermutation, BlockFilter } from "@minecraft/server"; import { CustomArgType, commandSyntaxError, Vector } from "@notbeer-api"; import { Token } from "./extern/tokenizr.js"; import { @@ -12,13 +12,14 @@ import { parsedBlock, blockPermutation2ParsedBlock, BlockUnit, - parsedBlock2CommandArg, + parsedBlock2BlockPermutation, } from "./block_parsing.js"; +import { iterateBlockPermutations } from "server/util.js"; export class Mask implements CustomArgType { private condition: MaskNode; private stringObj = ""; - private simpleCache: string[]; + private simpleCache: BlockFilter; constructor(mask = "") { if (mask) { @@ -109,19 +110,51 @@ export class Mask implements CustomArgType { } isSimple() { - return !this.condition || this.condition instanceof BlockMask || (this.condition instanceof ChainMask && this.condition.nodes.every((node) => node instanceof BlockMask)); + const root = this.condition; + const child = root.nodes[0]; + return ( + !root || + root instanceof BlockMask || + (root instanceof ChainMask && root.nodes.every((node) => node instanceof BlockMask)) || + (root instanceof NegateMask && (child instanceof BlockMask || (child instanceof ChainMask && child.nodes.every((node) => node instanceof BlockMask)))) + ); } - getSimpleForCommandArgs() { - if (!this.simpleCache) { - if (this.condition instanceof BlockMask) { - this.simpleCache = [parsedBlock2CommandArg(this.condition.block)]; - } else if (this.condition instanceof ChainMask) { - this.simpleCache = this.condition.nodes.map((node) => parsedBlock2CommandArg((node).block)); + getSimpleBlockFilter() { + if (this.simpleCache) return this.simpleCache; + + const addToFilter = (block: parsedBlock, types: string[], perms: BlockPermutation[]) => { + const perm = parsedBlock2BlockPermutation(block); + if (block.states != null) { + const test = Array.from(block.states.entries()); + for (const states of iterateBlockPermutations(block.id)) { + if (!test.every(([key, value]) => states[key] === value)) continue; + perms.push(BlockPermutation.resolve(block.id, states)); + } } else { - this.simpleCache = []; + types.push(perm.type.id); } + }; + + const includeTypes: string[] = []; + const excludeTypes: string[] = []; + const includePerms: BlockPermutation[] = []; + const excludePerms: BlockPermutation[] = []; + this.simpleCache = {}; + + if (this.condition instanceof BlockMask) addToFilter(this.condition.block, includeTypes, includePerms); + else if (this.condition instanceof ChainMask) this.condition.nodes.forEach((node) => addToFilter((node).block, includeTypes, includePerms)); + else if (this.condition instanceof NegateMask) { + const negated = this.condition.nodes[0]; + if (negated instanceof BlockMask) addToFilter(negated.block, excludeTypes, excludePerms); + else if (negated instanceof ChainMask) negated.nodes.forEach((node) => addToFilter((node).block, excludeTypes, excludePerms)); } + + if (includeTypes.length) this.simpleCache.includeTypes = includeTypes; + if (excludeTypes.length) this.simpleCache.excludeTypes = excludeTypes; + if (includePerms.length) this.simpleCache.includePermutations = includePerms; + if (excludePerms.length) this.simpleCache.excludePermutations = excludePerms; + return this.simpleCache; } diff --git a/src/server/modules/pattern.ts b/src/server/modules/pattern.ts index b162642a3..46a7679dd 100644 --- a/src/server/modules/pattern.ts +++ b/src/server/modules/pattern.ts @@ -1,4 +1,4 @@ -import { Vector3, BlockPermutation, Player, Dimension } from "@minecraft/server"; +import { Vector3, BlockPermutation, Player, Dimension, BlockVolume } from "@minecraft/server"; import { CustomArgType, commandSyntaxError, Vector, Server } from "@notbeer-api"; import { PlayerSession } from "server/sessions.js"; import { wrap } from "server/util.js"; @@ -16,7 +16,6 @@ import { blockPermutation2ParsedBlock, parsedBlock2BlockPermutation, BlockUnit, - parsedBlock2CommandArg, } from "./block_parsing.js"; import { Cardinal } from "./directions.js"; import { Mask } from "./mask.js"; @@ -31,7 +30,7 @@ interface patternContext { export class Pattern implements CustomArgType { private block: PatternNode; private stringObj = ""; - private simpleCache: string; + private simpleCache: BlockPermutation; private context = {} as patternContext; @@ -148,20 +147,12 @@ export class Pattern implements CustomArgType { fillSimpleArea(dimension: Dimension, start: Vector3, end: Vector3, mask?: Mask) { if (!this.simpleCache) { if (this.block instanceof BlockPattern) { - this.simpleCache = parsedBlock2CommandArg(this.block.block); + this.simpleCache = parsedBlock2BlockPermutation(this.block.block); } else if (this.block instanceof ChainPattern) { - this.simpleCache = parsedBlock2CommandArg((this.block.nodes[0]).block); + this.simpleCache = parsedBlock2BlockPermutation((this.block.nodes[0]).block); } } - const command = `fill ${start.x} ${start.y} ${start.z} ${end.x} ${end.y} ${end.z} ${this.simpleCache}`; - const maskArgs = mask?.getSimpleForCommandArgs(); - let successCount = 0; - if (maskArgs?.length) { - maskArgs.forEach((m) => (successCount += dimension.runCommand(command + ` replace ${m}`).successCount)); - } else { - successCount += dimension.runCommand(command).successCount; - } - return !!successCount; + return dimension.fillBlocks(new BlockVolume(start, end), this.simpleCache, { blockFilter: mask?.getSimpleBlockFilter() }).getCapacity(); } getSimpleBlockFill() { diff --git a/src/server/shapes/base_shape.ts b/src/server/shapes/base_shape.ts index 1edd9723a..d90ed3b23 100644 --- a/src/server/shapes/base_shape.ts +++ b/src/server/shapes/base_shape.ts @@ -268,7 +268,7 @@ export abstract class Shape { const [min, max] = block; const volume = regionVolume(min, max); if (Jobs.inContext()) while (!Jobs.loadBlock(min)) yield sleep(1); - if (pattern.fillSimpleArea(dimension, min, max, mask)) count += volume; + count += pattern.fillSimpleArea(dimension, min, max, mask); yield Jobs.setProgress(progress / blocksAffected); progress += volume; } diff --git a/src/server/util.ts b/src/server/util.ts index e0913c513..01022dbae 100644 --- a/src/server/util.ts +++ b/src/server/util.ts @@ -1,4 +1,4 @@ -import { Block, Vector3, Dimension, Entity, Player, RawMessage, BlockComponentTypes } from "@minecraft/server"; +import { Block, Vector3, Dimension, Entity, Player, RawMessage, BlockComponentTypes, BlockPermutation, BlockStates } from "@minecraft/server"; import { Server, RawText, Vector } from "@notbeer-api"; import config from "config.js"; @@ -84,6 +84,27 @@ export function blockHasNBTData(block: Block) { return components.some((component) => !!block.getComponent(component)) || nbt_blocks.includes(block.typeId); } +/** + * Iterates through every possible block permutation for a specified block type. + */ +export function* iterateBlockPermutations(blockType: string) { + const perm = BlockPermutation.resolve(blockType); + const properties = Object.keys(perm.getAllStates()); + const values = properties.map((p) => BlockStates.get(p).validValues); + + function* combine(current: any[], depth: number): Generator, void> { + if (depth === values.length) { + yield Object.fromEntries(current.map((value, i) => [properties[i], value])); + return; + } + + for (let i = 0; i < values[depth].length; i++) { + yield* combine(current.concat(values[depth][i]), depth + 1); + } + } + yield* combine([], 0); +} + /** * Converts a location object to a string. * @param loc The object to convert