diff --git a/src/callbacks/block/touch/hurt.zig b/src/callbacks/block/touch/hurt.zig index afcaf5f9f8..d646df17aa 100644 --- a/src/callbacks/block/touch/hurt.zig +++ b/src/callbacks/block/touch/hurt.zig @@ -1,6 +1,7 @@ const std = @import("std"); const main = @import("main"); +const @"cubyz:health" = main.entity.components.@"cubyz:health"; dps: f32, damageType: main.game.DamageType, @@ -26,6 +27,6 @@ pub fn init(zon: main.ZonElement, _: main.callbacks.Creator) ?*@This() { pub fn run(self: *@This(), params: main.callbacks.BlockTouchCallback.Params) main.callbacks.Result { std.debug.assert(params.entity == &main.game.Player.super); // TODO: Implement on the server side const damage = self.dps*@as(f32, @floatCast(params.deltaTime)); - main.sync.addHealth(-damage, self.damageType, .client, main.game.Player.id); + @"cubyz:health".client.addHealth(main.game.Player.id, -damage); return .handled; } diff --git a/src/entity.zig b/src/entity.zig index 8fb965a8f1..ae0e9e57a4 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,5 +1,7 @@ const std = @import("std"); const main = @import("main.zig"); +const utils = main.utils; +const BinaryReader = utils.BinaryReader; const vec = main.vec; const Mat4f = vec.Mat4f; const Vec3d = vec.Vec3d; @@ -29,10 +31,12 @@ pub const Entity = enum(u32) { }; pub const EntityComponentId = u32; const EntityComponentVTable = struct { - serverLoad: *const fn (entity: Entity, reader: *main.utils.BinaryReader, version: u32) EntityComponentLoadError!void, - clientLoad: *const fn (entity: Entity, reader: *main.utils.BinaryReader, version: u32) EntityComponentLoadError!void, + serverLoad: *const fn (entity: Entity, reader: *BinaryReader, version: u32) EntityComponentLoadError!void, + clientLoad: *const fn (entity: Entity, reader: *BinaryReader, version: u32) EntityComponentLoadError!void, serverUnload: *const fn (entity: Entity) void, clientUnload: *const fn (entity: Entity) void, + modifyServerComponent: *const fn (entity: Entity, reader: *BinaryReader) void, + modifyClientComponent: *const fn (entity: Entity, reader: *BinaryReader) void, }; var componentList: []?EntityComponentVTable = undefined; @@ -51,6 +55,8 @@ pub fn initComponents() void { .clientLoad = @field(components, decl.name).client.load, .serverUnload = @field(components, decl.name).server.unload, .clientUnload = @field(components, decl.name).client.unload, + .modifyServerComponent = @field(components, decl.name).server.modifyComponent, + .modifyClientComponent = @field(components, decl.name).client.modifyComponent, }; } else { std.log.err("entity components: Duplicate list id {}.", .{componentId}); @@ -66,7 +72,7 @@ pub fn loadComponent(comptime side: main.sync.Side, componentId: EntityComponent std.log.err("unknown Component Id {} ", .{componentId}); return error.UnknownComponentId; } - var componentReader = main.utils.BinaryReader.init(componentData); + var componentReader = BinaryReader.init(componentData); if (componentList[componentId]) |vtable| { switch (side) { .server => vtable.serverLoad(entity, &componentReader, componentVersion) catch |err| { @@ -97,6 +103,23 @@ pub fn unloadComponent(comptime side: main.sync.Side, componentId: EntityCompone } } +pub fn modifyComponent(comptime side: main.sync.Side, componentId: EntityComponentId, entity: Entity, componentData: []const u8) EntityComponentLoadError!void { + if (componentId >= componentList.len) { + std.log.err("unknown Component Id {} ", .{componentId}); + return error.UnknownComponentId; + } + var componentReader = BinaryReader.init(componentData); + if (componentList[componentId]) |vtable| { + switch (side) { + .server => vtable.modifyServerComponent(entity, &componentReader), + .client => vtable.modifyClientComponent(entity, &componentReader), + } + } else { + std.log.err("unknown Component Id {} ", .{componentId}); + return error.UnknownComponentId; + } +} + pub const client = struct { pub fn init() void { inline for (@typeInfo(components).@"struct".decls) |decl| { @@ -185,7 +208,7 @@ pub fn loadComponentsFromBase64(base64Data: []const u8, entity: Entity, comptime const data = main.utils.fromBase64(main.stackAllocator, base64Data) catch return EntityComponentLoadError.DecodingBase64; defer main.stackAllocator.free(data); - var reader = main.utils.BinaryReader.init(data); + var reader = BinaryReader.init(data); var lastError: EntityComponentLoadError!void = {}; while (reader.remaining.len != 0) { const componentId: EntityComponentId = reader.readVarInt(EntityComponentId) catch return EntityComponentLoadError.UnreadableId; diff --git a/src/entityComponent/_list.zig b/src/entityComponent/_list.zig index d162bf66e0..98ed9e8536 100644 --- a/src/entityComponent/_list.zig +++ b/src/entityComponent/_list.zig @@ -3,3 +3,4 @@ pub const @"cubyz:bag" = @import("bag.zig"); pub const @"cubyz:model" = @import("model.zig"); pub const @"cubyz:permissions" = @import("permissions.zig"); pub const @"cubyz:player" = @import("player.zig"); +pub const @"cubyz:health" = @import("health.zig"); diff --git a/src/entityComponent/_template.zig b/src/entityComponent/_template.zig index 1b2d134c73..9ecab227c1 100644 --- a/src/entityComponent/_template.zig +++ b/src/entityComponent/_template.zig @@ -44,6 +44,11 @@ pub const client = struct { pub fn init() void {} pub fn deinit() void {} pub fn clear() void {} + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; // ############################# Server only stuff ################################ pub const server = struct { @@ -70,4 +75,8 @@ pub const server = struct { pub fn unload(entity: Entity) void { _ = entity; } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; diff --git a/src/entityComponent/bag.zig b/src/entityComponent/bag.zig index 8942eebcfc..208dbbf8c3 100644 --- a/src/entityComponent/bag.zig +++ b/src/entityComponent/bag.zig @@ -62,6 +62,10 @@ pub const client = struct { const bag = components.fetchRemove(entity) catch return; bag.bag.deinit(); } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; // ############################# Server only stuff ################################ @@ -103,4 +107,8 @@ pub const server = struct { const bag = components.fetchRemove(entity) catch return; bag.bag.deinit(); } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; diff --git a/src/entityComponent/health.zig b/src/entityComponent/health.zig new file mode 100644 index 0000000000..6a81938b86 --- /dev/null +++ b/src/entityComponent/health.zig @@ -0,0 +1,156 @@ +const std = @import("std"); + +const main = @import("main"); +const chunk = main.chunk; +const Entity = main.entity.Entity; +const ServerChunk = chunk.ServerChunk; +const game = main.game; +const graphics = main.graphics; +const ZonElement = main.ZonElement; +const renderer = main.renderer; +const settings = main.settings; +const utils = main.utils; +const BinaryReader = utils.BinaryReader; +const BinaryWriter = utils.BinaryWriter; +const vec = main.vec; +const Mat4f = vec.Mat4f; +const Vec3d = vec.Vec3d; +const Vec3f = vec.Vec3f; +const Vec4f = vec.Vec4f; +const Vec3i = vec.Vec3i; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; +const blocks = main.blocks; +const World = game.World; +const ServerWorld = main.server.ServerWorld; +const items = main.items; +const ItemStack = items.ItemStack; +const random = main.random; + +const c = @import("c"); +const Self = @This(); + +pub var entityComponentID: main.entity.EntityComponentId = undefined; +pub const entityComponentVersion = 0; + +var playerBagSizeLimit = 120; + +// ############################# Client only stuff ################################ +pub const client = struct { + const Component = struct { + health: f32, + maxHealth: f32, + }; + pub var components: main.utils.SparseSet(Component, Entity) = .{}; + + pub fn init() void {} + pub fn deinit() void { + components.deinit(main.globalAllocator); + } + pub fn clear() void { + components.clear(); + } + + pub fn getHealth(entity: Entity) ?f32 { + return (components.get(entity) orelse return null).health; + } + pub fn getMaxHealth(entity: Entity) ?f32 { + return (components.get(entity) orelse return null).maxHealth; + } + pub fn addHealth(entity: Entity, healthChange: f32) void { + var binaryWriter = main.utils.BinaryWriter.init(main.stackAllocator); + defer binaryWriter.deinit(); + binaryWriter.writeFloat(f32, healthChange); + main.network.protocols.EntityComponentUpdate.modify(main.game.world.?.conn, entity, Self.entityComponentID, binaryWriter.data.items); + } + + pub fn load(entity: Entity, reader: *utils.BinaryReader, version: u32) main.entity.EntityComponentLoadError!void { + if (version != entityComponentVersion) return error.InvalidComponentVersion; + const component = components.add(main.globalAllocator, entity); + const health = &component.health; + const maxHealth = &component.maxHealth; + health.* = reader.readFloat(f32) catch return error.UnreadableComponentData; + maxHealth.* = reader.readFloat(f32) catch return error.UnreadableComponentData; + } + pub fn unload(entity: Entity) void { + components.remove(entity) catch {}; + } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } +}; + +// ############################# Server only stuff ################################ +pub const server = struct { + pub const Component = struct { + health: f32, + maxHealth: f32, + pub fn save(self: Component, writer: *utils.BinaryWriter, audience: main.entity.AudienceInfo) main.entity.ComponentSaveBehaviour { + if (audience != .disk and audience != .playerHimself) return .discard; + writer.writeFloat(f32, self.health); + writer.writeFloat(f32, self.maxHealth); + return .save; + } + }; + pub var components: main.utils.SparseSet(Component, Entity) = .{}; + + pub fn init() void { + components = .{}; + } + pub fn deinit() void { + components.deinit(main.globalAllocator); + } + + pub fn get(entity: Entity) ?Component { + return (components.get(entity) orelse return null).*; + } + pub fn getHealth(entity: Entity) ?f32 { + return (components.get(entity) orelse return null).health; + } + pub fn getMaxHealth(entity: Entity) ?f32 { + return (components.get(entity) orelse return null).maxHealth; + } + + pub fn loadFromData(entity: Entity, reader: *utils.BinaryReader, version: u32) main.entity.EntityComponentLoadError!void { + if (version != entityComponentVersion) return error.InvalidComponentVersion; + const component = components.add(main.globalAllocator, entity); + const health = &component.health; + const maxHealth = &component.maxHealth; + health.* = reader.readFloat(f32) catch return error.UnreadableComponentData; + maxHealth.* = reader.readFloat(f32) catch return error.UnreadableComponentData; + } + pub fn loadFromNum(entity: Entity, givenHealth: f32) void { + const component = components.add(main.globalAllocator, entity); + const health = &component.health; + const maxHealth = &component.maxHealth; + health.* = givenHealth; + maxHealth.* = givenHealth; + } + pub fn unload(entity: Entity) void { + components.remove(entity) catch {}; + } + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + const addedHealth = reader.readFloat(f32) catch return; + addHealth(entity, addedHealth); + } + + pub fn addHealth(entity: Entity, healthChange: f32) void { + const health = &(components.get(entity) orelse return).health; + health.* += healthChange; + std.log.debug("modifed component {}", .{health}); + + if (health.* <= 0) { + die(entity); + } + + main.entity.server.transmitChange(Self, entity); + } + + fn die(entity: Entity) void { + const component = components.get(entity) orelse return; + const health = &component.health; + const maxHealth = &component.maxHealth; + health.* = maxHealth.*; + } +}; diff --git a/src/entityComponent/model.zig b/src/entityComponent/model.zig index 1154d37e5a..98d098cf59 100644 --- a/src/entityComponent/model.zig +++ b/src/entityComponent/model.zig @@ -75,6 +75,10 @@ pub const client = struct { pub fn get(entity: Entity) ?*Component { return components.get(entity); } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; // ############################# Server only stuff ################################ @@ -114,4 +118,8 @@ pub const server = struct { pub fn get(entity: Entity) ?*const Component { return components.get(entity); } + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; diff --git a/src/entityComponent/permissions.zig b/src/entityComponent/permissions.zig index a23e34779b..785ec2a17d 100644 --- a/src/entityComponent/permissions.zig +++ b/src/entityComponent/permissions.zig @@ -23,6 +23,11 @@ pub const client = struct { pub fn init() void {} pub fn deinit() void {} pub fn clear() void {} + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; // ############################# Server only stuff ################################ pub const server = struct { @@ -84,4 +89,9 @@ pub const server = struct { const permissions = components.fetchRemove(entity) catch return; permissions.permissions.deinit(); } + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; diff --git a/src/entityComponent/player.zig b/src/entityComponent/player.zig index d20a73a5f7..c8f5be5703 100644 --- a/src/entityComponent/player.zig +++ b/src/entityComponent/player.zig @@ -51,6 +51,11 @@ pub const client = struct { pub fn get(entity: Entity) ?*Component { return components.get(entity); } + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; // ############################# Server only stuff ################################ @@ -92,4 +97,9 @@ pub const server = struct { pub fn get(entity: Entity) ?*Component { return components.get(entity); } + + pub fn modifyComponent(entity: Entity, reader: *utils.BinaryReader) void { + _ = entity; + _ = reader; + } }; diff --git a/src/game.zig b/src/game.zig index 89e6b9273d..39d7d655f6 100644 --- a/src/game.zig +++ b/src/game.zig @@ -26,6 +26,7 @@ const settings = @import("settings.zig"); const Block = main.blocks.Block; const physics = main.physics; const KeyBoard = main.KeyBoard; +const @"cubyz:health" = main.entity.components.@"cubyz:health"; pub const camera = struct { // MARK: camera pub var rotation: Vec3f = Vec3f{0, 0, 0}; @@ -204,7 +205,6 @@ pub const Player = struct { // MARK: Player Player.super.pos = spawnPos; Player.super.vel = .{0, 0, 0}; - Player.super.health = Player.super.maxHealth; Player.super.energy = Player.super.maxEnergy; Player.eye = .{}; @@ -764,7 +764,7 @@ pub fn update(deltaTime: f64) void { // MARK: update() const velocityChange = @abs(@abs(prevVel[2]) - @abs(Player.super.vel[2])); const damage: f32 = @floatCast(@round(@max((velocityChange*velocityChange)/(2*physics.baseGravity) - 7, 0))/2); if (damage > 0.01) { - main.sync.addHealth(-damage, .fall, .client, Player.id); + @"cubyz:health".server.addHealth(Player.id, -damage); } } physics.calculateVerticalCollisionEyeMovement(deltaTime, &Player.eye, didCollide, Player.onGround, wasOnGround, prevPos, Player.super.pos, prevVel, Player.super.vel, motion, Player.steppingHeight()[2]); diff --git a/src/gui/windows/healthbar.zig b/src/gui/windows/healthbar.zig index af3a9c850a..fd88eec845 100644 --- a/src/gui/windows/healthbar.zig +++ b/src/gui/windows/healthbar.zig @@ -11,6 +11,7 @@ const GuiWindow = gui.GuiWindow; const GuiComponent = gui.GuiComponent; const hotbar = @import("hotbar.zig"); +const @"cubyz:health" = main.entity.components.@"cubyz:health"; pub var window = GuiWindow{ .scale = 0.5, @@ -44,12 +45,15 @@ pub fn deinit() void { pub fn render() void { if (main.game.Player.isCreative()) return; + + const playerHealth: f32 = @"cubyz:health".client.getHealth(main.game.Player.id) orelse 0.0; + const playerMaxHealth: f32 = @"cubyz:health".client.getMaxHealth(main.game.Player.id) orelse 0.0; - const displayHealth = @max(0, main.game.Player.super.health); + const displayHealth = @max(0, playerHealth); const halfHeartUnits: usize = @ceil(displayHealth*2); const wholeHearts = halfHeartUnits/2; const halfHeart = halfHeartUnits%2; - const totalHearts: usize = @ceil(main.game.Player.super.maxHealth); + const totalHearts: usize = @ceil(playerMaxHealth); var x: f32 = 0; var y: f32 = 0; diff --git a/src/network/protocols.zig b/src/network/protocols.zig index 6201e1f66e..69fcf09c35 100644 --- a/src/network/protocols.zig +++ b/src/network/protocols.zig @@ -1067,8 +1067,18 @@ pub const EntityComponentUpdate = struct { // MARK: EntityComponentUpdate const ActionType = enum(u8) { unload = 0, load = 1, + modify = 2, }; - + + fn serverReceive(_: *Connection, reader: *utils.BinaryReader) !void { + const entityId: main.entity.Entity = @enumFromInt(try reader.readVarInt(u32)); + const componentId = try reader.readVarInt(u32); + const actionType: ActionType = try reader.readEnum(ActionType); + if (reader.remaining[0] == 0xff) return error.Invalid; + if (actionType == .modify) { + try main.entity.modifyComponent(.server, componentId, entityId,reader.remaining); + } + } fn clientReceive(_: *Connection, reader: *utils.BinaryReader) !void { const entityId: main.entity.Entity = @enumFromInt(try reader.readVarInt(u32)); const componentId = try reader.readVarInt(u32); @@ -1079,6 +1089,8 @@ pub const EntityComponentUpdate = struct { // MARK: EntityComponentUpdate try main.entity.loadComponent(.client, componentId, entityId, reader.remaining, componentVersion); } else if (actionType == .unload) { try main.entity.unloadComponent(.client, componentId, entityId); + } else if (actionType == .modify) { + try main.entity.modifyComponent(.client, componentId, entityId, reader.remaining); } } pub fn unload(conn: *Connection, entityId: main.entity.Entity, componentId: u32) void { @@ -1102,6 +1114,18 @@ pub const EntityComponentUpdate = struct { // MARK: EntityComponentUpdate writer.writeVarInt(u32, version); writer.writeSlice(componentData); + conn.send(.secure, id, writer.data.items); + } + pub fn modify(conn: *Connection, entityId: main.entity.Entity, componentId: u32, componentData: []const u8) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + writer.writeVarInt(u32, @intFromEnum(entityId)); + writer.writeVarInt(u32, componentId); + writer.writeEnum(ActionType, ActionType.modify); + // specific to `modify` + writer.writeSlice(componentData); + conn.send(.secure, id, writer.data.items); } }; diff --git a/src/server/Entity.zig b/src/server/Entity.zig index 169412a84f..41d74ab1e0 100644 --- a/src/server/Entity.zig +++ b/src/server/Entity.zig @@ -11,8 +11,6 @@ pos: Vec3d = .{0, 0, 0}, vel: Vec3d = .{0, 0, 0}, rot: Vec3f = .{0, 0, 0}, -health: f32 = 8, -maxHealth: f32 = 8, energy: f32 = 8, maxEnergy: f32 = 8, name: ?[]const u8 = null, @@ -23,7 +21,6 @@ pub fn loadFrom(self: *@This(), id: main.entity.Entity, zon: ZonElement, comptim self.pos = zon.get(Vec3d, "position") orelse defaultPos; self.vel = zon.get(Vec3d, "velocity") orelse .{0, 0, 0}; self.rot = zon.get(Vec3f, "rotation") orelse .{0, 0, 0}; - self.health = zon.get(f32, "health") orelse self.maxHealth; self.energy = zon.get(f32, "energy") orelse self.maxEnergy; if (zon.getChildOrNull("components")) |components| { try main.entity.loadComponentsFromBase64(components.as([]const u8) orelse "", self.id, side); @@ -49,7 +46,6 @@ pub fn save(self: *const @This(), allocator: NeverFailingAllocator, audience: ma zon.put("position", self.pos); zon.put("velocity", self.vel); zon.put("rotation", self.rot); - zon.put("health", self.health); zon.put("energy", self.energy); zon.put("id", @intFromEnum(self.id)); diff --git a/src/server/command/kill.zig b/src/server/command/kill.zig index 994f6655f5..b4aed352e8 100644 --- a/src/server/command/kill.zig +++ b/src/server/command/kill.zig @@ -4,6 +4,8 @@ const main = @import("main"); const command = main.server.command; const Source = command.Source; +const @"cubyz:health" = main.entity.components.@"cubyz:health"; + pub const description = "Kills the player"; pub const usage = \\/kill @@ -17,5 +19,5 @@ pub const Args = union(enum) { pub fn execute(args: Args, source: Source) void { const target = command.Target.fromPlayerIndex(args.@"/kill ".playerIndex, source) catch return; - main.sync.addHealth(-std.math.floatMax(f32), .kill, .server, target.user.id); + @"cubyz:health".server.addHealth(target.user.id, -std.math.floatMax(f32)); } diff --git a/src/server/server.zig b/src/server/server.zig index 0fbc82d540..ca5d4dc8c7 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -299,6 +299,9 @@ pub const User = struct { // MARK: User world.?.loadPlayer(self) catch { std.log.err("Error while loading player data of {s}. Discarding data.", .{self.name}); }; + if (main.entity.components.@"cubyz:health".server.get(self.id) == null) { + main.entity.components.@"cubyz:health".server.loadFromNum(self.id, 8); + } if (main.entity.components.@"cubyz:model".server.get(self.id) == null) { if (main.entityModel.playerEntityModels.items.len != 0) { const defaultModel = main.entityModel.playerEntityModels.items[main.random.nextIntBounded(u32, &main.seed, @intCast(main.entityModel.playerEntityModels.items.len))]; diff --git a/src/sync.zig b/src/sync.zig index b0a6ab80a3..f9b23f0981 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -213,15 +213,6 @@ pub const server = struct { // MARK: server } }; -pub fn addHealth(health: f32, cause: main.game.DamageType, side: Side, entity: main.entity.Entity) void { - threadContext.assertCorrectContext(side); - if (side == .client) { - client.executeCommand(.{.addHealth = .{.target = entity, .health = health, .cause = cause}}); - } else { - server.executeCommand(.{.addHealth = .{.target = entity, .health = health, .cause = cause}}, null); - } -} - pub fn setGamemode(user: ?*main.server.User, gamemode: Gamemode) void { if (user == null) { client.setGamemode(gamemode); @@ -248,7 +239,6 @@ pub const Command = struct { // MARK: Command craftProceduralItem = 15, clear = 8, updateBlock = 9, - addHealth = 10, chatCommand = 12, }; pub const Payload = union(PayloadType) { @@ -269,7 +259,6 @@ pub const Command = struct { // MARK: Command craftProceduralItem: CraftProceduralItem, clear: Clear, updateBlock: UpdateBlock, - addHealth: AddHealth, chatCommand: ChatCommand, }; @@ -281,7 +270,6 @@ pub const Command = struct { // MARK: Command moveToBag = 7, takeFromBag = 8, useDurability = 4, - addHealth = 5, addEnergy = 6, }; @@ -327,12 +315,6 @@ 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, - }, addEnergy: struct { target: ?*main.server.User, energy: f32, @@ -344,7 +326,6 @@ pub const Command = struct { // MARK: Command create = 0, delete = 1, useDurability = 2, - health = 3, kill = 4, energy = 5, rotation = 6, @@ -367,10 +348,6 @@ pub const Command = struct { // MARK: Command inv: InventoryAndSlot, durability: u32, }, - health: struct { - target: ?*main.server.User, - health: f32, - }, kill: struct { target: ?*main.server.User, spawnPoint: Vec3d, @@ -420,9 +397,6 @@ 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 => |kill| { main.game.Player.kill(kill.spawnPoint); }, @@ -445,7 +419,7 @@ pub const Command = struct { // MARK: Command } return result; }, - inline .health, .kill, .energy, .rotation => |data| { + inline .kill, .energy, .rotation => |data| { const out = allocator.alloc(*main.server.User, 1); out[0] = data.target.?; return out; @@ -455,7 +429,7 @@ pub const Command = struct { // MARK: Command pub fn ignoreSource(self: SyncOperation) bool { return switch (self) { - .create, .delete, .useDurability, .health, .energy, .rotation => true, + .create, .delete, .useDurability, .energy, .rotation => true, .kill => false, }; } @@ -488,12 +462,6 @@ pub const Command = struct { // MARK: Command return out; }, - .health => { - return .{.health = .{ - .target = null, - .health = try reader.readFloat(f32), - }}; - }, .kill => { return .{.kill = .{ .target = null, @@ -534,9 +502,6 @@ pub const Command = struct { // MARK: Command durability.inv.write(&writer); writer.writeInt(u32, durability.durability); }, - .health => |health| { - writer.writeFloat(f32, health.health); - }, .kill => |kill| { writer.writeVec(Vec3d, kill.spawnPoint); }, @@ -647,9 +612,6 @@ pub const Command = struct { // MARK: Command info.item.proceduralItem.durability = info.previousDurability; info.source.inv.update(); }, - .addHealth => |info| { - main.game.Player.super.health = info.previous; - }, .addEnergy => |info| { main.game.Player.super.energy = info.previous; }, @@ -660,7 +622,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, .addEnergy => {}, .delete => |info| { info.item.deinit(); }, @@ -810,31 +772,6 @@ 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.?, - .spawnPoint = info.target.?.getSpawnPos(), - }}); - } 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); - } - }, .addEnergy => |*info| { if (side == .server) { info.previous = info.target.?.player().energy; @@ -1569,10 +1506,86 @@ pub const Command = struct { // MARK: Command const UpdateBlock = struct { // MARK: UpdateBlock source: InventoryAndSlot, pos: Vec3i, - dropLocation: BlockDrop.Location, + dropLocation: BlockDropLocation, oldBlock: Block, newBlock: Block, + const half = @as(Vec3f, @splat(0.5)); + const itemHitBoxMargin: f32 = @floatCast(main.itemdrop.ItemDropManager.radius); + const itemHitBoxMarginVec: Vec3f = @splat(itemHitBoxMargin); + + const BlockDropLocation = struct { + normalDir: Vec3f, + min: Vec3f, + max: Vec3f, + + pub fn drop(self: BlockDropLocation, pos: Vec3i, newBlock: Block, _drop: BlockDrop) void { + if (newBlock.collide()) { + self.dropOutside(pos, _drop); + } else { + self.dropInside(pos, _drop); + } + } + fn dropInside(self: BlockDropLocation, pos: Vec3i, _drop: BlockDrop) void { + for (_drop.itemStacks) |itemStack| { + main.server.world.?.drop(itemStack.clone(), self.insidePos(pos), self.dropDir(), self.dropVelocity()); + } + } + fn insidePos(self: BlockDropLocation, _pos: Vec3i) Vec3d { + const pos: Vec3d = @floatFromInt(_pos); + return pos + self.randomOffset(); + } + fn randomOffset(self: BlockDropLocation) Vec3f { + const max = @min(@as(Vec3f, @splat(1.0)) - itemHitBoxMarginVec, @max(itemHitBoxMarginVec, self.max - itemHitBoxMarginVec)); + const min = @min(max, @max(itemHitBoxMarginVec, self.min + itemHitBoxMarginVec)); + const center = (max + min)*half; + const width = (max - min)*half; + return center + width*main.random.nextFloatVectorSigned(3, &main.seed)*half; + } + fn dropOutside(self: BlockDropLocation, pos: Vec3i, _drop: BlockDrop) void { + for (_drop.itemStacks) |itemStack| { + main.server.world.?.drop(itemStack.clone(), self.outsidePos(pos), self.dropDir(), self.dropVelocity()); + } + } + fn outsidePos(self: BlockDropLocation, _pos: Vec3i) Vec3d { + const pos: Vec3d = @floatFromInt(_pos); + const random = self.randomOffset(); + const minorVectors = minors(self); + const minor1Offset = @as(Vec3f, @splat(vec.dot(random, minorVectors[0])))*minorVectors[0]; + const minor2Offset = @as(Vec3f, @splat(vec.dot(random, minorVectors[1])))*minorVectors[1]; + return pos + minor1Offset + minor2Offset + self.directionOffset()*self.major() + self.direction()*itemHitBoxMarginVec; + } + fn directionOffset(self: BlockDropLocation) Vec3d { + return half + self.direction()*half; + } + inline fn direction(self: BlockDropLocation) Vec3f { + return self.normalDir; + } + inline fn major(self: BlockDropLocation) Vec3f { + return @abs(self.normalDir); + } + inline fn minors(self: BlockDropLocation) struct { Vec3f, Vec3f } { + const minor1 = vec.normalize(vec.cross(self.normalDir, if (@reduce(.And, @abs(self.normalDir) == Vec3f{1.0, 0.0, 0.0})) Vec3f{0.0, 1.0, 0.0} else Vec3f{1.0, 0.0, 0.0})); + const minor2 = vec.normalize(vec.cross(self.normalDir, minor1)); + return .{minor1, minor2}; + } + fn dropDir(self: BlockDropLocation) Vec3f { + const randomnessVec: Vec3f = main.random.nextFloatVectorSigned(3, &main.seed)*@as(Vec3f, @splat(0.25)); + const directionVec: Vec3f = @as(Vec3f, @floatCast(self.direction())) + randomnessVec; + const z: f32 = directionVec[2]; + return vec.normalize(Vec3f{ + directionVec[0], + directionVec[1], + if (z < -0.5) 0 else if (z < 0.0) (z + 0.5)*4.0 else z + 2.0, + }); + } + fn dropVelocity(self: BlockDropLocation) f32 { + const velocity = 3.5 + main.random.nextFloatSigned(&main.seed)*0.5; + if (self.direction()[2] < -0.5) return velocity*0.333; + return velocity; + } + }; + fn run(self: UpdateBlock, ctx: Context) error{serverFailure}!void { const stack = self.source.ref(); @@ -1628,12 +1641,16 @@ pub const Command = struct { // MARK: Command }, } if (ctx.side == .server and ctx.gamemode != .creative and shouldDropSourceBlockOnSuccess) { - const dropCtx = BlockDrop.Context{ - .oldBlock = self.oldBlock, - .newBlock = self.newBlock, - .item = handItem, - }; - dropCtx.drop(self.dropLocation, self.pos); + const dropAmount = self.oldBlock.mode().itemDropsOnChange(self.oldBlock, self.newBlock); + for (0..dropAmount) |_| { + for (self.oldBlock.blockDrops()) |drop| { + if (!drop.isDroppedWhenBrokenWithItem(handItem)) continue; + + if (drop.chance == 1 or main.random.nextFloat(&main.seed) < drop.chance) { + self.dropLocation.drop(self.pos, self.newBlock, drop); + } + } + } } } @@ -1662,56 +1679,6 @@ pub const Command = struct { // MARK: Command } }; - const AddHealth = struct { // MARK: AddHealth - target: main.entity.Entity, - health: f32, - cause: main.game.DamageType, - - pub fn run(self: AddHealth, ctx: Context) error{serverFailure}!void { - 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; - - if (target.?.gamemode.raw == .creative) return; - } else { - if (main.game.Player.gamemode.raw == .creative) return; - } - - ctx.execute(.{.addHealth = .{ - .target = target, - .health = self.health, - .cause = self.cause, - .previous = if (ctx.side == .server) target.?.player().health else main.game.Player.super.health, - }}); - } - - fn serialize(self: AddHealth, writer: *BinaryWriter) void { - writer.writeEnum(main.entity.Entity, self.target); - writer.writeInt(u32, @bitCast(self.health)); - writer.writeEnum(main.game.DamageType, self.cause); - } - - fn deserialize(reader: *BinaryReader, _: Side, user: ?*main.server.User) !AddHealth { - const result: AddHealth = .{ - .target = try reader.readEnum(main.entity.Entity), - .health = @bitCast(try reader.readInt(u32)), - .cause = try reader.readEnum(main.game.DamageType), - }; - if (user.?.id != result.target) return error.Invalid; - return result; - } - }; - const ChatCommand = struct { // MARK: ChatCommand message: []const u8,