Skip to content

Commit 0a62ea8

Browse files
committed
Implemented consumeable item usage, aka foods
1 parent 6c0be69 commit 0a62ea8

File tree

1 file changed

+94
-4
lines changed
  • azalea-client/src/plugins/interact

1 file changed

+94
-4
lines changed

azalea-client/src/plugins/interact/mod.rs

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ use azalea_physics::{
2525
local_player::PhysicsState,
2626
};
2727
use azalea_protocol::packets::game::{
28-
ServerboundInteract, ServerboundUseItem,
28+
ServerboundInteract, ServerboundPlayerAction, ServerboundUseItem,
2929
s_interact::{self, InteractionHand},
30+
s_player_action::Action,
3031
s_swing::ServerboundSwing,
3132
s_use_item_on::ServerboundUseItemOn,
3233
};
@@ -44,6 +45,7 @@ use crate::{
4445
movement::MoveEventsSystems,
4546
packet::game::SendGamePacketEvent,
4647
respawn::perform_respawn,
48+
tick_counter::TicksConnected,
4749
};
4850

4951
/// A plugin that allows clients to interact with blocks in the world.
@@ -68,7 +70,10 @@ impl Plugin for InteractPlugin {
6870
)
6971
.add_systems(
7072
GameTick,
71-
handle_start_use_item_queued.before(PhysicsSystems),
73+
(
74+
handle_start_use_item_queued.before(PhysicsSystems),
75+
handle_using_item.before(PhysicsSystems),
76+
),
7277
)
7378
.add_observer(handle_entity_interact)
7479
.add_observer(handle_swing_arm_trigger);
@@ -208,6 +213,21 @@ pub struct StartUseItemQueued {
208213
/// it, but should be avoided to stay compatible with anticheats.
209214
pub force_block: Option<BlockPos>,
210215
}
216+
/// A component that tracks ongoing item use, like eating food.
217+
///
218+
/// While this component exists, the client will send [`ServerboundUseItem`]
219+
/// packets every tick. When the use duration expires, it sends
220+
/// [`ServerboundPlayerAction`] with [`ReleaseUseItem`] and removes this
221+
/// component.
222+
#[derive(Component, Debug)]
223+
pub struct UsingItem {
224+
pub hand: InteractionHand,
225+
/// The tick when the use started.
226+
pub start_tick: u64,
227+
/// The duration in seconds.
228+
pub duration_seconds: f32,
229+
}
230+
211231
#[allow(clippy::type_complexity)]
212232
pub fn handle_start_use_item_queued(
213233
mut commands: Commands,
@@ -217,11 +237,21 @@ pub fn handle_start_use_item_queued(
217237
&mut BlockStatePredictionHandler,
218238
&HitResultComponent,
219239
&LookDirection,
240+
&Inventory,
241+
&TicksConnected,
220242
Option<&Mining>,
221243
)>,
222244
) {
223-
for (entity, start_use_item, mut prediction_handler, hit_result, look_direction, mining) in
224-
query
245+
for (
246+
entity,
247+
start_use_item,
248+
mut prediction_handler,
249+
hit_result,
250+
look_direction,
251+
inventory,
252+
ticks_connected,
253+
mining,
254+
) in query
225255
{
226256
commands.entity(entity).remove::<StartUseItemQueued>();
227257

@@ -258,6 +288,23 @@ pub fn handle_start_use_item_queued(
258288
HitResult::Block(r) => {
259289
let seq = prediction_handler.start_predicting();
260290
if r.miss {
291+
// Check if the held item is consumable (like food)
292+
let held_item = inventory.held_item();
293+
let is_consumable = matches!(held_item, ItemStack::Present(item) if item.get_component::<components::Consumable>().is_some());
294+
295+
if is_consumable {
296+
// Start consuming the item
297+
let consume_seconds = held_item
298+
.get_component::<components::Consumable>()
299+
.map(|c| c.consume_seconds)
300+
.unwrap_or(1.6);
301+
commands.entity(entity).insert(UsingItem {
302+
hand: start_use_item.hand,
303+
start_tick: ticks_connected.0,
304+
duration_seconds: consume_seconds,
305+
});
306+
}
307+
261308
commands.trigger(SendGamePacketEvent::new(
262309
entity,
263310
ServerboundUseItem {
@@ -293,6 +340,49 @@ pub fn handle_start_use_item_queued(
293340
}
294341
}
295342

343+
pub fn handle_using_item(
344+
mut commands: Commands,
345+
query: Query<(
346+
Entity,
347+
&UsingItem,
348+
&mut BlockStatePredictionHandler,
349+
&LookDirection,
350+
&TicksConnected,
351+
)>,
352+
) {
353+
for (entity, using_item, mut prediction_handler, look_direction, ticks_connected) in query {
354+
let ticks_elapsed = ticks_connected.0 - using_item.start_tick;
355+
let duration_ticks = (using_item.duration_seconds * 20.0) as u64; // 20 ticks per second
356+
357+
if ticks_elapsed >= duration_ticks {
358+
// Finished using the item, send release packet
359+
let seq = prediction_handler.start_predicting();
360+
commands.trigger(SendGamePacketEvent::new(
361+
entity,
362+
ServerboundPlayerAction {
363+
action: Action::ReleaseUseItem,
364+
pos: BlockPos::new(0, 0, 0), // Not used for ReleaseUseItem
365+
direction: Direction::Down, // Not used for ReleaseUseItem
366+
seq,
367+
},
368+
));
369+
commands.entity(entity).remove::<UsingItem>();
370+
} else {
371+
// Continue using the item, send use packet
372+
let seq = prediction_handler.start_predicting();
373+
commands.trigger(SendGamePacketEvent::new(
374+
entity,
375+
ServerboundUseItem {
376+
hand: using_item.hand,
377+
seq,
378+
x_rot: look_direction.x_rot(),
379+
y_rot: look_direction.y_rot(),
380+
},
381+
));
382+
}
383+
}
384+
}
385+
296386
/// An ECS `Event` that makes the client tell the server that we right-clicked
297387
/// an entity.
298388
#[derive(Clone, Debug, EntityEvent)]

0 commit comments

Comments
 (0)