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
This commit is contained in:
IntegratedQuantum 2024-12-24 23:52:11 +01:00
parent 363edfb22d
commit 0c0ca2ed15
8 changed files with 238 additions and 40 deletions

View File

@ -2,12 +2,14 @@ const std = @import("std");
const main = @import("main.zig"); const main = @import("main.zig");
const BaseItem = main.items.BaseItem; const BaseItem = main.items.BaseItem;
const Block = main.blocks.Block;
const Item = main.items.Item; const Item = main.items.Item;
const ItemStack = main.items.ItemStack; const ItemStack = main.items.ItemStack;
const Tool = main.items.Tool; const Tool = main.items.Tool;
const NeverFailingAllocator = main.utils.NeverFailingAllocator; const NeverFailingAllocator = main.utils.NeverFailingAllocator;
const vec = main.vec; const vec = main.vec;
const Vec3f = vec.Vec3f; const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement; const ZonElement = main.ZonElement;
@ -48,7 +50,7 @@ pub const Sync = struct { // MARK: Sync
mutex.lock(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
cmd.do(main.globalAllocator, .client, null); cmd.do(main.globalAllocator, .client, null) catch unreachable;
const data = cmd.serializePayload(main.stackAllocator); const data = cmd.serializePayload(main.stackAllocator);
defer main.stackAllocator.free(data); defer main.stackAllocator.free(data);
main.network.Protocols.inventory.sendCommand(main.game.world.?.conn, cmd.payload, 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); 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 { pub fn receiveSyncOperation(data: []const u8) !void {
mutex.lock(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
@ -114,13 +137,13 @@ pub const Sync = struct { // MARK: Sync
try Command.SyncOperation.executeFromData(data); try Command.SyncOperation.executeFromData(data);
while(tempData.popOrNull()) |_cmd| { while(tempData.popOrNull()) |_cmd| {
var cmd = _cmd; var cmd = _cmd;
cmd.do(main.globalAllocator, .client, null); cmd.do(main.globalAllocator, .client, null) catch unreachable;
commands.enqueue(cmd); commands.enqueue(cmd);
} }
} }
}; };
pub const ServerSide = struct { pub const ServerSide = struct { // MARK: ServerSide
const ServerInventory = struct { const ServerInventory = struct {
inv: Inventory, inv: Inventory,
users: main.ListUnmanaged(*main.server.User), users: main.ListUnmanaged(*main.server.User),
@ -206,7 +229,10 @@ pub const Sync = struct { // MARK: Sync
var command = Command { var command = Command {
.payload = payload, .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) { if(source != null) {
const confirmationData = command.confirmationData(main.stackAllocator); const confirmationData = command.confirmationData(main.stackAllocator);
defer main.stackAllocator.free(confirmationData); defer main.stackAllocator.free(confirmationData);
@ -338,6 +364,7 @@ pub const Command = struct { // MARK: Command
fillFromCreative = 6, fillFromCreative = 6,
depositOrDrop = 7, depositOrDrop = 7,
clear = 8, clear = 8,
updateBlock = 9,
}; };
pub const Payload = union(PayloadType) { pub const Payload = union(PayloadType) {
open: Open, open: Open,
@ -349,6 +376,7 @@ pub const Command = struct { // MARK: Command
fillFromCreative: FillFromCreative, fillFromCreative: FillFromCreative,
depositOrDrop: DepositOrDrop, depositOrDrop: DepositOrDrop,
clear: Clear, clear: Clear,
updateBlock: UpdateBlock,
}; };
const BaseOperationType = enum(u8) { const BaseOperationType = enum(u8) {
@ -473,11 +501,11 @@ pub const Command = struct { // MARK: Command
return list.toOwnedSlice(); 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 std.debug.assert(self.baseOperations.items.len == 0); // do called twice without cleaning up
switch(self.payload) { switch(self.payload) {
inline else => |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, inv: Inventory,
source: Source, 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 { fn finalize(self: Open, side: Side, data: []const u8) void {
if(side != .client) return; if(side != .client) return;
@ -761,7 +789,7 @@ pub const Command = struct { // MARK: Command
inv: Inventory, inv: Inventory,
allocator: NeverFailingAllocator, 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 { fn finalize(self: Close, side: Side, data: []const u8) void {
if(side != .client) return; if(side != .client) return;
@ -789,10 +817,10 @@ pub const Command = struct { // MARK: Command
dest: InventoryAndSlot, dest: InventoryAndSlot,
source: 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); std.debug.assert(self.source.inv.type == .normal);
if(self.dest.inv.type == .creative) { 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; return;
} }
if(self.dest.inv.type == .crafting) { if(self.dest.inv.type == .crafting) {
@ -852,7 +880,7 @@ pub const Command = struct { // MARK: Command
source: InventoryAndSlot, source: InventoryAndSlot,
amount: u16, 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); std.debug.assert(self.source.inv.type == .normal);
if(self.dest.inv.type == .creative) return; if(self.dest.inv.type == .creative) return;
if(self.dest.inv.type == .crafting) return; if(self.dest.inv.type == .crafting) return;
@ -898,12 +926,12 @@ pub const Command = struct { // MARK: Command
dest: InventoryAndSlot, dest: InventoryAndSlot,
source: 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); std.debug.assert(self.dest.inv.type == .normal);
if(self.source.inv.type == .creative) { if(self.source.inv.type == .creative) {
if(self.dest.ref().item == null) { if(self.dest.ref().item == null) {
const item = self.source.ref().item; 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; return;
} }
@ -961,7 +989,7 @@ pub const Command = struct { // MARK: Command
source: InventoryAndSlot, source: InventoryAndSlot,
desiredAmount: u16 = 0xffff, 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.inv.type == .creative) return;
if(self.source.ref().item == null) return; if(self.source.ref().item == null) return;
if(self.source.inv.type == .crafting) { if(self.source.inv.type == .crafting) {
@ -1017,7 +1045,7 @@ pub const Command = struct { // MARK: Command
item: ?Item, item: ?Item,
amount: u16 = 0, 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.inv.type == .workbench and self.dest.slot == 25) return;
if(!self.dest.ref().empty()) { if(!self.dest.ref().empty()) {
@ -1069,7 +1097,7 @@ pub const Command = struct { // MARK: Command
dest: Inventory, dest: Inventory,
source: 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); std.debug.assert(self.dest.type == .normal);
if(self.source.type == .creative) return; if(self.source.type == .creative) return;
if(self.source.type == .crafting) return; if(self.source.type == .crafting) return;
@ -1129,7 +1157,7 @@ pub const Command = struct { // MARK: Command
const Clear = struct { const Clear = struct {
inv: Inventory, 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 == .creative) return;
if(self.inv.type == .crafting) return; if(self.inv.type == .crafting) return;
var items = self.inv._items; 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) { 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}}); Sync.ClientSide.executeCommand(.{.fillFromCreative = .{.dest = .{.inv = dest, .slot = destSlot}, .item = item, .amount = amount}});
} }
pub fn placeBlock(self: Inventory, slot: u32, unlimitedBlocks: bool) void { pub fn placeBlock(self: Inventory, slot: u32) void {
main.renderer.MeshSelection.placeBlock(&self._items[slot], unlimitedBlocks); main.renderer.MeshSelection.placeBlock(self, slot);
} }
pub fn breakBlock(self: Inventory, slot: u32) void { 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 { pub fn size(self: Inventory) usize {

View File

@ -337,6 +337,10 @@ pub const Block = packed struct { // MARK: Block
pub inline fn opaqueVariant(self: Block) u16 { pub inline fn opaqueVariant(self: Block) u16 {
return _opaqueVariant[self.typ]; 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);
}
}; };

View File

@ -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 pub fn breakBlock() void { // TODO: Breaking animation and tools

View File

@ -1029,6 +1029,7 @@ pub const Tool = struct { // MARK: Tool
.stone => self.pickaxePower, .stone => self.pickaxePower,
.unbreakable => 0, .unbreakable => 0,
.wood => self.axePower, .wood => self.axePower,
.air => 0,
}; };
} }

View File

@ -905,7 +905,7 @@ pub const Protocols = struct {
const z = std.mem.readInt(i32, data[8..12], .big); const z = std.mem.readInt(i32, data[8..12], .big);
const newBlock = Block.fromInt(std.mem.readInt(u32, data[12..16], .big)); const newBlock = Block.fromInt(std.mem.readInt(u32, data[12..16], .big));
if(conn.user != null) { if(conn.user != null) {
main.server.world.?.updateBlock(x, y, z, newBlock); return error.InvalidPacket;
} else { } else {
renderer.mesh_storage.updateBlock(x, y, z, newBlock); renderer.mesh_storage.updateBlock(x, y, z, newBlock);
} }
@ -1162,6 +1162,8 @@ pub const Protocols = struct {
} else { } else {
if(data[0] == 0xff) { // Confirmation if(data[0] == 0xff) { // Confirmation
items.Inventory.Sync.ClientSide.receiveConfirmation(data[1..]); items.Inventory.Sync.ClientSide.receiveConfirmation(data[1..]);
} else if(data[0] == 0xfe) { // Failure
items.Inventory.Sync.ClientSide.receiveFailure();
} else { } else {
try items.Inventory.Sync.ClientSide.receiveSyncOperation(data[1..]); try items.Inventory.Sync.ClientSide.receiveSyncOperation(data[1..]);
} }
@ -1184,6 +1186,10 @@ pub const Protocols = struct {
@memcpy(data[1..], _data); @memcpy(data[1..], _data);
conn.sendImportant(id, 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 { pub fn sendSyncOperation(conn: *Connection, _data: []const u8) void {
std.debug.assert(conn.user != null); std.debug.assert(conn.user != null);
var data = main.stackAllocator.alloc(u8, _data.len + 1); var data = main.stackAllocator.alloc(u8, _data.len + 1);

View File

@ -752,12 +752,11 @@ pub const MeshSelection = struct { // MARK: MeshSelection
return true; // TODO: Check other entities return true; // TODO: Check other entities
} }
pub fn placeBlock(inventoryStack: *main.items.ItemStack, unlimitedBlocks: bool) void { pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void {
const removeAmount: i32 = if(unlimitedBlocks) 0 else -1;
_ = removeAmount; // TODO
if(selectedBlockPos) |selectedPos| { if(selectedBlockPos) |selectedPos| {
var block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; var oldBlock = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return;
if(inventoryStack.item) |item| { var block = oldBlock;
if(inventory.getItem(slot)) |item| {
switch(item) { switch(item) {
.baseItem => |baseItem| { .baseItem => |baseItem| {
if(baseItem.block) |itemBlock| { if(baseItem.block) |itemBlock| {
@ -768,8 +767,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos))); 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(rotationMode.generateData(main.game.world.?, selectedPos, relPos, lastDir, neighborDir, &block, .{.typ = 0, .data = 0}, false)) {
if(!canPlaceBlock(selectedPos, block)) return; if(!canPlaceBlock(selectedPos, block)) return;
updateBlockAndSendUpdate(selectedPos[0], selectedPos[1], selectedPos[2], block); updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], oldBlock, block);
// TODO: _ = inventoryStack.add(item, @as(i32, removeAmount));
return; return;
} }
} }
@ -778,12 +776,12 @@ pub const MeshSelection = struct { // MARK: MeshSelection
neighborDir = selectedPos - posBeforeBlock; neighborDir = selectedPos - posBeforeBlock;
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos)));
const neighborBlock = block; 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(block.typ == itemBlock) {
if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, false)) { if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, false)) {
if(!canPlaceBlock(neighborPos, block)) return; if(!canPlaceBlock(neighborPos, block)) return;
updateBlockAndSendUpdate(neighborPos[0], neighborPos[1], neighborPos[2], block); updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block);
// TODO: _ = inventoryStack.add(item, @as(i32, removeAmount));
return; return;
} }
} else { } else {
@ -792,8 +790,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
block.data = 0; block.data = 0;
if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, true)) { if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, neighborBlock, true)) {
if(!canPlaceBlock(neighborPos, block)) return; if(!canPlaceBlock(neighborPos, block)) return;
updateBlockAndSendUpdate(neighborPos[0], neighborPos[1], neighborPos[2], block); updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block);
// TODO: _ = inventoryStack.add(item, @as(i32, removeAmount));
return; 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| { if(selectedBlockPos) |selectedPos| {
const block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; const block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return;
var newBlock = block; var newBlock = block;
// TODO: Breaking animation and tools. // TODO: Breaking animation and tools.
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos))); 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)) { 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 { fn updateBlockAndSendUpdate(source: main.items.Inventory, slot: u32, x: i32, y: i32, z: i32, oldBlock: blocks.Block, newBlock: blocks.Block) void {
main.network.Protocols.blockUpdate.send(main.game.world.?.conn, x, y, z, newBlock); 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); mesh_storage.updateBlock(x, y, z, newBlock);
} }

View File

@ -60,6 +60,39 @@ pub const RotationMode = struct { // MARK: RotationMode
fn onBlockBreaking(_: ?main.items.Item, _: Vec3f, _: Vec3f, currentData: *Block) void { fn onBlockBreaking(_: ?main.items.Item, _: Vec3f, _: Vec3f, currentData: *Block) void {
currentData.* = .{.typ = 0, .data = 0}; 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. /// 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, 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, 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; var rotationModes: std.StringHashMap(RotationMode) = undefined;
@ -571,6 +606,15 @@ pub const RotationModes = struct {
} }
return RotationMode.DefaultFunctions.onBlockBreaking(item, relativePlayerPos, playerDir, currentData); 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 Torch = struct { // MARK: Torch
pub const id: []const u8 = "torch"; pub const id: []const u8 = "torch";
@ -719,6 +763,21 @@ pub const RotationModes = struct {
currentData.data &= ~bit; currentData.data &= ~bit;
if(currentData.data == 0) currentData.typ = 0; 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 Carpet = struct { // MARK: Carpet
pub const id: []const u8 = "carpet"; pub const id: []const u8 = "carpet";
@ -886,6 +945,10 @@ pub const RotationModes = struct {
currentData.data &= ~bit; currentData.data &= ~bit;
if(currentData.data == 0) currentData.typ = 0; 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);
}
}; };
}; };

View File

@ -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); 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}); 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(); defer baseChunk.decreaseRefCount();
const x: u5 = @intCast(wx & chunk.chunkMask); const x: u5 = @intCast(wx & chunk.chunkMask);
const y: u5 = @intCast(wy & chunk.chunkMask); const y: u5 = @intCast(wy & chunk.chunkMask);
const z: u5 = @intCast(wz & 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; var newBlock = _newBlock;
for(chunk.Neighbor.iterable) |neighbor| { for(chunk.Neighbor.iterable) |neighbor| {
const nx = x + neighbor.relX(); const nx = x + neighbor.relX();
@ -938,6 +949,11 @@ pub const ServerWorld = struct { // MARK: ServerWorld
for(main.server.users.items) |user| { for(main.server.users.items) |user| {
main.network.Protocols.blockUpdate.send(user.conn, wx, wy, wz, _newBlock); 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 { pub fn queueChunkUpdateAndDecreaseRefCount(self: *ServerWorld, ch: *ServerChunk) void {