Implement Furnace (#1058)

* fix: the fields of the struct CookingRecipe should be pub.

* Add furnace screen handler and slot

* Add furnace block entity

* Add furnace block

* fix bug

* add client-side sync

* fix furnace facing

* remove unused statements

* fix spelling error

* Listening for property changes and syncing them to the client-side.

* fix furnace lit state

* fix fuel should be placed at fuel slot in quick_move()

* fix spelling error

* remove unused comment

* fix: only one listener is needed.

* pumpkin-data: add recipe_remainder

* Set the bottom item as the remainder if it has one

* pumpkin-data && furnace:fix spelling error

* fix building error

* use_with_item is the same as normal_use

* fix quick_move()

---------

Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
AyaSanae
2025-07-28 16:10:17 +08:00
committed by GitHub
parent 15c9aa4ac8
commit f80650c36c
13 changed files with 975 additions and 26 deletions

View File

@@ -0,0 +1,7 @@
{
"968": 967,
"969": 967,
"973": 967,
"1238": 1075,
"1308": 1075
}

View File

@@ -25,6 +25,7 @@ mod noise_parameter;
mod noise_router;
mod packet;
mod particle;
mod recipe_remainder;
mod recipes;
mod scoreboard_slot;
mod screen;
@@ -79,6 +80,7 @@ pub fn main() {
(recipes::build, "recipes.rs"),
(enchantments::build, "enchantment.rs"),
(fuels::build, "fuels.rs"),
(recipe_remainder::build, "recipe_remainder.rs"),
];
build_functions.par_iter().for_each(|(build_fn, file)| {

View File

@@ -0,0 +1,26 @@
use proc_macro2::TokenStream;
use quote::quote;
use std::{collections::HashMap, fs};
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/recipe_remainder.json");
let remainder: HashMap<u16, u16> =
serde_json::from_str(&fs::read_to_string("../assets/recipe_remainder.json").unwrap())
.expect("Failed to parse recipe_remainder.json");
let mut variants = TokenStream::new();
for (item_id, remainder_id) in remainder {
variants.extend(quote! {
#item_id => Some(#remainder_id),
});
}
quote! {
#[must_use]
pub const fn get_recipe_remainder_id(item_id: u16) -> Option<u16> {
match item_id {
#variants
_ => None,
}
}
}
}

View File

@@ -409,12 +409,12 @@ pub(crate) fn build() -> TokenStream {
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct CookingRecipe {
category: RecipeCategoryTypes,
group: Option<&'static str>,
ingredient: RecipeIngredientTypes,
cookingtime: i32,
experience: f32,
result: RecipeResultStruct,
pub category: RecipeCategoryTypes,
pub group: Option<&'static str>,
pub ingredient: RecipeIngredientTypes,
pub cookingtime: i32,
pub experience: f32,
pub result: RecipeResultStruct,
}
#[derive(Clone, Debug)]

View File

@@ -141,6 +141,10 @@ pub mod flower_pot_transformations;
#[path = "generated/fuels.rs"]
pub mod fuels;
#[rustfmt::skip]
#[path = "generated/recipe_remainder.rs"]
pub mod recipe_remainder;
mod block_direction;
pub mod block_state;
mod blocks;

View File

@@ -0,0 +1,142 @@
use std::{any::Any, sync::Arc};
use async_trait::async_trait;
use pumpkin_data::fuels::is_fuel;
use pumpkin_world::{block::entities::BlockEntity, inventory::Inventory, item::ItemStack};
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerListener,
ScreenProperty,
},
};
use super::furnace_slot::{FurnaceSlot, FurnaceSlotType};
pub struct FurnaceScreenHandler {
pub inventory: Arc<dyn Inventory>,
behaviour: ScreenHandlerBehaviour,
}
impl FurnaceScreenHandler {
pub async fn new(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
furnace_block_entity: Arc<dyn BlockEntity>,
) -> Self {
let furnace_property_delegate = furnace_block_entity.to_property_delegate().unwrap();
let mut handler = Self {
inventory,
behaviour: ScreenHandlerBehaviour::new(
sync_id,
Some(pumpkin_data::screen::WindowType::Furnace),
),
};
struct FurnaceScreenListener;
#[async_trait]
impl ScreenHandlerListener for FurnaceScreenListener {
async fn on_property_update(
&self,
screen_handler: &ScreenHandlerBehaviour,
property: u8,
value: i32,
) {
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler
.update_property(screen_handler, property as i32, value)
.await;
}
}
}
// 0: Fire icon (fuel left) counting from fuel burn time down to 0 (in-game ticks)
// 1: Maximum fuel burn time fuel burn time or 0 (in-game ticks)
// 2: Progress arrow counting from 0 to maximum progress (in-game ticks)
// 3: Maximum progress always 200 on the vanilla server
for i in 0..4 {
handler.add_property(ScreenProperty::new(furnace_property_delegate.clone(), i));
}
handler.add_listener(Arc::new(FurnaceScreenListener)).await;
handler.add_inventory_slots();
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
handler.add_player_slots(&player_inventory);
handler
}
fn add_inventory_slots(&mut self) {
self.add_slot(Arc::new(FurnaceSlot::new(
self.inventory.clone(),
FurnaceSlotType::Top,
)));
self.add_slot(Arc::new(FurnaceSlot::new(
self.inventory.clone(),
FurnaceSlotType::Bottom,
)));
self.add_slot(Arc::new(FurnaceSlot::new(
self.inventory.clone(),
FurnaceSlotType::Side,
)));
}
}
#[async_trait]
impl ScreenHandler for FurnaceScreenHandler {
async fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player).await;
//TODO: self.inventory.on_closed(player).await;
}
fn as_any(&self) -> &dyn Any {
self
}
fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
&self.behaviour
}
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
&mut self.behaviour
}
async fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
const FUEL_SLOT: i32 = 1;
let mut stack_left = ItemStack::EMPTY;
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if !slot.has_stack().await {
return stack_left;
}
let slot_stack = slot.get_stack().await;
let mut stack = slot_stack.lock().await;
stack_left = *stack;
let success = if slot_index < 3 {
self.insert_item(&mut stack, 3, self.get_behaviour().slots.len() as i32, true)
.await
} else if is_fuel(stack.item.id) {
self.insert_item(&mut stack, FUEL_SLOT, 3, false).await
} else {
self.insert_item(&mut stack, 0, 3, false).await
};
if !success {
return ItemStack::EMPTY;
}
if stack.is_empty() {
drop(stack);
slot.set_stack(ItemStack::EMPTY).await;
} else {
slot.mark_dirty().await;
}
stack_left
}
}

