Skip to content

Commit

Permalink
Add super pickaxe (#215)
Browse files Browse the repository at this point in the history
* Tool refactor and Superpickaxe

* area and recursive mode, bug fixes

* fix not working at height limit

* fixes
  • Loading branch information
CrackedMatter committed Nov 13, 2023
1 parent 66090f0 commit 95f3d34
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 65 deletions.
8 changes: 8 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/library/@types/Events.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Dimension,
Entity,
PlayerBreakBlockBeforeEvent,
EntityHitBlockAfterEvent,
EffectAddAfterEvent,
ExplosionAfterEvent,
PistonActivateAfterEvent,
Expand All @@ -28,6 +29,7 @@ export interface EventList {
itemUseBefore: [ItemUseBeforeEvent],
itemUseOnBefore: [ItemUseOnBeforeEvent],
blockBreak: [PlayerBreakBlockBeforeEvent],
blockHit: [EntityHitBlockAfterEvent],
tick: [TickEvent],
entityEffected: [EffectAddAfterEvent],
entityCreate: [EntityCreateEvent],
Expand Down
4 changes: 4 additions & 0 deletions src/library/Minecraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
1 change: 1 addition & 0 deletions src/server/commands/command_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
53 changes: 53 additions & 0 deletions src/server/commands/tool/superpickaxe.ts
Original file line number Diff line number Diff line change
@@ -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;
});
12 changes: 12 additions & 0 deletions src/server/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
55 changes: 28 additions & 27 deletions src/server/tools/base_tool.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand All @@ -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<unknown>;
readonly useOn: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator<unknown, void>;
/**
* 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<unknown>;
readonly break: (self: Tool, player: Player, session: PlayerSession, loc: Vector) => void | Generator<unknown, void>;
/**
* 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<unknown, void>;
/**
* The function that's called every tick the tool is held.
*/
readonly tick: (self: Tool, player: Player, session: PlayerSession, tick: number) => Generator<unknown>;
/**
* The permission required for the tool to be used.
*/
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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<void, void>;
} 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<void, void>;
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<unknown, void>;
} 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) {
Expand All @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions src/server/tools/cycler_tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
1 change: 1 addition & 0 deletions src/server/tools/register_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import "./replacer_tool.js";
import "./cycler_tool.js";
import "./button_tools.js";
import "./brush_tools.js";
import "./superpickaxe_tool.js";
4 changes: 2 additions & 2 deletions src/server/tools/replacer_tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/server/tools/selection_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`]);
};
}
Expand All @@ -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");
};
}
Expand Down
76 changes: 76 additions & 0 deletions src/server/tools/superpickaxe_tool.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<string>();
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");
}
}
Loading

0 comments on commit 95f3d34

Please sign in to comment.