Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/cubyz/ui/inventory/sort_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/Inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Item = main.items.Item;
const ItemStack = main.items.ItemStack;
const ProceduralItem = main.items.ProceduralItem;
const utils = main.utils;
const Tag = main.Tag;
const BinaryWriter = utils.BinaryWriter;
const BinaryReader = utils.BinaryReader;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
Expand Down Expand Up @@ -531,6 +532,67 @@ pub const ClientInventory = struct { // MARK: ClientInventory
main.sync.client.executeCommand(.{.craftProceduralItem = .init(destinations, workbenchInv)});
}

pub fn sortItems(source: ClientInventory, ignoredSlotCount: usize) void {
compressItems(source);
const ctx: SortContext = .{.inv = source};
std.sort.insertionContext(ignoredSlotCount, source.super._items.len, ctx);
}

pub fn compressItems(source: ClientInventory) void {
for (source.super._items, 0..) |invStack, slot| {
for (source.super._items, 0..) |checkedInvStack, checkedSlot| {
Comment thread
Crepestrom marked this conversation as resolved.
if (checkedSlot < slot) continue;
if (std.meta.eql(invStack.item, checkedInvStack.item)) {
main.sync.client.executeCommand(.{.deposit = .{.dest = .{.inv = source.super, .slot = @intCast(checkedSlot)}, .source = .{.inv = source.super, .slot = @intCast(slot)}, .amount = checkedInvStack.amount}});
}
}
}
}

const SortContext = struct {
inv: ClientInventory,

pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
const itemA: Item = ctx.inv.getItem(a);
const itemB: Item = ctx.inv.getItem(b);

if (itemA == .null) return false;
if (itemB == .null) return true;
if ((itemA != .proceduralItem) and (itemB == .proceduralItem)) return false;
if ((itemA == .proceduralItem) and (itemB != .proceduralItem)) return true;

const itemATags = getTagsFromItem(itemA);
const itemBTags = getTagsFromItem(itemB);

for (0..@min(itemATags.len, itemBTags.len)) |i| {
if (itemATags[i] == itemBTags[i]) continue;
return std.mem.lessThan(u8, itemATags[i].getName(), itemBTags[i].getName());
}
if (itemATags.len != itemBTags.len) return itemATags.len < itemBTags.len;
Comment on lines +567 to +571

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably not the best thing (I know it is from me but it was at that time made up on the spot). We probably need to enforce that the tags are sorted so that for example when ItemA has a,b tags and ItemB has c,a,b It gets compared with a,b and a,b,c. Either by sorting them when we load the tags or here. Or some other way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely on load.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do this I think it would cause read order issues if I make it check if the first tag of ItemA exists in ItemB because they could have different orders
I could fix this with forcing them to read based on the enum int value but I like what I have now because it gives control to developers on how items are catogorized and sorted

you could create tags that are used in nothing but sorting so that all flowers are sorted together under the category of cuttable

@Wunka Wunka Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do this I think it would cause read order issues if I make it check if the first tag of ItemA exists in ItemB because they could have different orders I could fix this with forcing them to read based on the enum int value but I like what I have now because it gives control to developers on how items are catogorized and sorted

you could create tags that are used in nothing but sorting so that all flowers are sorted together under the category of cuttable

We didn't mean that. We meant that the tags should be already sorted so what you at the start said doesn't need to be done. This sorting algorithm could then also be changed by a user if they want to.
You just need to add in src/tag.zig

pub fn loadTagsFromZon(_allocator: main.heap.NeverFailingAllocator, zon: main.ZonElement) []Tag {
	const result = _allocator.alloc(Tag, zon.toSlice().len);
	for (zon.toSlice(), 0..) |tagZon, i| {
		result[i] = Tag.find(tagZon.as([]const u8) orelse blk: {
			std.log.err("Tag array field {s} has incorrect type, expected string", .{@tagName(tagZon)});
			break :blk "incorrect";
		});
	}
+	// some kind of sorting before returning the result
	return result;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t understand what you mean by sorting the tags

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see
You mean so that
C A B
B A C
C B A

all get sorted together A B C
Right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see You mean so that C A B B A C C B A

all get sorted together A B C Right?

yes exactly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No
Id rather give the developer control on sorting rather than enforce is based on the order tags were read into memory
Even if that means mistakes can make sorting look weird

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the sorting of the tags is also clientside. So it can also be changed.

if ((itemA == .proceduralItem) and (itemB == .proceduralItem)) {
std.log.debug("checking durability", .{});
return (itemA.proceduralItem.durability > itemB.proceduralItem.durability);
}

return std.mem.lessThan(u8, itemA.id().?, itemB.id().?);
Comment thread
Crepestrom marked this conversation as resolved.
}

pub fn swap(ctx: @This(), a: usize, b: usize) void {
main.sync.client.executeCommand(.{.depositOrSwap = .{
.dest = .{.inv = ctx.inv.super, .slot = @intCast(a)},
.source = .{.inv = ctx.inv.super, .slot = @intCast(b)},
}});
}
};