View File

@@ -0,0 +1,60 @@
use std::sync::{Arc, atomic::AtomicU8};
use async_trait::async_trait;
use pumpkin_data::{fuels::is_fuel, item::Item};
use pumpkin_world::inventory::Inventory;
use crate::slot::Slot;
#[derive(Debug, Clone, Copy)]
pub enum FurnaceSlotType {
Top = 0,
Bottom = 1,
Side = 2,
}
#[derive(Debug)]
pub struct FurnaceSlot {
pub inventory: Arc<dyn Inventory>,
pub slot_type: FurnaceSlotType,
pub index: usize,
pub id: AtomicU8,
}
impl FurnaceSlot {
pub fn new(inventory: Arc<dyn Inventory>, slot_type: FurnaceSlotType) -> Self {
Self {
inventory,
slot_type,
index: slot_type as usize,
id: AtomicU8::new(0),
}
}
}
#[async_trait]
impl Slot for FurnaceSlot {
fn get_inventory(&self) -> Arc<dyn Inventory> {
self.inventory.clone()
}
fn get_index(&self) -> usize {
self.index
}
fn set_id(&self, id: usize) {
self.id
.store(id as u8, std::sync::atomic::Ordering::Relaxed);
}
async fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
async fn can_insert(&self, stack: &pumpkin_world::item::ItemStack) -> bool {
match self.slot_type {
FurnaceSlotType::Top => true,
FurnaceSlotType::Bottom => is_fuel(stack.item.id) || stack.item.id == Item::BUCKET.id,
FurnaceSlotType::Side => false,
}
}
}

View File

@@ -0,0 +1,2 @@
pub mod furnace_screen_handler;
pub mod furnace_slot;

View File

@@ -4,6 +4,7 @@ pub mod drag_handler;
pub mod entity_equipment;
pub mod equipment_slot;
mod error;
pub mod furnace;
pub mod generic_container_screen_handler;
pub mod player;
pub mod screen_handler;

View File

@@ -18,8 +18,11 @@ use pumpkin_protocol::{
},
};
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{ComparableInventory, Inventory};
use pumpkin_world::item::ItemStack;
use pumpkin_world::{
block::entities::PropertyDelegate,
inventory::{ComparableInventory, Inventory},
};
use std::cmp::max;
use std::sync::atomic::{AtomicU32, Ordering};
use std::{any::Any, collections::HashMap, sync::Arc};
@@ -28,18 +31,33 @@ use tokio::sync::Mutex;
const SLOT_INDEX_OUTSIDE: i32 = -999;
pub struct ScreenProperty {
_old_value: i32,
_index: u8,
value: i32,
old_value: i32,
index: u8,
value: Arc<dyn PropertyDelegate>,
}
impl ScreenProperty {
pub fn new(value: Arc<dyn PropertyDelegate>, index: u8) -> Self {
Self {
old_value: value.get_property(index as i32),
index,
value,
}
}
pub fn get(&self) -> i32 {
self.value
self.value.get_property(self.index as i32)
}
pub fn set(&mut self, value: i32) {
self.value = value;
self.value.set_property(self.index as i32, value);
}
pub fn has_changed(&mut self) -> bool {
let value = self.get();
let has_changed = !value.eq(&self.old_value);
self.old_value = value;
has_changed
}
}
@@ -257,15 +275,52 @@ pub trait ScreenHandler: Send + Sync {
self.update_tracked_slot(i, stack).await;
}
/* TODO: Implement this
for i in 0..self.prop_size() {
let property = self.get_property(i);
self.set_tracked_property(i, property);
} */
let behaviour = self.get_behaviour_mut();
let mut prop_vec = vec![];
for (idx, prop) in behaviour.properties.iter_mut().enumerate() {
let value = prop.get();
if prop.has_changed() {
prop_vec.push((idx, value));
}
}
for (idx, value) in prop_vec {
self.update_tracked_properties(idx as i32, value).await;
self.check_property_updates(idx as i32, value).await;
}
self.sync_state().await;
}
async fn update_tracked_properties(&mut self, idx: i32, value: i32) {
let behaviour = self.get_behaviour_mut();
if idx <= behaviour.tracked_property_values.len() as i32 {
behaviour.tracked_property_values[idx as usize] = value;
for listener in behaviour.listeners.iter() {
listener
.on_property_update(behaviour, idx as u8, value)
.await;
}
}
}
async fn check_property_updates(&mut self, idx: i32, value: i32) {
let behaviour = self.get_behaviour_mut();
if !behaviour.disable_sync {
if let Some(old_value) = behaviour.tracked_property_values.get(idx as usize) {
let old_value = *old_value;
if old_value != value {
behaviour
.tracked_property_values
.insert(idx as usize, value);
if let Some(ref sync_handler) = behaviour.sync_handler {
sync_handler.update_property(behaviour, idx, value).await;
}
}
}
}
}
async fn update_tracked_slot(&mut self, slot: usize, stack: ItemStack) {
let behaviour = self.get_behaviour_mut();
let other_stack = &behaviour.tracked_stacks[slot];
@@ -325,11 +380,19 @@ pub trait ScreenHandler: Send + Sync {
self.check_cursor_stack_updates().await;
/* TODO: Implement this
for i in 0..self.prop_size() {
let property = self.get_property(i);
self.set_tracked_property(i, property);
} */
let behaviour = self.get_behaviour_mut();
let mut prop_vec = vec![];
for (idx, prop) in behaviour.properties.iter_mut().enumerate() {
let value = prop.get();
if prop.has_changed() {
prop_vec.push((idx, value));
}
}
for (idx, value) in prop_vec {
self.update_tracked_properties(idx as i32, value).await;
self.check_property_updates(idx as i32, value).await;
}
}
async fn is_slot_valid(&self, slot: i32) -> bool {
@@ -831,7 +894,7 @@ pub trait ScreenHandlerListener: Send + Sync {
_stack: ItemStack,
) {
}
fn on_property_update(
async fn on_property_update(
&self,
_screen_handler: &ScreenHandlerBehaviour,
_property: u8,

View File

@@ -0,0 +1,464 @@
use std::{
array::from_fn,
sync::{
Arc,
atomic::{AtomicBool, AtomicU16, Ordering},
},
};
use async_trait::async_trait;
use pumpkin_data::{
block_properties::{BlockProperties, FurnaceLikeProperties},
fuels::get_item_burn_ticks,
item::Item,
recipe_remainder::get_recipe_remainder_id,
recipes::{CookingRecipe, CookingRecipeType, RECIPES_COOKING},
};
use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::{
inventory::{Clearable, Inventory, split_stack},
item::ItemStack,
world::{BlockFlags, SimpleWorld},
};
use super::{BlockEntity, PropertyDelegate};
#[derive(Debug)]
pub struct FurnaceBlockEntity {
pub position: BlockPos,
pub dirty: AtomicBool,
pub cooking_time_spent: AtomicU16,
pub cooking_total_time: AtomicU16,
pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16,
pub items: [Arc<Mutex<ItemStack>>; 3],
}
impl FurnaceBlockEntity {
#[must_use]
pub fn is_burning(&self) -> bool {
self.lit_time_remaining.load(Ordering::Relaxed) > 0
}
pub fn get_furnace_cooking_recipe(item: &Item) -> Option<&CookingRecipe> {
if let Some(recipe_type) = RECIPES_COOKING.iter().find(|recipe| match recipe {
CookingRecipeType::Smelting(smelting_recipe) => {
smelting_recipe.ingredient.match_item(item)
}
_ => false,
}) {
match recipe_type {
CookingRecipeType::Smelting(cooking_recipe) => {
return Some(cooking_recipe);
}
_ => {
return None;
}
}
}
None
}
async fn can_accept_recipe_output(
&self,
recipe: Option<&CookingRecipe>,
max_count: u8,
) -> bool {
let recipe = match recipe {
Some(cooking_recipe) => cooking_recipe,
None => return false,
};
let top_item_stack = self.items[0].lock().await;
let is_top_items_empty = top_item_stack.is_empty();
drop(top_item_stack);
let side_item_stack = self.items[2].lock().await;
if side_item_stack.is_empty() {
return !is_top_items_empty;
}
if let Some(recipe_output_item) =
Item::from_registry_key(recipe.result.id.strip_prefix("minecraft:").unwrap())
{
if !is_top_items_empty
&& recipe_output_item.id == side_item_stack.item.id
&& side_item_stack.item_count < max_count
&& side_item_stack.item_count < side_item_stack.get_max_stack_size()
{
return true;
}
}
false
}
async fn craft_recipe(&self, recipe: Option<&CookingRecipe>) -> bool {
let can_accepet_output = self
.can_accept_recipe_output(recipe, self.get_max_count_per_stack())
.await;
if let Some(recipe) = recipe {
if can_accepet_output {
let mut side_items = self.items[2].lock().await;
let output_item = match Item::from_registry_key(
recipe.result.id.strip_prefix("minecraft:").unwrap(),
) {
Some(item) => item,
None => return false,
};
let output_item_stack = ItemStack::new(recipe.result.count, output_item);
if side_items.are_equal(&ItemStack::EMPTY) {
drop(side_items);
self.set_stack(2, output_item_stack).await;
} else if side_items.are_items_and_components_equal(&output_item_stack) {
side_items.increment(1);
}
}
let bottom_items = self.items[1].lock().await;
let mut top_items = self.items[0].lock().await;
if top_items.item.id == Item::WET_SPONGE.id
&& !bottom_items.is_empty()
&& bottom_items.item.id == Item::BUCKET.id
{
drop(bottom_items);
self.set_stack(1, ItemStack::new(1, &Item::WATER_BUCKET))
.await;
}
top_items.decrement(1);
return true;
}
false
}
pub async fn get_cook_progress(&self) -> f32 {
let current = self.cooking_time_spent.load(Ordering::Relaxed) as i32;
let total = self.cooking_total_time.load(Ordering::Relaxed) as i32;
if total != 0 && current != 0 {
(current as f32 / total as f32).clamp(0.0, 1.0)
} else {
0.0
}
}
pub async fn get_fuel_progress(&self) -> f32 {
let remaining = self.lit_time_remaining.load(Ordering::Relaxed) as i32;
let total = self.lit_total_time.load(Ordering::Relaxed) as i32;
let adjusted_total = if total == 0 { 200 } else { total };
(remaining as f32 / adjusted_total as f32).clamp(0.0, 1.0)
}
}
#[async_trait]
impl BlockEntity for FurnaceBlockEntity {
async fn tick(&self, world: &Arc<dyn SimpleWorld>) {
let is_burning = self.is_burning();
let mut is_dirty = false;
if self.is_burning() {
self.lit_time_remaining.fetch_sub(1, Ordering::Relaxed);
}
let top_items = self.items[0].lock().await;
let is_top_items_empty = top_items.is_empty();
let furnace_recipe = Self::get_furnace_cooking_recipe(top_items.item);
drop(top_items);
let can_accepet_output = self
.can_accept_recipe_output(furnace_recipe, self.get_max_count_per_stack())
.await;
let bottom_items_is_empty = self.items[1].lock().await.is_empty();
if self.is_burning() || !bottom_items_is_empty && !is_top_items_empty {
if !self.is_burning() && can_accepet_output {
let mut bottom_items = self.items[1].lock().await;
let fuel_ticks = get_item_burn_ticks(bottom_items.item.id).unwrap_or(0);
self.lit_time_remaining.store(fuel_ticks, Ordering::Relaxed);
self.lit_total_time.store(fuel_ticks, Ordering::Relaxed);
if self.is_burning() {
is_dirty = true;
if !bottom_items.is_empty() {
bottom_items.decrement(1);
if let Some(remainder_id) = get_recipe_remainder_id(bottom_items.item.id)
&& bottom_items.is_empty()
&& let Some(remainder_item) = Item::from_id(remainder_id)
{
drop(bottom_items);
self.set_stack(1, ItemStack::new(1, remainder_item)).await;
}
}
}
}
if self.is_burning() && can_accepet_output {
self.cooking_time_spent.fetch_add(1, Ordering::Relaxed);
if self.cooking_time_spent.load(Ordering::Relaxed)
== self.cooking_total_time.load(Ordering::Relaxed)
{
self.cooking_time_spent.store(0, Ordering::Relaxed);
if let Some(cooking_recipe) = furnace_recipe {
let cooking_total_time = cooking_recipe.cookingtime;
self.cooking_total_time
.store(cooking_total_time as u16, Ordering::Relaxed);
self.craft_recipe(Some(cooking_recipe)).await;
is_dirty = true;
}
}
} else {
self.cooking_time_spent.store(0, Ordering::Relaxed);
}
} else if !self.is_burning() && self.cooking_time_spent.load(Ordering::Relaxed) > 0 {
self.cooking_time_spent
.fetch_update(Ordering::Acquire, Ordering::Acquire, |v| {
Some(
v.saturating_sub(2)
.min(self.cooking_total_time.load(Ordering::Acquire)),
)
})
.unwrap();
}
if is_burning != self.is_burning() {
is_dirty = true;
let world = world.clone();
let (furnace_block, furnace_block_state) =
world.get_block_and_state(&self.position).await;
let mut props =
FurnaceLikeProperties::from_state_id(furnace_block_state.id, furnace_block);
if self.is_burning() {
props.lit = true;
world
.set_block_state(
&self.position,
props.to_state_id(furnace_block),
BlockFlags::NOTIFY_ALL,
)
.await;
} else {
props.lit = false;
world
.set_block_state(
&self.position,
props.to_state_id(furnace_block),
BlockFlags::NOTIFY_ALL,
)
.await;
}
}
if is_dirty {
self.is_dirty();
}
}
fn resource_location(&self) -> &'static str {
Self::ID
}
fn get_position(&self) -> BlockPos {
self.position
}
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
where
Self: Sized,
{
let cooking_total_time = AtomicU16::new(
nbt.get_short("cooking_total_time")
.map_or(0, |cooking_total_time| cooking_total_time as u16),
);
let cooking_time_spent = AtomicU16::new(
nbt.get_short("cooking_time_spent")
.map_or(0, |cooking_time_spent| cooking_time_spent as u16),
);
let lit_total_time = AtomicU16::new(
nbt.get_short("lit_total_time")
.map_or(0, |lit_total_time| lit_total_time as u16),
);
let lit_time_remaining = AtomicU16::new(
nbt.get_short("lit_time_remaining")
.map_or(0, |lit_time_remaining| lit_time_remaining as u16),
);
let furnace = Self {
position,
dirty: AtomicBool::new(false),
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY))),
cooking_total_time,
cooking_time_spent,
lit_total_time,
lit_time_remaining,
};
furnace.read_data(nbt, &furnace.items);
furnace
}
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
nbt.put_short(
"cooking_total_time",
self.cooking_total_time.load(Ordering::Relaxed) as i16,
);
nbt.put_short(
"cooking_time_spent",
self.cooking_time_spent.load(Ordering::Relaxed) as i16,
);
nbt.put_short(
"lit_total_time",
self.lit_total_time.load(Ordering::Relaxed) as i16,
);
nbt.put_short(
"lit_time_remaining",
self.lit_time_remaining.load(Ordering::Relaxed) as i16,
);
self.write_data(nbt, &self.items, true).await;
// Safety precaution
// self.clear().await;
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
Some(self)
}
fn is_dirty(&self) -> bool {
self.dirty.load(Ordering::Relaxed)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
Some(self as Arc<dyn PropertyDelegate>)
}
}
impl FurnaceBlockEntity {
pub const ID: &'static str = "minecraft:furnace";
pub fn new(position: BlockPos) -> Self {
Self {
position,
dirty: AtomicBool::new(false),
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY))),
cooking_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0),
lit_time_remaining: AtomicU16::new(0),
}
}
}
#[async_trait]
impl Inventory for FurnaceBlockEntity {
fn size(&self) -> usize {
self.items.len()
}
async fn is_empty(&self) -> bool {
for slot in self.items.iter() {
if !slot.lock().await.is_empty() {
return false;
}
}
true
}
async fn get_stack(&self, slot: usize) -> Arc<Mutex<ItemStack>> {
self.items[slot].clone()
}
async fn remove_stack(&self, slot: usize) -> ItemStack {
let mut removed = ItemStack::EMPTY;
let mut guard = self.items[slot].lock().await;
std::mem::swap(&mut removed, &mut *guard);
removed
}
async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
split_stack(&self.items, slot, amount).await
}
async fn set_stack(&self, slot: usize, stack: ItemStack) {
let furnace_stack = self.get_stack(slot).await;
let mut furnace_stack = furnace_stack.lock().await;
let is_same_item =
!stack.is_empty() && ItemStack::are_items_and_components_equal(&furnace_stack, &stack);
*furnace_stack = stack;
drop(furnace_stack);
if slot == 0 && !is_same_item {
if let Some(recipe) = Self::get_furnace_cooking_recipe(stack.item) {
self.cooking_total_time
.store(recipe.cookingtime as u16, Ordering::Relaxed);
} else {
self.cooking_total_time.store(0, Ordering::Relaxed);
}
self.cooking_time_spent.store(0, Ordering::Relaxed);
self.mark_dirty();
}
}
fn mark_dirty(&self) {
self.dirty.store(true, Ordering::Relaxed);
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[async_trait]
impl Clearable for FurnaceBlockEntity {
async fn clear(&self) {
for slot in self.items.iter() {
*slot.lock().await = ItemStack::EMPTY;
}
}
}
impl PropertyDelegate for FurnaceBlockEntity {
fn get_property(&self, index: i32) -> i32 {
let value = match index {
0 => self.lit_time_remaining.load(Ordering::Relaxed),
1 => self.lit_total_time.load(Ordering::Relaxed),
2 => self.cooking_time_spent.load(Ordering::Relaxed),
3 => self.cooking_total_time.load(Ordering::Relaxed),
_ => 0,
};
value as i32
}
fn set_property(&self, index: i32, value: i32) {
let value = value as u16;
match index {
0 => self.lit_time_remaining.store(value, Ordering::Relaxed),
1 => self.lit_total_time.store(value, Ordering::Relaxed),
2 => self.cooking_time_spent.store(value, Ordering::Relaxed),
3 => self.cooking_total_time.store(value, Ordering::Relaxed),
_ => {}
}
}
fn get_properties_size(&self) -> i32 {
4
}
}

View File

@@ -6,6 +6,7 @@ use bed::BedBlockEntity;
use chest::ChestBlockEntity;
use comparator::ComparatorBlockEntity;
use end_portal::EndPortalBlockEntity;
use furnace::FurnaceBlockEntity;
use piston::PistonBlockEntity;
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
use pumpkin_nbt::compound::NbtCompound;
@@ -27,6 +28,7 @@ pub mod command_block;
pub mod comparator;
pub mod dropper;
pub mod end_portal;
pub mod furnace;
pub mod hopper;
pub mod piston;
pub mod shulker_box;
@@ -69,6 +71,9 @@ pub trait BlockEntity: Send + Sync {
false
}
fn as_any(&self) -> &dyn Any;
fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
None
}
}
pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
@@ -99,6 +104,7 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
ChiseledBookshelfBlockEntity::ID => Arc::new(block_entity_from_generic::<
ChiseledBookshelfBlockEntity,
>(nbt)),
FurnaceBlockEntity::ID => Arc::new(block_entity_from_generic::<FurnaceBlockEntity>(nbt)),
_ => return None,
})
}
@@ -106,3 +112,9 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
pub fn has_block_block_entity(block: &Block) -> bool {
BLOCK_ENTITY_TYPES.contains(&block.name)
}
pub trait PropertyDelegate: Sync + Send {
fn get_property(&self, _index: i32) -> i32;
fn set_property(&self, _index: i32, _value: i32);
fn get_properties_size(&self) -> i32;
}

