diff --git a/steel-core/build/build.rs b/steel-core/build/build.rs index 58810ffa12e..38b6fd2351e 100644 --- a/steel-core/build/build.rs +++ b/steel-core/build/build.rs @@ -12,6 +12,7 @@ use std::fs; use syn::Ident; mod blocks; +mod candle_cakes; mod common; mod items; mod strippables; @@ -40,6 +41,8 @@ pub fn main() { blocks::build(&classes.blocks), ) .expect("Failed to write blocks.rs"); + fs::write(format!("{out_dir}/candle_cakes.rs"), candle_cakes::build()) + .expect("Failed to write candle_cakes.rs"); fs::write(format!("{out_dir}/items.rs"), items::build(&classes.items)) .expect("Failed to write items.rs"); fs::write(format!("{out_dir}/waxables.rs"), waxables::build()) diff --git a/steel-core/build/candle_cakes.json b/steel-core/build/candle_cakes.json new file mode 100644 index 00000000000..c183fafaf48 --- /dev/null +++ b/steel-core/build/candle_cakes.json @@ -0,0 +1,19 @@ +{ + "blue_candle": "blue_candle_cake", + "green_candle": "green_candle_cake", + "red_candle": "red_candle_cake", + "pink_candle": "pink_candle_cake", + "yellow_candle": "yellow_candle_cake", + "magenta_candle": "magenta_candle_cake", + "brown_candle": "brown_candle_cake", + "light_gray_candle": "light_gray_candle_cake", + "candle": "candle_cake", + "white_candle": "white_candle_cake", + "gray_candle": "gray_candle_cake", + "cyan_candle": "cyan_candle_cake", + "lime_candle": "lime_candle_cake", + "orange_candle": "orange_candle_cake", + "black_candle": "black_candle_cake", + "light_blue_candle": "light_blue_candle_cake", + "purple_candle": "purple_candle_cake" +} \ No newline at end of file diff --git a/steel-core/build/candle_cakes.rs b/steel-core/build/candle_cakes.rs new file mode 100644 index 00000000000..129e4bf91c2 --- /dev/null +++ b/steel-core/build/candle_cakes.rs @@ -0,0 +1,35 @@ +use std::{collections::BTreeMap, fs}; + +use proc_macro2::Span; +use quote::quote; +use syn::Ident; + +use crate::to_block_ident; + +pub fn build() -> String { + println!("cargo:rerun-if-changed=build/candle_cakes.json"); + + let candle_cakes_json = + fs::read_to_string("build/candle_cakes.json").expect("Failed to read candle_cakes.json"); + let candle_cakes_raw: BTreeMap = + serde_json::from_str(&candle_cakes_json).expect("Failed to parse candle_cakes.json"); + + let by_candle: Vec = candle_cakes_raw + .iter() + .map(|(key, value)| (Ident::new(key.to_lowercase().as_str(), Span::call_site()), to_block_ident(value))) + .map(|(key, value)| quote! { i if i == &vanilla_items::ITEMS.#key => Some(&vanilla_blocks::#value), }) + .collect(); + + let output = quote! { + use steel_registry::{blocks::BlockRef, items::ItemRef, vanilla_blocks, vanilla_items}; + + pub fn candle_to_candle_cake(item: ItemRef) -> Option { + match item { + #(#by_candle)* + _ => None + } + } + }; + + output.to_string() +} diff --git a/steel-core/src/behavior/block.rs b/steel-core/src/behavior/block.rs index 4ce3f9c9932..c214584bc99 100644 --- a/steel-core/src/behavior/block.rs +++ b/steel-core/src/behavior/block.rs @@ -12,6 +12,7 @@ use steel_registry::{REGISTRY, RegistryEntry, RegistryExt}; use steel_utils::types::{InteractionHand, UpdateFlags}; use steel_utils::{BlockPos, BlockStateId}; +use crate::behavior::InventoryAccess; use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult}; use crate::block_entity::SharedBlockEntity; use crate::entity::Entity; @@ -144,13 +145,13 @@ pub trait BlockBehavior: Send + Sync { )] fn use_item_on( &self, - item_stack: &ItemStack, state: BlockStateId, world: &Arc, pos: BlockPos, player: &Player, hand: InteractionHand, hit_result: &BlockHitResult, + inv: &mut InventoryAccess, ) -> InteractionResult { InteractionResult::TryEmptyHandInteraction } @@ -171,6 +172,7 @@ pub trait BlockBehavior: Send + Sync { pos: BlockPos, player: &Player, hit_result: &BlockHitResult, + inv: &mut InventoryAccess, ) -> InteractionResult { InteractionResult::Pass } diff --git a/steel-core/src/behavior/blocks/container/barrel_block.rs b/steel-core/src/behavior/blocks/container/barrel_block.rs index 8b879b1ccd4..e33b61a8f44 100644 --- a/steel-core/src/behavior/blocks/container/barrel_block.rs +++ b/steel-core/src/behavior/blocks/container/barrel_block.rs @@ -12,6 +12,7 @@ use steel_registry::vanilla_block_entity_types; use steel_utils::{BlockPos, BlockStateId, translations}; use text_components::TextComponent; +use crate::behavior::InventoryAccess; use crate::behavior::block::BlockBehavior; use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult}; use crate::block_entity::{BLOCK_ENTITIES, SharedBlockEntity}; @@ -57,6 +58,7 @@ impl BlockBehavior for BarrelBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { // Get the block entity let Some(block_entity) = world.get_block_entity(pos) else { diff --git a/steel-core/src/behavior/blocks/container/crafting_table_block.rs b/steel-core/src/behavior/blocks/container/crafting_table_block.rs index 82723b8c11c..05d8abc8310 100644 --- a/steel-core/src/behavior/blocks/container/crafting_table_block.rs +++ b/steel-core/src/behavior/blocks/container/crafting_table_block.rs @@ -8,6 +8,7 @@ use steel_macros::block_behavior; use steel_registry::blocks::BlockRef; use steel_utils::{BlockPos, BlockStateId}; +use crate::behavior::InventoryAccess; use crate::behavior::block::BlockBehavior; use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult}; use crate::inventory::CraftingMenuProvider; @@ -43,6 +44,7 @@ impl BlockBehavior for CraftingTableBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { player.open_menu(&CraftingMenuProvider::new(player.inventory.clone(), pos)); // TODO: Award stat INTERACT_WITH_CRAFTING_TABLE diff --git a/steel-core/src/behavior/blocks/decoration/cake_block.rs b/steel-core/src/behavior/blocks/decoration/cake_block.rs new file mode 100644 index 00000000000..e36d55e3acd --- /dev/null +++ b/steel-core/src/behavior/blocks/decoration/cake_block.rs @@ -0,0 +1,173 @@ +use std::sync::Arc; + +use steel_macros::block_behavior; +use steel_registry::{ + REGISTRY, TaggedRegistryExt, + blocks::{BlockRef, block_state_ext::BlockStateExt, properties::BlockStateProperties}, + items::item::BlockHitResult, + sound_events, vanilla_blocks, vanilla_item_tags, +}; +use steel_utils::{ + BlockPos, BlockStateId, Direction, + types::{InteractionHand, UpdateFlags}, +}; + +use crate::{ + behavior::{ + BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess, candle_cakes, + }, + player::Player, + world::World, +}; + +/// Behavior for Cakes +/// TODO: +/// - [ ] animation ticks +/// - [ ] onProjectile +/// - [ ] onExplosion +#[block_behavior] +pub struct CakeBlock { + block: BlockRef, +} + +impl CakeBlock { + /// Cakes a new Cake Block Behavior + #[must_use] + pub const fn new(block: BlockRef) -> Self { + Self { block } + } + + /// Eats a slice of the cake for the player and updates the block + pub fn eat( + world: &Arc, + pos: BlockPos, + state: BlockStateId, + player: &Player, + ) -> InteractionResult { + if player.can_eat(false) { + let mut food_data = player.food_data.lock(); + food_data.eat(2, 0.1); + let bites = state.get_value(&BlockStateProperties::BITES); + let new_state = if bites < 6 { + state.set_value(&BlockStateProperties::BITES, bites + 1) + } else { + vanilla_blocks::AIR.default_state() + }; + world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL); + return InteractionResult::Success; + } + InteractionResult::Pass + } + + /// Analog Output Signal for the Amount of Bites + #[must_use] + pub const fn analog_output_signal(bites: i32) -> i32 { + (7 - bites) * 2 + } +} + +impl BlockBehavior for CakeBlock { + fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option { + if context + .world + .get_block_state(context.relative_pos.below()) + .is_solid() + { + Some(self.block.default_state()) + } else { + None + } + } + + fn can_survive(&self, _state: BlockStateId, world: &Arc, pos: BlockPos) -> bool { + world.get_block_state(pos.below()).is_solid() + } + + fn update_shape( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + direction: Direction, + _neighbor_pos: BlockPos, + _neighbor_state: BlockStateId, + ) -> BlockStateId { + if direction == Direction::Down && !self.can_survive(state, world, pos) { + vanilla_blocks::AIR.default_state() + } else { + state + } + } + + fn use_without_item( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + player: &Player, + _hit_result: &BlockHitResult, + inv: &mut InventoryAccess, + ) -> InteractionResult { + if Self::eat(world, pos, state, player).consumes_action() { + return InteractionResult::Success; + } + + if inv.item().is_empty() { + return InteractionResult::Fail; + } + InteractionResult::Pass + } + + fn use_item_on( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + player: &Player, + _hand: InteractionHand, + _hit_result: &BlockHitResult, + inv: &mut InventoryAccess, + ) -> InteractionResult { + let item_stack = inv.item(); + if REGISTRY + .items + .is_in_tag(item_stack.item(), &vanilla_item_tags::CANDLES_TAG) + && state.get_value(&BlockStateProperties::BITES) == 0 + { + if !player.has_infinite_materials() { + item_stack.shrink(1); + } + world.play_block_sound( + sound_events::BLOCK_CAKE_ADD_CANDLE, + pos, + 1.0, + 1.0, + Some(player.id), + ); + world.set_block( + pos, + candle_cakes::candle_to_candle_cake(item_stack.item()) + .expect( + "Candle Item is in CANDLES_TAG but isnt in the candle_to_candle_cake map", + ) + .default_state(), + UpdateFlags::UPDATE_ALL, + ); + return InteractionResult::Success; + } + InteractionResult::TryEmptyHandInteraction + } + + fn get_analog_output_signal( + &self, + state: BlockStateId, + _world: &Arc, + _pos: BlockPos, + ) -> i32 { + Self::analog_output_signal(i32::from(state.get_value(&BlockStateProperties::BITES))) + } + + fn has_analog_output_signal(&self, _state: BlockStateId) -> bool { + true + } +} diff --git a/steel-core/src/behavior/blocks/decoration/candle_block.rs b/steel-core/src/behavior/blocks/decoration/candle_block.rs index 00792baf0cd..795beb58240 100644 --- a/steel-core/src/behavior/blocks/decoration/candle_block.rs +++ b/steel-core/src/behavior/blocks/decoration/candle_block.rs @@ -10,7 +10,6 @@ use steel_registry::{ shapes::SupportType, }, entity_data::Direction, - item_stack::ItemStack, items::item::BlockHitResult, vanilla_blocks, vanilla_item_tags, }; @@ -20,7 +19,7 @@ use steel_utils::{ }; use crate::{ - behavior::{BlockBehavior, BlockPlaceContext, InteractionResult}, + behavior::{BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess}, player, world::World, }; @@ -85,14 +84,15 @@ impl BlockBehavior for CandleBlock { fn use_item_on( &self, - item_stack: &ItemStack, state: steel_utils::BlockStateId, world: &Arc, pos: BlockPos, _player: &player::Player, _hand: types::InteractionHand, _hit_result: &BlockHitResult, + inv: &mut InventoryAccess, ) -> InteractionResult { + let item_stack = inv.item(); if item_stack.is_empty() { if !state.get_value(&LIT_PROPERTY) { return InteractionResult::Pass; diff --git a/steel-core/src/behavior/blocks/decoration/candle_cake_block.rs b/steel-core/src/behavior/blocks/decoration/candle_cake_block.rs new file mode 100644 index 00000000000..7785db7de3e --- /dev/null +++ b/steel-core/src/behavior/blocks/decoration/candle_cake_block.rs @@ -0,0 +1,148 @@ +use std::sync::Arc; + +use steel_macros::block_behavior; +use steel_registry::{ + blocks::{BlockRef, block_state_ext::BlockStateExt, properties::BlockStateProperties}, + item_stack::ItemStack, + items::item::BlockHitResult, + sound_events, vanilla_blocks, vanilla_items, +}; +use steel_utils::{ + BlockPos, BlockStateId, Direction, + types::{InteractionHand, UpdateFlags}, +}; + +use crate::{ + behavior::{ + BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess, blocks::CakeBlock, + }, + player::Player, + world::World, +}; + +/// Behavior for Candle Cakes +/// TODO: +/// - [ ] animation ticks +/// - [ ] onProjectile +/// - [ ] onExplosion +#[block_behavior] +pub struct CandleCakeBlock { + block: BlockRef, +} + +impl CandleCakeBlock { + /// Creates a new Candle Cake Block Behavior + #[must_use] + pub const fn new(block: BlockRef) -> Self { + Self { block } + } +} + +impl BlockBehavior for CandleCakeBlock { + fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option { + if context + .world + .get_block_state(context.relative_pos.below()) + .is_solid() + { + Some(self.block.default_state()) + } else { + None + } + } + + fn use_item_on( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + player: &Player, + _hand: InteractionHand, + hit_result: &BlockHitResult, + inv: &mut InventoryAccess, + ) -> InteractionResult { + let item_stack = inv.item(); + if item_stack.is(&vanilla_items::ITEMS.fire_charge) + || item_stack.is(&vanilla_items::ITEMS.flint_and_steel) + { + return InteractionResult::Pass; // lighting of candles and candle cakes is handled by the flint and steel/fire charge implementation + } else if (hit_result.location.y - f64::from(hit_result.block_pos.y())) > 0.5 + && item_stack.is_empty() + && state.get_value(&BlockStateProperties::LIT) + { + world.set_block( + pos, + state.set_value(&BlockStateProperties::LIT, false), + UpdateFlags::UPDATE_ALL, + ); + // TODO: particles! + world.play_block_sound( + sound_events::BLOCK_CANDLE_EXTINGUISH, + pos, + 1.0, + 1.0, + Some(player.id), + ); + return InteractionResult::Success; + } + InteractionResult::TryEmptyHandInteraction + } + + fn use_without_item( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + player: &Player, + _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, + ) -> InteractionResult { + let result = CakeBlock::eat(world, pos, vanilla_blocks::CAKE.default_state(), player); + if result.consumes_action() { + world.drop_resources(state, pos); + } + result + } + + fn can_survive(&self, _state: BlockStateId, world: &Arc, pos: BlockPos) -> bool { + world.get_block_state(pos.below()).is_solid() + } + + fn update_shape( + &self, + state: BlockStateId, + world: &Arc, + pos: BlockPos, + direction: steel_utils::Direction, + _neighbor_pos: BlockPos, + _neighbor_state: BlockStateId, + ) -> BlockStateId { + if direction == Direction::Down && !self.can_survive(state, world, pos) { + vanilla_blocks::AIR.default_state() + } else { + state + } + } + + fn get_clone_item_stack( + &self, + _block: BlockRef, + _state: BlockStateId, + _include_data: bool, + ) -> Option { + Some(ItemStack::new(&vanilla_items::ITEMS.cake)) + } + + fn get_analog_output_signal( + &self, + _state: BlockStateId, + _world: &Arc, + _pos: BlockPos, + ) -> i32 { + CakeBlock::analog_output_signal(0) + } + + fn has_analog_output_signal(&self, _state: BlockStateId) -> bool { + true + } +} diff --git a/steel-core/src/behavior/blocks/decoration/mod.rs b/steel-core/src/behavior/blocks/decoration/mod.rs index cb48d0136fa..06dcd07e43c 100644 --- a/steel-core/src/behavior/blocks/decoration/mod.rs +++ b/steel-core/src/behavior/blocks/decoration/mod.rs @@ -1,8 +1,12 @@ +mod cake_block; mod candle_block; +mod candle_cake_block; mod sign_block; mod torch_block; +pub use cake_block::CakeBlock; pub use candle_block::CandleBlock; +pub use candle_cake_block::CandleCakeBlock; pub use sign_block::{ CeilingHangingSignBlock, StandingSignBlock, WallHangingSignBlock, WallSignBlock, }; diff --git a/steel-core/src/behavior/blocks/decoration/sign_block.rs b/steel-core/src/behavior/blocks/decoration/sign_block.rs index cef18838cca..c56fbfbd2c9 100644 --- a/steel-core/src/behavior/blocks/decoration/sign_block.rs +++ b/steel-core/src/behavior/blocks/decoration/sign_block.rs @@ -16,6 +16,7 @@ use steel_registry::vanilla_blocks; use steel_utils::locks::SyncMutex; use steel_utils::{BlockPos, BlockStateId}; +use crate::behavior::InventoryAccess; use crate::behavior::block::BlockBehavior; use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult}; use crate::block_entity::SharedBlockEntity; @@ -348,6 +349,7 @@ impl BlockBehavior for StandingSignBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { try_open_sign_editor(state, world, pos, player) } @@ -436,6 +438,7 @@ impl BlockBehavior for WallSignBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { try_open_sign_editor(state, world, pos, player) } @@ -558,6 +561,7 @@ impl BlockBehavior for CeilingHangingSignBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { try_open_sign_editor(state, world, pos, player) } @@ -664,6 +668,7 @@ impl BlockBehavior for WallHangingSignBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { try_open_sign_editor(state, world, pos, player) } diff --git a/steel-core/src/behavior/blocks/mod.rs b/steel-core/src/behavior/blocks/mod.rs index 5237595a796..da12612ecbc 100644 --- a/steel-core/src/behavior/blocks/mod.rs +++ b/steel-core/src/behavior/blocks/mod.rs @@ -16,8 +16,8 @@ pub use building::{ }; pub use container::{BarrelBlock, CraftingTableBlock}; pub use decoration::{ - CandleBlock, CeilingHangingSignBlock, StandingSignBlock, TorchBlock, WallHangingSignBlock, - WallSignBlock, WallTorchBlock, + CakeBlock, CandleBlock, CandleCakeBlock, CeilingHangingSignBlock, StandingSignBlock, + TorchBlock, WallHangingSignBlock, WallSignBlock, WallTorchBlock, }; pub use farming::{CactusBlock, CactusFlowerBlock, CropBlock, FarmlandBlock}; pub use fluid::LiquidBlock; diff --git a/steel-core/src/behavior/blocks/redstone/button_block.rs b/steel-core/src/behavior/blocks/redstone/button_block.rs index bfc5eb46a8a..7ef92723ba8 100644 --- a/steel-core/src/behavior/blocks/redstone/button_block.rs +++ b/steel-core/src/behavior/blocks/redstone/button_block.rs @@ -17,6 +17,7 @@ use steel_utils::math::Axis; use steel_utils::types::UpdateFlags; use steel_utils::{BlockPos, BlockStateId}; +use crate::behavior::InventoryAccess; use crate::behavior::block::BlockBehavior; use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult}; use crate::player::Player; @@ -154,6 +155,7 @@ impl BlockBehavior for ButtonBlock { pos: BlockPos, player: &Player, _hit_result: &BlockHitResult, + _inv: &mut InventoryAccess, ) -> InteractionResult { let powered: bool = state.get_value(&BlockStateProperties::POWERED); if powered { diff --git a/steel-core/src/behavior/context.rs b/steel-core/src/behavior/context.rs index c0cb698e2a5..cefe7e4fe32 100644 --- a/steel-core/src/behavior/context.rs +++ b/steel-core/src/behavior/context.rs @@ -116,7 +116,19 @@ pub struct InventoryAccess<'a> { inv_id: ContainerId, } -impl InventoryAccess<'_> { +impl<'a> InventoryAccess<'a> { + /// Creates a new `InventoryAccess` instance. + pub const fn new( + inv_guard: &'a mut ContainerLockGuard, + hand: InteractionHand, + inv_id: ContainerId, + ) -> Self { + Self { + inv_guard, + hand, + inv_id, + } + } /// Returns a mutable reference to the item in the player's hand. /// /// Cannot be held simultaneously with `inventory()` or `guard()`. diff --git a/steel-core/src/behavior/mod.rs b/steel-core/src/behavior/mod.rs index e9150bfa872..960f30d63b5 100644 --- a/steel-core/src/behavior/mod.rs +++ b/steel-core/src/behavior/mod.rs @@ -38,6 +38,11 @@ pub mod block_behaviors; #[expect(warnings)] #[rustfmt::skip] +#[path = "generated/candle_cakes.rs"] +pub mod candle_cakes; + +#[allow(warnings)] +#[rustfmt::skip] #[path = "generated/items.rs"] pub mod item_behaviors; diff --git a/steel-core/src/player/game_mode.rs b/steel-core/src/player/game_mode.rs index 0eb10b3dc15..04a657fc091 100644 --- a/steel-core/src/player/game_mode.rs +++ b/steel-core/src/player/game_mode.rs @@ -20,7 +20,8 @@ use steel_utils::types::{Difficulty, GameType, InteractionHand}; use text_components::TextComponent; use crate::behavior::{ - BLOCK_BEHAVIORS, BlockHitResult, ITEM_BEHAVIORS, InteractionResult, UseOnContext, + BLOCK_BEHAVIORS, BlockHitResult, ITEM_BEHAVIORS, InteractionResult, InventoryAccess, + UseOnContext, }; use crate::block_entity::BlockEntity; use crate::block_entity::entities::SignBlockEntity; @@ -84,11 +85,20 @@ pub fn use_item_on( }; let behavior = block_behaviors.get_behavior(block); - // Brief lock for an immutable snapshot used during block interaction check - let item_snapshot = player.inventory.lock().get_item_in_hand(hand).clone(); + let inv_ref = ContainerRef::PlayerInventory(player.inventory.clone()); + let mut guard = ContainerLockGuard::lock_all(&[&inv_ref]); + let inv_id = inv_ref.container_id(); + let mut inventory_access = InventoryAccess::new(&mut guard, hand, inv_id); - let block_result = - behavior.use_item_on(&item_snapshot, state, world, pos, player, hand, hit_result); + let block_result = behavior.use_item_on( + state, + world, + pos, + player, + hand, + hit_result, + &mut inventory_access, + ); if block_result.consumes_action() { return block_result; @@ -97,7 +107,14 @@ pub fn use_item_on( if matches!(block_result, InteractionResult::TryEmptyHandInteraction) && hand == InteractionHand::MainHand { - let empty_result = behavior.use_without_item(state, world, pos, player, hit_result); + let empty_result = behavior.use_without_item( + state, + world, + pos, + player, + hit_result, + &mut inventory_access, + ); if empty_result.consumes_action() { return empty_result; diff --git a/steel-core/src/player/mod.rs b/steel-core/src/player/mod.rs index 87f681c02bc..f5a5ebab1ff 100644 --- a/steel-core/src/player/mod.rs +++ b/steel-core/src/player/mod.rs @@ -260,7 +260,7 @@ pub struct Player { living_base: SyncMutex, /// Player food/hunger state (food level, saturation, exhaustion). - food_data: SyncMutex, + pub food_data: SyncMutex, /// Delta-tracking state for `CSetHealth` deduplication. health_sync: SyncMutex, @@ -939,6 +939,13 @@ impl Player { } } + /// Returns whether the Player can eat + pub fn can_eat(&self, can_always_eat: bool) -> bool { + let invulnerable = { self.abilities.lock().invulnerable }; + let needs_foods = { self.food_data.lock().needs_food() }; + invulnerable || can_always_eat || needs_foods + } + /// Cleans up player resources. #[expect(clippy::unused_self, reason = "this is an api function")] pub const fn cleanup(&self) {} diff --git a/steel-core/src/player/player_inventory.rs b/steel-core/src/player/player_inventory.rs index 1999a0756d4..f577631ac63 100644 --- a/steel-core/src/player/player_inventory.rs +++ b/steel-core/src/player/player_inventory.rs @@ -128,13 +128,13 @@ impl PlayerInventory { f(&self.items[self.selected as usize]) } - /// Returns a clone of the currently selected item (main hand). + /// Returns the currently selected item (main hand). #[must_use] pub const fn get_selected_item(&self) -> &ItemStack { &self.items[self.selected as usize] } - /// Returns a clone of the currently selected item (main hand). + /// Returns the currently selected item (main hand). #[must_use] pub const fn get_selected_item_mut(&mut self) -> &mut ItemStack { &mut self.items[self.selected as usize]