From 95f3d34b2b0cd096dd1f808a665457091bc7d816 Mon Sep 17 00:00:00 2001 From: CrackedMatter <81803926+CrackedMatter@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:55:50 +0100 Subject: [PATCH] Add super pickaxe (#215) * Tool refactor and Superpickaxe * area and recursive mode, bug fixes * fix not working at height limit * fixes --- src/config.ts | 8 +++ src/library/@types/Events.d.ts | 2 + src/library/Minecraft.ts | 4 ++ src/server/commands/command_list.ts | 1 + src/server/commands/tool/superpickaxe.ts | 53 ++++++++++++++ src/server/sessions.ts | 12 ++++ src/server/tools/base_tool.ts | 55 ++++++++------- src/server/tools/cycler_tool.ts | 4 +- src/server/tools/register_tools.ts | 1 + src/server/tools/replacer_tool.ts | 4 +- src/server/tools/selection_tools.ts | 4 +- src/server/tools/superpickaxe_tool.ts | 76 ++++++++++++++++++++ src/server/tools/tool_manager.ts | 89 ++++++++++++++++-------- texts/en_US.po | 12 ++++ tools/sync2com-mojang.py | 4 +- worldedit_settings.json | 8 +++ 16 files changed, 272 insertions(+), 65 deletions(-) create mode 100644 src/server/commands/tool/superpickaxe.ts create mode 100644 src/server/tools/superpickaxe_tool.ts diff --git a/src/config.ts b/src/config.ts index 9d297fada..f0e445a8c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -46,6 +46,14 @@ export default { */ maxBrushRadius: 6, /** + * Whether blocks broken by the super pickaxe in "single" mode drop. + */ + superPickaxeDrop: true, + /** + * Whether blocks broken by the super pickaxe in "area" and "recursive" mode drop. + */ + superPickaxeManyDrop: false, + /** * The default amount of blocks that can be "potentially" affected within a single operation. */ defaultChangeLimit: -1, diff --git a/src/library/@types/Events.d.ts b/src/library/@types/Events.d.ts index e4497640e..049f555ee 100644 --- a/src/library/@types/Events.d.ts +++ b/src/library/@types/Events.d.ts @@ -10,6 +10,7 @@ import { Dimension, Entity, PlayerBreakBlockBeforeEvent, + EntityHitBlockAfterEvent, EffectAddAfterEvent, ExplosionAfterEvent, PistonActivateAfterEvent, @@ -28,6 +29,7 @@ export interface EventList { itemUseBefore: [ItemUseBeforeEvent], itemUseOnBefore: [ItemUseOnBeforeEvent], blockBreak: [PlayerBreakBlockBeforeEvent], + blockHit: [EntityHitBlockAfterEvent], tick: [TickEvent], entityEffected: [EffectAddAfterEvent], entityCreate: [EntityCreateEvent], diff --git a/src/library/Minecraft.ts b/src/library/Minecraft.ts index f79f587e9..2056112c1 100644 --- a/src/library/Minecraft.ts +++ b/src/library/Minecraft.ts @@ -127,6 +127,10 @@ class ServerBuild extends ServerBuilder { * Emit to 'blockBreak' event listener */ beforeEvents.playerBreakBlock.subscribe(data => this.emit("blockBreak", data)); + /** + * Emit to 'blockHit' event listener + */ + afterEvents.entityHitBlock.subscribe(data => this.emit("blockHit", data)); /** * Emit to 'worldInitialize' event listener */ diff --git a/src/server/commands/command_list.ts b/src/server/commands/command_list.ts index 83b059e0f..d2e456b3d 100644 --- a/src/server/commands/command_list.ts +++ b/src/server/commands/command_list.ts @@ -90,6 +90,7 @@ import "./navigation/descend.js"; import "./navigation/ceil.js"; import "./tool/tool.js"; +import "./tool/superpickaxe.js"; import "./brush/brush.js"; import "./brush/mask.js"; diff --git a/src/server/commands/tool/superpickaxe.ts b/src/server/commands/tool/superpickaxe.ts new file mode 100644 index 000000000..024fabba1 --- /dev/null +++ b/src/server/commands/tool/superpickaxe.ts @@ -0,0 +1,53 @@ +import { registerCommand } from "../register_commands.js"; + +const registerInformation = { + name: "superpickaxe", + permission: "worldedit.superpickaxe", + description: "commands.wedit:superpickaxe.description", + aliases : ["sp"], + usage: [ + { + subName: "single", + }, + { + subName: "area", + args: [ + { + name: "range", + type: "int", + range: [0, 5] as [number, number] + } + ] + }, + { + subName: "recursive", + args: [ + { + name: "range", + type: "int", + range: [0, 5] as [number, number] + } + ] + }, + { + subName: "_default" + } + ] +}; + +registerCommand(registerInformation, function (session, builder, args) { + if (args.has("single")) { + session.superPickaxe.mode = "single"; + } else if (args.has("area")) { + session.superPickaxe.mode = "area"; + session.superPickaxe.range = args.get("range"); + } else if (args.has("recursive")) { + session.superPickaxe.mode = "recursive"; + session.superPickaxe.range = args.get("range"); + } else { + const enabled = (session.superPickaxe.enabled = !session.superPickaxe.enabled); + return "commands.wedit:superpickaxe." + (enabled ? "enabled" : "disabled"); + } + session.superPickaxe.enabled = true; + return "commands.wedit:superpickaxe." + session.superPickaxe.mode; +}); diff --git a/src/server/sessions.ts b/src/server/sessions.ts index 19688b722..d6f25937e 100644 --- a/src/server/sessions.ts +++ b/src/server/sessions.ts @@ -25,6 +25,12 @@ interface regionTransform { flip: Vector } +interface superPickaxe { + enabled: boolean, + mode: "single" | "area" | "recursive", + range: number +} + /** * Represents a WorldEdit user's current session with the addon. * It manages their selections, operation history, and other things related to WorldEdit per player. @@ -82,6 +88,12 @@ export class PlayerSession { flip: Vector.ONE }; + public superPickaxe: superPickaxe = { + enabled: false, + mode: "single", + range: 0 + }; + public selection: Selection; private player: Player; diff --git a/src/server/tools/base_tool.ts b/src/server/tools/base_tool.ts index 24d2f2094..a4f12a1c2 100644 --- a/src/server/tools/base_tool.ts +++ b/src/server/tools/base_tool.ts @@ -1,9 +1,16 @@ -import { BlockPermutation, Player, system } from "@minecraft/server"; +import { Player, system } from "@minecraft/server"; import { PlayerSession } from "../sessions.js"; import { Server, Thread } from "@notbeer-api"; import { print, printerr } from "../util.js"; import { RawText, Vector } from "@notbeer-api"; +export enum ToolAction { + USE = "use", + USE_ON = "useOn", + BREAK = "break", + HIT = "hit" +} + /** * The base tool class for handling tools that WorldEdit builders may use. */ @@ -15,15 +22,19 @@ export abstract class Tool { /** * The function that's called when the tool is being used on a block. */ - readonly useOn: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator; + readonly useOn: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator; /** - * The function that's called every tick the tool is held. + * The function that's called when the tool has broken a block. */ - readonly tick: (self: Tool, player: Player, session: PlayerSession, tick: number) => Generator; + readonly break: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator; /** - * The function that's called when the tool has broken a block. + * The function that's called when the tool has hit a block. */ - readonly breakOn: (self: Tool, player: Player, session: PlayerSession, loc: Vector, brokenBlock: BlockPermutation) => void; + readonly hit: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator; + /** + * The function that's called every tick the tool is held. + */ + readonly tick: (self: Tool, player: Player, session: PlayerSession, tick: number) => Generator; /** * The permission required for the tool to be used. */ @@ -47,12 +58,11 @@ export abstract class Tool { private useOnTick = 0; private lastUse = system.currentTick; - process(session: PlayerSession, tick: number, loc?: Vector, brokenBlock?: BlockPermutation): boolean { + process(session: PlayerSession, tick: number, action: ToolAction, loc?: Vector): boolean { const player = session.getPlayer(); - if (!loc && !this.use || loc && !brokenBlock && !this.useOn || brokenBlock && !this.breakOn) { - return false; - } + if (!this[action]) return false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const onFail = (e: any) => { printerr(e.message ? RawText.text(`${e.name}: `).append("translate", e.message) : e, player, true); @@ -61,7 +71,7 @@ export abstract class Tool { } }; - new Thread().start(function* (self: Tool, player: Player, session: PlayerSession, loc: Vector, brokenBlock: BlockPermutation) { + new Thread().start(function* (self: Tool, player: Player, session: PlayerSession, action: ToolAction, loc: Vector) { self.currentPlayer = player; session.usingItem = true; try { @@ -72,23 +82,14 @@ export abstract class Tool { if (system.currentTick - self.lastUse > 4 || self.noDelay) { self.lastUse = system.currentTick; - if (!loc) { - if (self.useOnTick != tick) { - if (self.use.constructor.name == "GeneratorFunction") { - yield* self.use(self, player, session) as Generator; - } else { - self.use(self, player, session) as void; - } - } - } else if (!brokenBlock) { - self.useOnTick = tick; - if (self.useOn.constructor.name == "GeneratorFunction") { - yield* self.useOn(self, player, session, loc) as Generator; + if (!(action == ToolAction.USE && self.useOnTick == tick)) { + if (action == ToolAction.USE_ON) self.useOnTick = tick; + const func = self[action]; + if (func.constructor.name == "GeneratorFunction") { + yield* func(self, player, session, loc) as Generator; } else { - self.useOn(self, player, session, loc) as void; + func(self, player, session, loc) as void; } - } else { - self.breakOn(self, player, session, loc, brokenBlock); } } } catch(e) { @@ -97,7 +98,7 @@ export abstract class Tool { session.usingItem = false; } self.currentPlayer = null; - }, this, player, session, loc, brokenBlock); + }, this, player, session, action, loc); return true; } diff --git a/src/server/tools/cycler_tool.ts b/src/server/tools/cycler_tool.ts index fbe9e3c44..dd76b7515 100644 --- a/src/server/tools/cycler_tool.ts +++ b/src/server/tools/cycler_tool.ts @@ -10,13 +10,13 @@ class BlockCyclerTool extends Tool { permission = "worldedit.cycler"; useOn = function (self: BlockCyclerTool, player: Player, session: PlayerSession, loc: Vector3) { if (player.isSneaking) { - self.breakOn(self, player, session, loc); + self.break(self, player, session, loc); } else { self.update(player, loc, true); } }; - breakOn = function (self: BlockCyclerTool, player: Player, session: PlayerSession, loc: Vector3) { + break = function (self: BlockCyclerTool, player: Player, session: PlayerSession, loc: Vector3) { self.stateIndex++; self.update(player, loc, false); }; diff --git a/src/server/tools/register_tools.ts b/src/server/tools/register_tools.ts index c5a542698..e70869dc1 100644 --- a/src/server/tools/register_tools.ts +++ b/src/server/tools/register_tools.ts @@ -7,3 +7,4 @@ import "./replacer_tool.js"; import "./cycler_tool.js"; import "./button_tools.js"; import "./brush_tools.js"; +import "./superpickaxe_tool.js"; diff --git a/src/server/tools/replacer_tool.ts b/src/server/tools/replacer_tool.ts index b676d8090..4dfee34a1 100644 --- a/src/server/tools/replacer_tool.ts +++ b/src/server/tools/replacer_tool.ts @@ -11,13 +11,13 @@ class BlockReplacerTool extends Tool { permission = "worldedit.repl"; useOn = function (self: BlockReplacerTool, player: Player, session: PlayerSession, loc: Vector3) { if (player.isSneaking) { - self.breakOn(self, player, session, loc); + self.break(self, player, session, loc); } else { self.pattern.setBlock(player.dimension.getBlock(loc)); } }; - breakOn = function (self: BlockReplacerTool, player: Player, session: PlayerSession, loc: Vector3) { + break = function (self: BlockReplacerTool, player: Player, session: PlayerSession, loc: Vector3) { const pattern = new Pattern(); pattern.addBlock(player.dimension.getBlock(loc).permutation); session.setToolProperty(null, "pattern", pattern); diff --git a/src/server/tools/selection_tools.ts b/src/server/tools/selection_tools.ts index d90afcb8a..ab060e207 100644 --- a/src/server/tools/selection_tools.ts +++ b/src/server/tools/selection_tools.ts @@ -9,7 +9,7 @@ class SelectionTool extends Tool { useOn = function (self: Tool, player: Player, session: PlayerSession, loc: Vector3) { Server.command.callCommand(player, "pos2", [`${loc.x}`, `${loc.y}`, `${loc.z}`]); }; - breakOn = function (self: Tool, player: Player, session: PlayerSession, loc: Vector3) { + break = function (self: Tool, player: Player, session: PlayerSession, loc: Vector3) { Server.command.callCommand(player, "pos1", [`${loc.x}`, `${loc.y}`, `${loc.z}`]); }; } @@ -20,7 +20,7 @@ class FarSelectionTool extends Tool { use = function (self: Tool, player: Player) { Server.command.callCommand(player, player.isSneaking ? "hpos1" : "hpos2"); }; - breakOn = function (self: Tool, player: Player) { + break = function (self: Tool, player: Player) { Server.command.callCommand(player, "hpos1"); }; } diff --git a/src/server/tools/superpickaxe_tool.ts b/src/server/tools/superpickaxe_tool.ts new file mode 100644 index 000000000..498ad9dec --- /dev/null +++ b/src/server/tools/superpickaxe_tool.ts @@ -0,0 +1,76 @@ +import { Dimension, Player, Vector3 } from "@minecraft/server"; +import { PlayerSession } from "../sessions.js"; +import { Tool } from "./base_tool.js"; +import { Tools } from "./tool_manager.js"; +import { Server, Vector, regionIterateBlocks } from "@notbeer-api"; +import { getWorldHeightLimits, locToString } from "server/util.js"; +import config from "config.js"; + +class SuperPickaxeTool extends Tool { + noDelay = true; + permission = "worldedit.superpickaxe"; + + break = function* (self: SuperPickaxeTool, player: Player, session: PlayerSession, loc: Vector): Generator { + const dimension = player.dimension; + const typeId = dimension.getBlock(loc).typeId; + if (typeId == "minecraft:air") return; + const { mode, range } = session.superPickaxe; + if (mode == "single") { + destroyBlock(dimension, loc, config.superPickaxeDrop); + return; + } + const limits = getWorldHeightLimits(dimension); + if (mode == "area") { + const min = loc.sub(range); + const max = loc.add(range); + min.y = Math.max(min.y, limits[0]); + max.y = Math.min(max.y, limits[1]); + for (const block of regionIterateBlocks(min, max)) { + if (dimension.getBlock(block).typeId == typeId) { + destroyBlock(dimension, block, config.superPickaxeManyDrop); + } + yield; + } + return; + } + const rangeSqr = range * range; + const queue: Vector[] = [loc]; + const visited = new Set(); + while (queue.length) { + const block = queue.shift(); + const str = locToString(block); + if (!visited.has(str) && loc.sub(block).lengthSqr <= rangeSqr && block.y >= limits[0] && block.y <= limits[1] && dimension.getBlock(block).typeId == typeId) { + visited.add(str); + destroyBlock(dimension, block, config.superPickaxeManyDrop); + for (const offset of [[0, 1, 0], [0, -1, 0], [1, 0, 0], [-1, 0, 0], [0, 0, 1], [0, 0, -1]] as [number, number, number][]) { + queue.push(block.add(offset)); + } + } + yield; + } + }; + + hit = this.break; + + constructor() { + super(); + } +} +Tools.register(SuperPickaxeTool, "superpickaxe", [ + "minecraft:diamond_pickaxe", + "minecraft:golden_pickaxe", + "minecraft:iron_pickaxe", + "minecraft:netherite_pickaxe", + "minecraft:stone_pickaxe", + "minecraft:wooden_pickaxe" +], function (player: Player, session: PlayerSession) { + return session.superPickaxe.enabled; +}); + +function destroyBlock(dimension: Dimension, loc: Vector3, drop: boolean) { + if (drop) { + Server.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air destroy`, dimension); + } else { + dimension.getBlock(loc).setType("minecraft:air"); + } +} diff --git a/src/server/tools/tool_manager.ts b/src/server/tools/tool_manager.ts index c37e939ab..b029fb247 100644 --- a/src/server/tools/tool_manager.ts +++ b/src/server/tools/tool_manager.ts @@ -1,37 +1,47 @@ -import { Player, ItemStack, ItemUseBeforeEvent, world, EntityInventoryComponent, PlayerBreakBlockBeforeEvent } from "@minecraft/server"; +import { Player, ItemStack, ItemUseBeforeEvent, world, PlayerBreakBlockBeforeEvent, EntityHitBlockAfterEvent } from "@minecraft/server"; import { contentLog, Server, sleep, Thread, Vector, Database } from "@notbeer-api"; -import { Tool } from "./base_tool.js"; -import { getSession, hasSession } from "../sessions.js"; +import { Tool, ToolAction } from "./base_tool.js"; +import { PlayerSession, getSession, hasSession } from "../sessions.js"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type toolConstruct = new (...args: any[]) => Tool; +type toolCondition = (player: Player, session: PlayerSession) => boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any type toolObject = {[key: string]: any} & Tool; class ToolBuilder { private tools = new Map(); private bindings = new Map>(); - private databases = new Map; + private databases = new Map(); private fixedBindings = new Map(); + private conditionalBindings = new Map(); private disabled: string[] = []; private currentTick = 0; constructor() { Server.on("itemUseBefore", ev => { - if (ev.source.typeId != "minecraft:player" || !ev.itemStack) { - return; - } - + if (ev.source.typeId != "minecraft:player" || !ev.itemStack) return; this.onItemUse(ev.itemStack, ev.source as Player, ev); }); + Server.on("itemUseOnBefore", ev => { - if (ev.source.typeId != "minecraft:player" || !ev.itemStack) { - return; - } + if (ev.source.typeId != "minecraft:player" || !ev.itemStack) return; this.onItemUse(ev.itemStack, ev.source as Player, ev, Vector.from(ev.block)); }); + Server.on("blockBreak", ev => { + if (!ev.itemStack) return; + this.onBlockBreak(ev.itemStack, ev.player, ev, Vector.from(ev.block)); + }); + + Server.on("blockHit", ev => { + if (ev.damagingEntity.typeId != "minecraft:player") return; + const item = Server.player.getHeldItem(ev.damagingEntity as Player); + if (!item) return; + this.onBlockHit(item, ev.damagingEntity as Player, ev, Vector.from(ev.hitBlock)); + }); + Server.on("tick", ev => { this.currentTick = ev.currentTick; }); @@ -50,20 +60,17 @@ class ToolBuilder { yield sleep(1); } }, this); - - Server.on("blockBreak", ev => { - const item = Server.player.getHeldItem(ev.player); - if (!item) { - return; - } - this.onBlockBreak(item, ev.player, ev); - }); } - register(toolClass: toolConstruct, name: string, item?: string) { + register(toolClass: toolConstruct, name: string, item?: string | string[], condition?: toolCondition) { this.tools.set(name, toolClass); - if (item) { - this.fixedBindings.set(item, new (toolClass)()); + if (typeof item == "string") { + this.fixedBindings.set(item, new toolClass()); + } else if (condition && Array.isArray(item)) { + const tool = { condition, tool: new toolClass() }; + for (const key of item) { + this.conditionalBindings.set(key, tool); + } } } @@ -202,22 +209,43 @@ class ToolBuilder { tool = this.bindings.get(player.id).get(key); } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); + } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { + tool = this.conditionalBindings.get(key).tool; } else { return; } - tool.process(getSession(player), this.currentTick, loc); - ev.cancel = true; + if (tool.process(getSession(player), this.currentTick, loc ? ToolAction.USE_ON : ToolAction.USE, loc)) { + ev.cancel = true; + } } - private onBlockBreak(item: ItemStack, player: Player, ev: PlayerBreakBlockBeforeEvent) { + private onBlockBreak(item: ItemStack, player: Player, ev: PlayerBreakBlockBeforeEvent, loc: Vector) { if (this.disabled.includes(player.id)) { return; } - const comp = player.getComponent("inventory") as EntityInventoryComponent; - if (comp.container.getItem(player.selectedSlot) == null) return; - item = comp.container.getItem(player.selectedSlot); + const key = item.typeId; + let tool: Tool; + if (this.bindings.get(player.id)?.has(key)) { + tool = this.bindings.get(player.id).get(key); + } else if (this.fixedBindings.has(key)) { + tool = this.fixedBindings.get(key); + } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { + tool = this.conditionalBindings.get(key).tool; + } else { + return; + } + + if (tool.process(getSession(player), this.currentTick, ToolAction.BREAK, loc)) { + ev.cancel = true; + } + } + + private onBlockHit(item: ItemStack, player: Player, ev: EntityHitBlockAfterEvent, loc: Vector) { + if (this.disabled.includes(player.id)) { + return; + } const key = item.typeId; let tool: Tool; @@ -225,12 +253,13 @@ class ToolBuilder { tool = this.bindings.get(player.id).get(key); } else if (this.fixedBindings.has(key)) { tool = this.fixedBindings.get(key); + } else if (this.conditionalBindings.get(key)?.condition(player, getSession(player))) { + tool = this.conditionalBindings.get(key).tool; } else { return; } - const processed = tool.process(getSession(player), this.currentTick, Vector.from(ev.block), ev.block.permutation); - if (processed) ev.cancel = true; + tool.process(getSession(player), this.currentTick, ToolAction.HIT, loc); } private createPlayerBindingMap(playerId: string) { diff --git a/texts/en_US.po b/texts/en_US.po index c3067a31f..6b73e8d62 100644 --- a/texts/en_US.po +++ b/texts/en_US.po @@ -588,6 +588,18 @@ msgid "commands.wedit:size.type" msgstr "Type: %s" msgid "commands.wedit:snow.description" msgstr "Simulate snow in the area" +msgid "commands.wedit:superpickaxe.area" +msgstr "Super pickaxe mode is now: area." +msgid "commands.wedit:superpickaxe.description" +msgstr "Toggle the super pickaxe" +msgid "commands.wedit:superpickaxe.disabled" +msgstr "Super pickaxe disabled." +msgid "commands.wedit:superpickaxe.enabled" +msgstr "Super pickaxe enabled." +msgid "commands.wedit:superpickaxe.recursive" +msgstr "Super pickaxe mode is now: recursive." +msgid "commands.wedit:superpickaxe.single" +msgstr "Super pickaxe mode is now: single." msgid "commands.wedit:thaw.description" msgstr "Melt snow and ice exposed to the sky" msgid "commands.wedit:stack.description" diff --git a/tools/sync2com-mojang.py b/tools/sync2com-mojang.py index ffeadedbc..8dc5cc48e 100644 --- a/tools/sync2com-mojang.py +++ b/tools/sync2com-mojang.py @@ -19,8 +19,8 @@ pack_folder = 'WorldEdit' -behaviour_pack = com_mojang + f'\\development_behavior_packs\\{pack_folder} BP' -resource_pack = com_mojang + f'\\development_resource_packs\\{pack_folder} RP' +behaviour_pack = os.path.join(com_mojang, 'development_behavior_packs', f'{pack_folder} BP') +resource_pack = os.path.join(com_mojang, 'development_resource_packs', f'{pack_folder} RP') def sync_file(path, from_root, to_root): from_file = Path(path).relative_to(from_root) diff --git a/worldedit_settings.json b/worldedit_settings.json index 2692fd5a6..5f694d225 100644 --- a/worldedit_settings.json +++ b/worldedit_settings.json @@ -39,6 +39,14 @@ "description": "The maximum brush radius allowed.", "default": 12 }, + "superPickaxeDrop": { + "description": "Whether blocks broken by the super pickaxe in \"single\" mode drop.", + "default": true + }, + "superPickaxeManyDrop": { + "description": "Whether blocks broken by the super pickaxe in \"area\" and \"recursive\" mode drop.", + "default": false + }, "defaultChangeLimit": { "description": "The default amount of blocks that can be \"potentially\" affected within a single operation.", "default": -1