From 6b153c73e4e36062d53f236a53cc7496b0cd544e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 27 Apr 2025 00:30:23 +0200 Subject: [PATCH 1/2] Add blueprint scopes --- src/blueprint.zig | 2 +- src/network.zig | 163 ++++++++++ src/server/command/worldedit/blueprint.zig | 340 +++++++++++++-------- src/server/server.zig | 11 + 4 files changed, 392 insertions(+), 124 deletions(-) diff --git a/src/blueprint.zig b/src/blueprint.zig index c103919cb3..1e22ad24af 100644 --- a/src/blueprint.zig +++ b/src/blueprint.zig @@ -129,7 +129,7 @@ pub const Blueprint = struct { } } } - pub fn load(allocator: NeverFailingAllocator, inputBuffer: []u8) !Blueprint { + pub fn load(allocator: NeverFailingAllocator, inputBuffer: []const u8) !Blueprint { var compressedReader = BinaryReader.init(inputBuffer); const version = try compressedReader.readInt(u16); diff --git a/src/network.zig b/src/network.zig index 9fcb39908e..b0015d6319 100644 --- a/src/network.zig +++ b/src/network.zig @@ -20,6 +20,7 @@ const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; const NeverFailingAllocator = main.heap.NeverFailingAllocator; +const Blueprint = main.blueprint.Blueprint; //TODO: Might want to use SSL or something similar to encode the message @@ -954,6 +955,10 @@ pub const Protocols = struct { teleport = 1, worldEditPos = 2, timeAndBiome = 3, + blueprintSave = 4, + blueprintDelete = 5, + blueprintLoad = 6, + blueprintList = 7, }; const WorldEditPosition = enum(u2) { @@ -1022,6 +1027,70 @@ pub const Protocols = struct { } } }, + .blueprintSave => { + if(conn.isServerSide()) return error.InvalidPacket; + const filePath = try reader.readUntilDelimiter(0); + const blueprintData = reader.remaining; + + var split = std.mem.splitBackwardsScalar(u8, filePath, '/'); + const fileName = split.first(); + const fileDir = split.rest(); + + var dir = main.files.openDir(fileDir) catch |err| { + return conn.user.?.sendWarningAndLog("Failed to open directory '{s}' ({s})", .{fileDir, @errorName(err)}); + }; + defer dir.close(); + + dir.write(fileName, blueprintData) catch |err| { + return conn.user.?.sendWarningAndLog("Failed to write blueprint file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + }, + .blueprintDelete => { + if(conn.isServerSide()) return error.InvalidPacket; + const path = try reader.readUntilDelimiter(0); + + std.fs.cwd().deleteFile(path) catch |err| { + std.log.err("Failed to delete file '{s}': {s}", .{path, @errorName(err)}); + return err; + }; + }, + .blueprintLoad => { + if(conn.isServerSide()) { + const blueprint = Blueprint.load(main.globalAllocator, reader.remaining) catch |err| { + return conn.user.?.sendWarningAndLog("Failed to load blueprint ({s})", .{@errorName(err)}); + }; + + const oldClipboard = conn.user.?.worldEditData.clipboard; + conn.user.?.worldEditData.clipboard = blueprint; + + if(oldClipboard) |_oldClipboard| { + _oldClipboard.deinit(main.globalAllocator); + } + } else { + const path = try reader.readUntilDelimiter(0); + + var blueprintFile = std.fs.cwd().openFile(path, .{.mode = .read_only}) catch |err| { + return conn.user.?.sendWarningAndLog("Failed to open blueprint file '{s}' ({s})", .{path, @errorName(err)}); + }; + defer blueprintFile.close(); + + const raw = blueprintFile.readToEndAlloc(main.stackAllocator.allocator, std.math.maxInt(usize)) catch |err| { + return conn.user.?.sendWarningAndLog("Failed to write blueprint to file '{s}' ({s})", .{path, @errorName(err)}); + }; + defer main.stackAllocator.free(raw); + sendBlueprintUpload(conn, raw); + } + }, + .blueprintList => { + if(conn.isServerSide()) { + while(reader.remaining.len != 0) { + const path = try reader.readUntilDelimiter(0); + conn.user.?.sendMessage("#ffffff{s}", .{path}); + } + } else { + sendBlueprintListResponse(conn); + } + }, } } @@ -1064,6 +1133,100 @@ pub const Protocols = struct { conn.send(.fast, id, writer.data.items); } + + pub fn sendBlueprintSave(conn: *Connection, path: []const u8, blueprint: Blueprint) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintSave); + writer.writeWithDelimiter(path, 0); + + const data = blueprint.store(main.stackAllocator); + defer main.stackAllocator.free(data); + + writer.writeSlice(data); + + conn.send(.fast, id, writer.data.items); + } + + pub fn sendBlueprintDelete(conn: *Connection, path: []const u8) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintSave); + writer.writeWithDelimiter(path, 0); + + conn.send(.fast, id, writer.data.items); + } + + pub fn sendBlueprintLoadRequest(conn: *Connection, path: []const u8) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintLoad); + writer.writeWithDelimiter(path, 0); + + conn.send(.fast, id, writer.data.items); + } + + pub fn sendBlueprintUpload(conn: *Connection, blueprintRawData: []const u8) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintLoad); + writer.writeSlice(blueprintRawData); + + conn.send(.fast, id, writer.data.items); + } + + pub fn sendBlueprintListRequest(conn: *Connection) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintList); + conn.send(.fast, id, writer.data.items); + } + + pub fn sendBlueprintListResponse(conn: *Connection) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .blueprintList); + var isEmpty = true; + + outer: { + var blueprintsDir = std.fs.cwd().openDir("blueprints", .{.iterate = true}) catch break :outer; + defer blueprintsDir.close(); + + var iterator = blueprintsDir.iterate(); + while(iterator.next() catch break :outer) |entry| { + if(entry.kind != .directory) continue; + + var addonDir = blueprintsDir.openDir(entry.name, .{.iterate = true}) catch continue; + defer addonDir.close(); + + var walker = addonDir.walk(main.stackAllocator.allocator) catch continue; + defer walker.deinit(); + + while(walker.next() catch continue) |addonEntry| { + if(addonEntry.kind != .file) continue; + + var split = std.mem.splitBackwardsScalar(u8, addonEntry.path, '.'); + _ = split.first(); + const formatted = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}:{s}", .{entry.name, split.rest()}) catch unreachable; + defer main.stackAllocator.free(formatted); + + writer.writeWithDelimiter(formatted, 0); + isEmpty = false; + } + } + } + if(isEmpty) { + writer.writeWithDelimiter("No blueprints found.", 0); + } + + conn.send(.fast, id, writer.data.items); + } }; pub const chat = struct { pub const id: u8 = 10; diff --git a/src/server/command/worldedit/blueprint.zig b/src/server/command/worldedit/blueprint.zig index 59e57c2c6f..7dcfb65aff 100644 --- a/src/server/command/worldedit/blueprint.zig +++ b/src/server/command/worldedit/blueprint.zig @@ -14,10 +14,10 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const description = "Input-output operations on blueprints."; pub const usage = - \\/blueprint save - \\/blueprint delete - \\/blueprint load - \\/blueprint list + \\/blueprint save [--scope/-s ] + \\/blueprint delete [--scope/-s ] + \\/blueprint load [--scope/-s ] + \\/blueprint list [--scope/-s ] ; const BlueprintSubCommand = enum { @@ -25,163 +25,257 @@ const BlueprintSubCommand = enum { delete, load, list, - unknown, - empty, +}; - fn fromString(string: []const u8) BlueprintSubCommand { - return std.meta.stringToEnum(BlueprintSubCommand, string) orelse { - if(string.len == 0) return .empty; - return .unknown; - }; +const StorageScope = enum { + remote, + local, + game, + world, +}; + +pub const ParsedId = struct { + addon: []const u8, + asset: []const u8, + params: []const u8, + + pub fn parse(id: []const u8) !ParsedId { + var self: ParsedId = undefined; + var split = std.mem.splitScalar(u8, id, ':'); + + self.addon = split.next() orelse ""; + for(0..self.addon.len) |i| { + if(!std.ascii.isAlphanumeric(self.addon[i])) return error.InvalidAddonName; + } + self.asset = split.next() orelse ""; + for(0..self.asset.len) |i| { + const c = self.asset[i]; + if(!std.ascii.isAlphanumeric(c) and c != '/') return error.InvalidAssetName; + } + self.params = split.next() orelse ""; + + return self; + } +}; + +const CommandParams = struct { + command: ?BlueprintSubCommand = null, + id: ?[]const u8 = null, + scope: StorageScope = .remote, + + pub fn getPath(self: CommandParams, allocator: main.heap.NeverFailingAllocator) ![]const u8 { + const parsed = try ParsedId.parse(self.id.?); + + switch(self.scope) { + .remote => return std.fmt.allocPrint(allocator.allocator, "./blueprints/{s}/{s}.blp", .{parsed.addon, parsed.asset}), + .local => return std.fmt.allocPrint(allocator.allocator, "./blueprints/{s}/{s}.blp", .{parsed.addon, parsed.asset}), + .game => return std.fmt.allocPrint(allocator.allocator, "./assets/{s}/blueprints/{s}.blp", .{parsed.addon, parsed.asset}), + .world => return std.fmt.allocPrint(allocator.allocator, "./saves/{s}/assets/{s}/blueprints/{s}.blp", .{main.server.world.?.name, parsed.addon, parsed.asset}), + } } }; pub fn execute(args: []const u8, source: *User) void { - var argsList = List([]const u8).init(main.stackAllocator); - defer argsList.deinit(); + var params = CommandParams{}; var splitIterator = std.mem.splitScalar(u8, args, ' '); - while(splitIterator.next()) |a| { - argsList.append(a); + const command = splitIterator.next() orelse { + return source.sendMessage("#ff0000Missing subcommand for '/blueprint', usage: {s}", .{usage}); + }; + params.command = std.meta.stringToEnum(BlueprintSubCommand, command) orelse { + return source.sendMessage("#ff0000Invalid subcommand for '/blueprint': '{s}', usage: {s}", .{command, usage}); + }; + while(splitIterator.next()) |next| { + if(std.mem.eql(u8, next, "--scope") or std.mem.eql(u8, next, "-s")) { + const scope = splitIterator.next() orelse { + return source.sendMessage("#ff0000Missing argument for '--scope' option, usage: {s}", .{usage}); + }; + params.scope = std.meta.stringToEnum(StorageScope, scope) orelse { + return source.sendMessage("#ff0000Invalid scope '{s}', usage: {s}", .{@tagName(params.scope), usage}); + }; + continue; + } + if(params.id == null) { + params.id = next; + continue; + } + return source.sendMessage("#ff0000Too many arguments for /blueprint command, usage: {s}", .{usage}); } - if(argsList.items.len < 1) { - source.sendMessage("#ff0000Not enough arguments for /blueprint, expected at least 1.", .{}); - return; - } - const subcommand = BlueprintSubCommand.fromString(argsList.items[0]); - switch(subcommand) { - .save => blueprintSave(argsList.items, source), - .delete => blueprintDelete(argsList.items, source), - .load => blueprintLoad(argsList.items, source), - .list => blueprintList(source), - .unknown => { - source.sendMessage("#ff0000Unrecognized subcommand for /blueprint: '{s}'", .{argsList.items[0]}); - }, - .empty => { - source.sendMessage("#ff0000Missing subcommand for /blueprint, usage: {s} ", .{usage}); - }, + switch(params.command.?) { + .save => blueprintSave(params, source), + .delete => blueprintDelete(params, source), + .load => blueprintLoad(params, source), + .list => blueprintList(params, source), } } -fn blueprintSave(args: []const []const u8, source: *User) void { - if(args.len < 2) { - return source.sendMessage("#ff0000/blueprint save requires file-name argument.", .{}); - } - if(args.len >= 3) { - return source.sendMessage("#ff0000Too many arguments for /blueprint save. Expected 1 argument, file-name.", .{}); +fn blueprintSave(params: CommandParams, source: *User) void { + if(params.id == null) { + return source.sendMessage("#ff0000'/blueprint save' requires blueprint id argument.", .{}); } - if(source.worldEditData.clipboard) |clipboard| { - const storedBlueprint = clipboard.store(main.stackAllocator); - defer main.stackAllocator.free(storedBlueprint); - - const fileName: []const u8 = ensureBlueprintExtension(main.stackAllocator, args[1]); - defer main.stackAllocator.free(fileName); - - var blueprintsDir = openBlueprintsDir(source) orelse return; - defer blueprintsDir.close(); - - blueprintsDir.write(fileName, storedBlueprint) catch |err| { - return sendWarningAndLog("Failed to write blueprint file '{s}' ({s})", .{fileName, @errorName(err)}, source); + const filePath: []const u8 = params.getPath(main.stackAllocator) catch |err| { + return source.sendWarningAndLog("Failed to determine path for blueprint file '{s}' ({s})", .{params.id.?, @errorName(err)}); }; - - sendInfoAndLog("Saved clipboard to blueprint file: {s}", .{fileName}, source); + defer main.stackAllocator.free(filePath); + + switch(params.scope) { + .local => main.network.Protocols.genericUpdate.sendBlueprintSave(source.conn, filePath, clipboard), + .remote, .game, .world => { + const storedBlueprint = clipboard.store(main.stackAllocator); + defer main.stackAllocator.free(storedBlueprint); + + var split = std.mem.splitBackwardsScalar(u8, filePath, '/'); + const fileName = split.first(); + const fileDir = split.rest(); + + var dir = main.files.openDir(fileDir) catch |err| { + return source.sendWarningAndLog("Failed to open directory '{s}' ({s})", .{fileDir, @errorName(err)}); + }; + defer dir.close(); + + dir.write(fileName, storedBlueprint) catch |err| { + return source.sendWarningAndLog("Failed to write blueprint file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + }, + } + source.sendInfoAndLog("Saved clipboard to blueprint to file: '{s}'", .{filePath}); } else { source.sendMessage("#ff0000Error: No clipboard content to save.", .{}); } } -fn sendWarningAndLog(comptime fmt: []const u8, args: anytype, user: *User) void { - std.log.warn(fmt, args); - user.sendMessage("#ff0000" ++ fmt, args); +fn blueprintDelete(params: CommandParams, source: *User) void { + if(params.id == null) { + return source.sendMessage("#ff0000'/blueprint delete' requires blueprint id argument.", .{}); + } + const filePath: []const u8 = params.getPath(main.stackAllocator) catch |err| { + return source.sendWarningAndLog("Failed to determine path for blueprint file '{s}' ({s})", .{params.id.?, @errorName(err)}); + }; + defer main.stackAllocator.free(filePath); + + switch(params.scope) { + .local => main.network.Protocols.genericUpdate.sendBlueprintDelete(source.conn, filePath), + .remote, .game, .world => { + std.fs.cwd().deleteFile(filePath) catch |err| { + return source.sendWarningAndLog("Failed to delete blueprint file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + source.sendInfoAndLog("Deleted blueprint file: '{s}'", .{filePath}); + }, + } } -fn sendInfoAndLog(comptime fmt: []const u8, args: anytype, user: *User) void { - std.log.info(fmt, args); - user.sendMessage("#00ff00" ++ fmt, args); -} +fn blueprintList(params: CommandParams, source: *User) void { + switch(params.scope) { + .local => main.network.Protocols.genericUpdate.sendBlueprintListRequest(source.conn), + .remote => { + var blueprintsDir = std.fs.cwd().openDir("blueprints", .{.iterate = true}) catch return; + defer blueprintsDir.close(); -fn openBlueprintsDir(source: *User) ?Dir { - return openDir("blueprints") catch |err| blk: { - sendWarningAndLog("Failed to open 'blueprints' directory ({s})", .{@errorName(err)}, source); - break :blk null; - }; -} + var isEmpty = true; -fn ensureBlueprintExtension(allocator: NeverFailingAllocator, fileName: []const u8) []const u8 { - if(!std.ascii.endsWithIgnoreCase(fileName, ".blp")) { - return std.fmt.allocPrint(allocator.allocator, "{s}.blp", .{fileName}) catch unreachable; - } else { - return allocator.dupe(u8, fileName); - } -} + var iterator = blueprintsDir.iterate(); + while(iterator.next() catch return) |addon| { + if(addon.kind != .directory) continue; -fn blueprintDelete(args: []const []const u8, source: *User) void { - if(args.len < 2) { - return source.sendMessage("#ff0000/blueprint delete requires file-name argument.", .{}); - } - if(args.len >= 3) { - return source.sendMessage("#ff0000Too many arguments for /blueprint delete. Expected 1 argument, file-name.", .{}); - } + var addonDir = blueprintsDir.openDir(addon.name, .{.iterate = true}) catch continue; + defer addonDir.close(); - const fileName: []const u8 = ensureBlueprintExtension(main.stackAllocator, args[1]); - defer main.stackAllocator.free(fileName); + var walker = addonDir.walk(main.stackAllocator.allocator) catch continue; + defer walker.deinit(); - var blueprintsDir = openBlueprintsDir(source) orelse return; - defer blueprintsDir.close(); + while(walker.next() catch continue) |entry| { + if(entry.kind != .file) continue; - blueprintsDir.dir.deleteFile(fileName) catch |err| { - return sendWarningAndLog("Failed to delete blueprint file '{s}' ({s})", .{fileName, @errorName(err)}, source); - }; + var split = std.mem.splitBackwardsScalar(u8, entry.path, '.'); + _ = split.first(); + source.sendMessage("#ffffff{s}:{s}", .{addon.name, split.rest()}); - sendWarningAndLog("Deleted blueprint file: {s}", .{fileName}, source); -} + isEmpty = false; + } + } + if(isEmpty) { + source.sendInfoAndLog("No blueprints found.", .{}); + } + }, + .world, .game => { + const assetsPath = switch(params.scope) { + .game => main.stackAllocator.allocator.dupe(u8, "assets") catch unreachable, + .world => std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets", .{main.server.world.?.name}) catch unreachable, + else => unreachable, + }; + defer main.stackAllocator.free(assetsPath); -fn blueprintList(source: *User) void { - var blueprintsDir = std.fs.cwd().makeOpenPath("blueprints", .{.iterate = true}) catch |err| { - return sendWarningAndLog("Failed to open 'blueprints' directory ({s})", .{@errorName(err)}, source); - }; - defer blueprintsDir.close(); + var assetsDir = std.fs.cwd().openDir(assetsPath, .{.iterate = true}) catch return; + defer assetsDir.close(); - var directoryIterator = blueprintsDir.iterate(); + var isEmpty = true; - while(directoryIterator.next() catch |err| { - return sendWarningAndLog("Failed to read blueprint directory ({s})", .{@errorName(err)}, source); - }) |entry| { - if(entry.kind != .file) break; - if(!std.ascii.endsWithIgnoreCase(entry.name, ".blp")) break; + var iterator = assetsDir.iterate(); + while(iterator.next() catch return) |addon| { + if(addon.kind != .directory) continue; - source.sendMessage("#ffffff- {s}", .{entry.name}); - } -} + var addonDir = assetsDir.openDir(addon.name, .{.iterate = true}) catch continue; + defer addonDir.close(); -fn blueprintLoad(args: []const []const u8, source: *User) void { - if(args.len < 2) { - return source.sendMessage("#ff0000/blueprint load requires file-name argument.", .{}); - } - if(args.len >= 3) { - return source.sendMessage("#ff0000Too many arguments for /blueprint load. Expected 1 argument, file-name.", .{}); - } + var blueprintsDir = addonDir.openDir("blueprints", .{.iterate = true}) catch continue; + defer blueprintsDir.close(); - const fileName: []const u8 = ensureBlueprintExtension(main.stackAllocator, args[1]); - defer main.stackAllocator.free(fileName); - var blueprintsDir = openBlueprintsDir(source) orelse return; - defer blueprintsDir.close(); + var walker = blueprintsDir.walk(main.stackAllocator.allocator) catch continue; + defer walker.deinit(); - const storedBlueprint = blueprintsDir.read(main.stackAllocator, fileName) catch |err| { - sendWarningAndLog("Failed to read blueprint file '{s}' ({s})", .{fileName, @errorName(err)}, source); - return; - }; - defer main.stackAllocator.free(storedBlueprint); + while(walker.next() catch continue) |entry| { + if(entry.kind != .file) continue; + + var split = std.mem.splitBackwardsScalar(u8, entry.path, '.'); + _ = split.first(); + source.sendMessage("#ffffff{s}:{s}", .{addon.name, split.rest()}); + + isEmpty = false; + } + } + if(isEmpty) { + source.sendInfoAndLog("No blueprints found.", .{}); + } + }, + } +} - if(source.worldEditData.clipboard) |oldClipboard| { - oldClipboard.deinit(main.globalAllocator); +fn blueprintLoad(params: CommandParams, source: *User) void { + if(params.id == null) { + return source.sendMessage("#ff0000'/blueprint load' requires blueprint id argument.", .{}); } - source.worldEditData.clipboard = Blueprint.load(main.globalAllocator, storedBlueprint) catch |err| { - return sendWarningAndLog("Failed to load blueprint file '{s}' ({s})", .{fileName, @errorName(err)}, source); + const filePath: []const u8 = params.getPath(main.stackAllocator) catch |err| { + return source.sendWarningAndLog("Failed to determine path for blueprint file '{s}' ({s})", .{params.id.?, @errorName(err)}); }; - - sendInfoAndLog("Loaded blueprint file: {s}", .{fileName}, source); + defer main.stackAllocator.free(filePath); + + switch(params.scope) { + .local => main.network.Protocols.genericUpdate.sendBlueprintLoadRequest(source.conn, filePath), + .remote, .game, .world => { + var blueprintFile = std.fs.cwd().openFile(filePath, .{.mode = .read_only}) catch |err| { + return source.sendWarningAndLog("Failed to open blueprint file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + defer blueprintFile.close(); + + const raw = blueprintFile.readToEndAlloc(main.stackAllocator.allocator, std.math.maxInt(usize)) catch |err| { + return source.sendWarningAndLog("Failed to write blueprint to file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + defer main.stackAllocator.free(raw); + + const blueprint = Blueprint.load(main.globalAllocator, raw) catch |err| { + return source.sendWarningAndLog("Failed to load blueprint from file '{s}' ({s})", .{filePath, @errorName(err)}); + }; + + const oldClipboard = source.worldEditData.clipboard; + source.worldEditData.clipboard = blueprint; + + if(oldClipboard) |_oldClipboard| { + _oldClipboard.deinit(main.globalAllocator); + } + }, + } + source.sendInfoAndLog("Saved clipboard to blueprint to file: '{s}'", .{filePath}); } diff --git a/src/server/server.zig b/src/server/server.zig index c7e5c2e71b..e497c4869e 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -265,6 +265,17 @@ pub const User = struct { // MARK: User defer main.stackAllocator.free(msg); self.sendRawMessage(msg); } + + pub fn sendWarningAndLog(user: *User, comptime fmt: []const u8, args: anytype) void { + std.log.warn(fmt, args); + user.sendMessage("#ff0000" ++ fmt, args); + } + + pub fn sendInfoAndLog(user: *User, comptime fmt: []const u8, args: anytype) void { + std.log.info(fmt, args); + user.sendMessage("#00ff00" ++ fmt, args); + } + fn sendRawMessage(self: *User, msg: []const u8) void { main.network.Protocols.chat.send(self.conn, msg); } From 667d44278cd62dea6d79c359084e0600c94c112a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 27 Apr 2025 00:30:46 +0200 Subject: [PATCH 2/2] Fix formatting --- src/server/command/worldedit/blueprint.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/command/worldedit/blueprint.zig b/src/server/command/worldedit/blueprint.zig index 7dcfb65aff..1d5f8731a7 100644 --- a/src/server/command/worldedit/blueprint.zig +++ b/src/server/command/worldedit/blueprint.zig @@ -222,7 +222,6 @@ fn blueprintList(params: CommandParams, source: *User) void { var blueprintsDir = addonDir.openDir("blueprints", .{.iterate = true}) catch continue; defer blueprintsDir.close(); - var walker = blueprintsDir.walk(main.stackAllocator.allocator) catch continue; defer walker.deinit();