diff --git a/assets/cubyz/items/apple.zig.zon b/assets/cubyz/items/apple.zig.zon index caa8727a0f..0d9926fdc6 100644 --- a/assets/cubyz/items/apple.zig.zon +++ b/assets/cubyz/items/apple.zig.zon @@ -1,4 +1,7 @@ .{ .texture = "apple.png", - .food = 4, + .onUse = .{ + .type = .eat, + .energy = 0.5, + }, } diff --git a/assets/cubyz/items/raw_meat.zig.zon b/assets/cubyz/items/raw_meat.zig.zon index 361a02df32..d89b83d30c 100644 --- a/assets/cubyz/items/raw_meat.zig.zon +++ b/assets/cubyz/items/raw_meat.zig.zon @@ -1,4 +1,7 @@ .{ .texture = "raw_meat.png", - .food = 3, + .onUse = .{ + .type = .eat, + .energy = 3, + }, } diff --git a/src/Inventory.zig b/src/Inventory.zig index 62688bbc76..f7c2082875 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -21,7 +21,7 @@ const ToolTypeIndex = main.items.ToolTypeIndex; const Gamemode = main.game.Gamemode; -const Side = enum {client, server}; +pub const Side = enum {client, server}; pub const InventoryId = enum(u32) {_}; @@ -507,6 +507,13 @@ pub const Sync = struct { // MARK: Sync Sync.ServerSide.executeCommand(.{.addHealth = .{.target = userId, .health = health, .cause = cause}}, null); } } + pub fn useItem(source: Command.InventoryAndSlot, side: Side) void { + if(side == .client) { + Sync.ClientSide.executeCommand(.{.useItem = .{.source = source}}); + } else { + Sync.ServerSide.executeCommand(.{.useItem = .{.source = source}}, null); + } + } pub fn getInventory(id: InventoryId, side: Side, user: ?*main.server.User) ?Inventory { return switch(side) { @@ -538,6 +545,7 @@ pub const Command = struct { // MARK: Command clear = 8, updateBlock = 9, addHealth = 10, + useItem = 13, }; pub const Payload = union(PayloadType) { open: Open, @@ -552,6 +560,7 @@ pub const Command = struct { // MARK: Command clear: Clear, updateBlock: UpdateBlock, addHealth: AddHealth, + useItem: UseItem, }; const BaseOperationType = enum(u8) { @@ -564,11 +573,11 @@ pub const Command = struct { // MARK: Command addEnergy = 6, }; - const InventoryAndSlot = struct { + pub const InventoryAndSlot = struct { inv: Inventory, slot: u32, - fn ref(self: InventoryAndSlot) *ItemStack { + pub fn ref(self: InventoryAndSlot) *ItemStack { return &self.inv._items[self.slot]; } @@ -979,7 +988,7 @@ pub const Command = struct { // MARK: Command } } - fn executeBaseOperation(self: *Command, allocator: NeverFailingAllocator, _op: BaseOperation, side: Side) void { // MARK: executeBaseOperation() + pub fn executeBaseOperation(self: *Command, allocator: NeverFailingAllocator, _op: BaseOperation, side: Side) void { // MARK: executeBaseOperation() var op = _op; switch(op) { .move => |info| { @@ -1947,6 +1956,34 @@ pub const Command = struct { // MARK: Command return result; } }; + + const UseItem = struct { // MARK: UseItem + source: InventoryAndSlot, + + pub fn run(self: UseItem, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User, gamemode: Gamemode) error{serverFailure}!void { + if(self.source.inv.type != .normal) return; + const stack = self.source.ref(); + + switch(stack.item) { + .baseItem => |baseItem| { + _ = baseItem.onUse().run(.{.source = self.source, .allocator = allocator, .cmd = cmd, .side = side, .user = user, .gamemode = gamemode}); + }, + .tool => {}, + .null => {}, + } + } + + fn serialize(self: UseItem, writer: *utils.BinaryWriter) void { + self.source.write(writer); + } + + fn deserialize(reader: *utils.BinaryReader, side: Side, user: ?*main.server.User) !UseItem { + const result: UseItem = .{ + .source = try InventoryAndSlot.read(reader, side, user), + }; + return result; + } + }; }; const SourceType = enum(u8) { diff --git a/src/assets.zig b/src/assets.zig index 2c6f317442..7bdf1293a7 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -593,6 +593,11 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale // block drops: blocks_zig.finishBlocks(worldAssets.blocks); + // Finalize all item (setting their callbacks) + for(0..main.items.itemListSize) |i| { + main.items.itemList[i].finalize(); + } + iterator = worldAssets.recipes.iterator(); while(iterator.next()) |entry| { registerRecipesFromZon(entry.value_ptr.*); diff --git a/src/callbacks/callbacks.zig b/src/callbacks/callbacks.zig index f85d5c72d1..36ea0d2fe6 100644 --- a/src/callbacks/callbacks.zig +++ b/src/callbacks/callbacks.zig @@ -4,18 +4,22 @@ const main = @import("main"); const Block = main.blocks.Block; const vec = main.vec; const Vec3i = vec.Vec3i; +const Inventory = main.items.Inventory; pub const ClientBlockCallback = Callback(struct {block: Block, blockPos: Vec3i}, @import("block/client/_list.zig")); pub const ServerBlockCallback = Callback(struct {block: Block, chunk: *main.chunk.ServerChunk, blockPos: main.chunk.BlockPos}, @import("block/server/_list.zig")); pub const BlockTouchCallback = Callback(struct {entity: *main.server.Entity, source: Block, blockPos: Vec3i, deltaTime: f64}, @import("block/touch/_list.zig")); +pub const UseItemCallback = Callback(struct {cmd: *Inventory.Command, source: Inventory.Command.InventoryAndSlot, allocator: main.heap.NeverFailingAllocator, side: Inventory.Side, user: ?*main.server.User, gamemode: main.game.Gamemode}, @import("item/_list.zig")); + pub const Result = enum {handled, ignored}; pub fn init() void { ClientBlockCallback.globalInit(); ServerBlockCallback.globalInit(); BlockTouchCallback.globalInit(); + UseItemCallback.globalInit(); } fn Callback(_Params: type, list: type) type { diff --git a/src/callbacks/item/_list.zig b/src/callbacks/item/_list.zig new file mode 100644 index 0000000000..9a6fab246f --- /dev/null +++ b/src/callbacks/item/_list.zig @@ -0,0 +1 @@ +pub const eat = @import("eat.zig"); diff --git a/src/callbacks/item/eat.zig b/src/callbacks/item/eat.zig new file mode 100644 index 0000000000..c525a81c19 --- /dev/null +++ b/src/callbacks/item/eat.zig @@ -0,0 +1,45 @@ +const std = @import("std"); + +const main = @import("main"); + +energy: f32, + +pub fn init(zon: main.ZonElement) ?*@This() { + const result = main.worldArena.create(@This()); + result.* = .{ + .energy = zon.get(f32, "energy", 1.0), + }; + return result; +} + +pub fn run(self: *@This(), params: main.callbacks.UseItemCallback.Params) main.callbacks.Result { + const cmd = params.cmd; + const allocator = params.allocator; + const user = params.user; + const side = params.side; + const stack = params.source.ref(); + + // enough items there? + if(stack.amount < 1) + return .ignored; + + const previous = if(side == .server) user.?.player.energy else main.game.Player.super.energy; + const maxEnergy = if(side == .server) user.?.player.maxEnergy else main.game.Player.super.maxEnergy; + if(self.energy > 0 and previous >= maxEnergy) + return .ignored; + + cmd.executeBaseOperation(allocator, .{.addEnergy = .{ + .target = user, + .energy = self.energy, + .previous = previous, + }}, side); + + // Apply inventory changes: + if(params.gamemode == .creative) return .handled; + + cmd.executeBaseOperation(allocator, .{.delete = .{ + .source = params.source, + .amount = 1, + }}, side); + return .handled; +} diff --git a/src/game.zig b/src/game.zig index e7ac75b25e..7899631d05 100644 --- a/src/game.zig +++ b/src/game.zig @@ -528,6 +528,18 @@ pub const Player = struct { // MARK: Player } } + pub fn useItem(_: main.Window.Key.Modifiers) bool { + const item = inventory.getItem(selectedSlot); + main.items.Inventory.Sync.useItem(.{.inv = inventory, .slot = selectedSlot}, .client); + switch(item) { + .baseItem => { + return !item.baseItem.onUse().isNoop(); + }, + .null => {}, + .tool => {}, + } + return false; + } pub fn placeBlock(mods: main.Window.Key.Modifiers) void { if(main.renderer.MeshSelection.selectedBlockPos) |blockPos| { if(!mods.shift) { @@ -548,7 +560,7 @@ pub const Player = struct { // MARK: Player Player.super.vel = .{0, 0, 0}; Player.super.health = Player.super.maxHealth; - Player.super.energy = Player.super.maxEnergy; + Player.super.energy = 0; Player.eye = .{}; Player.jumpCoyote = 0; @@ -771,7 +783,9 @@ var nextBlockBreakTime: ?std.Io.Timestamp = null; pub fn pressPlace(mods: main.Window.Key.Modifiers) void { const time = main.timestamp(); nextBlockPlaceTime = time.addDuration(main.settings.updateRepeatDelay); - Player.placeBlock(mods); + if(!Player.useItem(mods)) { + Player.placeBlock(mods); + } } pub fn releasePlace(_: main.Window.Key.Modifiers) void { diff --git a/src/items.zig b/src/items.zig index 348bfa54ad..5836bf789b 100644 --- a/src/items.zig +++ b/src/items.zig @@ -242,15 +242,20 @@ pub const BaseItemIndex = enum(u16) { // MARK: BaseItemIndex pub fn getTooltip(self: BaseItemIndex) []const u8 { return itemList[@intFromEnum(self)].getTooltip(); } + pub fn onUse(self: BaseItemIndex) main.callbacks.UseItemCallback { + return itemList[@intFromEnum(self)].onUse; + } }; pub const BaseItem = struct { // MARK: BaseItem image: graphics.Image, texture: ?graphics.Texture, // TODO: Properly deinit id: []const u8, + zon: ZonElement, name: []const u8, tags: []const Tag, tooltip: []const u8, + onUse: main.callbacks.UseItemCallback, stackSize: u16, material: ?Material, @@ -258,6 +263,7 @@ pub const BaseItem = struct { // MARK: BaseItem foodValue: f32, // TODO: Effects. fn init(self: *BaseItem, allocator: NeverFailingAllocator, texturePath: []const u8, replacementTexturePath: []const u8, id: []const u8, zon: ZonElement) void { + self.zon = zon.clone(allocator); self.id = allocator.dupe(u8, id); if(texturePath.len == 0) { self.image = graphics.Image.defaultImage; @@ -281,7 +287,7 @@ pub const BaseItem = struct { // MARK: BaseItem break :blk blocks.getTypeById(zon.get(?[]const u8, "block", null) orelse break :blk null); }; self.texture = null; - self.foodValue = zon.get(f32, "food", 0); + self.foodValue = zon.get(f32, "energy", 0); var tooltip: main.List(u8) = .init(allocator); tooltip.appendSlice(self.name); @@ -301,6 +307,16 @@ pub const BaseItem = struct { // MARK: BaseItem _ = tooltip.swapRemove(tooltip.items.len - 1); } self.tooltip = tooltip.toOwnedSlice(); + self.onUse = .noop; + } + + pub fn finalize(self: *BaseItem) void { + self.onUse = blk: { + break :blk main.callbacks.UseItemCallback.init(self.zon.getChildOrNull("onUse") orelse break :blk .noop) orelse { + std.log.err("Failed to load onUse event for item {s}", .{self.id}); + break :blk .noop; + }; + }; } fn hashCode(self: BaseItem) u32 { diff --git a/src/server/Entity.zig b/src/server/Entity.zig index 42226f8e28..97658d5855 100644 --- a/src/server/Entity.zig +++ b/src/server/Entity.zig @@ -22,7 +22,7 @@ pub fn loadFrom(self: *@This(), zon: ZonElement) void { self.vel = zon.get(Vec3d, "velocity", .{0, 0, 0}); self.rot = zon.get(Vec3f, "rotation", .{0, 0, 0}); self.health = zon.get(f32, "health", self.maxHealth); - self.energy = zon.get(f32, "energy", self.maxEnergy); + self.energy = zon.get(f32, "energy", 0); } pub fn save(self: *@This(), allocator: NeverFailingAllocator) ZonElement {