From 0c0ca2ed153bbf6b7df8cc18dbe807a7a7a8f6f7 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Tue, 24 Dec 2024 23:52:11 +0100 Subject: [PATCH] Synchronize block breaking and placing throug the inventory synchronization system. fixes #751 This allowed reenabling item consumption when placing blocks. Durability and item drops are still work in progress. Currently all game modes consume items. Temporarily reverts some changes from #739, until the game mode is synchronized to the server as well. progress towards #670 --- src/Inventory.zig | 151 +++++++++++++++++++++++++++++++++++++------ src/blocks.zig | 4 ++ src/game.zig | 2 +- src/items.zig | 1 + src/network.zig | 8 ++- src/renderer.zig | 31 ++++----- src/rotation.zig | 63 ++++++++++++++++++ src/server/world.zig | 18 +++++- 8 files changed, 238 insertions(+), 40 deletions(-) diff --git a/src/Inventory.zig b/src/Inventory.zig index 73e35808..87ace605 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -2,12 +2,14 @@ const std = @import("std"); const main = @import("main.zig"); const BaseItem = main.items.BaseItem; +const Block = main.blocks.Block; const Item = main.items.Item; const ItemStack = main.items.ItemStack; const Tool = main.items.Tool; const NeverFailingAllocator = main.utils.NeverFailingAllocator; const vec = main.vec; const Vec3f = vec.Vec3f; +const Vec3i = vec.Vec3i; const ZonElement = main.ZonElement; @@ -48,7 +50,7 @@ pub const Sync = struct { // MARK: Sync mutex.lock(); defer mutex.unlock(); - cmd.do(main.globalAllocator, .client, null); + cmd.do(main.globalAllocator, .client, null) catch unreachable; const data = cmd.serializePayload(main.stackAllocator); defer main.stackAllocator.free(data); main.network.Protocols.inventory.sendCommand(main.game.world.?.conn, cmd.payload, data); @@ -101,6 +103,27 @@ pub const Sync = struct { // MARK: Sync commands.dequeue().?.finalize(main.globalAllocator, .client, data); } + pub fn receiveFailure() void { + mutex.lock(); + defer mutex.unlock(); + var tempData = main.List(Command).init(main.stackAllocator); + defer tempData.deinit(); + while(commands.dequeue_front()) |_cmd| { + var cmd = _cmd; + cmd.undo(); + tempData.append(cmd); + } + if(tempData.popOrNull()) |_cmd| { + var cmd = _cmd; + cmd.finalize(main.globalAllocator, .client, &.{}); + } + while(tempData.popOrNull()) |_cmd| { + var cmd = _cmd; + cmd.do(main.globalAllocator, .client, null) catch unreachable; + commands.enqueue(cmd); + } + } + pub fn receiveSyncOperation(data: []const u8) !void { mutex.lock(); defer mutex.unlock(); @@ -114,13 +137,13 @@ pub const Sync = struct { // MARK: Sync try Command.SyncOperation.executeFromData(data); while(tempData.popOrNull()) |_cmd| { var cmd = _cmd; - cmd.do(main.globalAllocator, .client, null); + cmd.do(main.globalAllocator, .client, null) catch unreachable; commands.enqueue(cmd); } } }; - pub const ServerSide = struct { + pub const ServerSide = struct { // MARK: ServerSide const ServerInventory = struct { inv: Inventory, users: main.ListUnmanaged(*main.server.User), @@ -206,7 +229,10 @@ pub const Sync = struct { // MARK: Sync var command = Command { .payload = payload, }; - command.do(main.globalAllocator, .server, source); + command.do(main.globalAllocator, .server, source) catch { + main.network.Protocols.inventory.sendFailure(source.?.conn); + return; + }; if(source != null) { const confirmationData = command.confirmationData(main.stackAllocator); defer main.stackAllocator.free(confirmationData); @@ -338,6 +364,7 @@ pub const Command = struct { // MARK: Command fillFromCreative = 6, depositOrDrop = 7, clear = 8, + updateBlock = 9, }; pub const Payload = union(PayloadType) { open: Open, @@ -349,6 +376,7 @@ pub const Command = struct { // MARK: Command fillFromCreative: FillFromCreative, depositOrDrop: DepositOrDrop, clear: Clear, + updateBlock: UpdateBlock, }; const BaseOperationType = enum(u8) { @@ -473,11 +501,11 @@ pub const Command = struct { // MARK: Command return list.toOwnedSlice(); } - fn do(self: *Command, allocator: NeverFailingAllocator, side: Side, user: ?*main.server.User) void { + fn do(self: *Command, allocator: NeverFailingAllocator, side: Side, user: ?*main.server.User) error{serverFailure}!void { std.debug.assert(self.baseOperations.items.len == 0); // do called twice without cleaning up switch(self.payload) { inline else => |payload| { - payload.run(allocator, self, side, user); + try payload.run(allocator, self, side, user); }, } } @@ -709,7 +737,7 @@ pub const Command = struct { // MARK: Command inv: Inventory, source: Source, - fn run(_: Open, _: NeverFailingAllocator, _: *Command, _: Side, _: ?*main.server.User) void {} + fn run(_: Open, _: NeverFailingAllocator, _: *Command, _: Side, _: ?*main.server.User) error{serverFailure}!void {} fn finalize(self: Open, side: Side, data: []const u8) void { if(side != .client) return; @@ -761,7 +789,7 @@ pub const Command = struct { // MARK: Command inv: Inventory, allocator: NeverFailingAllocator, - fn run(_: Close, _: NeverFailingAllocator, _: *Command, _: Side, _: ?*main.server.User) void {} + fn run(_: Close, _: NeverFailingAllocator, _: *Command, _: Side, _: ?*main.server.User) error{serverFailure}!void {} fn finalize(self: Close, side: Side, data: []const u8) void { if(side != .client) return; @@ -789,10 +817,10 @@ pub const Command = struct { // MARK: Command dest: InventoryAndSlot, source: InventoryAndSlot, - fn run(self: DepositOrSwap, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) void { + fn run(self: DepositOrSwap, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) error{serverFailure}!void { std.debug.assert(self.source.inv.type == .normal); if(self.dest.inv.type == .creative) { - FillFromCreative.run(.{.dest = self.source, .item = self.dest.ref().item}, allocator, cmd, side, user); + try FillFromCreative.run(.{.dest = self.source, .item = self.dest.ref().item}, allocator, cmd, side, user); return; } if(self.dest.inv.type == .crafting) { @@ -852,7 +880,7 @@ pub const Command = struct { // MARK: Command source: InventoryAndSlot, amount: u16, - fn run(self: Deposit, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) void { + fn run(self: Deposit, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) error{serverFailure}!void { std.debug.assert(self.source.inv.type == .normal); if(self.dest.inv.type == .creative) return; if(self.dest.inv.type == .crafting) return; @@ -898,12 +926,12 @@ pub const Command = struct { // MARK: Command dest: InventoryAndSlot, source: InventoryAndSlot, - fn run(self: TakeHalf, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) void { + fn run(self: TakeHalf, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) error{serverFailure}!void { std.debug.assert(self.dest.inv.type == .normal); if(self.source.inv.type == .creative) { if(self.dest.ref().item == null) { const item = self.source.ref().item; - FillFromCreative.run(.{.dest = self.dest, .item = item}, allocator, cmd, side, user); + try FillFromCreative.run(.{.dest = self.dest, .item = item}, allocator, cmd, side, user); } return; } @@ -961,7 +989,7 @@ pub const Command = struct { // MARK: Command source: InventoryAndSlot, desiredAmount: u16 = 0xffff, - fn run(self: Drop, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) void { + fn run(self: Drop, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) error{serverFailure}!void { if(self.source.inv.type == .creative) return; if(self.source.ref().item == null) return; if(self.source.inv.type == .crafting) { @@ -1017,7 +1045,7 @@ pub const Command = struct { // MARK: Command item: ?Item, amount: u16 = 0, - fn run(self: FillFromCreative, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) void { + fn run(self: FillFromCreative, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) error{serverFailure}!void { if(self.dest.inv.type == .workbench and self.dest.slot == 25) return; if(!self.dest.ref().empty()) { @@ -1069,7 +1097,7 @@ pub const Command = struct { // MARK: Command dest: Inventory, source: Inventory, - pub fn run(self: DepositOrDrop, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) void { + pub fn run(self: DepositOrDrop, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) error{serverFailure}!void { std.debug.assert(self.dest.type == .normal); if(self.source.type == .creative) return; if(self.source.type == .crafting) return; @@ -1129,7 +1157,7 @@ pub const Command = struct { // MARK: Command const Clear = struct { inv: Inventory, - pub fn run(self: Clear, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) void { + pub fn run(self: Clear, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User) error{serverFailure}!void { if(self.inv.type == .creative) return; if(self.inv.type == .crafting) return; var items = self.inv._items; @@ -1156,6 +1184,89 @@ pub const Command = struct { // MARK: Command }; } }; + + const UpdateBlock = struct { + source: InventoryAndSlot, + pos: Vec3i, + oldBlock: Block, + newBlock: Block, + + fn run(self: UpdateBlock, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User) error{serverFailure}!void { + if(self.source.inv.type != .normal) return; + + const stack = self.source.ref(); + + const costOfChange = self.oldBlock.canBeChangedInto(self.newBlock, stack.*); + + // Check if we can change it: + if(!switch(costOfChange) { + .no => false, + .yes => true, + .yes_costsDurability => |durability| stack.item != null and stack.item.? == .tool and stack.item.?.tool.durability >= durability, + .yes_costsItems => |amount| stack.amount >= amount, + .yes_dropsItems => true, + }) { + if(side == .server) { + // Inform the client of the actual block: + main.network.Protocols.blockUpdate.send(user.?.conn, self.pos[0], self.pos[1], self.pos[2], main.server.world.?.getBlock(self.pos[0], self.pos[1], self.pos[2]) orelse return); + } + return; + } + + if(side == .server) { + if(main.server.world.?.cmpxchgBlock(self.pos[0], self.pos[1], self.pos[2], self.oldBlock, self.newBlock)) |actualBlock| { + // Inform the client of the actual block: + main.network.Protocols.blockUpdate.send(user.?.conn, self.pos[0], self.pos[1], self.pos[2], actualBlock); + return error.serverFailure; + } + } + + // Apply inventory changes: + switch(costOfChange) { + .no => unreachable, + .yes => {}, + .yes_costsDurability => |durability| { + // TODO: Add operations to track tool durability. + _ = durability; + }, + .yes_costsItems => |amount| { + cmd.executeBaseOperation(allocator, .{.delete = .{ + .source = self.source, + .amount = amount, + }}, side); + }, + .yes_dropsItems => |amount| { + if(side == .server) { + // TODO: Drop block items + _ = amount; + } + }, + } + } + + fn serialize(self: UpdateBlock, data: *main.List(u8)) void { + self.source.write(data.addMany(8)[0..8]); + std.mem.writeInt(i32, data.addMany(4)[0..4], self.pos[0], .big); + std.mem.writeInt(i32, data.addMany(4)[0..4], self.pos[1], .big); + std.mem.writeInt(i32, data.addMany(4)[0..4], self.pos[2], .big); + std.mem.writeInt(u32, data.addMany(4)[0..4], @as(u32, @bitCast(self.oldBlock)), .big); + std.mem.writeInt(u32, data.addMany(4)[0..4], @as(u32, @bitCast(self.newBlock)), .big); + } + + fn deserialize(data: []const u8, side: Side, user: ?*main.server.User) !UpdateBlock { + if(data.len != 28) return error.Invalid; + return .{ + .source = try InventoryAndSlot.read(data[0..8], side, user), + .pos = .{ + std.mem.readInt(i32, data[8..12], .big), + std.mem.readInt(i32, data[12..16], .big), + std.mem.readInt(i32, data[16..20], .big), + }, + .oldBlock = @bitCast(std.mem.readInt(u32, data[20..24], .big)), + .newBlock = @bitCast(std.mem.readInt(u32, data[24..28], .big)), + }; + } + }; }; const SourceType = enum(u8) { @@ -1281,12 +1392,12 @@ pub fn fillAmountFromCreative(dest: Inventory, destSlot: u32, item: ?Item, amoun Sync.ClientSide.executeCommand(.{.fillFromCreative = .{.dest = .{.inv = dest, .slot = destSlot}, .item = item, .amount = amount}}); } -pub fn placeBlock(self: Inventory, slot: u32, unlimitedBlocks: bool) void { - main.renderer.MeshSelection.placeBlock(&self._items[slot], unlimitedBlocks); +pub fn placeBlock(self: Inventory, slot: u32) void { + main.renderer.MeshSelection.placeBlock(self, slot); } pub fn breakBlock(self: Inventory, slot: u32) void { - main.renderer.MeshSelection.breakBlock(&self._items[slot]); + main.renderer.MeshSelection.breakBlock(self, slot); } pub fn size(self: Inventory) usize { diff --git a/src/blocks.zig b/src/blocks.zig index 8d61af53..8d586b83 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -337,6 +337,10 @@ pub const Block = packed struct { // MARK: Block pub inline fn opaqueVariant(self: Block) u16 { return _opaqueVariant[self.typ]; } + + pub fn canBeChangedInto(self: Block, newBlock: Block, item: main.items.ItemStack) main.rotation.RotationMode.CanBeChangedInto { + return newBlock.mode().canBeChangedInto(self, newBlock, item); + } }; diff --git a/src/game.zig b/src/game.zig index f03ee47f..2d83356d 100644 --- a/src/game.zig +++ b/src/game.zig @@ -439,7 +439,7 @@ pub const Player = struct { // MARK: Player } } - inventory.placeBlock(selectedSlot, isCreative()); + inventory.placeBlock(selectedSlot); } pub fn breakBlock() void { // TODO: Breaking animation and tools diff --git a/src/items.zig b/src/items.zig index d92ebbfc..08c56ee3 100644 --- a/src/items.zig +++ b/src/items.zig @@ -1029,6 +1029,7 @@ pub const Tool = struct { // MARK: Tool .stone => self.pickaxePower, .unbreakable => 0, .wood => self.axePower, + .air => 0, }; } diff --git a/src/network.zig b/src/network.zig index d3394ff9..bd0c4a28 100644 --- a/src/network.zig +++ b/src/network.zig @@ -905,7 +905,7 @@ pub const Protocols = struct { const z = std.mem.readInt(i32, data[8..12], .big); const newBlock = Block.fromInt(std.mem.readInt(u32, data[12..16], .big)); if(conn.user != null) { - main.server.world.?.updateBlock(x, y, z, newBlock); + return error.InvalidPacket; } else { renderer.mesh_storage.updateBlock(x, y, z, newBlock); } @@ -1162,6 +1162,8 @@ pub const Protocols = struct { } else { if(data[0] == 0xff) { // Confirmation items.Inventory.Sync.ClientSide.receiveConfirmation(data[1..]); + } else if(data[0] == 0xfe) { // Failure + items.Inventory.Sync.ClientSide.receiveFailure(); } else { try items.Inventory.Sync.ClientSide.receiveSyncOperation(data[1..]); } @@ -1184,6 +1186,10 @@ pub const Protocols = struct { @memcpy(data[1..], _data); conn.sendImportant(id, data); } + pub fn sendFailure(conn: *Connection) void { + std.debug.assert(conn.user != null); + conn.sendImportant(id, &.{0xfe}); + } pub fn sendSyncOperation(conn: *Connection, _data: []const u8) void { std.debug.assert(conn.user != null); var data = main.stackAllocator.alloc(u8, _data.len + 1); diff --git a/src/renderer.zig b/src/renderer.zig index a03568c9..4f2c5c11 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -752,12 +752,11 @@ pub const MeshSelection = struct { // MARK: MeshSelection return true; // TODO: Check other entities } - pub fn placeBlock(inventoryStack: *main.items.ItemStack, unlimitedBlocks: bool) void { - const removeAmount: i32 = if(unlimitedBlocks) 0 else -1; - _ = removeAmount; // TODO + pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void { if(selectedBlockPos) |selectedPos| { - var block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; - if(inventoryStack.item) |item| { + var oldBlock = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; + var block = oldBlock; + if(inventory.getItem(slot)) |item| { switch(item) { .baseItem => |baseItem| { if(baseItem.block) |itemBlock| { @@ -768,8 +767,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos))); if(rotationMode.generateData(main.game.world.?, selectedPos, relPos, lastDir, neighborDir, &block, .{.typ = 0, .data = 0}, false)) { if(!canPlaceBlock(selectedPos, block)) return; - updateBlockAndSendUpdate(selectedPos[0], selectedPos[1], selectedPos[2], block); - // TODO: _ = inventoryStack.add(item, @as(i32, removeAmount)); + updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], oldBlock, block); return; } } @@ -778,12 +776,12 @@ pub const MeshSelection = struct { // MARK: MeshSelection neighborDir = selectedPos - posBeforeBlock; const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const neighborBlock = block; - block = mesh_storage.getBlock(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; + oldBlock = mesh_storage.getBlock(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; + block = oldBlock; if(block.typ == itemBlock) { if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, false)) { if(!canPlaceBlock(neighborPos, block)) return; - updateBlockAndSendUpdate(neighborPos[0], neighborPos[1], neighborPos[2], block); - // TODO: _ = inventoryStack.add(item, @as(i32, removeAmount)); + updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block); return; } } else { @@ -792,8 +790,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection block.data = 0; if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, true)) { if(!canPlaceBlock(neighborPos, block)) return; - updateBlockAndSendUpdate(neighborPos[0], neighborPos[1], neighborPos[2], block); - // TODO: _ = inventoryStack.add(item, @as(i32, removeAmount)); + updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block); return; } } @@ -807,21 +804,21 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } - pub fn breakBlock(inventoryStack: *main.items.ItemStack) void { + pub fn breakBlock(inventory: main.items.Inventory, slot: u32) void { if(selectedBlockPos) |selectedPos| { const block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; var newBlock = block; // TODO: Breaking animation and tools. const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos))); - block.mode().onBlockBreaking(inventoryStack.item, relPos, lastDir, &newBlock); + block.mode().onBlockBreaking(inventory.getStack(slot).item, relPos, lastDir, &newBlock); if(!std.meta.eql(newBlock, block)) { - updateBlockAndSendUpdate(selectedPos[0], selectedPos[1], selectedPos[2], newBlock); + updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], block, newBlock); } } } - fn updateBlockAndSendUpdate(x: i32, y: i32, z: i32, newBlock: blocks.Block) void { - main.network.Protocols.blockUpdate.send(main.game.world.?.conn, x, y, z, newBlock); + fn updateBlockAndSendUpdate(source: main.items.Inventory, slot: u32, x: i32, y: i32, z: i32, oldBlock: blocks.Block, newBlock: blocks.Block) void { + main.items.Inventory.Sync.ClientSide.executeCommand(.{.updateBlock = .{.source = .{.inv = source, .slot = slot}, .pos = .{x, y, z}, .oldBlock = oldBlock, .newBlock = newBlock}}); mesh_storage.updateBlock(x, y, z, newBlock); } diff --git a/src/rotation.zig b/src/rotation.zig index 2fa749d2..eade0f26 100644 --- a/src/rotation.zig +++ b/src/rotation.zig @@ -60,6 +60,39 @@ pub const RotationMode = struct { // MARK: RotationMode fn onBlockBreaking(_: ?main.items.Item, _: Vec3f, _: Vec3f, currentData: *Block) void { currentData.* = .{.typ = 0, .data = 0}; } + fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack) CanBeChangedInto { + if(std.meta.eql(oldBlock, newBlock)) return .no; + if(oldBlock.typ == newBlock.typ) return .yes; + if(oldBlock.solid()) { + var power: f32 = 0; + const isTool = item.item != null and item.item.? == .tool; + if(isTool) { + power = item.item.?.tool.getPowerByBlockClass(oldBlock.blockClass()); + } + if(power >= oldBlock.breakingPower()) { + if(isTool) { + return .{.yes_costsDurability = 1}; + } else return .yes; + } + } else { + if(item.item) |_item| { + if(_item == .baseItem) { + if(_item.baseItem.block != null and _item.baseItem.block.? == newBlock.typ) { + return .{.yes_costsItems = 1}; + } + } + } + } + return .no; + } + }; + + pub const CanBeChangedInto = union(enum) { + no: void, + yes: void, + yes_costsDurability: u16, + yes_costsItems: u16, + yes_dropsItems: u16, }; /// if the block should be destroyed or changed when a certain neighbor is removed. @@ -82,6 +115,8 @@ pub const RotationMode = struct { // MARK: RotationMode rayIntersection: *const fn(block: Block, item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f) ?RayIntersectionResult = &DefaultFunctions.rayIntersection, onBlockBreaking: *const fn(item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void = &DefaultFunctions.onBlockBreaking, + + canBeChangedInto: *const fn(oldBlock: Block, newBlock: Block, item: main.items.ItemStack) CanBeChangedInto = DefaultFunctions.canBeChangedInto, }; var rotationModes: std.StringHashMap(RotationMode) = undefined; @@ -571,6 +606,15 @@ pub const RotationModes = struct { } return RotationMode.DefaultFunctions.onBlockBreaking(item, relativePlayerPos, playerDir, currentData); } + + pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack) RotationMode.CanBeChangedInto { + if(oldBlock.typ != newBlock.typ) return RotationMode.DefaultFunctions.canBeChangedInto(oldBlock, newBlock, item); + if(oldBlock.data == newBlock.data) return .no; + if(item.item != null and item.item.? == .baseItem and std.mem.eql(u8, item.item.?.baseItem.id, "cubyz:chisel")) { + return .yes; // TODO: Durability change, after making the chisel a proper tool. + } + return .no; + } }; pub const Torch = struct { // MARK: Torch pub const id: []const u8 = "torch"; @@ -719,6 +763,21 @@ pub const RotationModes = struct { currentData.data &= ~bit; if(currentData.data == 0) currentData.typ = 0; } + + pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack) RotationMode.CanBeChangedInto { + switch(RotationMode.DefaultFunctions.canBeChangedInto(oldBlock, newBlock, item)) { + .no, .yes_costsDurability, .yes_dropsItems => return .no, + .yes, .yes_costsItems => { + const torchAmountChange = @as(i32, @popCount(newBlock.data)) - if(oldBlock.typ == newBlock.typ) @as(i32, @popCount(oldBlock.data)) else 0; + if(torchAmountChange <= 0) { + return .{.yes_dropsItems = @intCast(-torchAmountChange)}; + } else { + if(item.item == null or item.item.? != .baseItem or !std.meta.eql(item.item.?.baseItem.block, newBlock.typ)) return .no; + return .{.yes_costsItems = @intCast(torchAmountChange)}; + } + }, + } + } }; pub const Carpet = struct { // MARK: Carpet pub const id: []const u8 = "carpet"; @@ -886,6 +945,10 @@ pub const RotationModes = struct { currentData.data &= ~bit; if(currentData.data == 0) currentData.typ = 0; } + + pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack) RotationMode.CanBeChangedInto { + return Torch.canBeChangedInto(oldBlock, newBlock, item); + } }; }; diff --git a/src/server/world.zig b/src/server/world.zig index 709d71f3..d0fe5092 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -895,12 +895,23 @@ pub const ServerWorld = struct { // MARK: ServerWorld return ch.getBlock(x - ch.super.pos.wx, y - ch.super.pos.wy, z - ch.super.pos.wz); } - pub fn updateBlock(_: *ServerWorld, wx: i32, wy: i32, wz: i32, _newBlock: Block) void { + /// Returns the actual block on failure + pub fn cmpxchgBlock(_: *ServerWorld, wx: i32, wy: i32, wz: i32, oldBlock: ?Block, _newBlock: Block) ?Block { const baseChunk = ChunkManager.getOrGenerateChunkAndIncreaseRefCount(.{.wx = wx & ~@as(i32, chunk.chunkMask), .wy = wy & ~@as(i32, chunk.chunkMask), .wz = wz & ~@as(i32, chunk.chunkMask), .voxelSize = 1}); defer baseChunk.decreaseRefCount(); const x: u5 = @intCast(wx & chunk.chunkMask); const y: u5 = @intCast(wy & chunk.chunkMask); const z: u5 = @intCast(wz & chunk.chunkMask); + baseChunk.mutex.lock(); + const currentBlock = baseChunk.getBlock(x, y, z); + if(oldBlock != null) { + if(!std.meta.eql(oldBlock.?, currentBlock)) { + baseChunk.mutex.unlock(); + return currentBlock; + } + baseChunk.updateBlockAndSetChanged(x, y, z, _newBlock); + } + baseChunk.mutex.unlock(); var newBlock = _newBlock; for(chunk.Neighbor.iterable) |neighbor| { const nx = x + neighbor.relX(); @@ -938,6 +949,11 @@ pub const ServerWorld = struct { // MARK: ServerWorld for(main.server.users.items) |user| { main.network.Protocols.blockUpdate.send(user.conn, wx, wy, wz, _newBlock); } + return null; + } + + pub fn updateBlock(self: *ServerWorld, wx: i32, wy: i32, wz: i32, newBlock: Block) void { + _ = self.cmpxchgBlock(wx, wy, wz, null, newBlock); } pub fn queueChunkUpdateAndDecreaseRefCount(self: *ServerWorld, ch: *ServerChunk) void {