diff --git a/src/server/command.zig b/src/server/command.zig index db4894c50c..18d09dcd09 100644 --- a/src/server/command.zig +++ b/src/server/command.zig @@ -180,6 +180,18 @@ pub const BiomeId = struct { } }; +pub const BlockId = struct { + block: main.blocks.Block, + + pub fn parse(_: NeverFailingAllocator, name: []const u8, args: []const u8, errorMessage: *ListManaged(u8)) error{ParseError}!@This() { + const blockTyp = main.blocks.getBlockById(args) catch { + errorMessage.print("Couldn't find block for <{s}> with id \"{s}\"", .{name, args}); + return error.ParseError; + }; + return .{.block = .{.typ = blockTyp, .data = 0}}; + } +}; + pub const EntityModel = struct { index: main.entityModel.EntityModelIndex, diff --git a/src/server/command/_list.zig b/src/server/command/_list.zig index 00b2dd1f4e..2a8a813841 100644 --- a/src/server/command/_list.zig +++ b/src/server/command/_list.zig @@ -22,6 +22,7 @@ pub const pos1 = @import("worldedit/pos1.zig"); pub const pos2 = @import("worldedit/pos2.zig"); pub const deselect = @import("worldedit/deselect.zig"); pub const copy = @import("worldedit/copy.zig"); +pub const count = @import("worldedit/count.zig"); pub const paste = @import("worldedit/paste.zig"); pub const blueprint = @import("worldedit/blueprint.zig"); pub const rotate = @import("worldedit/rotate.zig"); diff --git a/src/server/command/worldedit/count.zig b/src/server/command/worldedit/count.zig new file mode 100644 index 0000000000..bd5cf20cc9 --- /dev/null +++ b/src/server/command/worldedit/count.zig @@ -0,0 +1,75 @@ +const std = @import("std"); + +const main = @import("main"); +const command = main.server.command; +const Source = command.Source; + +const Block = main.blocks.Block; +const Blueprint = main.blueprint.Blueprint; + +pub const description = "Count block(s) appearance(s) in selection."; +pub const usage = + \\/count + \\/count +; + +pub const Args = union(enum) { + @"/count": struct { block: ?command.BlockId }, +}; + +pub fn execute(args: Args, source: Source) void { + if (source != .user) { + source.sendMessage("Command cannot be run without a user", .{}); + return; + } + const user = source.user; + const selection = command.getCurrentSelection(user) catch return; + + var result = Blueprint.capture(main.stackAllocator, selection); + + switch (result) { + .success => |*blueprint| { + defer blueprint.deinit(main.stackAllocator); + + var context: std.AutoHashMapUnmanaged(u16, u32) = .{}; + defer context.deinit(main.stackAllocator.allocator); + + blueprint.apply(&context, countBlocks); + + if (args.@"/count".block) |block| { + const count = context.get(block.block.typ) orelse 0; + user.sendMessage("#ffff00{s} #ffffff{d}", .{block.block.id(), count}); + } else { + const TypAndCount = struct { typ: u16, count: u32 }; + var items: main.List(TypAndCount) = .empty; + + var iterator = context.iterator(); + while (iterator.next()) |next| items.append(main.stackAllocator, .{.typ = next.key_ptr.*, .count = next.value_ptr.*}); + + std.sort.insertion(TypAndCount, items.items, {}, struct { + fn lessThan(_: void, a: TypAndCount, b: TypAndCount) bool { + return a.count > b.count; + } + }.lessThan); + + for (items.items) |item| { + user.sendMessage("#ffffff{d: <6} #ffff00{s}", .{item.count, (Block{.typ = item.typ, .data = 0}).id()}); + } + } + }, + .failure => |e| { + user.sendMessage("#ff0000Error while capturing block {}: {s}", .{e.pos, e.message}); + std.log.warn("Error while capturing block {}: {s}", .{e.pos, e.message}); + }, + } +} + +fn countBlocks(context: *std.AutoHashMapUnmanaged(u16, u32), current: Block) Block { + const result = context.getOrPut(main.stackAllocator.allocator, current.typ) catch unreachable; + if (result.found_existing) { + result.value_ptr.* = result.value_ptr.* + 1; + } else { + result.value_ptr.* = 1; + } + return current; +}