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
This commit is contained in:
OneAvargeCoder193 2025-01-25 16:54:23 -05:00 committed by GitHub
parent 967e24a1c4
commit 818f736311
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 203 additions and 10 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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");

View File

@ -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);
}