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 = 13,
useItem = 12,
};
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");

food: f32,
pub fn init(zon: main.ZonElement) ?*@This() {
const result = main.worldArena.create(@This());
result.* = .{
.food = 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.food > 0 and previous >= maxEnergy)
return .ignored;

cmd.executeBaseOperation(allocator, .{.addEnergy = .{
.target = user,
.energy = self.food,
.previous = previous,
}}, side);

// Apply inventory changes:
if(gamemode == .creative) return .handled;

cmd.executeBaseOperation(allocator, .{.delete = .{
.source = source,
.amount = 1,
}}, side);
return .handled;
}
14 changes: 9 additions & 5 deletions 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 @@ -547,8 +550,8 @@ pub const Player = struct { // MARK: Player
Player.super.pos = world.?.spawn;
Player.super.vel = .{0, 0, 0};

Player.super.health = Player.super.maxHealth;
Player.super.energy = Player.super.maxEnergy;
Player.super.health = Player.super.maxHealth/2;
Player.super.energy = 0;

Player.eye = .{};
Player.jumpCoyote = 0;
Expand Down Expand Up @@ -768,13 +771,14 @@ pub var fog = Fog{.skyColor = .{0.8, 0.8, 1}, .fogColor = .{0.8, 0.8, 1}, .densi
var nextBlockPlaceTime: ?std.Io.Timestamp = null;
var nextBlockBreakTime: ?std.Io.Timestamp = null;

pub fn pressPlace(mods: main.Window.Key.Modifiers) void {
pub fn pressSecondary(mods: main.Window.Key.Modifiers) void {
const time = main.timestamp();
nextBlockPlaceTime = time.addDuration(main.settings.updateRepeatDelay);
Player.useItem(mods);
Player.placeBlock(mods);
}

pub fn releasePlace(_: main.Window.Key.Modifiers) void {
pub fn releaseSecondary(_: main.Window.Key.Modifiers) void {
nextBlockPlaceTime = null;
}

Expand Down Expand Up @@ -975,7 +979,7 @@ pub fn update(deltaTime: f64) void { // MARK: update()
if(nextBlockPlaceTime) |*placeTime| {
if(placeTime.durationTo(time).nanoseconds >= 0) {
placeTime.* = placeTime.addDuration(main.settings.updateRepeatSpeed);
Player.placeBlock(main.KeyBoard.key("placeBlock").modsOnPress);
Player.placeBlock(main.KeyBoard.key("use and place").modsOnPress);
}
}
if(nextBlockBreakTime) |*breakTime| {
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 inline 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: {
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/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ pub const KeyBoard = struct { // MARK: KeyBoard
.{.name = "ghost", .key = c.GLFW_KEY_G, .pressAction = &game.ghostToggle},
.{.name = "hyperSpeed", .key = c.GLFW_KEY_H, .pressAction = &game.hyperSpeedToggle},
.{.name = "fall", .key = c.GLFW_KEY_LEFT_SHIFT, .gamepadButton = c.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB},
.{.name = "placeBlock", .mouseButton = c.GLFW_MOUSE_BUTTON_RIGHT, .gamepadAxis = .{.axis = c.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER}, .pressAction = &game.pressPlace, .releaseAction = &game.releasePlace, .notifyRequirement = .inGame},
.{.name = "use and place", .mouseButton = c.GLFW_MOUSE_BUTTON_RIGHT, .gamepadAxis = .{.axis = c.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER}, .pressAction = &game.pressSecondary, .releaseAction = &game.releaseSecondary, .notifyRequirement = .inGame},
.{.name = "breakBlock", .mouseButton = c.GLFW_MOUSE_BUTTON_LEFT, .gamepadAxis = .{.axis = c.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER}, .pressAction = &game.pressBreak, .releaseAction = &game.releaseBreak, .notifyRequirement = .inGame},
.{.name = "acquireSelectedBlock", .mouseButton = c.GLFW_MOUSE_BUTTON_MIDDLE, .gamepadButton = c.GLFW_GAMEPAD_BUTTON_DPAD_LEFT, .pressAction = &game.pressAcquireSelectedBlock, .notifyRequirement = .inGame},
.{.name = "drop", .key = c.GLFW_KEY_Q, .repeatAction = &game.Player.dropFromHand, .notifyRequirement = .inGame},
Expand Down
14 changes: 14 additions & 0 deletions src/server/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub const User = struct { // MARK: User
interpolation: utils.GenericInterpolation(3) = undefined,
lastTime: i16 = undefined,
lastSaveTime: std.Io.Timestamp = .fromNanoseconds(0),
lastHealthRegenTime: std.Io.Timestamp = .fromNanoseconds(0),
name: []const u8 = "",
renderDistance: u16 = undefined,
clientUpdatePos: Vec3i = .{0, 0, 0},
Expand Down Expand Up @@ -256,6 +257,7 @@ pub const User = struct { // MARK: User
self.interpolation.update(time, self.lastTime);
self.lastTime = time;

self.regenerateHealth();
const saveTime = main.timestamp();
if(self.lastSaveTime.durationTo(saveTime).toSeconds() > 5) {
world.?.savePlayer(self) catch |err| {
Expand All @@ -267,6 +269,18 @@ pub const User = struct { // MARK: User
self.loadUnloadChunks();
}

pub fn regenerateHealth(self: *User) void {
const healthRegenTime = main.timestamp();
if(self.lastHealthRegenTime.durationTo(healthRegenTime).toSeconds() > 5 and self.player.energy > 0 and self.player.health < self.player.maxHealth) {
// give 1 Health every 5 seconds
main.items.Inventory.Sync.addHealth(1, .heal, .server, self.id);

// but take only 1/4 food for it.
main.items.Inventory.Sync.addEnergy(-0.25, .server, self.id);
self.lastHealthRegenTime = healthRegenTime;
}
}

pub fn receiveData(self: *User, reader: *BinaryReader) !void {
self.mutex.lock();
defer self.mutex.unlock();
Expand Down