pub fn getTagsFromItem(givenItem: Item) []const Tag {
switch (givenItem) {
.null => return &[_]Tag{},
.proceduralItem => return givenItem.proceduralItem.type.tags(),
.baseItem => return givenItem.baseItem.tags(),
}
}
Comment on lines +588 to +594

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the Item struct and use a switch(self) so you don't need the else at the end


pub fn placeBlock(self: ClientInventory, slot: u32) void {
std.debug.assert(self.type == .serverShared);
main.renderer.MeshSelection.placeBlock(self, slot);
Expand Down
1 change: 1 addition & 0 deletions src/assets.zig
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ fn assignBlockItem(stringId: []const u8) !void {
const index = items.BaseItemIndex.fromId(stringId) orelse unreachable;
const item = &items.itemList[@intFromEnum(index)];
item.block = block;
item.tags = blocks.parseBlock(stringId).tags();
}

fn registerBiome(numericId: u32, stringId: []const u8, zon: ZonElement) void {
Expand Down
2 changes: 1 addition & 1 deletion src/blocks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 {
const rotation_tags = _mode[size].getBlockTags();
const block_tags = Tag.loadTagsFromZon(main.stackAllocator, zon.getChild("tags"));
defer main.stackAllocator.free(block_tags);
_tags[size] = std.mem.concat(main.worldArena.allocator, Tag, &.{rotation_tags, block_tags}) catch unreachable;
_tags[size] = std.mem.concat(main.worldArena.allocator, Tag, &.{block_tags, rotation_tags}) catch unreachable;

if (_tags[size].len == 0) std.log.err("Block {s} is missing 'tags' field", .{id});
for (_tags[size]) |tag| {
Expand Down
13 changes: 12 additions & 1 deletion src/gui/windows/inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ pub var window = GuiWindow{
const padding: f32 = 8;

var craftingIcon: Texture = undefined;
var sortIcon: Texture = undefined;

pub fn init() void {
craftingIcon = Texture.initFromFile("assets/cubyz/ui/inventory/crafting_icon.png");
sortIcon = Texture.initFromFile("assets/cubyz/ui/inventory/sort_icon.png");
}

pub fn deinit() void {
Expand All @@ -41,14 +43,23 @@ pub fn deinit() void {

var itemSlots: [20]*ItemSlot = undefined;

pub fn sortItems(target: main.items.Inventory.ClientInventory) void {
target.sortItems(12);
}


pub fn onOpen() void {
const sortCallback: main.callbacks.SimpleCallback = .{.inner = @ptrCast(&sortItems), .data = &Player.inventory};
window.titleBar = HorizontalList.init();
window.titleBar.?.add(Button.initIcon(.{0, 0}, .{9, 9}, sortIcon, false, .{.onAction = sortCallback}));

const list = VerticalList.init(.{padding, padding + 16}, 300, 0);
// Some miscellanious slots and buttons:
// TODO: armor slots, backpack slot + stack-based backpack inventory, other items maybe?
{
const row = HorizontalList.init();
blk: {
row.add(GuiComponent.BagSlot.init(.{0, 0}, main.entity.components.@"cubyz:bag".client.getBag(main.game.Player.id) orelse break :blk));
row.add(GuiComponent.BagSlot.init(.{32, 0}, main.entity.components.@"cubyz:bag".client.getBag(main.game.Player.id) orelse break :blk));
}
row.add(Button.initIcon(.{32, 0}, .{32, 32}, craftingIcon, true, .{.onAction = gui.openWindowCallback("inventory_crafting")}));
list.add(row);
Expand Down
Loading