Skip to content

Commit

Permalink
Improved simple pattern/mask logic;
Browse files Browse the repository at this point in the history
Old PE blocks can once again be used in simple block patterns;
Block change count more accurate with simple block patterns;
Added support for simple negate masks
  • Loading branch information
SIsilicon committed Nov 4, 2024
1 parent 24b5319 commit 25cd1b9
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 27 deletions.
55 changes: 44 additions & 11 deletions src/server/modules/mask.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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((<BlockMask>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((<BlockMask>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((<BlockMask>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;
}

Expand Down
19 changes: 5 additions & 14 deletions src/server/modules/pattern.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -16,7 +16,6 @@ import {
blockPermutation2ParsedBlock,
parsedBlock2BlockPermutation,
BlockUnit,
parsedBlock2CommandArg,
} from "./block_parsing.js";
import { Cardinal } from "./directions.js";
import { Mask } from "./mask.js";
Expand All @@ -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;

Expand Down Expand Up @@ -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((<BlockPattern>this.block.nodes[0]).block);
this.simpleCache = parsedBlock2BlockPermutation((<BlockPattern>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() {
Expand Down
2 changes: 1 addition & 1 deletion src/server/shapes/base_shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
23 changes: 22 additions & 1 deletion src/server/util.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<Record<string, any>, 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
Expand Down

0 comments on commit 25cd1b9

Please sign in to comment.