Move /set to be method on Blueprint (#1352)

* Move Pattern into Blueprint

* Improve error message on capture failure

* Change memory management
This commit is contained in:
Krzysztof Wiśniewski 2025-05-02 10:21:25 +02:00 committed by GitHub
parent 64ce0ed991
commit 86e0a9ae4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 70 deletions

View File

@ -20,6 +20,9 @@ const BlockStorageType = u32;
const BinaryWriter = main.utils.BinaryWriter; const BinaryWriter = main.utils.BinaryWriter;
const BinaryReader = main.utils.BinaryReader; const BinaryReader = main.utils.BinaryReader;
const AliasTable = main.utils.AliasTable;
const ListUnmanaged = main.ListUnmanaged;
pub const blueprintVersion = 0; pub const blueprintVersion = 0;
var voidType: ?u16 = null; var voidType: ?u16 = null;
@ -36,7 +39,7 @@ pub const Blueprint = struct {
pub fn deinit(self: Blueprint, allocator: NeverFailingAllocator) void { pub fn deinit(self: Blueprint, allocator: NeverFailingAllocator) void {
self.blocks.deinit(allocator); 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)}; return .{.blocks = self.blocks.clone(allocator)};
} }
pub fn rotateZ(self: Blueprint, allocator: NeverFailingAllocator, angle: Degrees) Blueprint { 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 { pub fn registerVoidBlock(block: Block) void {

View File

@ -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);
}

View File

@ -1,11 +1,12 @@
const std = @import("std"); const std = @import("std");
const main = @import("main"); const main = @import("main");
const Vec3i = main.vec.Vec3i;
const User = main.server.User; const User = main.server.User;
const Pattern = @import("Pattern.zig");
const Block = main.blocks.Block; const Block = main.blocks.Block;
const Blueprint = main.blueprint.Blueprint; const Blueprint = main.blueprint.Blueprint;
const Pattern = main.blueprint.Pattern;
pub const description = "Set all blocks within selection to a block."; pub const description = "Set all blocks within selection to a block.";
pub const usage = "/set <pattern>"; pub const usage = "/set <pattern>";
@ -27,25 +28,24 @@ pub fn execute(args: []const u8, source: *User) void {
}; };
defer pattern.deinit(main.stackAllocator); defer pattern.deinit(main.stackAllocator);
const startX = @min(pos1[0], pos2[0]); const posStart: Vec3i = @min(pos1, pos2);
const startY = @min(pos1[1], pos2[1]); const posEnd: Vec3i = @max(pos1, pos2);
const startZ = @min(pos1[2], pos2[2]);
const width = @abs(pos2[0] - pos1[0]) + 1; const selection = Blueprint.capture(main.globalAllocator, posStart, posEnd);
const depth = @abs(pos2[1] - pos1[1]) + 1;
const height = @abs(pos2[2] - pos1[2]) + 1;
for(0..width) |x| { switch(selection) {
const worldX = startX +% @as(i32, @intCast(x)); .success => |blueprint| {
source.worldEditData.undoHistory.push(.init(blueprint, posStart, "set"));
source.worldEditData.redoHistory.clear();
for(0..depth) |y| { var modifiedBlueprint = blueprint.clone(main.stackAllocator);
const worldY = startY +% @as(i32, @intCast(y)); defer modifiedBlueprint.deinit(main.stackAllocator);
for(0..height) |z| { modifiedBlueprint.set(pattern);
const worldZ = startZ +% @as(i32, @intCast(z)); modifiedBlueprint.paste(posStart, .{.preserveVoid = true});
},
_ = main.server.world.?.updateBlock(worldX, worldY, worldZ, pattern.blocks.sample(&main.seed).block); .failure => |err| {
} source.sendMessage("#ff0000Error: Could not capture selection. (at {}, {s})", .{err.pos, err.message});
} },
} }
} }