From 818f736311b85e06ccdefc91c06d83bfabc3a50d Mon Sep 17 00:00:00 2001 From: OneAvargeCoder193 <85588535+OneAvargeCoder193@users.noreply.github.com> Date: Sat, 25 Jan 2025 16:54:23 -0500 Subject: [PATCH] Added fall damage and /kill (#959) * add player health and fall damage and /kill * less latency for taking damage * clamp health * changes * final changes maybe * return serverFailure if target not found * fix error * remove /damage --- src/Inventory.zig | 150 +++++++++++++++++++++++++++++++++- src/game.zig | 34 +++++++- src/gui/windows/healthbar.zig | 6 +- src/server/Entity.zig | 7 +- src/server/command/_list.zig | 1 + src/server/command/kill.zig | 15 ++++ 6 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 src/server/command/kill.zig diff --git a/src/Inventory.zig b/src/Inventory.zig index a4992b8d8..21259bb41 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -267,7 +267,7 @@ pub const Sync = struct { // MARK: Sync defer main.stackAllocator.free(users); for (users) |user| { - if (user == source) continue; + if (user == source and op.ignoreSource()) continue; main.network.Protocols.inventory.sendSyncOperation(user.conn, syncData); } } @@ -410,6 +410,14 @@ pub const Sync = struct { // MARK: Sync } }; + pub fn addHealth(health: f32, cause: main.game.DamageType, side: Side, id: u32) void { + if (side == .client) { + Sync.ClientSide.executeCommand(.{.addHealth = .{.target = id, .health = health, .cause = cause}}); + } else { + Sync.ServerSide.executeCommand(.{.addHealth = .{.target = id, .health = health, .cause = cause}}, null); + } + } + pub fn getInventory(id: u32, side: Side, user: ?*main.server.User) ?Inventory { return switch(side) { .client => ClientSide.getInventory(id), @@ -438,6 +446,7 @@ pub const Command = struct { // MARK: Command depositOrDrop = 7, clear = 8, updateBlock = 9, + addHealth = 10, }; pub const Payload = union(PayloadType) { open: Open, @@ -450,6 +459,7 @@ pub const Command = struct { // MARK: Command depositOrDrop: DepositOrDrop, clear: Clear, updateBlock: UpdateBlock, + addHealth: AddHealth, }; const BaseOperationType = enum(u8) { @@ -458,6 +468,7 @@ pub const Command = struct { // MARK: Command delete = 2, create = 3, useDurability = 4, + addHealth = 5, }; const InventoryAndSlot = struct { @@ -508,12 +519,20 @@ pub const Command = struct { // MARK: Command durability: u31, previousDurability: u32 = undefined, }, + addHealth: struct { + target: ?*main.server.User, + health: f32, + cause: main.game.DamageType, + previous: f32 + } }; const SyncOperationType = enum(u8) { create = 0, delete = 1, useDurability = 2, + health = 3, + kill = 4, }; const SyncOperation = union(SyncOperationType) { // MARK: SyncOperation @@ -531,6 +550,13 @@ pub const Command = struct { // MARK: Command inv: InventoryAndSlot, durability: u32 }, + health: struct { + target: ?*main.server.User, + health: f32 + }, + kill: struct { + target: ?*main.server.User + }, pub fn executeFromData(data: []const u8) !void { std.debug.assert(data.len >= 1); @@ -569,6 +595,12 @@ pub const Command = struct { // MARK: Command } durability.inv.inv.update(); + }, + .health => |health| { + main.game.Player.super.health = std.math.clamp(main.game.Player.super.health + health.health, 0, main.game.Player.super.maxHealth); + }, + .kill => { + main.game.Player.kill(); } } } @@ -577,10 +609,22 @@ pub const Command = struct { // MARK: Command switch (self) { inline .create, .delete, .useDurability => |data| { return allocator.dupe(*main.server.User, Sync.ServerSide.inventories.items[data.inv.inv.id].users.items); + }, + inline .health, .kill => |data| { + const out = allocator.alloc(*main.server.User, 1); + out[0] = data.target.?; + return out; } } } + pub fn ignoreSource(self: SyncOperation) bool { + return switch (self) { + .create, .delete, .useDurability, .health => true, + .kill => false + }; + } + fn deserialize(fullData: []const u8) !SyncOperation { if (fullData.len == 0) { return error.Invalid; @@ -630,6 +674,25 @@ pub const Command = struct { // MARK: Command }}; return out; + }, + .health => { + if (data.len != 4) { + return error.Invalid; + } + + return .{.health = .{ + .target = null, + .health = @bitCast(std.mem.readInt(u32, data[0..4], .big)) + }}; + }, + .kill => { + if (data.len != 0) { + return error.Invalid; + } + + return .{.kill = .{ + .target = null, + }}; } } } @@ -657,7 +720,11 @@ pub const Command = struct { // MARK: Command .useDurability => |durability| { durability.inv.write(data.addMany(8)[0..8]); std.mem.writeInt(u32, data.addMany(4)[0..4], durability.durability, .big); - } + }, + .health => |health| { + std.mem.writeInt(u32, data.addMany(4)[0..4], @bitCast(health.health), .big); + }, + .kill => {}, } return data.toOwnedSlice(); } @@ -729,6 +796,9 @@ pub const Command = struct { // MARK: Command info.source.ref().item = info.item; info.item.tool.durability = info.previousDurability; info.source.inv.update(); + }, + .addHealth => |info| { + main.game.Player.super.health = info.previous; } } } @@ -737,7 +807,7 @@ pub const Command = struct { // MARK: Command fn finalize(self: Command, allocator: NeverFailingAllocator, side: Side, data: []const u8) void { for(self.baseOperations.items) |step| { switch(step) { - .move, .swap, .create => {}, + .move, .swap, .create, .addHealth => {}, .delete => |info| { info.item.?.deinit(); }, @@ -854,6 +924,30 @@ pub const Command = struct { // MARK: Command self.executeDurabilityUseOperation(allocator, side, info.source, info.durability); info.source.inv.update(); }, + .addHealth => |*info| { + if (side == .server) { + info.previous = info.target.?.player.health; + + info.target.?.player.health = std.math.clamp(info.target.?.player.health + info.health, 0, info.target.?.player.maxHealth); + + if (info.target.?.player.health <= 0) { + info.target.?.player.health = info.target.?.player.maxHealth; + info.cause.sendMessage(info.target.?.name); + + self.syncOperations.append(allocator, .{.kill = .{ + .target = info.target.? + }}); + } else { + self.syncOperations.append(allocator, .{.health = .{ + .target = info.target.?, + .health = info.health + }}); + } + } else { + info.previous = main.game.Player.super.health; + main.game.Player.super.health = std.math.clamp(main.game.Player.super.health + info.health, 0, main.game.Player.super.maxHealth); + } + } } self.baseOperations.append(allocator, op); } @@ -1497,6 +1591,56 @@ pub const Command = struct { // MARK: Command }; } }; + + const AddHealth = struct { // MARK: AddHealth + target: u32, + health: f32, + cause: main.game.DamageType, + + pub fn run(self: AddHealth, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User, _: Gamemode) error{serverFailure}!void { + var target: ?*main.server.User = null; + + if (side == .server) { + const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator); + defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList); + for (userList) |user| { + if (user.id == self.target) { + target = user; + break; + } + } + + if (target == null) return error.serverFailure; + + if (target.?.gamemode.raw == .creative) return; + } else { + if (main.game.Player.gamemode.raw == .creative) return; + } + + cmd.executeBaseOperation(allocator, .{.addHealth = .{ + .target = target, + .health = self.health, + .cause = self.cause, + .previous = if (side == .server) target.?.player.health else main.game.Player.super.health + }}, side); + } + + fn serialize(self: AddHealth, data: *main.List(u8)) void { + std.mem.writeInt(u32, data.addMany(4)[0..4], self.target, .big); + std.mem.writeInt(u32, data.addMany(4)[0..4], @bitCast(self.health), .big); + data.append(@intFromEnum(self.cause)); + } + + fn deserialize(data: []const u8, _: Side, _: ?*main.server.User) !AddHealth { + if(data.len != 9) return error.Invalid; + + return .{ + .target = std.mem.readInt(u32, data[0..4], .big), + .health = @bitCast(std.mem.readInt(u32, data[4..8], .big)), + .cause = @enumFromInt(data[8]), + }; + } + }; }; const SourceType = enum(u8) { diff --git a/src/game.zig b/src/game.zig index 6561d4bb0..949269f3c 100644 --- a/src/game.zig +++ b/src/game.zig @@ -326,6 +326,20 @@ pub const collision = struct { pub const Gamemode = enum(u8) { survival = 0, creative = 1 }; +pub const DamageType = enum(u8) { + heal = 0, // For when you are adding health + kill = 1, + fall = 2, + + pub fn sendMessage(self: DamageType, name: []const u8) void { + switch (self) { + .heal => main.server.sendMessage("{s}§#ffffff was healed", .{name}), + .kill => main.server.sendMessage("{s}§#ffffff was killed", .{name}), + .fall => main.server.sendMessage("{s}§#ffffff died of fall damage", .{name}), + } + } +}; + pub const Player = struct { // MARK: Player pub var super: main.server.Entity = .{}; pub var eyePos: Vec3d = .{0, 0, 0}; @@ -341,9 +355,6 @@ pub const Player = struct { // MARK: Player pub var inventory: Inventory = undefined; pub var selectedSlot: u32 = 0; - pub var maxHealth: f32 = 8; - pub var health: f32 = 4.5; - pub var onGround: bool = false; pub var jumpCooldown: f64 = 0; const jumpCooldownConstant = 0.3; @@ -442,6 +453,17 @@ pub const Player = struct { // MARK: Player inventory.placeBlock(selectedSlot); } + pub fn kill() void { + Player.super.pos = world.?.spawn; + Player.super.vel = .{0, 0, 0}; + + Player.super.health = Player.super.maxHealth; + + Player.eyeVel = .{0, 0, 0}; + Player.eyeCoyote = 0; + Player.eyeStep = .{false, false, false}; + } + pub fn breakBlock(deltaTime: f64) void { if(!main.Window.grabbed) return; inventory.breakBlock(selectedSlot, deltaTime); @@ -955,6 +977,12 @@ pub fn update(deltaTime: f64) void { // MARK: update() } else { Player.super.pos[2] = box.min[2] - hitBox.max[2]; } + + const damage: f32 = @floatCast(@round(@max((Player.super.vel[2] * Player.super.vel[2]) / (2 * gravity) - 3, 0)) / 2); + if (damage > 0.01) { + Inventory.Sync.addHealth(-damage, .fall, .client, Player.id); + } + Player.super.vel[2] = 0; // Always unstuck upwards for now diff --git a/src/gui/windows/healthbar.zig b/src/gui/windows/healthbar.zig index 8d6bd9812..22b80a658 100644 --- a/src/gui/windows/healthbar.zig +++ b/src/gui/windows/healthbar.zig @@ -47,14 +47,14 @@ pub fn render() void { var y: f32 = 0; var x: f32 = 0; var health: f32 = 0; - while(health < main.game.Player.maxHealth) : (health += 1) { + while(health < main.game.Player.super.maxHealth) : (health += 1) { if(x >= window.contentSize[0]) { x = 0; y += 20; } - if(health + 1 <= main.game.Player.health) { + if(health + 1 <= main.game.Player.super.health) { heartTexture.bindTo(0); - } else if(health + 0.5 <= main.game.Player.health) { + } else if(health + 0.5 <= main.game.Player.super.health) { halfHeartTexture.bindTo(0); } else { deadHeartTexture.bindTo(0); diff --git a/src/server/Entity.zig b/src/server/Entity.zig index 7a5fba54a..9876ae236 100644 --- a/src/server/Entity.zig +++ b/src/server/Entity.zig @@ -10,13 +10,17 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator; pos: Vec3d = .{0, 0, 0}, vel: Vec3d = .{0, 0, 0}, rot: Vec3f = .{0, 0, 0}, -// TODO: Health and hunger + +health: f32 = 8, +maxHealth: f32 = 8, +// TODO: Hunger // TODO: Name pub fn loadFrom(self: *@This(), zon: ZonElement) void { self.pos = zon.get(Vec3d, "position", .{0, 0, 0}); self.vel = zon.get(Vec3d, "velocity", .{0, 0, 0}); self.rot = zon.get(Vec3f, "rotation", .{0, 0, 0}); + self.health = zon.get(f32, "health", self.maxHealth); } pub fn save(self: *@This(), allocator: NeverFailingAllocator) ZonElement { @@ -24,5 +28,6 @@ pub fn save(self: *@This(), allocator: NeverFailingAllocator) ZonElement { zon.put("position", self.pos); zon.put("velocity", self.vel); zon.put("rotation", self.rot); + zon.put("health", self.health); return zon; } diff --git a/src/server/command/_list.zig b/src/server/command/_list.zig index 846f45df6..1a5770bcb 100644 --- a/src/server/command/_list.zig +++ b/src/server/command/_list.zig @@ -4,5 +4,6 @@ pub const clear = @import("clear.zig"); 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 time = @import("time.zig"); pub const tp = @import("tp.zig"); \ No newline at end of file diff --git a/src/server/command/kill.zig b/src/server/command/kill.zig new file mode 100644 index 000000000..48dacfa89 --- /dev/null +++ b/src/server/command/kill.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +const main = @import("root"); +const User = main.server.User; + +pub const description = "Kills the player"; +pub const usage = "/kill"; + +pub fn execute(args: []const u8, source: *User) void { + if(args.len != 0) { + source.sendMessage("#ff0000Too many arguments for command /kill. Expected no arguments.", .{}); + return; + } + main.items.Inventory.Sync.addHealth(-std.math.floatMax(f32), .kill, .server, source.id); +} \ No newline at end of file