Skip to content

Commit

Permalink
Add player and inventory saving (#878)
Browse files Browse the repository at this point in the history
* Save the player's location and inventory, and also added the ability to load the player's location

* remove the comment from utils

* make the player save every 5 seconds

* fix gamemode loading

* remove comment

* added inventory loading

* fix bug

* fix unintentional changes

* fix segfault

* fixes

* Some small fixes.

---------

Co-authored-by: IntegratedQuantum <[email protected]>
  • Loading branch information
OneAvargeCoder193 and IntegratedQuantum authored Jan 2, 2025
1 parent 0a69ab7 commit 5986c76
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 7 deletions.
34 changes: 33 additions & 1 deletion src/Inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub const Sync = struct { // MARK: Sync
}
}
};
var mutex: std.Thread.Mutex = .{};
pub var mutex: std.Thread.Mutex = .{};

var inventories: main.List(ServerInventory) = undefined;
var maxId: u32 = 0;
Expand Down Expand Up @@ -309,6 +309,28 @@ pub const Sync = struct { // MARK: Sync
.alreadyFreed => unreachable,
}
const inventory = ServerInventory.init(len, typ, source);

switch (source) {
.sharedTestingInventory => {},
.playerInventory => {
const dest: []u8 = main.stackAllocator.alloc(u8, std.base64.url_safe.Encoder.calcSize(user.name.len));
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;
defer main.stackAllocator.free(path);

const playerData = main.files.readToZon(main.stackAllocator, path) catch .null;
defer playerData.deinit(main.stackAllocator);

const inventoryZon = playerData.getChild("inventory");

inventory.inv.loadFromZon(inventoryZon);
},
.other => {},
.alreadyFreed => unreachable,
}

inventories.items[inventory.inv.id] = inventory;
inventories.items[inventory.inv.id].addUser(user, clientId);
}
Expand All @@ -325,6 +347,16 @@ pub const Sync = struct { // MARK: Sync
return inventories.items[serverId].inv;
}

pub fn getInventoryFromSource(source: SourceType) ?Inventory {
main.utils.assertLocked(&mutex);
for(inventories.items) |inv| {
if (inv.source == source) {
return inv.inv;
}
}
return null;
}

pub fn clearPlayerInventory(user: *main.server.User) void {
mutex.lock();
defer mutex.unlock();
Expand Down
10 changes: 10 additions & 0 deletions src/server/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub const User = struct { // MARK: User
timeDifference: utils.TimeDifference = .{},
interpolation: utils.GenericInterpolation(3) = undefined,
lastTime: i16 = undefined,
lastSaveTime: i64 = 0,
name: []const u8 = "",
renderDistance: u16 = undefined,
clientUpdatePos: Vec3i = .{0, 0, 0},
Expand Down Expand Up @@ -165,6 +166,15 @@ pub const User = struct { // MARK: User
time -%= self.timeDifference.difference.load(.monotonic);
self.interpolation.update(time, self.lastTime);
self.lastTime = time;

const saveTime = std.time.milliTimestamp();
if (saveTime -% self.lastSaveTime > 5000) {
world.?.savePlayer(self) catch |err| {
std.log.err("Failed to save player {s}: {s}", .{self.name, @errorName(err)});
};
self.lastSaveTime = saveTime;
}

self.loadUnloadChunks();
}

Expand Down
65 changes: 60 additions & 5 deletions src/server/world.zig
Original file line number Diff line number Diff line change
Expand Up @@ -761,23 +761,78 @@ pub const ServerWorld = struct { // MARK: ServerWorld
self.itemDropManager.loadFrom(zon);
}


pub fn findPlayer(self: *ServerWorld, user: *User) void {
var buf: [1024]u8 = undefined;
const playerData = files.readToZon(main.stackAllocator, std.fmt.bufPrint(&buf, "saves/{s}/player/{s}.zig.zon", .{self.name, user.name}) catch "") catch .null; // TODO: Utils.escapeFolderName(user.name)
const dest: []u8 = main.stackAllocator.alloc(u8, std.base64.url_safe.Encoder.calcSize(user.name.len));
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;
defer main.stackAllocator.free(path);

const playerData = files.readToZon(main.stackAllocator, path) catch .null;
defer playerData.deinit(main.stackAllocator);
const player = &user.player;
if(playerData == .null) {
// Generate a new player:
player.pos = @floatFromInt(self.spawn);
} else {
player.loadFrom(playerData);
player.loadFrom(playerData.getChild("entity"));

main.items.Inventory.Sync.setGamemode(user, std.meta.stringToEnum(main.game.Gamemode, playerData.get([]const u8, "gamemode", "survival")) orelse .survival);
}
}

pub fn savePlayer(self: *ServerWorld, user: *User) !void {
const dest: []u8 = main.stackAllocator.alloc(u8, std.base64.url_safe.Encoder.calcSize(user.name.len));
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;
defer main.stackAllocator.free(path);

var playerZon: ZonElement = files.readToZon(main.stackAllocator, path) catch .null;
defer playerZon.deinit(main.stackAllocator);

if (playerZon != .object) {
playerZon.deinit(main.stackAllocator);
playerZon = ZonElement.initObject(main.stackAllocator);
}

playerZon.put("name", user.name);

playerZon.put("entity", user.player.save(main.stackAllocator));
playerZon.put("gamemode", @tagName(user.gamemode.load(.monotonic)));

{
main.items.Inventory.Sync.ServerSide.mutex.lock();
defer main.items.Inventory.Sync.ServerSide.mutex.unlock();
if (main.items.Inventory.Sync.ServerSide.getInventoryFromSource(.playerInventory)) |inv| {
playerZon.put("inventory", inv.save(main.stackAllocator));
}
}

const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.name}) catch unreachable;
defer main.stackAllocator.free(playerPath);

try files.makeDir(playerPath);

try files.writeZon(path, playerZon);
}

