diff --git a/src/server/command.zig b/src/server/command.zig index 1e8f0e6485..382f0ebd84 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..980a986eea 100644 --- a/src/server/command/tp.zig +++ b/src/server/command/tp.zig @@ -12,6 +12,8 @@ pub const usage = \\/tp @ \\/tp @ \\/tp @ @ + \\/tp + \\/tp @ ; pub const Args = union(enum) { @@ -32,12 +34,21 @@ 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 { const target = switch (args) { inline .@"/tp ", .@"/tp ", + .@"/tp ", .@"/tp ", => |params| command.Target.fromPlayerIndex(params.sourcePlayerIndex, source) catch return, else => command.Target.fromPlayerIndex(null, source) catch return, @@ -99,10 +110,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.sendSyncOperation(.{.rotation = .{.target = target.user, .rotation = command.resolveRotation(pos.yaw, pos.pitch, source) catch return}}, null); + 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 7d08c4aeca..6058d19086 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -139,6 +139,19 @@ pub const server = struct { // MARK: server threadContext = .other; } + pub fn sendSyncOperation(op: Command.SyncOperation, source: ?*main.server.User) void { + const syncData = op.serialize(main.stackAllocator); + defer main.stackAllocator.free(syncData); + + const users = op.getUsers(main.stackAllocator); + defer main.stackAllocator.free(users); + + for (users) |user| { + if (user == source and op.ignoreSource()) continue; + main.network.protocols.inventory.sendSyncOperation(user.conn, syncData); + } + } + pub fn executeCommand(payload: Command.Payload, source: ?*main.server.User) void { var command = Command{ .payload = payload, @@ -153,16 +166,7 @@ pub const server = struct { // MARK: server main.network.protocols.inventory.sendConfirmation(source.?.conn, confirmationData); } for (command.syncOperations.items) |op| { - const syncData = op.serialize(main.stackAllocator); - defer main.stackAllocator.free(syncData); - - const users = op.getUsers(main.stackAllocator); - defer main.stackAllocator.free(users); - - for (users) |user| { - if (user == source and op.ignoreSource()) continue; - main.network.protocols.inventory.sendSyncOperation(user.conn, syncData); - } + sendSyncOperation(op, source); } if (source != null and command.payload == .open) { // Send initial items for (command.payload.open.inv._items, 0..) |stack, slot| { @@ -333,6 +337,7 @@ pub const Command = struct { // MARK: Command health = 3, kill = 4, energy = 5, + rotation = 6, }; const SyncOperation = union(SyncOperationType) { // MARK: SyncOperation @@ -362,6 +367,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)) { @@ -408,6 +417,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; + }, } } @@ -421,7 +433,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; @@ -431,7 +443,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, }; } @@ -482,6 +494,12 @@ pub const Command = struct { // MARK: Command .energy = try reader.readFloat(f32), }}; }, + .rotation => { + return .{.rotation = .{ + .target = null, + .rotation = try reader.readVec(Vec3f), + }}; + }, } } @@ -513,6 +531,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(); }