From 8bcc00f5362c0269f7e18a43bacd8360daaaf26e Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Tue, 13 May 2025 20:38:07 +0200 Subject: [PATCH] Sort the world list in save selection by least recently used. Also did some further refactoring to make it easier to deal with different world paths (#606) in the future. fixes #1311 --- src/Inventory.zig | 2 +- src/gui/windows/save_creation.zig | 21 ++++++-- src/gui/windows/save_selection.zig | 66 ++++++++++++++++-------- src/network.zig | 2 +- src/server/server.zig | 3 +- src/server/storage.zig | 2 +- src/server/terrain/SurfaceMap.zig | 4 +- src/server/world.zig | 82 ++++++++++++++---------------- 8 files changed, 106 insertions(+), 76 deletions(-) diff --git a/src/Inventory.zig b/src/Inventory.zig index 806efa87..e51ed078 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -325,7 +325,7 @@ pub const Sync = struct { // MARK: Sync defer main.stackAllocator.free(dest); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{main.server.world.?.name, hashedName}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{main.server.world.?.path, hashedName}) catch unreachable; defer main.stackAllocator.free(path); const playerData = main.files.readToZon(main.stackAllocator, path) catch .null; diff --git a/src/gui/windows/save_creation.zig b/src/gui/windows/save_creation.zig index a2abb988..31ee00ad 100644 --- a/src/gui/windows/save_creation.zig +++ b/src/gui/windows/save_creation.zig @@ -46,7 +46,8 @@ fn createWorld(_: usize) void { fn flawedCreateWorld() !void { const worldName = textInput.currentString.items; - const saveFolder = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldName}) catch unreachable; + const worldPath = worldName; // TODO: Make sure that only valid file name characters are used, and add a check to allow different worlds of the same name. + const saveFolder = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldPath}) catch unreachable; defer main.stackAllocator.free(saveFolder); if(std.fs.cwd().openDir(saveFolder, .{})) |_dir| { var dir = _dir; @@ -55,7 +56,7 @@ fn flawedCreateWorld() !void { } else |_| {} try main.files.makeDir(saveFolder); { - const generatorSettingsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{worldName}) catch unreachable; + const generatorSettingsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{worldPath}) catch unreachable; defer main.stackAllocator.free(generatorSettingsPath); const generatorSettings = main.ZonElement.initObject(main.stackAllocator); defer generatorSettings.deinit(main.stackAllocator); @@ -75,7 +76,19 @@ fn flawedCreateWorld() !void { try main.files.writeZon(generatorSettingsPath, generatorSettings); } { - const gamerulePath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{worldName}) catch unreachable; + const worldInfoPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.zig.zon", .{worldPath}) catch unreachable; + defer main.stackAllocator.free(worldInfoPath); + const worldInfo = main.ZonElement.initObject(main.stackAllocator); + defer worldInfo.deinit(main.stackAllocator); + + worldInfo.put("name", worldName); + worldInfo.put("version", main.server.world_zig.worldDataVersion); + worldInfo.put("lastUsedTime", std.time.milliTimestamp()); + + try main.files.writeZon(worldInfoPath, worldInfo); + } + { + const gamerulePath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{worldPath}) catch unreachable; defer main.stackAllocator.free(gamerulePath); const gamerules = main.ZonElement.initObject(main.stackAllocator); defer gamerules.deinit(main.stackAllocator); @@ -86,7 +99,7 @@ fn flawedCreateWorld() !void { try main.files.writeZon(gamerulePath, gamerules); } { // Make assets subfolder - const assetsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets", .{worldName}) catch unreachable; + const assetsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets", .{worldPath}) catch unreachable; defer main.stackAllocator.free(assetsPath); try main.files.makeDir(assetsPath); } diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig index cea3944f..ef102032 100644 --- a/src/gui/windows/save_selection.zig +++ b/src/gui/windows/save_selection.zig @@ -29,6 +29,13 @@ pub var needsUpdate: bool = false; var deleteIcon: Texture = undefined; var fileExplorerIcon: Texture = undefined; +const WorldInfo = struct { + lastUsedTime: i64, + name: []const u8, + fileName: []const u8, +}; +var worldList: main.ListUnmanaged(WorldInfo) = .{}; + pub fn init() void { deleteIcon = Texture.initFromFile("assets/cubyz/ui/delete_icon.png"); fileExplorerIcon = Texture.initFromFile("assets/cubyz/ui/file_explorer_icon.png"); @@ -70,25 +77,18 @@ pub fn openWorld(name: []const u8) void { gui.openHud(); } -fn openWorldWrap(namePtr: usize) void { // TODO: Improve this situation. Maybe it makes sense to always use 2 arguments in the Callback. - const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr); - const name = std.mem.span(nullTerminatedName); - openWorld(name); +fn openWorldWrap(index: usize) void { // TODO: Improve this situation. Maybe it makes sense to always use 2 arguments in the Callback. + openWorld(worldList.items[index].fileName); } -fn deleteWorld(namePtr: usize) void { - const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr); - const name = std.mem.span(nullTerminatedName); +fn deleteWorld(index: usize) void { main.gui.closeWindow("delete_world_confirmation"); - main.gui.windowlist.delete_world_confirmation.setDeleteWorldName(name); + main.gui.windowlist.delete_world_confirmation.setDeleteWorldName(worldList.items[index].fileName); main.gui.openWindow("delete_world_confirmation"); } -fn openFolder(namePtr: usize) void { - const nullTerminatedName: [*:0]const u8 = @ptrFromInt(namePtr); - const name = std.mem.span(nullTerminatedName); - - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{name}) catch unreachable; +fn openFolder(index: usize) void { + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldList.items[index].fileName}) catch unreachable; defer main.stackAllocator.free(path); main.files.openDirInWindow(path); @@ -149,22 +149,41 @@ pub fn onOpen() void { break :readingSaves; }) |entry| { if(entry.kind == .directory) { - const row = HorizontalList.init(); + const worldInfoPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.zig.zon", .{entry.name}) catch unreachable; + defer main.stackAllocator.free(worldInfoPath); + const worldInfo = main.files.readToZon(main.stackAllocator, worldInfoPath) catch |err| { + std.log.err("Couldn't open save {s}: {s}", .{worldInfoPath, @errorName(err)}); + continue; + }; + defer worldInfo.deinit(main.stackAllocator); const decodedName = parseEscapedFolderName(main.stackAllocator, entry.name); defer main.stackAllocator.free(decodedName); - const name = buttonNameArena.allocator().dupeZ(u8, entry.name); // Null terminate, so we can later recover the string from just the pointer. - const buttonName = std.fmt.allocPrint(buttonNameArena.allocator().allocator, "{s}", .{decodedName}) catch unreachable; - row.add(Button.initText(.{0, 0}, 128, buttonName, .{.callback = &openWorldWrap, .arg = @intFromPtr(name.ptr)})); - row.add(Button.initIcon(.{8, 0}, .{16, 16}, fileExplorerIcon, false, .{.callback = &openFolder, .arg = @intFromPtr(name.ptr)})); - row.add(Button.initIcon(.{8, 0}, .{16, 16}, deleteIcon, false, .{.callback = &deleteWorld, .arg = @intFromPtr(name.ptr)})); - row.finish(.{0, 0}, .center); - list.add(row); + worldList.append(main.globalAllocator, .{ + .fileName = main.globalAllocator.dupe(u8, entry.name), + .lastUsedTime = worldInfo.get(i64, "lastUsedTime", 0), + .name = main.globalAllocator.dupe(u8, worldInfo.get([]const u8, "", decodedName)), + }); } } } + std.sort.insertion(WorldInfo, worldList.items, {}, struct { + fn lessThan(_: void, lhs: WorldInfo, rhs: WorldInfo)bool { + return rhs.lastUsedTime -% lhs.lastUsedTime < 0; + } + }.lessThan); + + for(worldList.items, 0..) |worldInfo, i| { + const row = HorizontalList.init(); + row.add(Button.initText(.{0, 0}, 128, worldInfo.name, .{.callback = &openWorldWrap, .arg = i})); + row.add(Button.initIcon(.{8, 0}, .{16, 16}, fileExplorerIcon, false, .{.callback = &openFolder, .arg = i})); + row.add(Button.initIcon(.{8, 0}, .{16, 16}, deleteIcon, false, .{.callback = &deleteWorld, .arg = i})); + row.finish(.{0, 0}, .center); + list.add(row); + } + list.finish(.center); window.rootComponent = list.toComponent(); window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding)); @@ -172,6 +191,11 @@ pub fn onOpen() void { } pub fn onClose() void { + for(worldList.items) |worldInfo| { + main.globalAllocator.free(worldInfo.fileName); + main.globalAllocator.free(worldInfo.name); + } + worldList.clearAndFree(main.globalAllocator); buttonNameArena.deinit(); if(window.rootComponent) |*comp| { comp.deinit(); diff --git a/src/network.zig b/src/network.zig index 78c084d8..0a8ca522 100644 --- a/src/network.zig +++ b/src/network.zig @@ -667,7 +667,7 @@ pub const Protocols = struct { std.log.info("User {s} joined using version {s}.", .{name, version}); { - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets/", .{main.server.world.?.name}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/assets/", .{main.server.world.?.path}) catch unreachable; defer main.stackAllocator.free(path); var dir = try std.fs.cwd().openDir(path, .{.iterate = true}); defer dir.close(); diff --git a/src/server/server.zig b/src/server/server.zig index 76b1bd6f..e3fef13e 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -17,7 +17,8 @@ const Blueprint = main.blueprint.Blueprint; const NeverFailingAllocator = main.heap.NeverFailingAllocator; const CircularBufferQueue = main.utils.CircularBufferQueue; -pub const ServerWorld = @import("world.zig").ServerWorld; +pub const world_zig = @import("world.zig"); +pub const ServerWorld = world_zig.ServerWorld; pub const terrain = @import("terrain/terrain.zig"); pub const Entity = @import("Entity.zig"); pub const storage = @import("storage.zig"); diff --git a/src/server/storage.zig b/src/server/storage.zig index a61d8bf6..4eca3de2 100644 --- a/src/server/storage.zig +++ b/src/server/storage.zig @@ -215,7 +215,7 @@ fn cacheInit(pos: chunk.ChunkPosition) *RegionFile { return region; } hashMapMutex.unlock(); - const path: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{server.world.?.name}) catch unreachable; + const path: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{server.world.?.path}) catch unreachable; defer main.stackAllocator.free(path); return RegionFile.init(pos, path); } diff --git a/src/server/terrain/SurfaceMap.zig b/src/server/terrain/SurfaceMap.zig index cb6020fa..551bf04f 100644 --- a/src/server/terrain/SurfaceMap.zig +++ b/src/server/terrain/SurfaceMap.zig @@ -125,7 +125,7 @@ pub const MapFragment = struct { // MARK: MapFragment }; pub fn load(self: *MapFragment, biomePalette: *main.assets.Palette, originalHeightMap: ?*[mapSize][mapSize]i32) !NeighborInfo { - const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable; + const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.path}) catch unreachable; defer main.stackAllocator.free(saveFolder); const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable; @@ -208,7 +208,7 @@ pub const MapFragment = struct { // MARK: MapFragment outputWriter.writeInt(u8, @bitCast(header.neighborInfo)); outputWriter.writeSlice(compressedData); - const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable; + const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.path}) catch unreachable; defer main.stackAllocator.free(saveFolder); const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable; diff --git a/src/server/world.zig b/src/server/world.zig index 4245b2c7..52dfc948 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -355,8 +355,9 @@ const ChunkManager = struct { // MARK: ChunkManager } }; +pub const worldDataVersion: u32 = 2; + const WorldIO = struct { // MARK: WorldIO - const worldDataVersion: u32 = 2; dir: files.Dir, world: *ServerWorld, @@ -372,10 +373,6 @@ const WorldIO = struct { // MARK: WorldIO self.dir.close(); } - pub fn hasWorldData(self: WorldIO) bool { - return self.dir.hasFile("world.zig.zon"); - } - /// Load the seed, which is needed before custom item and ore generation. pub fn loadWorldSeed(self: WorldIO) !u64 { const worldData = try self.dir.readToZon(main.stackAllocator, "world.zig.zon"); @@ -384,7 +381,7 @@ const WorldIO = struct { // MARK: WorldIO std.log.err("Cannot read world file version {}. Expected version {}.", .{worldData.get(u32, "version", 0), worldDataVersion}); return error.OldWorld; } - return worldData.get(u64, "seed", 0); + return worldData.get(?u64, "seed", null) orelse main.random.nextInt(u48, &main.seed); } pub fn loadWorldData(self: WorldIO) !void { @@ -395,6 +392,7 @@ const WorldIO = struct { // MARK: WorldIO self.world.gameTime = worldData.get(i64, "gameTime", 0); self.world.spawn = worldData.get(Vec3i, "spawn", .{0, 0, 0}); self.world.biomeChecksum = worldData.get(i64, "biomeChecksum", 0); + self.world.name = main.globalAllocator.dupe(u8, worldData.get([]const u8, "name", self.world.path)); } pub fn saveWorldData(self: WorldIO) !void { @@ -406,6 +404,8 @@ const WorldIO = struct { // MARK: WorldIO worldData.put("gameTime", self.world.gameTime); worldData.put("spawn", self.world.spawn); worldData.put("biomeChecksum", self.world.biomeChecksum); + worldData.put("name", self.world.name); + worldData.put("lastUsedTime", std.time.milliTimestamp()); // TODO: Save entities try self.dir.writeZon("world.zig.zon", worldData); } @@ -420,8 +420,6 @@ pub const ServerWorld = struct { // MARK: ServerWorld biomePalette: *main.assets.Palette = undefined, chunkManager: ChunkManager = undefined, - generated: bool = false, - gameTime: i64 = 0, milliTime: i64, lastUpdateTime: i64, @@ -432,7 +430,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld allowCheats: bool = undefined, seed: u64, - name: []const u8, + path: []const u8, + name: []const u8 = &.{}, spawn: Vec3i = undefined, wio: WorldIO = undefined, @@ -454,14 +453,14 @@ pub const ServerWorld = struct { // MARK: ServerWorld milliTimeStamp: i64, }; - pub fn init(name: []const u8, nullGeneratorSettings: ?ZonElement) !*ServerWorld { // MARK: init() + pub fn init(path: []const u8, nullGeneratorSettings: ?ZonElement) !*ServerWorld { // MARK: init() covert_old_worlds: { // TODO: Remove after #480 - const worldDatPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.dat", .{name}); + const worldDatPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.dat", .{path}); defer main.stackAllocator.free(worldDatPath); if(std.fs.cwd().openFile(worldDatPath, .{})) |file| { file.close(); - std.log.warn("Detected old world in saves/{s}. Converting all .json files to .zig.zon", .{name}); - const dirPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{name}); + std.log.warn("Detected old world in saves/{s}. Converting all .json files to .zig.zon", .{path}); + const dirPath = try std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{path}); defer main.stackAllocator.free(dirPath); var dir = std.fs.cwd().openDir(dirPath, .{.iterate = true}) catch |err| { std.log.err("Could not open world directory to convert json files: {s}. Conversion aborted", .{@errorName(err)}); @@ -490,7 +489,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld .milliTime = std.time.milliTimestamp(), .lastUnimportantDataSent = std.time.milliTimestamp(), .seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))), - .name = main.globalAllocator.dupe(u8, name), + .path = main.globalAllocator.dupe(u8, path), .chunkUpdateQueue = .init(main.globalAllocator, 256), .regionUpdateQueue = .init(main.globalAllocator, 256), }; @@ -505,45 +504,38 @@ pub const ServerWorld = struct { // MARK: ServerWorld if(nullGeneratorSettings) |_generatorSettings| { generatorSettings = _generatorSettings; // Store generator settings: - try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{name}), generatorSettings); + try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{path}), generatorSettings); } else { // Read the generator settings: - generatorSettings = try files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{name})); + generatorSettings = try files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/generatorSettings.zig.zon", .{path})); } - self.wio = WorldIO.init(try files.openDir(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}", .{name})), self); + self.wio = WorldIO.init(try files.openDir(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}", .{path})), self); errdefer self.wio.deinit(); - const blockPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{name})) catch .null; + const blockPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{path})) catch .null; self.blockPalette = try main.assets.Palette.init(main.globalAllocator, blockPaletteZon, "cubyz:air"); errdefer self.blockPalette.deinit(); std.log.info("Loaded save block palette with {} blocks.", .{self.blockPalette.size()}); - const itemPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{name})) catch .null; + const itemPaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{path})) catch .null; self.itemPalette = try main.assets.Palette.init(main.globalAllocator, itemPaletteZon, null); errdefer self.itemPalette.deinit(); std.log.info("Loaded save item palette with {} items.", .{self.itemPalette.size()}); - const biomePaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{name})) catch .null; + const biomePaletteZon = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{path})) catch .null; self.biomePalette = try main.assets.Palette.init(main.globalAllocator, biomePaletteZon, null); errdefer self.biomePalette.deinit(); std.log.info("Loaded save biome palette with {} biomes.", .{self.biomePalette.size()}); errdefer main.assets.unloadAssets(); - if(self.wio.hasWorldData()) { - self.seed = try self.wio.loadWorldSeed(); - self.generated = true; - try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{name}), self.blockPalette, self.itemPalette, self.biomePalette); - } else { - self.seed = main.random.nextInt(u48, &main.seed); - try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{name}), self.blockPalette, self.itemPalette, self.biomePalette); - try self.wio.saveWorldData(); - } + self.seed = try self.wio.loadWorldSeed(); + try main.assets.loadWorldAssets(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/assets/", .{path}), self.blockPalette, self.itemPalette, self.biomePalette); // Store the block palette now that everything is loaded. - try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{name}), self.blockPalette.storeToZon(arenaAllocator)); - try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{name}), self.biomePalette.storeToZon(arenaAllocator)); - try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{name}), self.itemPalette.storeToZon(arenaAllocator)); + try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/palette.zig.zon", .{path}), self.blockPalette.storeToZon(arenaAllocator)); + try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/biome_palette.zig.zon", .{path}), self.biomePalette.storeToZon(arenaAllocator)); + try files.writeZon(try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/item_palette.zig.zon", .{path}), self.itemPalette.storeToZon(arenaAllocator)); - var gamerules = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{name})) catch ZonElement.initObject(arenaAllocator); + var gamerules = files.readToZon(arenaAllocator, try std.fmt.allocPrint(arenaAllocator.allocator, "saves/{s}/gamerules.zig.zon", .{path})) catch ZonElement.initObject(arenaAllocator); self.defaultGamemode = std.meta.stringToEnum(main.game.Gamemode, gamerules.get([]const u8, "default_gamemode", "creative")) orelse .creative; self.allowCheats = gamerules.get(bool, "cheats", true); @@ -573,7 +565,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld self.itemPalette.deinit(); self.biomePalette.deinit(); self.wio.deinit(); - main.globalAllocator.free(self.name); + main.globalAllocator.free(self.path); + //main.globalAllocator.free(self.name); main.globalAllocator.destroy(self); } @@ -666,19 +659,19 @@ pub const ServerWorld = struct { // MARK: ServerWorld fn regenerateLOD(self: *ServerWorld, newBiomeCheckSum: i64) !void { std.log.info("Biomes have changed. Regenerating LODs... (this might take some time)", .{}); const hasSurfaceMaps = blk: { - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{self.name}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{self.path}) catch unreachable; defer main.stackAllocator.free(path); var dir = std.fs.cwd().openDir(path, .{}) catch break :blk false; defer dir.close(); break :blk true; }; if(hasSurfaceMaps) { - try terrain.SurfaceMap.regenerateLOD(self.name); + try terrain.SurfaceMap.regenerateLOD(self.path); } // Delete old LODs: for(1..main.settings.highestSupportedLod + 1) |i| { const lod = @as(u32, 1) << @intCast(i); - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{self.name}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks", .{self.path}) catch unreachable; defer main.stackAllocator.free(path); const dir = std.fmt.allocPrint(main.stackAllocator.allocator, "{}", .{lod}) catch unreachable; defer main.stackAllocator.free(dir); @@ -691,7 +684,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld // Find all the stored chunks: var chunkPositions = main.List(ChunkPosition).init(main.stackAllocator); defer chunkPositions.deinit(); - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks/1", .{self.name}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/chunks/1", .{self.path}) catch unreachable; defer main.stackAllocator.free(path); blk: { var dirX = std.fs.cwd().openDir(path, .{.iterate = true}) catch |err| { @@ -754,7 +747,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld pub fn generate(self: *ServerWorld) !void { try self.wio.loadWorldData(); // load data here in order for entities to also be loaded. - if(!self.generated) { + if(@reduce(.And, self.spawn == Vec3i{0, 0, 0})) { var seed: u64 = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))); std.log.info("Finding position..", .{}); foundPosition: { @@ -812,7 +805,6 @@ pub const ServerWorld = struct { // MARK: ServerWorld defer map.decreaseRefCount(); self.spawn[2] = map.getHeight(self.spawn[0], self.spawn[1]) + 1; } - self.generated = true; const newBiomeCheckSum: i64 = @bitCast(terrain.biomes.getBiomeCheckSum(self.seed)); if(newBiomeCheckSum != self.biomeChecksum) { self.regenerateLOD(newBiomeCheckSum) catch |err| { @@ -820,7 +812,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld }; } try self.wio.saveWorldData(); - const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.name}) catch unreachable; + const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.path}) catch unreachable; defer main.stackAllocator.free(itemsPath); const zon = files.readToZon(main.stackAllocator, itemsPath) catch .null; defer zon.deinit(main.stackAllocator); @@ -832,7 +824,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld defer main.stackAllocator.free(dest); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.name, hashedName}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.path, hashedName}) catch unreachable; defer main.stackAllocator.free(path); const playerData = files.readToZon(main.stackAllocator, path) catch .null; @@ -854,7 +846,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld defer main.stackAllocator.free(dest); const hashedName = std.base64.url_safe.Encoder.encode(dest, user.name); - const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.name, hashedName}) catch unreachable; + const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players/{s}.zig.zon", .{self.path, hashedName}) catch unreachable; defer main.stackAllocator.free(path); var playerZon: ZonElement = files.readToZon(main.stackAllocator, path) catch .null; @@ -882,7 +874,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld } } - const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.name}) catch unreachable; + const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.path}) catch unreachable; defer main.stackAllocator.free(playerPath); try files.makeDir(playerPath); @@ -907,7 +899,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld const itemDropZon = self.itemDropManager.store(main.stackAllocator); defer itemDropZon.deinit(main.stackAllocator); - const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.name}) catch unreachable; + const itemsPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/items.zig.zon", .{self.path}) catch unreachable; defer main.stackAllocator.free(itemsPath); try files.writeZon(itemsPath, itemDropZon); }