Squeeze all inventory operations through a simple structure that allows undoing operations.

more progress towards #643
This is the client-side prediction part of the client-server architecture.
This commit is contained in:
IntegratedQuantum 2024-10-18 22:17:47 +02:00
parent 051387e66b
commit b710ccad07
3 changed files with 534 additions and 209 deletions

View File

@ -657,7 +657,7 @@ pub const inventory = struct { // MARK: inventory
carried.dropStack(0);
}
} else {
if(leftClickSlots.items.len != 0) {
if(rightClickSlots.items.len != 0) {
rightClickSlots.clearRetainingCapacity();
} else if(hoveredItemSlot) |hovered| {
hovered.inventory.takeHalf(hovered.itemSlot, carried);

View File

@ -1186,6 +1186,524 @@ pub const ItemStack = struct { // MARK: ItemStack
}
};
pub const InventorySync = struct {
pub const ClientSide = struct {
var mutex: std.Thread.Mutex = .{};
var commands: main.utils.CircularBufferQueue(InventoryCommand) = undefined;
pub fn init() void {
commands = main.utils.CircularBufferQueue(InventoryCommand).init(main.globalAllocator, 256);
}
pub fn deinit() void {
while(commands.dequeue()) |cmd| {
cmd.deinit(main.globalAllocator);
}
commands.deinit();
}
pub fn executeCommand(typ: InventoryCommand.Type, dest: Inventory, destSlot: u32, source: Inventory, sourceSlot: u32, amount: u16, item: ?Item) void {
var cmd: InventoryCommand = .{
.type = typ,
.dest = dest,
.destSlot = destSlot,
.source = source,
.sourceSlot = sourceSlot,
.amount = amount,
.item = item,
};
mutex.lock();
defer mutex.unlock();
cmd.do(main.globalAllocator);
commands.enqueue(cmd);
}
pub fn undo() void {
if(commands.dequeue_front()) |_cmd| {
var cmd = _cmd;
mutex.lock();
defer mutex.unlock();
cmd.undo();
cmd.deinit(main.globalAllocator); // TODO: Return it or something.
}
}
};
pub fn executeCommand(typ: InventoryCommand.Type, dest: Inventory, destSlot: u32, source: Inventory, sourceSlot: u32, amount: u16, item: ?Item) void {
ClientSide.executeCommand(typ, dest, destSlot, source, sourceSlot, amount, item);
}
};
const InventoryCommand = struct {
const Type = enum {
depositOrSwap,
deposit,
takeHalf,
dropStack,
dropOne,
fillFromCreative,
depositOrDrop,
};
const UndoInfo = union(enum) {
move: struct {
dest: Inventory,
destSlot: u32,
source: Inventory,
sourceSlot: u32,
amount: u16,
},
swap: struct {
dest: Inventory,
destSlot: u32,
source: Inventory,
sourceSlot: u32,
},
delete: struct {
source: Inventory,
sourceSlot: u32,
item: ItemStack,
},
create: struct {
dest: Inventory,
destSlot: u32,
amount: u16,
},
};
type: Type,
dest: Inventory,
destSlot: u32,
source: Inventory,
sourceSlot: u32,
amount: u16,
item: ?Item,
undoSteps: main.ListUnmanaged(UndoInfo) = .{},
fn do(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
std.debug.assert(self.undoSteps.items.len == 0); // do called twice without cleaning up
switch(self.type) {
.depositOrSwap => {
self.depositOrSwap(allocator);
},
.deposit => {
self.deposit(allocator);
},
.takeHalf => {
self.takeHalf(allocator);
},
.dropStack => {
self.dropStack(allocator);
},
.dropOne => {
self.dropOne(allocator);
},
.fillFromCreative => {
self.fillFromCreative(allocator);
},
.depositOrDrop => {
self.depositOrDrop(allocator);
}
}
}
fn undo(self: *InventoryCommand) void {
// Iterating in reverse order!
while(self.undoSteps.popOrNull()) |step| {
switch(step) {
.move => |info| {
if(info.amount == 0) continue;
std.debug.assert(std.meta.eql(info.source._items[info.sourceSlot].item, info.dest._items[info.destSlot].item) or info.source._items[info.sourceSlot].item == null);
info.source._items[info.sourceSlot].item = info.dest._items[info.destSlot].item;
info.source._items[info.sourceSlot].amount += info.amount;
info.dest._items[info.destSlot].amount -= info.amount;
if(info.dest._items[info.destSlot].amount == 0) {
info.dest._items[info.destSlot].item = null;
}
info.source.update();
info.dest.update();
},
.swap => |info| {
const temp = info.dest._items[info.destSlot];
info.dest._items[info.destSlot] = info.source._items[info.sourceSlot];
info.source._items[info.sourceSlot] = temp;
info.source.update();
info.dest.update();
},
.delete => |info| {
std.debug.assert(info.source._items[info.sourceSlot].item == null or std.meta.eql(info.source._items[info.sourceSlot].item, info.item.item));
info.source._items[info.sourceSlot].item = info.item.item;
info.source._items[info.sourceSlot].amount += info.item.amount;
info.source.update();
},
.create => |info| {
std.debug.assert(info.dest._items[info.destSlot].amount >= info.amount);
info.dest._items[info.destSlot].amount -= info.amount;
if(info.dest._items[info.destSlot].amount == 0) {
info.dest._items[info.destSlot].item = null;
}
info.dest.update();
},
}
}
self.undoSteps.clearRetainingCapacity();
}
fn deinit(self: InventoryCommand, allocator: NeverFailingAllocator) void {
self.undoSteps.deinit(allocator);
}
fn removeToolCraftingIngredients(self: *InventoryCommand, allocator: NeverFailingAllocator, inv: Inventory) void {
std.debug.assert(inv.type == .workbench);
for(0..25) |i| {
if(inv._items[i].amount != 0) {
inv._items[i].amount -= 1;
self.undoSteps.append(allocator, .{.delete = .{
.source = inv,
.sourceSlot = @intCast(i),
.item = .{
.item = inv._items[i].item,
.amount = 1,
},
}});
if(inv._items[i].amount == 0) {
inv._items[i].item = null;
}
}
}
}
fn tryCraftingTo(self: *InventoryCommand, allocator: NeverFailingAllocator, dest: Inventory, destSlot: u32, source: Inventory, sourceSlot: u32) void {
std.debug.assert(source.type == .crafting);
std.debug.assert(dest.type == .normal);
if(sourceSlot != source._items.len - 1) return;
if(dest._items[destSlot].item != null and !std.meta.eql(dest._items[destSlot].item, source._items[sourceSlot].item)) return;
if(dest._items[destSlot].amount + source._items[sourceSlot].amount > source._items[sourceSlot].item.?.stackSize()) return;
// Can we even craft it?
for(source._items[0..sourceSlot]) |requiredStack| {
var amount: usize = 0;
// There might be duplicate entries:
for(source._items[0..sourceSlot]) |otherStack| {
if(std.meta.eql(requiredStack.item, otherStack.item))
amount += otherStack.amount;
}
for(main.game.Player.inventory._items) |otherStack| {
if(std.meta.eql(requiredStack.item, otherStack.item))
amount -|= otherStack.amount;
}
// Not enough ingredients
if(amount != 0)
return;
}
// Craft it
for(source._items[0..sourceSlot]) |requiredStack| {
var remainingAmount: usize = requiredStack.amount;
for(main.game.Player.inventory._items, 0..) |*otherStack, i| {
if(std.meta.eql(requiredStack.item, otherStack.item)) {
const amount = @min(remainingAmount, otherStack.amount);
otherStack.amount -= amount;
remainingAmount -= amount;
self.undoSteps.append(allocator, .{.delete = .{
.source = main.game.Player.inventory,
.sourceSlot = @intCast(i),
.item = .{
.item = otherStack.item,
.amount = amount,
},
}});
if(otherStack.amount == 0) otherStack.item = null;
if(remainingAmount == 0) break;
}
}
std.debug.assert(remainingAmount == 0);
}
dest._items[destSlot].item = source._items[sourceSlot].item;
dest._items[destSlot].amount += source._items[sourceSlot].amount;
self.undoSteps.append(allocator, .{.create = .{
.dest = dest,
.destSlot = destSlot,
.amount = source._items[sourceSlot].amount,
}});
}
fn depositOrSwap(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
std.debug.assert(self.source.type == .normal and self.dest.type != .creative);
if(self.dest.type == .crafting) {
self.tryCraftingTo(allocator, self.source, self.sourceSlot, self.dest, self.destSlot);
return;
}
if(self.dest.type == .workbench and self.destSlot == 25) {
if(self.source._items[self.sourceSlot].item == null and self.dest._items[self.destSlot].item != null) {
self.source._items[self.sourceSlot] = self.dest._items[self.destSlot];
self.undoSteps.append(allocator, .{.create = .{
.dest = self.source,
.destSlot = self.sourceSlot,
.amount = 1,
}});
self.dest._items[self.destSlot] = .{.item = null, .amount = 0};
self.removeToolCraftingIngredients(allocator, self.dest);
self.dest.update();
}
return;
}
defer self.dest.update();
if(self.dest._items[self.destSlot].item) |itemDest| {
if(self.source._items[self.sourceSlot].item) |itemSource| {
if(std.meta.eql(itemDest, itemSource)) {
if(self.dest._items[self.destSlot].amount >= itemDest.stackSize()) return;
const amount = @min(itemDest.stackSize() - self.dest._items[self.destSlot].amount, self.source._items[self.sourceSlot].amount);
self.dest._items[self.destSlot].amount += amount;
self.source._items[self.sourceSlot].amount -= amount;
if(self.source._items[self.sourceSlot].amount == 0) self.source._items[self.sourceSlot].item = null;
self.undoSteps.append(allocator, .{.move = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
.amount = amount,
}});
return;
}
}
}
const temp = self.dest._items[self.destSlot];
self.dest._items[self.destSlot] = self.source._items[self.sourceSlot];
self.source._items[self.sourceSlot] = temp;
self.undoSteps.append(allocator, .{.swap = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
}});
}
fn deposit(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
std.debug.assert(self.source.type == .normal);
if(self.dest.type == .creative) return;
if(self.dest.type == .crafting) return;
if(self.dest.type == .workbench and self.destSlot == 25) return;
defer self.dest.update();
const itemSource = self.source._items[self.sourceSlot].item orelse return;
if(self.dest._items[self.destSlot].item) |itemDest| {
if(std.meta.eql(itemDest, itemSource)) {
if(self.dest._items[self.destSlot].amount >= itemDest.stackSize()) return;
const amount = @min(itemDest.stackSize() - self.dest._items[self.destSlot].amount, self.amount);
self.dest._items[self.destSlot].amount += amount;
self.source._items[self.sourceSlot].amount -= amount;
if(self.source._items[self.sourceSlot].amount == 0) self.source._items[self.sourceSlot].item = null;
self.undoSteps.append(allocator, .{.move = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
.amount = amount,
}});
}
} else {
self.dest._items[self.destSlot].item = self.source._items[self.sourceSlot].item;
self.dest._items[self.destSlot].amount = self.amount;
self.source._items[self.sourceSlot].amount -= self.amount;
if(self.source._items[self.sourceSlot].amount == 0) self.source._items[self.sourceSlot].item = null;
self.undoSteps.append(allocator, .{.move = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
.amount = self.amount,
}});
}
}
fn takeHalf(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
std.debug.assert(self.dest.type == .normal);
if(self.source.type == .creative) {
if(self.dest._items[self.destSlot].item == null) {
self.item = self.source._items[self.sourceSlot].item;
self.fillFromCreative(allocator);
}
return;
}
if(self.source.type == .crafting) {
self.tryCraftingTo(allocator, self.dest, self.destSlot, self.source, self.sourceSlot);
return;
}
if(self.source.type == .workbench and self.sourceSlot == 25) {
if(self.dest._items[self.destSlot].item == null and self.source._items[self.sourceSlot].item != null) {
self.dest._items[self.destSlot] = self.source._items[self.sourceSlot];
self.undoSteps.append(allocator, .{.create = .{
.dest = self.dest,
.destSlot = self.destSlot,
.amount = 1,
}});
self.source._items[self.sourceSlot] = .{.item = null, .amount = 0};
self.removeToolCraftingIngredients(allocator, self.source);
self.source.update();
}
return;
}
defer self.source.update();
const itemSource = self.source._items[self.sourceSlot].item orelse return;
const desiredAmount = (1 + self.source._items[self.sourceSlot].amount)/2;
if(self.dest._items[self.destSlot].item) |itemDest| {
if(std.meta.eql(itemDest, itemSource)) {
if(self.dest._items[self.destSlot].amount >= itemDest.stackSize()) return;
const amount = @min(itemDest.stackSize() - self.dest._items[self.destSlot].amount, desiredAmount);
self.dest._items[self.destSlot].amount += amount;
self.source._items[self.sourceSlot].amount -= amount;
if(self.source._items[self.sourceSlot].amount == 0) self.source._items[self.sourceSlot].item = null;
self.undoSteps.append(allocator, .{ .move = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
.amount = amount,
}});
}
} else {
self.dest._items[self.destSlot].item = self.source._items[self.sourceSlot].item;
self.dest._items[self.destSlot].amount = desiredAmount;
self.source._items[self.sourceSlot].amount -= desiredAmount;
if(self.source._items[self.sourceSlot].amount == 0) self.source._items[self.sourceSlot].item = null;
self.undoSteps.append(allocator, .{ .move = .{
.dest = self.dest,
.destSlot = self.destSlot,
.source = self.source,
.sourceSlot = self.sourceSlot,
.amount = desiredAmount,
}});
}
}
pub fn dropStack(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
if(self.source.type == .creative) return;
if(self.source._items[self.sourceSlot].item == null) return;
if(self.source.type == .crafting) {
if(self.sourceSlot != self.source._items.len - 1) return;
var _items: [1]ItemStack = .{.{.item = null, .amount = 0}};
const temp: Inventory = .{
.type = .normal,
._items = &_items,
};
self.tryCraftingTo(allocator, temp, 0, self.source, self.sourceSlot);
std.debug.assert(self.undoSteps.pop().create.dest._items.ptr == temp._items.ptr); // Remove the extra step from undo list (we cannot undo dropped items)
if(_items[0].item != null) {
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, _items[0], @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
}
return;
}
if(self.source.type == .workbench and self.sourceSlot == 25) {
self.removeToolCraftingIngredients(allocator, self.source);
}
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, self.source.getStack(self.sourceSlot), @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
self.undoSteps.append(allocator, .{.delete = .{
.source = self.source,
.sourceSlot = self.sourceSlot,
.item = self.source._items[self.sourceSlot],
}});
self.source._items[self.sourceSlot].clear();
self.source.update();
}
pub fn dropOne(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
if((self.source.type == .workbench or self.source.type == .crafting) and self.sourceSlot == self.source._items.len - 1) {
self.dropStack(allocator);
}
if(self.source.type == .crafting) return;
if(self.source._items[self.sourceSlot].item == null) return;
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, .{.item = self.source.getStack(self.sourceSlot).item, .amount = 1}, @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
self.undoSteps.append(allocator, .{.delete = .{
.source = self.source,
.sourceSlot = self.sourceSlot,
.item = .{.item = self.source._items[self.sourceSlot].item, .amount = 1},
}});
if(self.source._items[self.sourceSlot].amount == 1) {
self.source._items[self.sourceSlot].clear();
} else {
self.source._items[self.sourceSlot].amount -= 1;
}
self.source.update();
}
pub fn fillFromCreative(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
if(self.dest.type == .crafting) return;
if(self.dest.type == .workbench and self.destSlot == 25) return;
if(!self.dest._items[self.destSlot].empty()) {
self.undoSteps.append(allocator, .{.delete = .{
.source = self.dest,
.sourceSlot = self.destSlot,
.item = self.dest._items[self.destSlot],
}});
self.dest._items[self.destSlot].clear();
}
if(self.item) |_item| {
self.dest._items[self.destSlot].item = _item;
self.dest._items[self.destSlot].amount = _item.stackSize();
self.undoSteps.append(allocator, .{.create = .{
.dest = self.dest,
.destSlot = self.destSlot,
.amount = _item.stackSize(),
}});
}
self.dest.update();
}
pub fn depositOrDrop(self: *InventoryCommand, allocator: NeverFailingAllocator) void {
std.debug.assert(self.dest.type == .normal);
if(self.source.type == .creative) return;
if(self.source.type == .crafting) return;
defer self.dest.update();
defer self.source.update();
var sourceItems = self.source._items;
if(self.source.type == .workbench) sourceItems = self.source._items[0..25];
outer: for(sourceItems, 0..) |*sourceStack, sourceSlot| {
if(sourceStack.item == null) continue;
for(self.dest._items, 0..) |*destStack, destSlot| {
if(std.meta.eql(destStack.item, sourceStack.item)) {
const amount = @min(destStack.item.?.stackSize() - destStack.amount, sourceStack.amount);
destStack.amount += amount;
sourceStack.amount -= amount;
self.undoSteps.append(allocator, .{.move = .{
.dest = self.dest,
.destSlot = @intCast(destSlot),
.source = self.source,
.sourceSlot = @intCast(sourceSlot),
.amount = amount,
}});
if(sourceStack.amount == 0) {
sourceStack.item = null;
continue :outer;
}
}
}
for(self.dest._items, 0..) |*destStack, destSlot| {
if(destStack.item == null) {
destStack.* = sourceStack.*;
sourceStack.amount = 0;
sourceStack.item = null;
self.undoSteps.append(allocator, .{.swap = .{
.dest = self.dest,
.destSlot = @intCast(destSlot),
.source = self.source,
.sourceSlot = @intCast(sourceSlot),
}});
continue :outer;
}
}
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, sourceStack.*, @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
self.undoSteps.append(allocator, .{.delete = .{
.source = self.source,
.sourceSlot = @intCast(sourceSlot),
.item = self.source._items[sourceSlot],
}});
sourceStack.clear();
}
}
};
pub const Inventory = struct { // MARK: Inventory
const Type = enum {
normal,
@ -1235,152 +1753,19 @@ pub const Inventory = struct { // MARK: Inventory
}
}
fn removeToolCraftingIngredients(self: Inventory) void {
std.debug.assert(self.type == .workbench);
for(0..25) |i| {
if(self._items[i].amount != 0) {
self._items[i].amount -= 1;
if(self._items[i].amount == 0) {
self._items[i].item = null;
}
}
}
}
fn tryCraftingToCarried(inv: Inventory, slot: u32, carried: Inventory) void {
std.debug.assert(inv.type == .crafting);
if(slot != inv._items.len - 1) return;
if(carried._items[0].item != null and !std.meta.eql(carried._items[0].item, inv._items[slot].item)) return;
if(carried._items[0].amount + inv._items[slot].amount > inv._items[slot].item.?.stackSize()) return;
// Can we even craft it?
for(inv._items[0..slot]) |requiredStack| {
var amount: usize = 0;
// There might be duplicate entries:
for(inv._items[0..slot]) |otherStack| {
if(std.meta.eql(requiredStack.item, otherStack.item))
amount += otherStack.amount;
}
for(main.game.Player.inventory._items) |otherStack| {
if(std.meta.eql(requiredStack.item, otherStack.item))
amount -|= otherStack.amount;
}
// Not enough ingredients
if(amount != 0)
return;
}
// Craft it
for(inv._items[0..slot]) |requiredStack| {
var remainingAmount: usize = requiredStack.amount;
for(main.game.Player.inventory._items) |*otherStack| {
if(std.meta.eql(requiredStack.item, otherStack.item)) {
const amount = @min(remainingAmount, otherStack.amount);
otherStack.amount -= amount;
remainingAmount -= amount;
if(otherStack.amount == 0) otherStack.item = null;
if(remainingAmount == 0) break;
}
}
std.debug.assert(remainingAmount == 0);
}
carried._items[0].item = inv._items[slot].item;
carried._items[0].amount += inv._items[slot].amount;
}
pub fn depositOrSwap(dest: Inventory, destSlot: u32, carried: Inventory) void {
if(dest.type == .creative) {
return fillFromCreative(carried, 0, dest._items[destSlot].item);
}
if(dest.type == .crafting) {
tryCraftingToCarried(dest, destSlot, carried);
return;
}
if(dest.type == .workbench and destSlot == 25) {
if(carried._items[0].item == null and dest._items[destSlot].item != null) {
carried._items[0] = dest._items[destSlot];
dest._items[destSlot] = .{.item = null, .amount = 0};
dest.removeToolCraftingIngredients();
dest.update();
}
return;
}
defer dest.update();
if(dest._items[destSlot].item) |itemDest| {
if(carried._items[0].item) |itemSource| {
if(std.meta.eql(itemDest, itemSource)) {
if(dest._items[destSlot].amount >= itemDest.stackSize()) return;
const amount = @min(itemDest.stackSize() - dest._items[destSlot].amount, carried._items[0].amount);
dest._items[destSlot].amount += amount;
carried._items[0].amount -= amount;
if(carried._items[0].amount == 0) carried._items[0].item = null;
return;
}
}
}
const temp = dest._items[destSlot];
dest._items[destSlot] = carried._items[0];
carried._items[0] = temp;
InventorySync.executeCommand(.depositOrSwap, dest, destSlot, carried, 0, undefined, undefined);
}
pub fn deposit(dest: Inventory, destSlot: u32, carried: Inventory, amount: u16) void {
if(dest.type == .creative) return;
if(dest.type == .crafting) return;
if(dest.type == .workbench and destSlot == 25) return;
defer dest.update();
const itemSource = carried._items[0].item orelse return;
if(dest._items[destSlot].item) |itemDest| {
if(std.meta.eql(itemDest, itemSource)) {
if(dest._items[destSlot].amount >= itemDest.stackSize()) return;
dest._items[destSlot].amount += amount;
carried._items[0].amount -= amount;
if(carried._items[0].amount == 0) carried._items[0].item = null;
}
} else {
dest._items[destSlot].item = carried._items[0].item;
dest._items[destSlot].amount = amount;
carried._items[0].amount -= amount;
if(carried._items[0].amount == 0) carried._items[0].item = null;
}
InventorySync.executeCommand(.deposit, dest, destSlot, carried, 0, amount, undefined);
}
pub fn takeHalf(source: Inventory, sourceSlot: u32, carried: Inventory) void {
if(source.type == .creative) {
if(carried._items[0].item == null) {
fillFromCreative(carried, 0, source._items[sourceSlot].item);
}
return;
}
if(source.type == .crafting) {
tryCraftingToCarried(source, sourceSlot, carried);
return;
}
if(source.type == .workbench and sourceSlot == 25) {
if(carried._items[0].item == null and source._items[sourceSlot].item != null) {
carried._items[0] = source._items[sourceSlot];
source._items[sourceSlot] = .{.item = null, .amount = 0};
source.removeToolCraftingIngredients();
source.update();
}
return;
}
defer source.update();
const itemSource = source._items[sourceSlot].item orelse return;
const desiredAmount = (1 + source._items[sourceSlot].amount)/2;
if(carried._items[0].item) |itemDest| {
if(std.meta.eql(itemDest, itemSource)) {
if(carried._items[0].amount >= itemDest.stackSize()) return;
const amount = @min(itemDest.stackSize() - carried._items[0].amount, desiredAmount);
carried._items[0].amount += amount;
source._items[sourceSlot].amount -= amount;
if(source._items[sourceSlot].amount == 0) source._items[sourceSlot].item = null;
}
} else {
carried._items[0].item = source._items[sourceSlot].item;
carried._items[0].amount = desiredAmount;
source._items[sourceSlot].amount -= desiredAmount;
if(source._items[sourceSlot].amount == 0) source._items[sourceSlot].item = null;
}
InventorySync.executeCommand(.takeHalf, carried, 0, source, sourceSlot, undefined, undefined);
}
pub fn distribute(carried: Inventory, destinationInventories: []const Inventory, destinationSlots: []const u32) void {
@ -1392,87 +1777,19 @@ pub const Inventory = struct { // MARK: Inventory
}
pub fn depositOrDrop(dest: Inventory, source: Inventory) void {
std.debug.assert(dest.type == .normal);
if(source.type == .creative) return;
if(source.type == .crafting) return;
defer dest.update();
defer source.update();
var sourceItems = source._items;
if(source.type == .workbench) sourceItems = source._items[0..25];
outer: for(sourceItems) |*sourceStack| {
if(sourceStack.item == null) continue;
for(dest._items) |*destStack| {
if(std.meta.eql(destStack.item, sourceStack.item)) {
const amount = @min(destStack.item.?.stackSize() - destStack.amount, sourceStack.amount);
destStack.amount += amount;
sourceStack.amount -= amount;
if(sourceStack.amount == 0) {
sourceStack.item = null;
continue :outer;
}
}
}
for(dest._items) |*destStack| {
if(destStack.item == null) {
destStack.* = sourceStack.*;
sourceStack.amount = 0;
sourceStack.item = null;
continue :outer;
}
}
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, sourceStack.*, @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
sourceStack.clear();
}
InventorySync.executeCommand(.depositOrDrop, dest, undefined, source, undefined, undefined, undefined);
}
pub fn dropStack(source: Inventory, sourceSlot: u32) void {
if(source.type == .creative) return;
if(source._items[sourceSlot].item == null) return;
if(source.type == .crafting) {
var _items: [1]ItemStack = .{.{.item = null, .amount = 0}};
const temp: Inventory = .{
.type = .normal,
._items = &_items,
};
tryCraftingToCarried(source, sourceSlot, temp);
if(_items[0].item != null) {
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, _items[0], @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
}
return;
}
if(source.type == .workbench and sourceSlot == 25) {
source.removeToolCraftingIngredients();
}
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, source.getStack(sourceSlot), @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
source._items[sourceSlot].clear();
source.update();
InventorySync.executeCommand(.dropStack, undefined, undefined, source, sourceSlot, undefined, undefined);
}
pub fn dropOne(source: Inventory, sourceSlot: u32) void {
if(source.type == .creative) return;
if(source.type == .crafting) return;
if(source._items[sourceSlot].item == null) return;
if(source.type == .workbench and sourceSlot == 25) {
source.removeToolCraftingIngredients();
}
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, .{.item = source.getStack(sourceSlot).item, .amount = 1}, @floatCast(main.game.Player.getPosBlocking()), main.game.camera.direction, 20);
if(source._items[sourceSlot].amount == 1) {
source._items[sourceSlot].clear();
} else {
source._items[sourceSlot].amount -= 1;
}
source.update();
InventorySync.executeCommand(.dropOne, undefined, undefined, source, sourceSlot, undefined, undefined);
}
pub fn fillFromCreative(dest: Inventory, destSlot: u32, item: ?Item) void {
if(dest.type == .crafting) return;
if(dest.type == .workbench and destSlot == 25) return;
dest._items[destSlot].clear();
if(item) |_item| {
dest._items[destSlot].item = _item;
dest._items[destSlot].amount = _item.stackSize();
}
dest.update();
InventorySync.executeCommand(.fillFromCreative, dest, destSlot, undefined, undefined, undefined, item);
}
pub fn placeBlock(self: Inventory, slot: u32, unlimitedBlocks: bool) void {
@ -1556,6 +1873,7 @@ pub fn globalInit() void {
reverseIndices = .init(arena.allocator().allocator);
recipeList = .init(arena.allocator());
itemListSize = 0;
InventorySync.ClientSide.init();
}
pub fn register(_: []const u8, texturePath: []const u8, replacementTexturePath: []const u8, id: []const u8, zon: ZonElement) *BaseItem {
@ -1656,6 +1974,7 @@ pub fn deinit() void {
reverseIndices.clearAndFree();
recipeList.clearAndFree();
arena.deinit();
InventorySync.ClientSide.deinit();
}
pub fn getByID(id: []const u8) ?*BaseItem {

View File

@ -366,6 +366,12 @@ pub fn CircularBufferQueue(comptime T: type) type { // MARK: CircularBufferQueue
return result;
}
pub fn dequeue_front(self: *Self) ?T {
if(self.startIndex == self.endIndex) return null;
self.endIndex = (self.endIndex -% 1) & self.mask;
return self.mem[self.endIndex];
}
pub fn peek(self: *Self) ?T {
if(self.startIndex == self.endIndex) return null;
return self.mem[self.startIndex];