Skip to content
5 changes: 4 additions & 1 deletion assets/cubyz/items/apple.zig.zon
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.{
.texture = "apple.png",
.food = 4,
.onUse = .{
.type = .eat,
.food = 0.5,
},
}
5 changes: 4 additions & 1 deletion assets/cubyz/items/raw_meat.zig.zon
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.{
.texture = "raw_meat.png",
.food = 3,
.onUse = .{
.type = .eat,
.food = 3,
},
}
97 changes: 94 additions & 3 deletions src/Inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {_};

Expand Down Expand Up @@ -507,6 +507,20 @@ pub const Sync = struct { // MARK: Sync
Sync.ServerSide.executeCommand(.{.addHealth = .{.target = userId, .health = health, .cause = cause}}, null);
}
}
pub fn addEnergy(energy: f32, side: Side, userId: u32) void {
if(side == .client) {
Sync.ClientSide.executeCommand(.{.addEnergy = .{.target = userId, .energy = energy}});
} else {
Sync.ServerSide.executeCommand(.{.addEnergy = .{.target = userId, .energy = energy}}, null);
}
}
pub fn useItem(source: Command.InventoryAndSlot, side: Side) void {
Copy link
Member

Choose a reason for hiding this comment

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

Since this function is only used on the client, it should only exist in ClientSide.

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) {
Expand Down Expand Up @@ -538,6 +552,8 @@ pub const Command = struct { // MARK: Command
clear = 8,
updateBlock = 9,
addHealth = 10,
addEnergy = 12,
useItem = 13,
};
pub const Payload = union(PayloadType) {
open: Open,
Expand All @@ -552,6 +568,8 @@ pub const Command = struct { // MARK: Command
clear: Clear,
updateBlock: UpdateBlock,
addHealth: AddHealth,
addEnergy: AddEnergy,
useItem: UseItem,
};

const BaseOperationType = enum(u8) {
Expand All @@ -564,7 +582,7 @@ pub const Command = struct { // MARK: Command
addEnergy = 6,
};

const InventoryAndSlot = struct {
pub const InventoryAndSlot = struct {
inv: Inventory,
slot: u32,

Expand Down Expand Up @@ -979,7 +997,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| {
Expand Down Expand Up @@ -1947,6 +1965,79 @@ pub const Command = struct { // MARK: Command
return result;
}
};
const AddEnergy = struct { // MARK: AddEnergy
target: u32,
energy: f32,

pub fn run(self: AddEnergy, allocator: NeverFailingAllocator, cmd: *Command, side: Side, _: ?*main.server.User, _: Gamemode) error{serverFailure}!void {
var target: ?*main.server.User = null;

if(side == .server) {
const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for(userList) |user| {
if(user.id == self.target) {
target = user;
break;
}
}

if(target == null) return error.serverFailure;

if(target.?.gamemode.raw == .creative) return;
} else {
if(main.game.Player.gamemode.raw == .creative) return;
}

cmd.executeBaseOperation(allocator, .{.addEnergy = .{
.target = target,
.energy = self.energy,
.previous = if(side == .server) target.?.player.energy else main.game.Player.super.energy,
}}, side);
}

fn serialize(self: AddEnergy, writer: *utils.BinaryWriter) void {
writer.writeInt(u32, self.target);
writer.writeInt(u32, @bitCast(self.energy));
}

fn deserialize(reader: *utils.BinaryReader, side: Side, user: ?*main.server.User) !AddEnergy {
const result: AddEnergy = .{
.target = try reader.readInt(u32),
.energy = @bitCast(try reader.readInt(u32)),
};
if(user.?.id != result.target or side != .server) return error.Invalid;
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(.{.stack = stack, .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) {
Expand Down
4 changes: 4 additions & 0 deletions src/callbacks/callbacks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {stack: *main.items.ItemStack, source: Inventory.Command.InventoryAndSlot, allocator: main.heap.NeverFailingAllocator, cmd: *Inventory.Command, 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 {
Expand Down
1 change: 1 addition & 0 deletions src/callbacks/item/_list.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub const eat = @import("eat.zig");
46 changes: 46 additions & 0 deletions src/callbacks/item/eat.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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, "food", 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 gamemode = params.gamemode;
const source = params.source;
const stack = params.stack;

// enough items there?
if(stack.amount < 1)
return .ignored;

const previous = if(side == .server) user.?.player.energy else main.game.Player.super.energy;
Copy link
Member

Choose a reason for hiding this comment

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

Please check that user is not null, because in fact in this PR you added code that passes null for this (see comment above)

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(gamemode == .creative) return .handled;

cmd.executeBaseOperation(allocator, .{.delete = .{
.source = source,
.amount = 1,
}}, side);
return .handled;
}
6 changes: 5 additions & 1 deletion src/game.zig
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ pub const Player = struct { // MARK: Player
}
}

pub fn useItem(_: main.Window.Key.Modifiers) void {
main.items.Inventory.Sync.useItem(.{.inv = inventory, .slot = selectedSlot}, .client);
}
pub fn placeBlock(mods: main.Window.Key.Modifiers) void {
if(main.renderer.MeshSelection.selectedBlockPos) |blockPos| {
if(!mods.shift) {
Expand All @@ -548,7 +551,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;
Expand Down Expand Up @@ -771,6 +774,7 @@ 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.useItem(mods);
Player.placeBlock(mods);
}

Expand Down
10 changes: 10 additions & 0 deletions src/items.zig
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ 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
Expand All @@ -251,6 +254,7 @@ pub const BaseItem = struct { // MARK: BaseItem
name: []const u8,
tags: []const Tag,
tooltip: []const u8,
onUse: main.callbacks.UseItemCallback,

stackSize: u16,
material: ?Material,
Expand Down Expand Up @@ -301,6 +305,12 @@ pub const BaseItem = struct { // MARK: BaseItem
_ = tooltip.swapRemove(tooltip.items.len - 1);
}
self.tooltip = tooltip.toOwnedSlice();
self.onUse = blk: {
Copy link
Member

Choose a reason for hiding this comment

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

Callbacks should be registered after everything else, to allow it to reference other items or blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

turns out to be more complex then for blocks, since blocks have an array of zon elements, but Item don't.
I only see ugly ways to implement this, like storing the zon in the BaseItem until everything is finished and then parsing the callbacks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

did it the ugly way with a finalize() function and storing .zon object

Copy link
Member

Choose a reason for hiding this comment

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

I don't like this hack. We shouldn't mix runtime and loadtime data in one struct.
Can you please use the same method as for blocks by aggregating all the item zons in a hashmap?

break :blk main.callbacks.UseItemCallback.init(zon.getChildOrNull("onUse") orelse break :blk .noop) orelse {
std.log.err("Failed to load onUse event for item {s}", .{id});
break :blk .noop;
};
};
}

fn hashCode(self: BaseItem) u32 {
Expand Down
2 changes: 1 addition & 1 deletion src/server/Entity.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down