Skip to content

[AAA] Part 3 Item callbacks#2898

Open
Wunka wants to merge 19 commits intoPixelGuys:masterfrom
Wunka:AAA-callbacks
Open

[AAA] Part 3 Item callbacks#2898
Wunka wants to merge 19 commits intoPixelGuys:masterfrom
Wunka:AAA-callbacks

Conversation

@Wunka
Copy link
Copy Markdown
Contributor

@Wunka Wunka commented Apr 12, 2026

This PR will add ItemCallbacks
For now this will only contain onLeftClick

Requires: #2891

What needs to be done before this PR is ready:

  • add the execution of the callback at all relevant places
  • move breaking of blocks in a callback
  • move special wand mechanic into a callback
  • remove testing callback

@Wunka Wunka marked this pull request as ready for review April 13, 2026 19:33
@Wunka Wunka changed the title [AAA] Item callbacks [AAA] Part 3 Item callbacks Apr 13, 2026
@Wunka
Copy link
Copy Markdown
Contributor Author

Wunka commented Apr 14, 2026

@Argmaster I will have to look over the code again to remove some things like the temporary zon creation. But I would like your input for the general structure atleast.

@Argmaster Argmaster self-requested a review April 14, 2026 08:09
Comment thread src/items.zig
damage: f32,
properties: [@typeInfo(ProceduralItemProperty).@"enum".fields.len]f32 = @splat(0),

durability: u32,
Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 14, 2026

Choose a reason for hiding this comment

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

I think we should consider replacing durability field with timesUsed or something alike, basically reversed durability, so when a tool gets balance changes it doesn't show up funny. Would have to be clamped at zero and tool with zero durability would have to break instantly.

ofc out of scope

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't that mean that for example if a player has an item wich has like 300 timesUsed and we reduce through an update the maxUsage to something below 300, then the item would just break instantly on joining the world.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We break items only when they are used.
But yes, general intention is to make the item asap.

Comment thread src/items.zig
Comment thread src/callbacks/callbacks.zig Outdated
item: main.items.Item,
target: union(enum) {
block: struct { block: Block, blockPos: Vec3i },
entity: *main.server.Entity,
Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 14, 2026

Choose a reason for hiding this comment

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

I think these may not be mutually exclusive.
A scythe, or a large axe may do AOE damage, what then? (Hitting multiple blocks or entities and blocks)
Should we even do that inside the callback or outside of it?
Additionally it may be very useful to get the source player as a parameter and source inventory too.
Although that last thing has no use for now, so that's something to ignore for now.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Honestly I would remove the target completely and instead provide tools to query the possible target from MeshSelection if the callback cares to do so. This means the params need to be extended with everything required for such query.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I removed it. callbacks now get the selectedBlockPos which they (if it is not null) can be used to query the block. This also already helped calls because the selection wand doesn't care about the block only its position

Comment thread src/renderer.zig
}

