diff --git a/src/Inventory.zig b/src/Inventory.zig index 62688bbc76..d285386d45 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -174,6 +174,24 @@ pub const Sync = struct { // MARK: Sync commands.pushBack(cmd); } } + + fn setSpawn(newSpawnPoint: Vec3d) void { + mutex.lock(); + defer mutex.unlock(); + main.game.Player.setSpawn(newSpawnPoint); + var tempData = main.List(Command).init(main.stackAllocator); + defer tempData.deinit(); + while(commands.popBack()) |_cmd| { + var cmd = _cmd; + cmd.undo(); + tempData.append(cmd); + } + while(tempData.popOrNull()) |_cmd| { + var cmd = _cmd; + cmd.do(main.globalAllocator, .client, null, main.game.Player.gamemode.raw) catch unreachable; + commands.pushBack(cmd); + } + } }; pub const ServerSide = struct { // MARK: ServerSide @@ -498,6 +516,13 @@ pub const Sync = struct { // MARK: Sync user.gamemode.store(gamemode, .monotonic); main.network.protocols.genericUpdate.sendGamemode(user.conn, gamemode); } + + fn setSpawn(user: *main.server.User, newSpawnPoint: Vec3d) void { + mutex.lock(); + defer mutex.unlock(); + user.playerSpawnPos = newSpawnPoint; + main.network.protocols.genericUpdate.sendSpawnPoint(user.conn, newSpawnPoint); + } }; pub fn addHealth(health: f32, cause: main.game.DamageType, side: Side, userId: u32) void { @@ -522,6 +547,14 @@ pub const Sync = struct { // MARK: Sync ServerSide.setGamemode(user.?, gamemode); } } + + pub fn setSpawn(user: ?*main.server.User, newSpawnPoint: Vec3d) void { + if(user == null) { + ClientSide.setSpawn(newSpawnPoint); + } else { + ServerSide.setSpawn(user.?, newSpawnPoint); + } + } }; pub const Command = struct { // MARK: Command diff --git a/src/game.zig b/src/game.zig index e7ac75b25e..73d40c3ae0 100644 --- a/src/game.zig +++ b/src/game.zig @@ -420,6 +420,7 @@ pub const Player = struct { // MARK: Player desiredPos: Vec3d = .{0, 0, 1.7 - standingBoundingBoxExtent[2]}, }; pub var super: main.server.Entity = .{}; + pub var playerSpawnPos: Vec3d = .{0, 0, 0}; pub var eye: EyeData = .{}; pub var crouching: bool = false; pub var id: u32 = 0; @@ -512,6 +513,10 @@ pub const Player = struct { // MARK: Player } } + pub fn setSpawn(newSpawnpoint: Vec3d) void { + playerSpawnPos = newSpawnpoint; + } + pub fn isCreative() bool { return gamemode.load(.monotonic) == .creative; } @@ -544,7 +549,7 @@ pub const Player = struct { // MARK: Player } pub fn kill() void { - Player.super.pos = world.?.spawn; + Player.super.pos = Player.playerSpawnPos; Player.super.vel = .{0, 0, 0}; Player.super.health = Player.super.maxHealth; diff --git a/src/network/protocols.zig b/src/network/protocols.zig index 547cb0d903..5f3c7b4844 100644 --- a/src/network/protocols.zig +++ b/src/network/protocols.zig @@ -511,6 +511,7 @@ pub const genericUpdate = struct { // MARK: genericUpdate time = 3, biome = 4, particles = 5, + setSpawn = 6, }; const WorldEditPosition = enum(u2) { @@ -579,12 +580,16 @@ pub const genericUpdate = struct { // MARK: genericUpdate const emitter: particles.Emitter = .init(particleId, collides); particles.ParticleSystem.addParticlesFromNetwork(emitter, pos, count); }, + .setSpawn => { + if(conn.isServerSide()) return error.InvalidPacket; + game.Player.setSpawn(try reader.readVec(Vec3d)); + }, } } fn serverReceive(conn: *Connection, reader: *utils.BinaryReader) !void { switch(try reader.readEnum(UpdateType)) { - .gamemode, .teleport, .time, .biome, .particles => return error.InvalidSide, + .gamemode, .teleport, .time, .biome, .particles, .setSpawn => return error.InvalidSide, .worldEditPos => { const typ = try reader.readEnum(WorldEditPosition); const pos: ?Vec3i = switch(typ) { @@ -607,6 +612,16 @@ pub const genericUpdate = struct { // MARK: genericUpdate conn.send(.fast, id, &.{@intFromEnum(UpdateType.gamemode), @intFromEnum(gamemode)}); } + pub fn sendSpawnPoint(conn: *Connection, pos: Vec3d) void { + var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 25); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .setSpawn); + writer.writeVec(Vec3d, pos); + + conn.send(.fast, id, writer.data.items); + } + pub fn sendTPCoordinates(conn: *Connection, pos: Vec3d) void { var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 25); defer writer.deinit(); diff --git a/src/server/command/_list.zig b/src/server/command/_list.zig index 0704320f02..278ecc0e45 100644 --- a/src/server/command/_list.zig +++ b/src/server/command/_list.zig @@ -3,6 +3,7 @@ pub const gamemode = @import("gamemode.zig"); pub const help = @import("help.zig"); pub const invite = @import("invite.zig"); pub const kill = @import("kill.zig"); +pub const setSpawn = @import("setSpawn.zig"); pub const particles = @import("particles.zig"); pub const tickspeed = @import("tickspeed.zig"); pub const time = @import("time.zig"); diff --git a/src/server/command/setSpawn.zig b/src/server/command/setSpawn.zig new file mode 100644 index 0000000000..1b403d80de --- /dev/null +++ b/src/server/command/setSpawn.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +const main = @import("main"); +const User = main.server.User; + +pub const description = "Sets the spawn point for the player"; +pub const usage = "/setSpawn"; + +pub fn execute(args: []const u8, source: *User) void { + var x: ?f64 = null; + var y: ?f64 = null; + var z: ?f64 = null; + var split = std.mem.splitScalar(u8, args, ' '); + while(split.next()) |arg| { + const num: f64 = std.fmt.parseFloat(f64, arg) catch { + source.sendMessage("#ff0000Expected number, found \"{s}\"", .{arg}); + return; + }; + if(x == null) { + x = num; + } else if(y == null) { + y = num; + } else if(z == null) { + z = num; + } else { + source.sendMessage("#ff0000Too many arguments for command /setspawn", .{}); + return; + } + } + if(x == null or y == null) { + source.sendMessage("#ff0000Too few arguments for command /setspawn", .{}); + return; + } + if(z == null) { + z = source.player.pos[2]; + } + x = std.math.clamp(x.?, -1e9, 1e9); // TODO: Remove after #310 is implemented + y = std.math.clamp(y.?, -1e9, 1e9); + z = std.math.clamp(z.?, -1e9, 1e9); + + main.items.Inventory.Sync.setSpawn(source, .{x.?, y.?, z.?}); +} diff --git a/src/server/server.zig b/src/server/server.zig index a92e8a5a2a..b8397f02d0 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -112,6 +112,7 @@ pub const User = struct { // MARK: User lastRenderDistance: u16 = 0, lastPos: Vec3i = @splat(0), gamemode: std.atomic.Value(main.game.Gamemode) = .init(.creative), + playerSpawnPos: Vec3d = .{0, 0, 0}, worldEditData: WorldEditData = undefined, lastSentBiomeId: u32 = 0xffffffff, diff --git a/src/server/world.zig b/src/server/world.zig index 18e3190404..b55b289cff 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -872,13 +872,17 @@ pub const ServerWorld = struct { // MARK: ServerWorld player.pos = @floatFromInt(self.spawn); main.items.Inventory.Sync.setGamemode(user, self.defaultGamemode); + main.items.Inventory.Sync.setSpawn(user, @as(Vec3d, @floatFromInt(self.spawn))); } else { player.loadFrom(playerData.getChild("entity")); main.items.Inventory.Sync.setGamemode(user, std.meta.stringToEnum(main.game.Gamemode, playerData.get([]const u8, "gamemode", @tagName(self.defaultGamemode))) orelse self.defaultGamemode); + main.items.Inventory.Sync.setSpawn(user, playerData.get(Vec3d, "playerSpawnPos", @as(Vec3d, @floatFromInt(self.spawn)))); } user.inventory = loadPlayerInventory(main.game.Player.inventorySize, playerData.get([]const u8, "playerInventory", ""), .{.playerInventory = user.id}, path); user.handInventory = loadPlayerInventory(1, playerData.get([]const u8, "hand", ""), .{.hand = user.id}, path); + + user.playerSpawnPos = playerData.get(Vec3d, "playerSpawnPos", @splat(0)); } fn loadPlayerInventory(size: usize, base64EncodedData: []const u8, source: main.items.Inventory.Source, playerDataFilePath: []const u8) main.items.Inventory.InventoryId { @@ -943,6 +947,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld } else @panic("The player hand inventory wasn't found. Cannot save player data."); } + playerZon.put("playerSpawnPos", user.playerSpawnPos); + const playerPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/players", .{self.path}) catch unreachable; defer main.stackAllocator.free(playerPath);