From 6f938b69b61408c8efbca8c5d3e594a36b6bbab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 31 Jul 2026 15:10:21 +0200 Subject: [PATCH 1/5] Add /count command --- src/server/command/_list.zig | 1 + src/server/command/worldedit/count.zig | 56 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/server/command/worldedit/count.zig 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..9202694029 --- /dev/null +++ b/src/server/command/worldedit/count.zig @@ -0,0 +1,56 @@ +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 blocks in selection."; +pub const usage = "/count"; + +pub const Args = union(enum) { + @"/count": struct {}, +}; + +pub fn execute(_: 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 => |*success| { + defer success.deinit(main.stackAllocator); + + var context: std.AutoHashMapUnmanaged(u16, u32) = .{}; + defer context.deinit(main.stackAllocator.allocator); + + success.apply(&context, count); + + var iterator = context.iterator(); + while (iterator.next()) |next| { + user.sendMessage("#ffff00{s} #ffffff{d}", .{(Block{.typ = next.key_ptr.*, .data = 0}).id(), next.value_ptr.*}); + } + }, + .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 count(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; +} From 3e2b84d9461a5594410fd19bfd77c950e044d09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 31 Jul 2026 18:42:30 +0200 Subject: [PATCH 2/5] Add option to count only one block --- src/server/command.zig | 12 ++++++++++++ src/server/command/worldedit/count.zig | 26 +++++++++++++++++--------- 2 files changed, 29 insertions(+), 9 deletions(-) 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/worldedit/count.zig b/src/server/command/worldedit/count.zig index 9202694029..3e6030433f 100644 --- a/src/server/command/worldedit/count.zig +++ b/src/server/command/worldedit/count.zig @@ -7,14 +7,17 @@ const Source = command.Source; const Block = main.blocks.Block; const Blueprint = main.blueprint.Blueprint; -pub const description = "Count blocks in selection."; -pub const usage = "/count"; +pub const description = "Count block(s) appearance(s) in selection."; +pub const usage = + \\/count + \\/count +; pub const Args = union(enum) { - @"/count": struct {}, + @"/count": struct { block: ?command.BlockId }, }; -pub fn execute(_: Args, source: Source) void { +pub fn execute(args: Args, source: Source) void { if (source != .user) { source.sendMessage("Command cannot be run without a user", .{}); return; @@ -31,11 +34,16 @@ pub fn execute(_: Args, source: Source) void { var context: std.AutoHashMapUnmanaged(u16, u32) = .{}; defer context.deinit(main.stackAllocator.allocator); - success.apply(&context, count); + success.apply(&context, countBlocks); - var iterator = context.iterator(); - while (iterator.next()) |next| { - user.sendMessage("#ffff00{s} #ffffff{d}", .{(Block{.typ = next.key_ptr.*, .data = 0}).id(), next.value_ptr.*}); + 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 { + var iterator = context.iterator(); + while (iterator.next()) |next| { + user.sendMessage("#ffff00{s} #ffffff{d}", .{(Block{.typ = next.key_ptr.*, .data = 0}).id(), next.value_ptr.*}); + } } }, .failure => |e| { @@ -45,7 +53,7 @@ pub fn execute(_: Args, source: Source) void { } } -fn count(context: *std.AutoHashMapUnmanaged(u16, u32), current: Block) Block { +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; From 488be7345b6f799341556bc4c0b30d78e8c95302 Mon Sep 17 00:00:00 2001 From: Argmaster Date: Sat, 8 Aug 2026 15:26:17 +0200 Subject: [PATCH 3/5] Update src/server/command/worldedit/count.zig Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com> --- src/server/command/worldedit/count.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/command/worldedit/count.zig b/src/server/command/worldedit/count.zig index 3e6030433f..58707e2d7f 100644 --- a/src/server/command/worldedit/count.zig +++ b/src/server/command/worldedit/count.zig @@ -28,7 +28,7 @@ pub fn execute(args: Args, source: Source) void { var result = Blueprint.capture(main.stackAllocator, selection); switch (result) { - .success => |*success| { + .success => |*blueprint| { defer success.deinit(main.stackAllocator); var context: std.AutoHashMapUnmanaged(u16, u32) = .{}; From 1fdd8720bd4d46ae19f4b7d739840507dd7cfbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 8 Aug 2026 15:27:39 +0200 Subject: [PATCH 4/5] Its no longer a success --- src/server/command/worldedit/count.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/command/worldedit/count.zig b/src/server/command/worldedit/count.zig index 58707e2d7f..0325521e49 100644 --- a/src/server/command/worldedit/count.zig +++ b/src/server/command/worldedit/count.zig @@ -29,12 +29,12 @@ pub fn execute(args: Args, source: Source) void { switch (result) { .success => |*blueprint| { - defer success.deinit(main.stackAllocator); + defer blueprint.deinit(main.stackAllocator); var context: std.AutoHashMapUnmanaged(u16, u32) = .{}; defer context.deinit(main.stackAllocator.allocator); - success.apply(&context, countBlocks); + blueprint.apply(&context, countBlocks); if (args.@"/count".block) |block| { const count = context.get(block.block.typ) orelse 0; From 3c437d6ae073f59d7be867aea058aa901502de20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 8 Aug 2026 16:02:25 +0200 Subject: [PATCH 5/5] Oh well --- src/server/command/worldedit/count.zig | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/server/command/worldedit/count.zig b/src/server/command/worldedit/count.zig index 0325521e49..bd5cf20cc9 100644 --- a/src/server/command/worldedit/count.zig +++ b/src/server/command/worldedit/count.zig @@ -40,9 +40,20 @@ pub fn execute(args: Args, source: Source) void { 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| { - user.sendMessage("#ffff00{s} #ffffff{d}", .{(Block{.typ = next.key_ptr.*, .data = 0}).id(), next.value_ptr.*}); + 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()}); } } },