pub fn saveAllPlayers(self: *ServerWorld) !void {
const userList = server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList);

for (userList) |user| {
try savePlayer(self, user);
}
}

pub fn forceSave(self: *ServerWorld) !void {
// TODO: Save chunks and player data
try self.wio.saveWorldData();

try self.saveAllPlayers();

const itemDropZon = self.itemDropManager.store(main.stackAllocator);
defer itemDropZon.deinit(main.stackAllocator);
var buf: [32768]u8 = undefined;
Expand Down
1 change: 0 additions & 1 deletion src/utils.zig
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,6 @@ pub fn ConcurrentQueue(comptime T: type) type { // MARK: ConcurrentQueue
mutex: std.Thread.Mutex = .{},

pub fn init(allocator: NeverFailingAllocator, initialCapacity: usize) Self {
comptime std.debug.assert(@sizeOf(Self) <= 64);
return .{
.super = .init(allocator, initialCapacity),
};
Expand Down
16 changes: 16 additions & 0 deletions src/zon.zig
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,27 @@ pub const ZonElement = union(enum) { // MARK: Zon

pub fn put(self: *const ZonElement, key: []const u8, value: anytype) void {
const result = createElementFromRandomType(value, self.object.allocator);

if (self.object.contains(key)) {
self.getChild(key).deinit(NeverFailingAllocator{.allocator = self.object.allocator, .IAssertThatTheProvidedAllocatorCantFail = {}});

self.object.put(key, result) catch unreachable;
return;
}

self.object.put(self.object.allocator.dupe(u8, key) catch unreachable, result) catch unreachable;
}

pub fn putOwnedString(self: *const ZonElement, key: []const u8, value: []const u8) void {
const result = ZonElement{.stringOwned = self.object.allocator.dupe(u8, value) catch unreachable};

if (self.object.contains(key)) {
self.getChild(key).deinit(NeverFailingAllocator{.allocator = self.object.allocator, .IAssertThatTheProvidedAllocatorCantFail = {}});

self.object.put(key, result) catch unreachable;
return;
}

self.object.put(self.object.allocator.dupe(u8, key) catch unreachable, result) catch unreachable;
}

Expand Down

0 comments on commit 5986c76

Please sign in to comment.