pub fn breakBlock(inventory: main.items.Inventory.ClientInventory, slot: u32, deltaTime: f64) void {
pub fn breakBlock(inventory: main.items.Inventory.ClientInventory, slot: u32, _deltaTime: f64) void {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think you should eliminate the chain of jumps before this call.
Currently you are jumping game.breakBlock -> Inventory.breakBlock -> renderer.MeshSelection.breakBlock -> <onLeftClick callback> for no reason IMO I think it should just call left click callback directly from game.breakBlock.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

but the game.breakBlock has no access to selectedPos I think the call order is ok.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Eh alright, then I guess there is unfortunately a reason. I will have a look at this when I have time to maybe find a better way around this.

Comment thread src/renderer.zig
pub fn breakBlock(inventory: main.items.Inventory.ClientInventory, slot: u32, deltaTime: f64) void {
pub fn breakBlock(inventory: main.items.Inventory.ClientInventory, slot: u32, _deltaTime: f64) void {
var deltaTime = _deltaTime;
if (selectedBlockPos) |selectedPos| {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think breaking animation should be handled inside the callback. You may want to extract it as helper function, but it should be called from inside the callback. There might be tools that don't remove breaking animation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that means callbacks would need a finsish method right? How would they else know that the they can remove the animation

Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 15, 2026

Choose a reason for hiding this comment

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

No? I think no.
This is clearing braking animation when you start braking different block.
Since this callback calls into updateBlock sync call, you can do stuff before and after it too, so you can also clear it after brekitn a block.

Comment thread src/renderer.zig Outdated
var selectionFace: chunk.Neighbor = undefined;
var lastPos: Vec3d = undefined;
var lastDir: Vec3f = undefined;
pub var lastDir: Vec3f = undefined;
Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 14, 2026

Choose a reason for hiding this comment

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

I don't think it should be made public; this should be a parameter to callback. Preferably inside a struct alongside lastPos as playerOrientation or alike. Callback should receive current player position and direction and should figure out storing that. Then the callback should figure out storing that, no?

Copy link
Copy Markdown
Contributor Author

@Wunka Wunka Apr 24, 2026

Choose a reason for hiding this comment

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

is now given over callback

Comment thread src/renderer.zig
}

fn updateBlockAndSendUpdate(source: main.items.Inventory.ClientInventory, slot: u32, pos: Vec3i, oldBlock: blocks.Block, newBlock: blocks.Block) void {
pub fn updateBlockAndSendUpdate(source: main.items.Inventory.ClientInventory, slot: u32, pos: Vec3i, oldBlock: blocks.Block, newBlock: blocks.Block) void {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No you don't. This function makes no sense as part of public API of renderer. Renderer does rendering, it doesn't send updates. Move it or call executeCommand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have to look how I will exactly handle this.

Comment thread src/renderer.zig Outdated
var currentBlockProgress: f32 = 0;
var currentSwingProgress: f32 = 0;
var currentSwingTime: f32 = 0;
pub var lastSelectedBlockPos: Vec3i = undefined;
Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 14, 2026

Choose a reason for hiding this comment

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

This should be managed by callback implementation IMO. MeshSelection may have its own use cases for it (does it?) but even then it shouldn't share it with everyone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

breakBlock seems to use it without reason, in the cases it uses it, it should be the same as selectedPos. I atleast didn't notice any difference in breaking animation.

@Wunka Wunka moved this to WIP/not ready for review in PRs to review Apr 19, 2026
@Wunka Wunka moved this from WIP/not ready for review to High Priority in PRs to review Apr 27, 2026
Copy link
Copy Markdown
Member

@IntegratedQuantum IntegratedQuantum left a comment

Choose a reason for hiding this comment

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

I'm not sure where to start here, but I think this is a bit of a step in the wrong direction.

First up, all player actions should be synced with the server to avoid cheating, so the item callback should be executed in the context of a sync command (see also #2381).
This is also important, since otherwise actions can happen out of sync (e.g. the way it's currently implemented the selection wand action and a world edit command may happen out of order since one is happening in the render thread, while the other is deferred to the next server tick as part of the sync system)

Secondly, all the animation state should be shared through an inventory component (@RanPix @tillpp maybe you already have something planned for this?), furthermore the current swinging system is planned to work differently, instead of only progressing when you keep the button pressed, a started swing should end even if you release the button (see also #1137).
In total I'd suggest to for now keep block breaking untouched, but only run it if there is no other successful callback.

Lastly, together with the points above: The callback should not know the deltaTime.

pub const BlockTouchCallback = Callback(struct { entity: *main.server.Entity, source: Block, blockPos: Vec3i, deltaTime: f64 }, @import("block/touch/_list.zig"));

pub const ItemUsedCallback = Callback(struct {
item: main.items.Item,
Copy link
Copy Markdown
Member

@IntegratedQuantum IntegratedQuantum Apr 30, 2026

Choose a reason for hiding this comment

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

Should pass the inventory slot, so you can run useDurability and similar commands on it.

@IntegratedQuantum IntegratedQuantum moved this from High Priority to In review in PRs to review Apr 30, 2026
@IntegratedQuantum
Copy link
Copy Markdown
Member

Also it might be worth considering to also pass the name of the keyboard key, this would make it easier to add other keybinds (at least right click and middle click are on the table for the base game)

@Argmaster
Copy link
Copy Markdown
Collaborator

I am not sure if I understand.
I assumed we are aiming for similar interface as touch callback, where we would call into sync system as we need.
With this assumptions I cannot agree that this PR is a step in wrong direction, as only thing it does is moving existing logic into our callback framework, which I find very much aligned with what I imagined.
Yes, the logic of using tools requires a complete overhaul, as mentioned in our discussion #2892 (review) but changing that is waaaay beyond what one PR can do (we already have 300 lines of changes). IMO its starting angle as good as any other.

In the long run, I imagined that those callbacks will be client side key stroke handlers and they will invoke sync system.
In a subsequent PRs we would add block damage tracking and corresponding sync commands. After that we would refactor breakBlock.zig so all that remain in it would be logic that prevents us from sending sync commands every time player spams the left click button. Malicious client can still DDOS us with those, so we will have to check it properly on server side, but we don't want to DDOS ourselves by default. I assumed similar logic will be needed every now and then.

Some more stuff that might want to be outside sync:

  • UI opening
  • not-implemented call to server
  • different sync commands depending on modifiers

@IntegratedQuantum
Copy link
Copy Markdown
Member

IntegratedQuantum commented Apr 30, 2026

touch callback also must be synced in the future together will all other player input, and I think at this point we should not introduce more callbacks that don't use the sync system, to make this rewrite harder than it already is.

In the long run, I imagined that those callbacks will be client side key stroke handlers and they will invoke sync system.

If the callback triggers a health increase, then how can the server verify that you actually are allowed to increase your health? Furthermore there is a bit of a synchronization issue, if another player places a cactus in your path, then you should take damage, even if your client didn't see it yet. That is only possible if the server evaluates the callback


I don't really get the DDOS concern, 1 packet per frame is already a given, and a few bytes more wouldn't make a difference there.

UI opening

Should be synced, so that when e.g. another player breaks your workbench, then the client can just roll back and close the UI (instead of the server having to tell the client about it).

@tillpp
Copy link
Copy Markdown
Contributor

tillpp commented Apr 30, 2026

all the animation state should be shared through an inventory component

We want to store animation Data in the model component.
(no idea what this entire conversation is about, i've got pinged here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants