diff --git a/src/gui/components/BagSlot.zig b/src/gui/components/BagSlot.zig index d34f5f7776..4ec95a19a4 100644 --- a/src/gui/components/BagSlot.zig +++ b/src/gui/components/BagSlot.zig @@ -25,6 +25,7 @@ size: Vec2f = .{sizeWithBorder, sizeWithBorder + 8}, inventory: *BagInventory, hovered: bool = false, pressed: bool = false, +itemColor: u32 = 0xffffffff, pub fn globalInit() void { texture = Texture.initFromFile("assets/cubyz/ui/inventory/bag_slot.png"); @@ -91,7 +92,7 @@ pub fn render(self: *BagSlot, _: Vec2f) void { const opacity: f32 = std.math.pow(f32, 0.5, @as(f32, @floatFromInt(i))); const oldColor = draw.setColor(0xffffff | @as(u32, @trunc(opacity*255)) << 24); defer draw.restoreColor(oldColor); - item.render(self.pos, @splat(sizeWithBorder), border); + item.render(self.pos, @splat(sizeWithBorder), border, self.itemColor); } const topItem = self.inventory.peek(0); diff --git a/src/gui/components/ItemSlot.zig b/src/gui/components/ItemSlot.zig index 9407c860ac..171b4cd943 100644 --- a/src/gui/components/ItemSlot.zig +++ b/src/gui/components/ItemSlot.zig @@ -29,6 +29,8 @@ size: Vec2f = @splat(sizeWithBorder), inventory: ClientInventory, itemSlot: u32, lastItemAmount: u16 = 0, +itemColor: u32 = 0xffffffff, +onHover: main.callbacks.SimpleCallback, text: TextBuffer, textSize: Vec2f = .{0, 0}, hovered: bool = false, @@ -57,6 +59,12 @@ const TextureParamType = union(enum) { } }; +const Options = struct { + texture: TextureParamType = .default, + mode: Mode = .normal, + onHover: main.callbacks.SimpleCallback = .{}, +}; + pub fn globalInit() void { defaultTexture = Texture.initFromFile("assets/cubyz/ui/inventory/slot.png"); immutableTexture = Texture.initFromFile("assets/cubyz/ui/inventory/immutable_slot.png"); @@ -69,7 +77,7 @@ pub fn globalDeinit() void { craftingResultTexture.deinit(); } -pub fn init(pos: Vec2f, inventory: ClientInventory, itemSlot: u32, texture: TextureParamType, mode: Mode) *ItemSlot { +pub fn init(pos: Vec2f, inventory: ClientInventory, itemSlot: u32, options: Options) *ItemSlot { const self = main.globalAllocator.create(ItemSlot); const amount = inventory.getAmount(itemSlot); var buf: [16]u8 = undefined; @@ -79,8 +87,9 @@ pub fn init(pos: Vec2f, inventory: ClientInventory, itemSlot: u32, texture: Text .pos = pos, .text = TextBuffer.init(main.globalAllocator, std.fmt.bufPrint(&buf, "{}", .{amount}) catch "∞", .{}, false, .right), .lastItemAmount = amount, - .texture = texture.value(), - .mode = mode, + .texture = options.texture.value(), + .mode = options.mode, + .onHover = options.onHover, }; self.textSize = self.text.calculateLineBreaks(8, self.size[0] - 2*border); return self; @@ -115,6 +124,7 @@ pub fn toComponent(self: *ItemSlot) GuiComponent { pub fn updateHovered(self: *ItemSlot, _: Vec2f) main.callbacks.Result { self.hovered = true; gui.hoveredItemSlot = self; + self.onHover.run(); return .handled; } @@ -137,7 +147,7 @@ pub fn render(self: *ItemSlot, _: Vec2f) void { } const item = self.inventory.getItem(self.itemSlot); if (item != .null) { - item.render(self.pos, self.size, border); + item.render(self.pos, self.size, border, self.itemColor); const shouldRenderStackSizeText = item.stackSize() > 1 and self.inventory.type != .creative; if (shouldRenderStackSizeText) { self.text.render(self.pos[0] + self.size[0] - self.textSize[0] - border, self.pos[1] + self.size[1] - self.textSize[1] - border, 8); diff --git a/src/gui/gui.zig b/src/gui/gui.zig index f3ea6d7993..220a8f68e8 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -667,7 +667,7 @@ pub const inventory = struct { // MARK: inventory pub fn init() void { carried = ClientInventory.init(main.globalAllocator, 1, .serverShared, .{.hand = main.game.Player.id}, .{}); - carriedItemSlot = ItemSlot.init(.{0, 0}, carried, 0, .default, .normal); + carriedItemSlot = ItemSlot.init(.{0, 0}, carried, 0, .{.texture = .default, .mode = .normal}); carriedItemSlot.renderFrame = false; initialized = true; isCrafting = false; diff --git a/src/gui/windows/chest.zig b/src/gui/windows/chest.zig index f41dd7cbcc..9ca41d35e9 100644 --- a/src/gui/windows/chest.zig +++ b/src/gui/windows/chest.zig @@ -46,7 +46,7 @@ pub fn onOpen() void { const row = HorizontalList.init(); for (0..10) |x| { const index: usize = y*10 + x; - const slot = ItemSlot.init(.{0, 0}, openInventory, @intCast(index), .default, .normal); + const slot = ItemSlot.init(.{0, 0}, openInventory, @intCast(index), .{.texture = .default, .mode = .normal}); itemSlots.append(main.globalAllocator, slot); row.add(slot); } diff --git a/src/gui/windows/creative_inventory.zig b/src/gui/windows/creative_inventory.zig index a4fdf57766..5134d5588e 100644 --- a/src/gui/windows/creative_inventory.zig +++ b/src/gui/windows/creative_inventory.zig @@ -107,9 +107,9 @@ fn initContent() void { const row = HorizontalList.init(); for (0..slotsPerRow) |_| { if (i >= items.items.len) { - row.add(ItemSlot.init(.{0, 0}, inventory, i, .immutable, .immutable)); + row.add(ItemSlot.init(.{0, 0}, inventory, i, .{.texture = .immutable, .mode = .immutable})); } else { - row.add(ItemSlot.init(.{0, 0}, inventory, i, .default, .takeOnly)); + row.add(ItemSlot.init(.{0, 0}, inventory, i, .{.texture = .default, .mode = .takeOnly})); } i += 1; } diff --git a/src/gui/windows/hotbar.zig b/src/gui/windows/hotbar.zig index ee92c35baf..c132f648d2 100644 --- a/src/gui/windows/hotbar.zig +++ b/src/gui/windows/hotbar.zig @@ -42,7 +42,7 @@ var itemSlots: [12]*ItemSlot = undefined; pub fn onOpen() void { const list = HorizontalList.init(); for (0..12) |i| { - itemSlots[i] = ItemSlot.init(.{0, 0}, Player.inventory, @intCast(i), .{.custom = hotbarSlotTexture}, .normal); + itemSlots[i] = ItemSlot.init(.{0, 0}, Player.inventory, @intCast(i), .{.texture = .{.custom = hotbarSlotTexture}, .mode = .normal}); list.add(itemSlots[i]); } list.finish(.{0, 0}, .center); diff --git a/src/gui/windows/inventory.zig b/src/gui/windows/inventory.zig index eece276b54..20e0222c9c 100644 --- a/src/gui/windows/inventory.zig +++ b/src/gui/windows/inventory.zig @@ -57,7 +57,7 @@ pub fn onOpen() void { const row = HorizontalList.init(); for (0..10) |x| { const index: usize = 12 + y*10 + x; - const slot = ItemSlot.init(.{0, 0}, Player.inventory, @intCast(index), .default, .normal); + const slot = ItemSlot.init(.{0, 0}, Player.inventory, @intCast(index), .{.texture = .default, .mode = .normal}); itemSlots[index - 12] = slot; row.add(slot); } diff --git a/src/gui/windows/inventory_crafting.zig b/src/gui/windows/inventory_crafting.zig index 9224473a95..ea729b8286 100644 --- a/src/gui/windows/inventory_crafting.zig +++ b/src/gui/windows/inventory_crafting.zig @@ -114,14 +114,14 @@ fn findAvailableRecipes(list: *VerticalList) bool { if (col < remainder) itemsThisColumn += 1; const columnList = VerticalList.init(.{0, 0}, std.math.inf(f32), 0); for (0..itemsThisColumn) |_| { - columnList.add(ItemSlot.init(.{0, 0}, inv, i, .immutable, .immutable)); + columnList.add(ItemSlot.init(.{0, 0}, inv, i, .{.texture = .immutable, .mode = .immutable})); i += 1; } columnList.finish(.center); rowList.add(columnList); } rowList.add(Icon.init(.{8, 0}, .{32, 32}, arrowTexture)); - const itemSlot = ItemSlot.init(.{8, 0}, inv, @intCast(recipe.sourceItems.len), .craftingResult, .takeOnly); + const itemSlot = ItemSlot.init(.{8, 0}, inv, @intCast(recipe.sourceItems.len), .{.texture = .default, .mode = .normal}); rowList.add(itemSlot); rowList.finish(.{0, 0}, .center); list.add(rowList); diff --git a/src/gui/windows/workbench.zig b/src/gui/windows/workbench.zig index 3c11d77072..2b07ea8ec5 100644 --- a/src/gui/windows/workbench.zig +++ b/src/gui/windows/workbench.zig @@ -60,6 +60,48 @@ fn updateResult(_: main.items.Inventory.Source) void { craftingResultInv.super._items[0] = .{.item = Item{.proceduralItem = main.items.ProceduralItem.initFromInventory(craftingGridInv.super) orelse return}, .amount = 1}; } +fn updateModifierRestrictionVisuals(hoveredSlotNumber: usize) void { + var slotItems: [25]?main.items.BaseItemIndex = @splat(null); + for (itemSlots, 0..) |slot, i| { + slot.itemColor = 0xffffffff; + slotItems[i] = if (slot.inventory.getItem(i) == .baseItem) slot.inventory.getItem(i).baseItem else null; + } + + const hoveredSlot = itemSlots[hoveredSlotNumber]; + const material = hoveredSlot.inventory.getItem(hoveredSlotNumber).baseItem.material() orelse return; + if (material.modifiers.len == 0) return; + const x: i32 = if (hoveredSlotNumber != 0) @intCast(@mod(hoveredSlotNumber, 5)) else 0; + const y: i32 = @intCast(@divFloor(hoveredSlotNumber, 5)); + for (0..25) |i| { + if (i == hoveredSlotNumber) continue; + var newTag: main.items.Checked = .always; + for (material.modifiers) |modifier| { + const searchedCheckedGrid = modifier.restriction.printCheckedGrid(slotItems, x, y)[i]; + switch (searchedCheckedGrid) { + .validTag => { + newTag = .validTag; + break; + }, + .invalidTag => if (newTag != .validTag) { + newTag = .invalidTag; + }, + .notChecked => if ((newTag != .validTag) and (newTag != .invalidTag)) { + newTag = .invalidTag; + }, + .always => if (newTag == .always) { + newTag = .always; + }, + } + } + switch (newTag) { + .validTag => itemSlots[i].itemColor = 0xcfddffdd, + .invalidTag => itemSlots[i].itemColor = 0xefeebbbb, + .notChecked => itemSlots[i].itemColor = 0x33eeeeff, + .always => continue, + } + } +} + fn openInventory() void { craftingGridInv = ClientInventory.init(main.globalAllocator, 25, .serverShared, .{.workbench = .{.playerId = main.game.Player.id, .proceduralItemIndex = proceduralItemTypes.items[currentProceduralItemType]}}, .{.onUpdateCallback = &updateResult, .canPutInto = items.ProceduralItem.canPutIntoWorkbenchCallback}); craftingResultInv = ClientInventory.init(main.globalAllocator, 1, .{.workbenchResult = craftingGridInv.super.id}, .other, .{}); @@ -72,7 +114,11 @@ fn openInventory() void { for (0..5) |x| { const index = x + y*5; const slotInfo = proceduralItemTypes.items[currentProceduralItemType].slotInfos()[index]; - const slot = ItemSlot.init(.{0, 0}, craftingGridInv, @intCast(index), if (slotInfo.disabled) .invisible else if (slotInfo.optional) .immutable else .default, if (slotInfo.disabled) .immutable else .normal); + const slot = ItemSlot.init(.{0, 0}, craftingGridInv, @intCast(index), .{ + .texture = if (slotInfo.disabled) .invisible else if (slotInfo.optional) .immutable else .default, + .mode = if (slotInfo.disabled) .immutable else .normal, + .onHover = .initWithInt(updateModifierRestrictionVisuals, @intCast(index)), + }); itemSlots[index] = slot; row.add(slot); } @@ -87,7 +133,7 @@ fn openInventory() void { const buttonHeight = verticalThing.size[1]; const craftingResultList = HorizontalList.init(); craftingResultList.add(Icon.init(.{0, 0}, .{32, 32}, inventory_crafting.arrowTexture)); - craftingResultList.add(ItemSlot.init(.{8, 0}, craftingResultInv, 0, .craftingResult, .takeOnly)); + craftingResultList.add(ItemSlot.init(.{8, 0}, craftingResultInv, 0, .{.texture = .craftingResult, .mode = .takeOnly})); craftingResultList.finish(.{padding, padding}, .center); verticalThing.add(craftingResultList); verticalThing.size[1] += buttonHeight + 2*padding; // Centering the thing diff --git a/src/items.zig b/src/items.zig index 5032419eda..409a458644 100644 --- a/src/items.zig +++ b/src/items.zig @@ -146,12 +146,17 @@ pub const ModifierRestriction = struct { satisfied: *const fn (data: *anyopaque, proceduralItem: *const ProceduralItem, x: i32, y: i32) bool, loadFromZon: *const fn (allocator: NeverFailingAllocator, zon: ZonElement) *anyopaque, printTooltip: *const fn (data: *anyopaque, outString: *main.ListManaged(u8)) void, + printCheckedGrid: *const fn (data: *anyopaque, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]Checked, }; pub fn satisfied(self: ModifierRestriction, proceduralItem: *const ProceduralItem, x: i32, y: i32) bool { return self.vTable.satisfied(self.data, proceduralItem, x, y); } + pub fn printCheckedGrid(self: ModifierRestriction, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]Checked { + return self.vTable.printCheckedGrid(self.data, givenGrid, x, y); + } + pub fn loadFromZon(allocator: NeverFailingAllocator, zon: ZonElement) ModifierRestriction { const id = zon.get([]const u8, "id") orelse "always"; const vTable = modifierRestrictions.get(id) orelse blk: { @@ -814,6 +819,13 @@ const ProceduralItemProperty = enum { } }; +pub const Checked = enum(u8) { + notChecked = 0, + invalidTag = 1, + validTag = 2, + always = 3, +}; + pub const ProceduralItem = struct { // MARK: ProceduralItem const craftingGridSize = 25; const CraftingGridMask = std.meta.Int(.unsigned, craftingGridSize); @@ -1005,10 +1017,25 @@ pub const ProceduralItem = struct { // MARK: ProceduralItem return hash; } - pub fn getItemAt(self: *const ProceduralItem, x: i32, y: i32) ?BaseItemIndex { + pub fn getItemAt(x: i32, y: i32, craftingGrid: [craftingGridSize]?BaseItemIndex) ?BaseItemIndex { if (x < 0 or x >= 5) return null; if (y < 0 or y >= 5) return null; - return self.craftingGrid[@intCast(x + y*5)]; + return craftingGrid[@intCast(x + y*5)]; + } + + pub fn getCheckedAt(x: i32, y: i32, craftingGrid: [craftingGridSize]?BaseItemIndex, tag: Tag, checkedGrid: *[25]Checked) void { + if (x < 0 or x >= 5) return; + if (y < 0 or y >= 5) return; + const item = ProceduralItem.getItemAt(x, y, craftingGrid); + const gridPos: usize = @intCast(x + y*5); + if (item == null) { + checkedGrid[gridPos] = .invalidTag; + } else if (item.?.hasTag(tag)) { + checkedGrid[gridPos] = .validTag; + } else { + std.log.debug("found invalid", .{}); + checkedGrid[gridPos] = .invalidTag; + } } pub fn getProperty(self: *ProceduralItem, prop: ProceduralItemProperty) f32 { @@ -1232,10 +1259,14 @@ pub const Item = union(ItemType) { // MARK: Item }; } - pub fn render(self: Item, pos: Vec2f, slotSize: Vec2f, border: f32) void { + pub fn render(self: Item, pos: Vec2f, slotSize: Vec2f, border: f32, color: u32) void { const itemTexture = self.getTexture(); itemTexture.bindTo(0); - graphics.draw.boundImage(pos + @as(Vec2f, @splat(border)), slotSize - @as(Vec2f, @splat(2*border))); + { + const oldColor = graphics.draw.setColor(color); + defer graphics.draw.restoreColor(oldColor); + graphics.draw.boundImage(pos + @as(Vec2f, @splat(border)), slotSize - @as(Vec2f, @splat(2*border))); + } if (self == .proceduralItem) { const proceduralItem = self.proceduralItem; @@ -1410,6 +1441,7 @@ pub fn globalInit() void { .satisfied = comptime main.meta.castFunctionSelfToAnyopaque(ModifierRestrictionStruct.satisfied), .loadFromZon = comptime main.meta.castFunctionReturnToAnyopaque(ModifierRestrictionStruct.loadFromZon), .printTooltip = comptime main.meta.castFunctionSelfToAnyopaque(ModifierRestrictionStruct.printTooltip), + .printCheckedGrid = comptime main.meta.castFunctionSelfToAnyopaque(ModifierRestrictionStruct.printCheckedGrid), }) catch unreachable; } Inventory.client.init(); diff --git a/src/proceduralItem/modifiers/restrictions/always.zig b/src/proceduralItem/modifiers/restrictions/always.zig index 11a9b8cd36..6345fddcb9 100644 --- a/src/proceduralItem/modifiers/restrictions/always.zig +++ b/src/proceduralItem/modifiers/restrictions/always.zig @@ -9,6 +9,11 @@ pub fn satisfied(_: *const anyopaque, _: *const ProceduralItem, _: i32, _: i32) return true; } +pub fn printCheckedGrid(_: *const anyopaque, _: [25]?main.items.BaseItemIndex, _: i32, _: i32) [25]main.items.Checked { + const checkedGrid: [25]main.items.Checked = @splat(.always); + return checkedGrid; +} + pub fn loadFromZon(_: NeverFailingAllocator, _: ZonElement) *const anyopaque { return undefined; } diff --git a/src/proceduralItem/modifiers/restrictions/and.zig b/src/proceduralItem/modifiers/restrictions/and.zig index e74c9cfefb..5ef8ee49ca 100644 --- a/src/proceduralItem/modifiers/restrictions/and.zig +++ b/src/proceduralItem/modifiers/restrictions/and.zig @@ -17,6 +17,33 @@ pub fn satisfied(self: *const And, proceduralItem: *const ProceduralItem, x: i32 return true; } +pub fn printCheckedGrid(self: *const And, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]main.items.Checked { + var checkedGrid: [25]main.items.Checked = @splat(.notChecked); + for (0..25) |i| { + var newTag: main.items.Checked = .always; + for (self.children) |child| { + const searchedCheckedGrid = child.printCheckedGrid(givenGrid, x, y)[i]; + switch (searchedCheckedGrid) { + .invalidTag => { + newTag = .invalidTag; + break; + }, + .validTag => if (newTag != .invalidTag) { + newTag = .validTag; + }, + .notChecked => if ((newTag != .validTag) and (newTag != .invalidTag)) { + newTag = .invalidTag; + }, + .always => if (newTag == .always) { + newTag = .always; + }, + } + } + checkedGrid[i] = newTag; + } + return checkedGrid; +} + pub fn loadFromZon(allocator: NeverFailingAllocator, zon: ZonElement) *const And { const result = allocator.create(And); const childrenZon = zon.getChild("children").toSlice(); diff --git a/src/proceduralItem/modifiers/restrictions/encased.zig b/src/proceduralItem/modifiers/restrictions/encased.zig index 48894e6613..355878ecdf 100644 --- a/src/proceduralItem/modifiers/restrictions/encased.zig +++ b/src/proceduralItem/modifiers/restrictions/encased.zig @@ -15,12 +15,22 @@ pub fn satisfied(self: *const Encased, proceduralItem: *const ProceduralItem, x: var count: usize = 0; for ([_]i32{-1, 0, 1}) |dx| { for ([_]i32{-1, 0, 1}) |dy| { - if ((proceduralItem.getItemAt(x + dx, y + dy) orelse continue).hasTag(self.tag)) count += 1; + if ((ProceduralItem.getItemAt(x + dx, y + dy, proceduralItem.craftingGrid) orelse continue).hasTag(self.tag)) count += 1; } } return count >= self.amount; } +pub fn printCheckedGrid(self: *const Encased, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]main.items.Checked { + var checkedGrid: [25]main.items.Checked = @splat(.notChecked); + for ([_]i32{-1, 0, 1}) |dx| { + for ([_]i32{-1, 0, 1}) |dy| { + ProceduralItem.getCheckedAt(x + dx, y + dy, givenGrid, self.tag, &checkedGrid); + } + } + return checkedGrid; +} + pub fn loadFromZon(allocator: NeverFailingAllocator, zon: ZonElement) *const Encased { const result = allocator.create(Encased); result.* = .{ diff --git a/src/proceduralItem/modifiers/restrictions/not.zig b/src/proceduralItem/modifiers/restrictions/not.zig index 7827f09cbd..6fd9cf3cb8 100644 --- a/src/proceduralItem/modifiers/restrictions/not.zig +++ b/src/proceduralItem/modifiers/restrictions/not.zig @@ -14,6 +14,11 @@ pub fn satisfied(self: *const Not, proceduralItem: *const ProceduralItem, x: i32 return !self.child.satisfied(proceduralItem, x, y); } +pub fn printCheckedGrid(self: *const Not, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]main.items.Checked { + const childCheckedGrid = self.child.printCheckedGrid(givenGrid, x, y); + return childCheckedGrid; +} + pub fn loadFromZon(allocator: NeverFailingAllocator, zon: ZonElement) *const Not { const result = allocator.create(Not); result.* = .{ diff --git a/src/proceduralItem/modifiers/restrictions/or.zig b/src/proceduralItem/modifiers/restrictions/or.zig index d7ea7e2e96..8afa3b1274 100644 --- a/src/proceduralItem/modifiers/restrictions/or.zig +++ b/src/proceduralItem/modifiers/restrictions/or.zig @@ -17,6 +17,33 @@ pub fn satisfied(self: *const Or, proceduralItem: *const ProceduralItem, x: i32, return false; } +pub fn printCheckedGrid(self: *const Or, givenGrid: [25]?main.items.BaseItemIndex, x: i32, y: i32) [25]main.items.Checked { + var checkedGrid: [25]main.items.Checked = @splat(.notChecked); + for (0..25) |i| { + var newTag: main.items.Checked = .notChecked; + for (self.children) |child| { + const searchedCheckedGrid = child.printCheckedGrid(givenGrid, x, y)[i]; + switch (searchedCheckedGrid) { + .validTag => { + newTag = .validTag; + break; + }, + .invalidTag => if (newTag != .validTag) { + newTag = .invalidTag; + }, + .notChecked => if ((newTag != .validTag) and (newTag != .invalidTag)) { + newTag = .invalidTag; + }, + .always => if (newTag == .always) { + newTag = .always; + }, + } + } + checkedGrid[i] = newTag; + } + return checkedGrid; +} + pub fn loadFromZon(allocator: NeverFailingAllocator, zon: ZonElement) *const Or { const result = allocator.create(Or); const childrenZon = zon.getChild("children").toSlice();