mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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:
7
assets/recipe_remainder.json
Normal file
7
assets/recipe_remainder.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"968": 967,
|
||||
"969": 967,
|
||||
"973": 967,
|
||||
"1238": 1075,
|
||||
"1308": 1075
|
||||
}
|
||||
@@ -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)| {
|
||||
|
||||
26
pumpkin-data/build/recipe_remainder.rs
Normal file
26
pumpkin-data/build/recipe_remainder.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
142
pumpkin-inventory/src/furnace/furnace_screen_handler.rs
Normal file
142
pumpkin-inventory/src/furnace/furnace_screen_handler.rs
Normal 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
|
||||
}
|
||||
}
|
||||
60
pumpkin-inventory/src/furnace/furnace_slot.rs
Normal file
60
pumpkin-inventory/src/furnace/furnace_slot.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
2
pumpkin-inventory/src/furnace/mod.rs
Normal file
2
pumpkin-inventory/src/furnace/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod furnace_screen_handler;
|
||||
pub mod furnace_slot;
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
464
pumpkin-world/src/block/entities/furnace.rs
Normal file
464
pumpkin-world/src/block/entities/furnace.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user