diff --git a/src/network/protocols.zig b/src/network/protocols.zig index 50a7350942..bdb4877d4c 100644 --- a/src/network/protocols.zig +++ b/src/network/protocols.zig @@ -606,6 +606,7 @@ pub const genericUpdate = struct { // MARK: genericUpdate biome = 4, particles = 5, clear = 6, + setRotation = 7, }; const WorldEditPosition = enum(u2) { @@ -626,6 +627,13 @@ pub const genericUpdate = struct { // MARK: genericUpdate .teleport => { game.Player.setPosBlocking(try reader.readVec(Vec3d)); }, + .setRotation => { + var rot: Vec3f = try reader.readVec(Vec3f); + const bound = std.math.pi/2.0 - 0.001; + rot[0] = std.math.clamp(rot[0], -bound, bound); + game.camera.rotation[0] = rot[0]; + game.camera.rotation[2] = rot[2]; + }, .worldEditPos => { const typ = try reader.readEnum(WorldEditPosition); const pos: ?Vec3i = switch (typ) { @@ -704,7 +712,7 @@ pub const genericUpdate = struct { // MARK: genericUpdate fn serverReceive(conn: *Connection, reader: *utils.BinaryReader) !void { switch (try reader.readEnum(UpdateType)) { - .gamemode, .teleport, .time, .biome, .particles, .clear => return error.InvalidSide, + .gamemode, .teleport, .setRotation, .time, .biome, .particles, .clear => return error.InvalidSide, .worldEditPos => { const typ = try reader.readEnum(WorldEditPosition); const pos: ?Vec3i = switch (typ) { @@ -737,6 +745,16 @@ pub const genericUpdate = struct { // MARK: genericUpdate conn.send(.secure, id, writer.data.items); } + pub fn sendTPRotation(conn: *Connection, rot: Vec3f) void { + var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 20); + defer writer.deinit(); + + writer.writeEnum(UpdateType, .setRotation); + writer.writeVec(Vec3f, rot); + + conn.send(.secure, id, writer.data.items); + } + pub fn sendWorldEditPos(conn: *Connection, posType: WorldEditPosition, maybePos: ?Vec3i) void { var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 25); defer writer.deinit(); diff --git a/src/server/command.zig b/src/server/command.zig index 1acdc5472e..5eb21b313a 100644 --- a/src/server/command.zig +++ b/src/server/command.zig @@ -108,6 +108,27 @@ pub const Coordinate = union(enum) { } }; +pub const Rotation = union(enum) { + relative: f32, // Relative rotations are indicated by leading `~`. + absolute: f32, + + pub fn parse(_: NeverFailingAllocator, name: []const u8, arg: []const u8, errorMessage: *ListManaged(u8)) error{ParseError}!Rotation { + const isRelative = arg[0] == '~'; + const numberSlice = if (isRelative) arg[1..] else arg; + if (isRelative and numberSlice.len == 0) return .{.relative = 0}; + if (isRelative) { + return .{.relative = std.fmt.parseFloat(f32, numberSlice) catch { + errorMessage.print("Expected number for <{s}>, found \"{s}\"", .{name, numberSlice}); + return error.ParseError; + }}; + } + return .{.absolute = std.fmt.parseFloat(f32, numberSlice) catch { + errorMessage.print("Expected number or \"~\" for <{s}>, found \"{s}\"", .{name, arg}); + return error.ParseError; + }}; + } +}; + pub fn resolveCoordinates(x: Coordinate, y: Coordinate, z: Coordinate, source: Source) error{InvalidArg}!main.vec.Vec3d { if (source != .user and (x == .relative or y == .relative or z == .relative)) { source.sendMessage("Command was run without a user; unable to interpret relative coordinates.", .{}); @@ -121,6 +142,19 @@ pub fn resolveCoordinates(x: Coordinate, y: Coordinate, z: Coordinate, source: S }; } +pub fn resolveRotation(yaw: Rotation, pitch: Rotation, source: Source) error{InvalidArg}!main.vec.Vec3f { + if (source != .user and (yaw == .relative or pitch == .relative)) { + source.sendMessage("Command was run without a user; unable to interpret relative rotation.", .{}); + return error.InvalidArg; + } + const bound = std.math.pi/2.0 - 0.001; + return .{ + std.math.clamp(if (yaw == .relative) source.user.player().rot[0] + yaw.relative*std.math.pi/180 else yaw.absolute*std.math.pi/180, -bound, bound), + 0, + if (pitch == .relative) source.user.player().rot[2] + @mod(pitch.relative, 360)*std.math.pi/180 else @mod(pitch.absolute, 360)*std.math.pi/180, + }; +} + pub const Target = struct { user: *User, diff --git a/src/server/command/tp.zig b/src/server/command/tp.zig index 45d0e94e4c..1a029aa51d 100644 --- a/src/server/command/tp.zig +++ b/src/server/command/tp.zig @@ -32,6 +32,14 @@ pub const Args = union(enum) { sourcePlayerIndex: command.PlayerIndex, destinationPlayerIndex: command.PlayerIndex, }, + @"/tp ": struct { + sourcePlayerIndex: ?command.PlayerIndex, + x: command.Coordinate, + y: command.Coordinate, + z: command.Coordinate, + yaw: command.Rotation, + pitch: command.Rotation, + }, }; pub fn execute(args: Args, source: Source) void { @@ -99,10 +107,15 @@ pub fn execute(args: Args, source: Source) void { .@"/tp " => |pos| { break :blk command.resolveCoordinates(pos.x, pos.y, pos.z, source) catch return; }, + .@"/tp " => |pos| { + main.sync.server.executeCommand(.{.setRotation = .{.target = target.user.id, .rotation = command.resolveRotation(pos.yaw, pos.pitch, source) catch return}}, source.user); + break :blk command.resolveCoordinates(pos.x, pos.y, pos.z, source) catch return; + }, inline .@"/tp ", .@"/tp " => |index| { const dest = command.Target.fromPlayerIndex(index.destinationPlayerIndex, source) catch return; break :blk dest.user.player().pos; }, }; - main.network.protocols.genericUpdate.sendTPCoordinates(target.user.conn, pos); + + if (!std.meta.eql(target.user.player().pos, pos)) main.network.protocols.genericUpdate.sendTPCoordinates(target.user.conn, pos); } diff --git a/src/sync.zig b/src/sync.zig index 6ec9a7e7c2..805c84a261 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -241,6 +241,7 @@ pub const Command = struct { // MARK: Command updateBlock = 9, addHealth = 10, chatCommand = 12, + setRotation = 18, }; pub const Payload = union(PayloadType) { open: Open, @@ -261,6 +262,7 @@ pub const Command = struct { // MARK: Command updateBlock: UpdateBlock, addHealth: AddHealth, chatCommand: ChatCommand, + setRotation: SetRotation, }; const BaseOperationType = enum(u8) { @@ -273,6 +275,7 @@ pub const Command = struct { // MARK: Command useDurability = 4, addHealth = 5, addEnergy = 6, + setRotation = 9, }; pub const BaseOperation = union(BaseOperationType) { @@ -322,6 +325,11 @@ pub const Command = struct { // MARK: Command energy: f32, previous: f32, }, + setRotation: struct { + target: ?*main.server.User, + rotation: Vec3f, + previous: Vec3f, + }, }; const SyncOperationType = enum(u8) { @@ -331,6 +339,7 @@ pub const Command = struct { // MARK: Command health = 3, kill = 4, energy = 5, + rotation = 6, }; const SyncOperation = union(SyncOperationType) { // MARK: SyncOperation @@ -360,6 +369,10 @@ pub const Command = struct { // MARK: Command target: ?*main.server.User, energy: f32, }, + rotation: struct { + target: ?*main.server.User, + rotation: Vec3f, + }, pub fn executeFromData(reader: *BinaryReader) !void { switch (try deserialize(reader)) { @@ -406,6 +419,9 @@ pub const Command = struct { // MARK: Command .energy => |energy| { main.game.Player.super.energy = std.math.clamp(main.game.Player.super.energy + energy.energy, 0, main.game.Player.super.maxEnergy); }, + .rotation => |rotation| { + main.game.camera.rotation = rotation.rotation; + }, } } @@ -419,7 +435,7 @@ pub const Command = struct { // MARK: Command } return result; }, - inline .health, .kill, .energy => |data| { + inline .health, .kill, .energy, .rotation => |data| { const out = allocator.alloc(*main.server.User, 1); out[0] = data.target.?; return out; @@ -429,7 +445,7 @@ pub const Command = struct { // MARK: Command pub fn ignoreSource(self: SyncOperation) bool { return switch (self) { - .create, .delete, .useDurability, .health, .energy => true, + .create, .delete, .useDurability, .health, .energy, .rotation => true, .kill => false, }; } @@ -480,6 +496,12 @@ pub const Command = struct { // MARK: Command .energy = try reader.readFloat(f32), }}; }, + .rotation => { + return .{.rotation = .{ + .target = null, + .rotation = try reader.readVec(Vec3f), + }}; + }, } } @@ -511,6 +533,9 @@ pub const Command = struct { // MARK: Command .energy => |energy| { writer.writeFloat(f32, energy.energy); }, + .rotation => |rotation| { + writer.writeVec(Vec3f, rotation.rotation); + }, } return writer.data.toOwnedSlice(); } @@ -618,6 +643,9 @@ pub const Command = struct { // MARK: Command .addEnergy => |info| { main.game.Player.super.energy = info.previous; }, + .setRotation => |info| { + main.game.camera.rotation = info.previous; + }, } } } @@ -625,7 +653,7 @@ pub const Command = struct { // MARK: Command fn finalize(self: Command, allocator: NeverFailingAllocator, side: Side, reader: *BinaryReader) !void { for (self.baseOperations.items) |step| { switch (step) { - .move, .swap, .create, .moveToBag, .takeFromBag, .addHealth, .addEnergy => {}, + .move, .swap, .create, .moveToBag, .takeFromBag, .addHealth, .addEnergy, .setRotation => {}, .delete => |info| { info.item.deinit(); }, @@ -814,6 +842,22 @@ pub const Command = struct { // MARK: Command main.game.Player.super.energy = std.math.clamp(main.game.Player.super.energy + info.energy, 0, main.game.Player.super.maxEnergy); } }, + .setRotation => |*info| { + if (side == .server) { + info.previous = info.target.?.player().rot; + std.log.debug("SetRotation executed on server; target=TRUNCATED, rotation={}", .{info.rotation}); + + info.target.?.player().rot = info.rotation; + self.syncOperations.append(allocator, .{.rotation = .{ + .target = info.target.?, + .rotation = info.rotation, + }}); + } else { + std.log.debug("SetRotation executed on client; target=TRUNCATED, rotation={}", .{info.rotation}); + info.previous = main.game.camera.rotation; + main.game.camera.rotation = info.rotation; + } + }, } self.baseOperations.append(allocator, op); } @@ -1730,6 +1774,49 @@ pub const Command = struct { // MARK: Command } }; + const SetRotation = struct { // MARK: SetRotation + target: main.entity.Entity, + rotation: Vec3f, + + fn run(self: SetRotation, ctx: Context) error{serverFailure}!void { + std.log.debug("SetRotation ran; target={}, rotation={}", .{self.target, self.rotation}); + var target: ?*main.server.User = null; + + if (ctx.side == .server) { + const userList = main.server.getUserList(main.stackAllocator); + defer main.stackAllocator.free(userList); + for (userList) |user| { + if (user.id == self.target) { + target = user; + break; + } + } + + if (target == null) return error.serverFailure; + } + + ctx.execute(.{.setRotation = .{ + .target = target, + .rotation = self.rotation, + .previous = if (ctx.side == .server) target.?.player().rot else main.game.camera.rotation, + }}); + } + + fn serialize(self: SetRotation, writer: *BinaryWriter) void { + writer.writeEnum(main.entity.Entity, self.target); + writer.writeVec(Vec3f, self.rotation); + } + + fn deserialize(reader: *BinaryReader, _: Side, user: ?*main.server.User) !SetRotation { + const result: SetRotation = .{ + .target = try reader.readEnum(main.entity.Entity), + .rotation = try reader.readVec(Vec3f), + }; + if (user.?.id != result.target) return error.Invalid; + return result; + } + }; + const ChatCommand = struct { // MARK: ChatCommand message: []const u8,