diff --git a/src/blueprint.zig b/src/blueprint.zig index c103919c..3ecbf0b3 100644 --- a/src/blueprint.zig +++ b/src/blueprint.zig @@ -20,6 +20,9 @@ const BlockStorageType = u32; const BinaryWriter = main.utils.BinaryWriter; const BinaryReader = main.utils.BinaryReader; +const AliasTable = main.utils.AliasTable; +const ListUnmanaged = main.ListUnmanaged; + pub const blueprintVersion = 0; var voidType: ?u16 = null; @@ -36,7 +39,7 @@ pub const Blueprint = struct { pub fn deinit(self: Blueprint, allocator: NeverFailingAllocator) void { self.blocks.deinit(allocator); } - pub fn clone(self: *Blueprint, allocator: NeverFailingAllocator) Blueprint { + pub fn clone(self: Blueprint, allocator: NeverFailingAllocator) Blueprint { return .{.blocks = self.blocks.clone(allocator)}; } pub fn rotateZ(self: Blueprint, allocator: NeverFailingAllocator, angle: Degrees) Blueprint { @@ -274,6 +277,62 @@ pub const Blueprint = struct { }, } } + pub fn set(self: *Blueprint, pattern: Pattern) void { + for(0..self.blocks.width) |x| { + for(0..self.blocks.depth) |y| { + for(0..self.blocks.height) |z| { + self.blocks.set(x, y, z, pattern.blocks.sample(&main.seed).block); + } + } + } + } +}; + +pub const Pattern = struct { + blocks: AliasTable(Entry), + + const Entry = struct { + block: Block, + chance: f32, + }; + + pub fn initFromString(allocator: NeverFailingAllocator, source: []const u8) !@This() { + var specifiers = std.mem.splitScalar(u8, source, ','); + var totalWeight: f32 = 0; + + var weightedEntries: ListUnmanaged(struct {block: Block, weight: f32}) = .{}; + defer weightedEntries.deinit(main.stackAllocator); + + while(specifiers.next()) |specifier| { + var iterator = std.mem.splitScalar(u8, specifier, '%'); + + var weight: f32 = undefined; + var block = main.blocks.parseBlock(iterator.rest()); + + const first = iterator.first(); + + weight = std.fmt.parseFloat(f32, first) catch blk: { + // To distinguish somehow between mistyped numeric values and actual block IDs we check for addon name separator. + if(!std.mem.containsAtLeastScalar(u8, first, 1, ':')) return error.PatternSyntaxError; + block = main.blocks.parseBlock(first); + break :blk 1.0; + }; + totalWeight += weight; + weightedEntries.append(main.stackAllocator, .{.block = block, .weight = weight}); + } + + const entries = allocator.alloc(Entry, weightedEntries.items.len); + for(weightedEntries.items, 0..) |entry, i| { + entries[i] = .{.block = entry.block, .chance = entry.weight/totalWeight}; + } + + return .{.blocks = .init(allocator, entries)}; + } + + pub fn deinit(self: @This(), allocator: NeverFailingAllocator) void { + self.blocks.deinit(allocator); + allocator.free(self.blocks.items); + } }; pub fn registerVoidBlock(block: Block) void { diff --git a/src/server/command/worldedit/Pattern.zig b/src/server/command/worldedit/Pattern.zig deleted file mode 100644 index 138a5fc6..00000000 --- a/src/server/command/worldedit/Pattern.zig +++ /dev/null @@ -1,52 +0,0 @@ -const std = @import("std"); - -const main = @import("main"); -const AliasTable = main.utils.AliasTable; -const Block = main.blocks.Block; -const ListUnmanaged = main.ListUnmanaged; -const NeverFailingAllocator = main.heap.NeverFailingAllocator; - -blocks: AliasTable(Entry), - -const Entry = struct { - block: Block, - chance: f32, -}; - -pub fn initFromString(allocator: NeverFailingAllocator, source: []const u8) !@This() { - var specifiers = std.mem.splitScalar(u8, source, ','); - var totalWeight: f32 = 0; - - var weightedEntries: ListUnmanaged(struct {block: Block, weight: f32}) = .{}; - defer weightedEntries.deinit(main.stackAllocator); - - while(specifiers.next()) |specifier| { - var iterator = std.mem.splitScalar(u8, specifier, '%'); - - var weight: f32 = undefined; - var block = main.blocks.parseBlock(iterator.rest()); - - const first = iterator.first(); - - weight = std.fmt.parseFloat(f32, first) catch blk: { - // To distinguish somehow between mistyped numeric values and actual block IDs we check for addon name separator. - if(!std.mem.containsAtLeastScalar(u8, first, 1, ':')) return error.PatternSyntaxError; - block = main.blocks.parseBlock(first); - break :blk 1.0; - }; - totalWeight += weight; - weightedEntries.append(main.stackAllocator, .{.block = block, .weight = weight}); - } - - const entries = allocator.alloc(Entry, weightedEntries.items.len); - for(weightedEntries.items, 0..) |entry, i| { - entries[i] = .{.block = entry.block, .chance = entry.weight/totalWeight}; - } - - return .{.blocks = .init(allocator, entries)}; -} - -pub fn deinit(self: @This(), allocator: NeverFailingAllocator) void { - self.blocks.deinit(allocator); - allocator.free(self.blocks.items); -} diff --git a/src/server/command/worldedit/set.zig b/src/server/command/worldedit/set.zig index ece4b51c..d52b2df7 100644 --- a/src/server/command/worldedit/set.zig +++ b/src/server/command/worldedit/set.zig @@ -1,11 +1,12 @@ const std = @import("std"); const main = @import("main"); +const Vec3i = main.vec.Vec3i; const User = main.server.User; -const Pattern = @import("Pattern.zig"); const Block = main.blocks.Block; const Blueprint = main.blueprint.Blueprint; +const Pattern = main.blueprint.Pattern; pub const description = "Set all blocks within selection to a block."; pub const usage = "/set "; @@ -27,25 +28,24 @@ pub fn execute(args: []const u8, source: *User) void { }; defer pattern.deinit(main.stackAllocator); - const startX = @min(pos1[0], pos2[0]); - const startY = @min(pos1[1], pos2[1]); - const startZ = @min(pos1[2], pos2[2]); + const posStart: Vec3i = @min(pos1, pos2); + const posEnd: Vec3i = @max(pos1, pos2); - const width = @abs(pos2[0] - pos1[0]) + 1; - const depth = @abs(pos2[1] - pos1[1]) + 1; - const height = @abs(pos2[2] - pos1[2]) + 1; + const selection = Blueprint.capture(main.globalAllocator, posStart, posEnd); - for(0..width) |x| { - const worldX = startX +% @as(i32, @intCast(x)); + switch(selection) { + .success => |blueprint| { + source.worldEditData.undoHistory.push(.init(blueprint, posStart, "set")); + source.worldEditData.redoHistory.clear(); - for(0..depth) |y| { - const worldY = startY +% @as(i32, @intCast(y)); + var modifiedBlueprint = blueprint.clone(main.stackAllocator); + defer modifiedBlueprint.deinit(main.stackAllocator); - for(0..height) |z| { - const worldZ = startZ +% @as(i32, @intCast(z)); - - _ = main.server.world.?.updateBlock(worldX, worldY, worldZ, pattern.blocks.sample(&main.seed).block); - } - } + modifiedBlueprint.set(pattern); + modifiedBlueprint.paste(posStart, .{.preserveVoid = true}); + }, + .failure => |err| { + source.sendMessage("#ff0000Error: Could not capture selection. (at {}, {s})", .{err.pos, err.message}); + }, } }