View File

@@ -1,15 +1,108 @@
use crate::block::pumpkin_block::{OnPlaceArgs, PumpkinBlock};
use std::sync::Arc;
use async_trait::async_trait;
use pumpkin_data::block_properties::{BlockProperties, FurnaceLikeProperties};
use pumpkin_inventory::{
furnace::furnace_screen_handler::FurnaceScreenHandler, screen_handler::ScreenHandlerFactory,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::BlockStateId;
use pumpkin_util::text::TextComponent;
use pumpkin_world::{
block::entities::{BlockEntity, furnace::FurnaceBlockEntity},
inventory::Inventory,
};
use tokio::sync::Mutex;
use crate::block::pumpkin_block::PumpkinBlock;
struct FurnaceScreenFactory {
inventory: Arc<dyn Inventory>,
block_entity: Arc<dyn BlockEntity>,
}
impl FurnaceScreenFactory {
fn new(inventory: Arc<dyn Inventory>, block_entity: Arc<dyn BlockEntity>) -> Self {
Self {
inventory,
block_entity,
}
}
}
#[async_trait]
impl ScreenHandlerFactory for FurnaceScreenFactory {
async fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
_player: &dyn pumpkin_inventory::screen_handler::InventoryPlayer,
) -> Option<Arc<Mutex<dyn pumpkin_inventory::screen_handler::ScreenHandler>>> {
let furnace_screen_handler = FurnaceScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
self.block_entity.clone(),
)
.await;
Some(Arc::new(Mutex::new(furnace_screen_handler)))
}
fn get_display_name(&self) -> pumpkin_util::text::TextComponent {
TextComponent::translate("container.furnace", &[])
}
}
#[pumpkin_block("minecraft:furnace")]
pub struct FurnaceBlock;
#[async_trait]
impl PumpkinBlock for FurnaceBlock {
async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
async fn normal_use(
&self,
args: crate::block::pumpkin_block::NormalUseArgs<'_>,
) -> crate::block::registry::BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position).await {
if let Some(inventory) = block_entity.clone().get_inventory() {
let furnace_screen_factory = FurnaceScreenFactory::new(inventory, block_entity);
args.player
.open_handled_screen(&furnace_screen_factory)
.await;
}
}
crate::block::registry::BlockActionResult::Consume
}
//Same to normal_use
async fn use_with_item(
&self,
_args: crate::block::pumpkin_block::UseWithItemArgs<'_>,
) -> crate::block::registry::BlockActionResult {
crate::block::registry::BlockActionResult::PassToDefaultBlockAction
}
async fn on_entity_collision(
&self,
_args: crate::block::pumpkin_block::OnEntityCollisionArgs<'_>,
) {
}
fn should_drop_items_on_explosion(&self) -> bool {
true
}
async fn explode(&self, _args: crate::block::pumpkin_block::ExplodeArgs<'_>) {}
async fn on_synced_block_event(
&self,
_args: crate::block::pumpkin_block::OnSyncedBlockEventArgs<'_>,
) -> bool {
false
}
async fn on_place(
&self,
args: crate::block::pumpkin_block::OnPlaceArgs<'_>,
) -> pumpkin_world::BlockStateId {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args
.player
@@ -17,6 +110,79 @@ impl PumpkinBlock for FurnaceBlock {
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
async fn random_tick(&self, _args: crate::block::pumpkin_block::RandomTickArgs<'_>) {}
async fn can_place_at(&self, _args: crate::block::pumpkin_block::CanPlaceAtArgs<'_>) -> bool {
true
}
async fn can_update_at(&self, _args: crate::block::pumpkin_block::CanUpdateAtArgs<'_>) -> bool {
false
}
async fn placed(&self, args: crate::block::pumpkin_block::PlacedArgs<'_>) {
let furnace_block_entity = FurnaceBlockEntity::new(*args.position);
args.world
.add_block_entity(Arc::new(furnace_block_entity))
.await;
}
async fn player_placed(&self, _args: crate::block::pumpkin_block::PlayerPlacedArgs<'_>) {}
async fn broken(&self, args: crate::block::pumpkin_block::BrokenArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
async fn on_neighbor_update(
&self,
_args: crate::block::pumpkin_block::OnNeighborUpdateArgs<'_>,
) {
}
async fn prepare(&self, _args: crate::block::pumpkin_block::PrepareArgs<'_>) {}
async fn get_state_for_neighbor_update(
&self,
args: crate::block::pumpkin_block::GetStateForNeighborUpdateArgs<'_>,
) -> pumpkin_world::BlockStateId {
args.state_id
}
async fn on_scheduled_tick(&self, _args: crate::block::pumpkin_block::OnScheduledTickArgs<'_>) {
}
async fn on_state_replaced(&self, _args: crate::block::pumpkin_block::OnStateReplacedArgs<'_>) {
}
async fn emits_redstone_power(
&self,
_args: crate::block::pumpkin_block::EmitsRedstonePowerArgs<'_>,
) -> bool {
false
}
async fn get_weak_redstone_power(
&self,
_args: crate::block::pumpkin_block::GetRedstonePowerArgs<'_>,
) -> u8 {
0
}
async fn get_strong_redstone_power(
&self,
_args: crate::block::pumpkin_block::GetRedstonePowerArgs<'_>,
) -> u8 {
0
}
async fn get_comparator_output(
&self,
_args: crate::block::pumpkin_block::GetComparatorOutputArgs<'_>,
) -> Option<u8> {
None
}
}