From b40191cbd984e50980800ab610327bb9ebfd71b0 Mon Sep 17 00:00:00 2001 From: Vinywar123 Date: Wed, 15 Jul 2026 18:45:21 -0700 Subject: [PATCH 1/5] finish --- assets/cubyz/recipes/cloth_recipes.zig.zon | 6 +++++ .../block/client/open_crafting_window.zig | 25 +++++++++++++++++++ src/gui/windows/inventory_crafting.zig | 19 ++++++++++++++ src/items.zig | 20 ++++++++++++++- src/items/recipes.zig | 15 ++++++++--- src/tag.zig | 1 + 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 src/callbacks/block/client/open_crafting_window.zig diff --git a/assets/cubyz/recipes/cloth_recipes.zig.zon b/assets/cubyz/recipes/cloth_recipes.zig.zon index 9a5e00ffa3..2b2f19a71d 100644 --- a/assets/cubyz/recipes/cloth_recipes.zig.zon +++ b/assets/cubyz/recipes/cloth_recipes.zig.zon @@ -4,4 +4,10 @@ .output = "cubyz:cloth/block/{color}", .reversible = true, }, + .{ + .inputs = .{"4 cubyz:copper_ingot"}, + .output = "cubyz:silver_ingot", + .reversible = true, + .craftingTags = .{.alchemy} + }, } diff --git a/src/callbacks/block/client/open_crafting_window.zig b/src/callbacks/block/client/open_crafting_window.zig new file mode 100644 index 0000000000..e7270cb881 --- /dev/null +++ b/src/callbacks/block/client/open_crafting_window.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +const main = @import("main"); +const Block = main.blocks.Block; +const vec = main.vec; +const Vec3i = vec.Vec3i; +const ZonElement = main.ZonElement; + +craftingTags: []main.Tag, + +pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { + const result = main.worldArena.create(@This()); + const craftingTags = main.Tag.loadTagsFromZon(main.stackAllocator, zon.getChild("craftingTags")); + if (craftingTags.len == 0) std.log.err("Missing craftingTags \"name\" for open_crafting_window event.", .{}) + result.* = .{ + .craftingTags = craftingTags + }; + return result; +} + +pub fn run(self: *@This(), _: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { + main.gui.windowlist.inventory_crafting.openFromCallback(craftingTags) + main.Window.setMouseGrabbed(false); + return .handled; +} diff --git a/src/gui/windows/inventory_crafting.zig b/src/gui/windows/inventory_crafting.zig index 9224473a95..ba56aab8f7 100644 --- a/src/gui/windows/inventory_crafting.zig +++ b/src/gui/windows/inventory_crafting.zig @@ -37,13 +37,24 @@ var itemAmount: main.ListManaged(u32) = undefined; var inventories: main.ListManaged(ClientInventory) = undefined; pub var arrowTexture: Texture = undefined; +pub var craftableFromTags: []main.Tag = undefined; pub fn init() void { arrowTexture = Texture.initFromFile("assets/cubyz/ui/inventory/crafting_arrow.png"); + craftableFromTags = main.stackAllocator.alloc(main.Tag, 1); + craftableFromTags[0] = main.Tag.handCraftable; } pub fn deinit() void { arrowTexture.deinit(); + main.stackAllocator.free(craftableFromTags); +} + +pub fn openFromCallback(craftingTags: []main.Tag) void { + gui.closeWindowFromRef(&window); + gui.openWindowFromRef(&window); + craftableFromTags = craftingTags; + main.Window.setMouseGrabbed(false); } fn addItemStackToAvailable(itemStack: ItemStack) void { @@ -93,6 +104,14 @@ fn findAvailableRecipes(list: *VerticalList) bool { } continue :outer; // Ingredient not found. } + middle: for (recipe.craftingTags) |tag| { + for (craftableFromTags) |craftingStationTag| { + if (tag == craftingStationTag) { + continue :middle; + } + } + continue :outer; // Ingredient not found or not craftable by this. + } // All ingredients found: Add it to the list. const inv = ClientInventory.init(main.globalAllocator, recipe.sourceItems.len + 1, .{.crafting = recipe}, .other, .{}); diff --git a/src/items.zig b/src/items.zig index 8fe590f816..d153742562 100644 --- a/src/items.zig +++ b/src/items.zig @@ -1160,16 +1160,21 @@ pub const Recipe = struct { // MARK: Recipe sourceAmounts: []u16, resultItem: BaseItemIndex, resultAmount: u16, + craftingTags: []Tag, fn getValidRecipe(self: Recipe) error{Invalid}!*Recipe { outer: for (main.items.getRecipes()) |*recipe| { if (recipe.resultItem != self.resultItem) continue; if (recipe.resultAmount != self.resultAmount) continue; if (recipe.sourceItems.len != self.sourceItems.len) continue; + if (recipe.craftingTags.len != self.craftingTags.len) continue; for (recipe.sourceItems, recipe.sourceAmounts, self.sourceItems, self.sourceAmounts) |recipeItem, recipeAmount, selfItem, selfAmount| { if (recipeItem != selfItem) continue :outer; if (recipeAmount != selfAmount) continue :outer; } + for (recipe.craftingTags, self.craftingTags) |recipeCraftingTag, selfCraftingTag| { + if (recipeCraftingTag != selfCraftingTag) continue :outer; + } return recipe; } return error.Invalid; @@ -1178,11 +1183,16 @@ pub const Recipe = struct { // MARK: Recipe pub fn toBytes(self: *const Recipe, writer: *BinaryWriter) void { writer.writeEnum(BaseItemIndex, self.resultItem); writer.writeVarInt(u16, self.resultAmount); + writer.writeVarInt(u16, self.resultAmount); writer.writeVarInt(usize, self.sourceItems.len); for (self.sourceItems, self.sourceAmounts) |item, amount| { writer.writeEnum(BaseItemIndex, item); writer.writeVarInt(u16, amount); } + writer.writeVarInt(usize, self.craftingTags.len); + for (self.craftingTags) |tag| { + writer.writeEnum(Tag, tag); + } } pub fn fromBytes(reader: *BinaryReader) !*Recipe { @@ -1200,7 +1210,15 @@ pub const Recipe = struct { // MARK: Recipe sourceAmounts.append(main.stackAllocator, try reader.readVarInt(u16)); } - return getValidRecipe(.{.sourceItems = sourceItems.items, .sourceAmounts = sourceAmounts.items, .resultItem = resultItem, .resultAmount = resultAmount}); + const tagCount = try reader.readVarInt(usize); + var craftingTagTypes: main.List(Tag) = .initCapacity(main.stackAllocator, @min(256, tagCount)); + defer craftingTagTypes.deinit(main.stackAllocator); + + while (reader.remaining.len > 0 and craftingTagTypes.items.len < tagCount) { + craftingTagTypes.append(main.stackAllocator, try reader.readEnum(Tag)); + } + + return getValidRecipe(.{.sourceItems = sourceItems.items, .sourceAmounts = sourceAmounts.items, .resultItem = resultItem, .resultAmount = resultAmount, .craftingTags = craftingTagTypes.items}); } }; diff --git a/src/items/recipes.zig b/src/items/recipes.zig index ab79b3bb0e..cd18049891 100644 --- a/src/items/recipes.zig +++ b/src/items/recipes.zig @@ -190,7 +190,7 @@ fn generateItemCombos(allocator: NeverFailingAllocator, recipe: []const ZonEleme return newInputCombos; } -pub fn addRecipe(itemCombo: []const ItemWithAmount, list: *main.ListManaged(Recipe)) void { +pub fn addRecipe(itemCombo: []const ItemWithAmount, list: *main.ListManaged(Recipe), craftingTags: []Tag) void { const inputs = itemCombo[0 .. itemCombo.len - 1]; const output = itemCombo[itemCombo.len - 1]; const recipe = Recipe{ @@ -198,11 +198,15 @@ pub fn addRecipe(itemCombo: []const ItemWithAmount, list: *main.ListManaged(Reci .sourceAmounts = main.worldArena.alloc(u16, inputs.len), .resultItem = output.item, .resultAmount = output.amount, + .craftingTags = main.worldArena.alloc(Tag, craftingTags.len) }; for (inputs, 0..) |input, i| { recipe.sourceItems[i] = input.item; recipe.sourceAmounts[i] = input.amount; } + for (craftingTags, 0..) |tag, i| { + recipe.craftingTags[i] = tag; + } list.append(recipe); } @@ -218,11 +222,16 @@ pub fn parseRecipe(zon: ZonElement, list: *main.ListManaged(Recipe)) !void { return error.InvalidReversibleRecipe; } + var defaultCraftingTags = arena.alloc(main.Tag, 1); + defaultCraftingTags[0] = main.Tag.handCraftable; + const foundCraftingTags = Tag.loadTagsFromZon(arena, zon.getChild("craftingTags")); + const craftingTags = if (foundCraftingTags.len != 0) foundCraftingTags else defaultCraftingTags; + const itemCombos = try generateItemCombos(arena, recipeItems); for (itemCombos) |itemCombo| { - addRecipe(itemCombo, list); + addRecipe(itemCombo, list, craftingTags); if (reversible) { - addRecipe(&.{itemCombo[1], itemCombo[0]}, list); + addRecipe(&.{itemCombo[1], itemCombo[0]}, list, craftingTags); } } } diff --git a/src/tag.zig b/src/tag.zig index 5474f581af..b09cbfcc09 100644 --- a/src/tag.zig +++ b/src/tag.zig @@ -12,6 +12,7 @@ pub const Tag = enum(u32) { fluidPlaceable = 3, chiselable = 4, playerModel = 5, + handCraftable = 6, _, pub fn initTags() void { From 8810f3331ef0660f0d6896c3be30ccff9d957a5f Mon Sep 17 00:00:00 2001 From: Vinywar123 Date: Wed, 15 Jul 2026 20:44:22 -0700 Subject: [PATCH 2/5] i dont understand why this is leaking --- .../cubyz/blocks/transmutation_workbench.zig.zon | 14 ++++++++++++++ src/callbacks/block/client/_list.zig | 1 + src/callbacks/block/client/edit_sign.zig | 2 +- src/callbacks/block/client/open_chest.zig | 2 +- .../block/client/open_crafting_window.zig | 6 +++--- src/callbacks/block/client/open_window.zig | 2 +- src/gui/windows/inventory_crafting.zig | 1 + src/items/recipes.zig | 5 ++++- 8 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 assets/cubyz/blocks/transmutation_workbench.zig.zon diff --git a/assets/cubyz/blocks/transmutation_workbench.zig.zon b/assets/cubyz/blocks/transmutation_workbench.zig.zon new file mode 100644 index 0000000000..cde732cf25 --- /dev/null +++ b/assets/cubyz/blocks/transmutation_workbench.zig.zon @@ -0,0 +1,14 @@ +.{ + .tags = .{.diggable}, + .blockHealth = 6.5, + .drops = .{ + .{.items = .{.auto}}, + }, + .model = "cubyz:cube", + .rotation = "cubyz:stairs", + .texture = "cubyz:aerosoil", + .onInteract = .{ + .type = .open_crafting_window, + .craftingTags = .{.alchemy} + }, +} diff --git a/src/callbacks/block/client/_list.zig b/src/callbacks/block/client/_list.zig index 9a77fb7eb6..c056756e4a 100644 --- a/src/callbacks/block/client/_list.zig +++ b/src/callbacks/block/client/_list.zig @@ -1,3 +1,4 @@ pub const edit_sign = @import("edit_sign.zig"); pub const open_chest = @import("open_chest.zig"); pub const open_window = @import("open_window.zig"); +pub const open_crafting_window = @import("open_crafting_window.zig"); diff --git a/src/callbacks/block/client/edit_sign.zig b/src/callbacks/block/client/edit_sign.zig index ba6b1de9ae..860baf9105 100644 --- a/src/callbacks/block/client/edit_sign.zig +++ b/src/callbacks/block/client/edit_sign.zig @@ -12,7 +12,7 @@ pub fn init(_: ZonElement, _: main.callbacks.Creator) ?*anyopaque { pub fn run(_: *anyopaque, params: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { if (params.block.blockEntity() == null or !std.mem.eql(u8, params.block.blockEntity().?.id, "cubyz:sign")) { - std.log.err("Can only edit sign if block entity of the block is a sign.", .{}); + std.log.err("Error: Can only edit sign if block entity of the block is a sign.", .{}); return .ignored; } main.block_entity.BlockEntityTypes.@"cubyz:sign".StorageClient.mutex.lock(); diff --git a/src/callbacks/block/client/open_chest.zig b/src/callbacks/block/client/open_chest.zig index a4846117ab..e4fe21cf13 100644 --- a/src/callbacks/block/client/open_chest.zig +++ b/src/callbacks/block/client/open_chest.zig @@ -12,7 +12,7 @@ pub fn init(_: ZonElement, _: main.callbacks.Creator) ?*anyopaque { pub fn run(_: *anyopaque, params: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { if (params.block.blockEntity() == null or !std.mem.eql(u8, params.block.blockEntity().?.id, "cubyz:chest")) { - std.log.err("Can only open chest if block entity of the block is a chest.", .{}); + std.log.err("Error: Can only open chest if block entity of the block is a chest.", .{}); return .ignored; } main.network.protocols.blockEntityUpdate.sendClientDataUpdateToServer(main.game.world.?.conn, params.blockPos); diff --git a/src/callbacks/block/client/open_crafting_window.zig b/src/callbacks/block/client/open_crafting_window.zig index e7270cb881..2740d59d65 100644 --- a/src/callbacks/block/client/open_crafting_window.zig +++ b/src/callbacks/block/client/open_crafting_window.zig @@ -11,15 +11,15 @@ craftingTags: []main.Tag, pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { const result = main.worldArena.create(@This()); const craftingTags = main.Tag.loadTagsFromZon(main.stackAllocator, zon.getChild("craftingTags")); - if (craftingTags.len == 0) std.log.err("Missing craftingTags \"name\" for open_crafting_window event.", .{}) + if (craftingTags.len == 0) std.log.err("Error: Missing craftingTags \"name\" for open_crafting_window event.", .{}); result.* = .{ - .craftingTags = craftingTags + .craftingTags = craftingTags, }; return result; } pub fn run(self: *@This(), _: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { - main.gui.windowlist.inventory_crafting.openFromCallback(craftingTags) + main.gui.windowlist.inventory_crafting.openFromCallback(self.craftingTags); main.Window.setMouseGrabbed(false); return .handled; } diff --git a/src/callbacks/block/client/open_window.zig b/src/callbacks/block/client/open_window.zig index f67fa9bfc0..ea08f8722a 100644 --- a/src/callbacks/block/client/open_window.zig +++ b/src/callbacks/block/client/open_window.zig @@ -12,7 +12,7 @@ pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { const result = main.worldArena.create(@This()); result.* = .{ .windowName = main.worldArena.dupe(u8, zon.get([]const u8, "name") orelse { - std.log.err("Missing field \"name\" for open_window event.", .{}); + std.log.err("Error: Missing field \"name\" for open_window event.", .{}); return null; }), }; diff --git a/src/gui/windows/inventory_crafting.zig b/src/gui/windows/inventory_crafting.zig index ba56aab8f7..84c80c1297 100644 --- a/src/gui/windows/inventory_crafting.zig +++ b/src/gui/windows/inventory_crafting.zig @@ -43,6 +43,7 @@ pub fn init() void { arrowTexture = Texture.initFromFile("assets/cubyz/ui/inventory/crafting_arrow.png"); craftableFromTags = main.stackAllocator.alloc(main.Tag, 1); craftableFromTags[0] = main.Tag.handCraftable; + main.stackAllocator.free(craftableFromTags); } pub fn deinit() void { diff --git a/src/items/recipes.zig b/src/items/recipes.zig index cd18049891..04dc0de36a 100644 --- a/src/items/recipes.zig +++ b/src/items/recipes.zig @@ -198,7 +198,7 @@ pub fn addRecipe(itemCombo: []const ItemWithAmount, list: *main.ListManaged(Reci .sourceAmounts = main.worldArena.alloc(u16, inputs.len), .resultItem = output.item, .resultAmount = output.amount, - .craftingTags = main.worldArena.alloc(Tag, craftingTags.len) + .craftingTags = main.worldArena.alloc(Tag, craftingTags.len), }; for (inputs, 0..) |input, i| { recipe.sourceItems[i] = input.item; @@ -226,6 +226,9 @@ pub fn parseRecipe(zon: ZonElement, list: *main.ListManaged(Recipe)) !void { defaultCraftingTags[0] = main.Tag.handCraftable; const foundCraftingTags = Tag.loadTagsFromZon(arena, zon.getChild("craftingTags")); const craftingTags = if (foundCraftingTags.len != 0) foundCraftingTags else defaultCraftingTags; + defer arena.free(foundCraftingTags); + defer arena.free(defaultCraftingTags); + defer arena.free(craftingTags); const itemCombos = try generateItemCombos(arena, recipeItems); for (itemCombos) |itemCombo| { From 1f80df6502e73556aebf8d91718ab2aadfc1ec17 Mon Sep 17 00:00:00 2001 From: Vinywar123 Date: Wed, 15 Jul 2026 20:57:38 -0700 Subject: [PATCH 3/5] formatting --- assets/cubyz/recipes/cloth_recipes.zig.zon | 2 +- src/callbacks/block/client/open_crafting_window.zig | 1 + src/gui/windows/inventory_crafting.zig | 3 +-- src/items/recipes.zig | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/cubyz/recipes/cloth_recipes.zig.zon b/assets/cubyz/recipes/cloth_recipes.zig.zon index 2b2f19a71d..a8da98d2d1 100644 --- a/assets/cubyz/recipes/cloth_recipes.zig.zon +++ b/assets/cubyz/recipes/cloth_recipes.zig.zon @@ -8,6 +8,6 @@ .inputs = .{"4 cubyz:copper_ingot"}, .output = "cubyz:silver_ingot", .reversible = true, - .craftingTags = .{.alchemy} + .craftingTags = .{.alchemy}, }, } diff --git a/src/callbacks/block/client/open_crafting_window.zig b/src/callbacks/block/client/open_crafting_window.zig index 2740d59d65..3ce9930513 100644 --- a/src/callbacks/block/client/open_crafting_window.zig +++ b/src/callbacks/block/client/open_crafting_window.zig @@ -21,5 +21,6 @@ pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { pub fn run(self: *@This(), _: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { main.gui.windowlist.inventory_crafting.openFromCallback(self.craftingTags); main.Window.setMouseGrabbed(false); + defer main.stackAllocator.free(self.craftingTags); return .handled; } diff --git a/src/gui/windows/inventory_crafting.zig b/src/gui/windows/inventory_crafting.zig index 84c80c1297..77d8e5fdfd 100644 --- a/src/gui/windows/inventory_crafting.zig +++ b/src/gui/windows/inventory_crafting.zig @@ -43,7 +43,6 @@ pub fn init() void { arrowTexture = Texture.initFromFile("assets/cubyz/ui/inventory/crafting_arrow.png"); craftableFromTags = main.stackAllocator.alloc(main.Tag, 1); craftableFromTags[0] = main.Tag.handCraftable; - main.stackAllocator.free(craftableFromTags); } pub fn deinit() void { @@ -111,7 +110,7 @@ fn findAvailableRecipes(list: *VerticalList) bool { continue :middle; } } - continue :outer; // Ingredient not found or not craftable by this. + continue :outer; // Recipe not craftable by this. } // All ingredients found: Add it to the list. const inv = ClientInventory.init(main.globalAllocator, recipe.sourceItems.len + 1, .{.crafting = recipe}, .other, .{}); diff --git a/src/items/recipes.zig b/src/items/recipes.zig index 04dc0de36a..a32b929d37 100644 --- a/src/items/recipes.zig +++ b/src/items/recipes.zig @@ -226,8 +226,8 @@ pub fn parseRecipe(zon: ZonElement, list: *main.ListManaged(Recipe)) !void { defaultCraftingTags[0] = main.Tag.handCraftable; const foundCraftingTags = Tag.loadTagsFromZon(arena, zon.getChild("craftingTags")); const craftingTags = if (foundCraftingTags.len != 0) foundCraftingTags else defaultCraftingTags; - defer arena.free(foundCraftingTags); defer arena.free(defaultCraftingTags); + defer arena.free(foundCraftingTags); defer arena.free(craftingTags); const itemCombos = try generateItemCombos(arena, recipeItems); From 26832f3a6a281d1352833e41485b60475a94c677 Mon Sep 17 00:00:00 2001 From: Vinywar123 Date: Wed, 15 Jul 2026 20:59:23 -0700 Subject: [PATCH 4/5] Update transmutation_workbench.zig.zon --- assets/cubyz/blocks/transmutation_workbench.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/cubyz/blocks/transmutation_workbench.zig.zon b/assets/cubyz/blocks/transmutation_workbench.zig.zon index cde732cf25..032cfdfb4c 100644 --- a/assets/cubyz/blocks/transmutation_workbench.zig.zon +++ b/assets/cubyz/blocks/transmutation_workbench.zig.zon @@ -9,6 +9,6 @@ .texture = "cubyz:aerosoil", .onInteract = .{ .type = .open_crafting_window, - .craftingTags = .{.alchemy} + .craftingTags = .{.alchemy}, }, } From 55114ce7250f4addb885158a6a57e303a9b10eee Mon Sep 17 00:00:00 2001 From: Vinywar123 Date: Thu, 16 Jul 2026 13:17:05 -0700 Subject: [PATCH 5/5] its not yelling about a leak anymore --- src/callbacks/block/client/open_crafting_window.zig | 4 ++-- src/items/recipes.zig | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/callbacks/block/client/open_crafting_window.zig b/src/callbacks/block/client/open_crafting_window.zig index 3ce9930513..9d6f39e3dd 100644 --- a/src/callbacks/block/client/open_crafting_window.zig +++ b/src/callbacks/block/client/open_crafting_window.zig @@ -10,7 +10,7 @@ craftingTags: []main.Tag, pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { const result = main.worldArena.create(@This()); - const craftingTags = main.Tag.loadTagsFromZon(main.stackAllocator, zon.getChild("craftingTags")); + const craftingTags = main.Tag.loadTagsFromZon(main.worldArena, zon.getChild("craftingTags")); if (craftingTags.len == 0) std.log.err("Error: Missing craftingTags \"name\" for open_crafting_window event.", .{}); result.* = .{ .craftingTags = craftingTags, @@ -21,6 +21,6 @@ pub fn init(zon: ZonElement, _: main.callbacks.Creator) ?*@This() { pub fn run(self: *@This(), _: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { main.gui.windowlist.inventory_crafting.openFromCallback(self.craftingTags); main.Window.setMouseGrabbed(false); - defer main.stackAllocator.free(self.craftingTags); + defer main.worldArena.free(self.craftingTags); return .handled; } diff --git a/src/items/recipes.zig b/src/items/recipes.zig index a32b929d37..13a3a7dac3 100644 --- a/src/items/recipes.zig +++ b/src/items/recipes.zig @@ -226,9 +226,6 @@ pub fn parseRecipe(zon: ZonElement, list: *main.ListManaged(Recipe)) !void { defaultCraftingTags[0] = main.Tag.handCraftable; const foundCraftingTags = Tag.loadTagsFromZon(arena, zon.getChild("craftingTags")); const craftingTags = if (foundCraftingTags.len != 0) foundCraftingTags else defaultCraftingTags; - defer arena.free(defaultCraftingTags); - defer arena.free(foundCraftingTags); - defer arena.free(craftingTags); const itemCombos = try generateItemCombos(arena, recipeItems); for (itemCombos) |itemCombo| {