diff --git a/pumpkin/src/block/blocks/fire/fire.rs b/pumpkin/src/block/blocks/fire/fire.rs new file mode 100644 index 000000000..aca0a176e --- /dev/null +++ b/pumpkin/src/block/blocks/fire/fire.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use pumpkin_world::chunk::TickPriority; +use rand::Rng; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; +use async_trait::async_trait; +use pumpkin_data::{Block, BlockState}; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::BlockStateId; +use pumpkin_world::block::BlockDirection; + +use super::FireBlockBase; + +#[pumpkin_block("minecraft:fire")] +pub struct FireBlock; + +impl FireBlock { + pub fn get_fire_tick_delay() -> i32 { + 30 + rand::thread_rng().gen_range(0..10) + } +} + +#[async_trait] +impl PumpkinBlock for FireBlock { + async fn placed( + &self, + world: &Arc, + block: &Block, + state_id: BlockStateId, + pos: &BlockPos, + old_state_id: BlockStateId, + notify: bool, + ) { + FireBlockBase::placed( + &FireBlockBase, + world, + block, + state_id, + pos, + old_state_id, + notify, + ) + .await; + world + .schedule_block_tick( + block, + *pos, + Self::get_fire_tick_delay() as u16, + TickPriority::Normal, + ) + .await; + } + + async fn get_state_for_neighbor_update( + &self, + world: &World, + _block: &Block, + state: BlockStateId, + block_pos: &BlockPos, + direction: BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: BlockStateId, + ) -> BlockStateId { + // TODO: add can_place_at + if self.can_place_at(world, block_pos, direction).await { + return state; + } + Block::AIR.default_state_id + } + + async fn broken( + &self, + block: &Block, + player: &Player, + position: BlockPos, + server: &Server, + world: Arc, + state: BlockState, + ) { + FireBlockBase::broken( + &FireBlockBase, + block, + player, + position, + server, + world, + state, + ) + .await; + } +} diff --git a/pumpkin/src/block/blocks/fire/mod.rs b/pumpkin/src/block/blocks/fire/mod.rs new file mode 100644 index 000000000..d7a41ec4a --- /dev/null +++ b/pumpkin/src/block/blocks/fire/mod.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; +use async_trait::async_trait; +use pumpkin_data::world::WorldEvent; +use pumpkin_data::{Block, BlockState}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::BlockStateId; +use pumpkin_world::block::BlockDirection; +use soul_fire::SoulFireBlock; + +#[expect(clippy::module_inception)] +pub mod fire; +pub mod soul_fire; + +pub struct FireBlockBase; + +impl FireBlockBase { + pub async fn get_state(world: &World, pos: &BlockPos) -> Block { + let (block, _block_state) = world.get_block_and_block_state(&pos.down()).await.unwrap(); + if SoulFireBlock::is_soul_base(&block) { + return Block::SOUL_FIRE; + } + // TODO + Block::FIRE + } +} + +#[async_trait] +impl PumpkinBlock for FireBlockBase { + async fn placed( + &self, + _world: &Arc, + _block: &Block, + state_id: BlockStateId, + _pos: &BlockPos, + old_state_id: BlockStateId, + _notify: bool, + ) { + if old_state_id == state_id { + return; + } + } + + async fn can_place_at( + &self, + world: &World, + block_pos: &BlockPos, + face: BlockDirection, + ) -> bool { + let block_state = world.get_block_state(block_pos).await.unwrap(); + + if !block_state.is_air() { + return false; + } + let block = Self::get_state(world, block_pos).await; + + if let Some(block) = world.block_registry.get_pumpkin_block(&block) { + return block.can_place_at(world, block_pos, face).await; + } + return false; + } + + async fn broken( + &self, + _block: &Block, + _player: &Player, + position: BlockPos, + _server: &Server, + world: Arc, + _state: BlockState, + ) { + world + .sync_world_event(WorldEvent::FireExtinguished, position, 0) + .await; + } +} diff --git a/pumpkin/src/block/blocks/fire/soul_fire.rs b/pumpkin/src/block/blocks/fire/soul_fire.rs new file mode 100644 index 000000000..2d4683d25 --- /dev/null +++ b/pumpkin/src/block/blocks/fire/soul_fire.rs @@ -0,0 +1,97 @@ +use std::sync::Arc; + +use pumpkin_data::tag::Tagable; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; +use async_trait::async_trait; +use pumpkin_data::{Block, BlockState}; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::BlockStateId; +use pumpkin_world::block::BlockDirection; + +use super::FireBlockBase; + +#[pumpkin_block("minecraft:soul_fire")] +pub struct SoulFireBlock; + +impl SoulFireBlock { + pub fn is_soul_base(block: &Block) -> bool { + block + .is_tagged_with("minecraft:soul_fire_base_blocks") + .unwrap() + } +} + +#[async_trait] +impl PumpkinBlock for SoulFireBlock { + async fn placed( + &self, + world: &Arc, + block: &Block, + state_id: BlockStateId, + pos: &BlockPos, + old_state_id: BlockStateId, + notify: bool, + ) { + FireBlockBase::placed( + &FireBlockBase, + world, + block, + state_id, + pos, + old_state_id, + notify, + ) + .await; + } + + async fn get_state_for_neighbor_update( + &self, + world: &World, + _block: &Block, + state: BlockStateId, + block_pos: &BlockPos, + direction: BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: BlockStateId, + ) -> BlockStateId { + if self.can_place_at(world, block_pos, direction).await { + return state; + } + Block::AIR.default_state_id + } + + async fn can_place_at( + &self, + world: &World, + block_pos: &BlockPos, + _face: BlockDirection, + ) -> bool { + Self::is_soul_base(&world.get_block(&block_pos.down()).await.unwrap()) + } + + async fn broken( + &self, + block: &Block, + player: &Player, + position: BlockPos, + server: &Server, + world: Arc, + state: BlockState, + ) { + FireBlockBase::broken( + &FireBlockBase, + block, + player, + position, + server, + world, + state, + ) + .await; + } +} diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 33411337f..03ba76fe8 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -7,6 +7,7 @@ use crate::server::Server; use crate::world::{BlockFlags, World}; use async_trait::async_trait; use pumpkin_data::item::Item; +use pumpkin_data::world::WorldEvent; use pumpkin_data::{ Block, BlockState, block_properties::{BlockProperties, JukeboxLikeProperties}, @@ -41,9 +42,11 @@ impl JukeboxBlock { .await; } - async fn stop_music(&self, block: &Block, location: BlockPos, world: &Arc) { - self.set_record(false, block, location, world).await; - world.stop_record(location).await; + async fn stop_music(&self, block: &Block, position: BlockPos, world: &Arc) { + self.set_record(false, block, position, world).await; + world + .sync_world_event(WorldEvent::JukeboxStopsPlaying, position, 0) + .await; } } @@ -95,7 +98,13 @@ impl PumpkinBlock for JukeboxBlock { //TODO: Update block nbt self.set_record(true, block, location, world).await; - world.play_record(jukebox_song as i32, location).await; + world + .sync_world_event( + WorldEvent::JukeboxStartsPlaying, + location, + jukebox_song as i32, + ) + .await; BlockActionResult::Consume } @@ -104,12 +113,14 @@ impl PumpkinBlock for JukeboxBlock { &self, _block: &Block, _player: &Player, - location: BlockPos, + position: BlockPos, _server: &Server, world: Arc, _state: BlockState, ) { // For now just stop the music at this position - world.stop_record(location).await; + world + .sync_world_event(WorldEvent::JukeboxStopsPlaying, position, 0) + .await; } } diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 4b267a052..75417a940 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -13,6 +13,7 @@ pub mod doors; pub mod farmland; pub mod fence_gates; pub mod fences; +pub mod fire; pub mod furnace; pub mod glass_panes; pub mod iron_bars; diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 2886528b7..35e1c5021 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -4,6 +4,8 @@ use blocks::doors::DoorBlock; use blocks::farmland::FarmLandBlock; use blocks::fence_gates::FenceGateBlock; use blocks::fences::FenceBlock; +use blocks::fire::fire::FireBlock; +use blocks::fire::soul_fire::SoulFireBlock; use blocks::glass_panes::GlassPaneBlock; use blocks::iron_bars::IronBarsBlock; use blocks::logs::LogBlock; @@ -52,7 +54,7 @@ use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::P use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity}; use std::sync::Arc; -mod blocks; +pub(crate) mod blocks; mod fluids; pub mod pumpkin_block; pub mod pumpkin_fluid; @@ -84,6 +86,10 @@ pub fn default_registry() -> Arc { manager.register(TorchBlock); manager.register(WallBlock); + // Fire + manager.register(SoulFireBlock); + manager.register(FireBlock); + // Redstone manager.register(ButtonBlock); manager.register(LeverBlock); diff --git a/pumpkin/src/item/items/flint_and_steel.rs b/pumpkin/src/item/items/flint_and_steel.rs new file mode 100644 index 000000000..a608c0054 --- /dev/null +++ b/pumpkin/src/item/items/flint_and_steel.rs @@ -0,0 +1,54 @@ +use crate::block::blocks::fire::FireBlockBase; +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem}; +use crate::server::Server; +use crate::world::BlockFlags; +use async_trait::async_trait; +use pumpkin_data::Block; +use pumpkin_data::item::Item; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::{BlockDirection, HorizontalFacingExt}; + +pub struct FlintAndSteelItem; + +impl ItemMetadata for FlintAndSteelItem { + fn ids() -> Box<[u16]> { + [Item::FLINT_AND_STEEL.id].into() + } +} + +#[async_trait] +impl PumpkinItem for FlintAndSteelItem { + async fn use_on_block( + &self, + _item: &Item, + player: &Player, + location: BlockPos, + face: BlockDirection, + _block: &Block, + _server: &Server, + ) { + // TODO: check CampfireBlock, CandleBlock and CandleCakeBlock + let world = player.world().await; + let pos = location.offset(face.to_offset()); + if FireBlockBase::can_place_at( + &FireBlockBase, + &world, + &pos, + player + .living_entity + .entity + .get_horizontal_facing() + .to_block_direction(), + ) + .await + { + let state = FireBlockBase::get_state(&world, &pos).await; + world + .set_block_state(&pos, state.default_state_id, BlockFlags::NOTIFY_ALL) + .await; + // TODO + } + } +} diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index b45f3dd57..ba8c458cf 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -1,5 +1,6 @@ mod axe; mod egg; +mod flint_and_steel; mod hoe; mod honeycomb; mod shovel; @@ -9,6 +10,7 @@ mod trident; use axe::AxeItem; use egg::EggItem; +use flint_and_steel::FlintAndSteelItem; use hoe::HoeItem; use honeycomb::HoneyCombItem; use shovel::ShovelItem; @@ -25,6 +27,7 @@ pub fn default_registry() -> Arc { manager.register(SnowBallItem); manager.register(HoeItem); manager.register(EggItem); + manager.register(FlintAndSteelItem); manager.register(SwordItem); manager.register(TridentItem); manager.register(ShovelItem); diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 6cd64727e..96f2960e3 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -44,6 +44,7 @@ use pumpkin_nbt::to_bytes_unnamed; use pumpkin_protocol::client::play::{ CRemoveMobEffect, CSetEntityMetadata, MetaDataType, Metadata, }; +use pumpkin_protocol::codec::identifier::Identifier; use pumpkin_protocol::ser::serializer::Serializer; use pumpkin_protocol::{ ClientPacket, IdOr, SoundEvent, @@ -54,7 +55,6 @@ use pumpkin_protocol::{ }, server::play::SChatMessage, }; -use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier}; use pumpkin_protocol::{ client::play::{ CBlockUpdate, CDisguisedChatMessage, CExplosion, CRespawn, CSetBlockDestroyStage, @@ -361,26 +361,6 @@ impl World { self.play_sound(sound, category, &new_vec).await; } - pub async fn play_record(&self, record_id: i32, position: BlockPos) { - self.broadcast_packet_all(&CLevelEvent::new( - WorldEvent::JukeboxStartsPlaying as i32, - position, - record_id, - false, - )) - .await; - } - - pub async fn stop_record(&self, position: BlockPos) { - self.broadcast_packet_all(&CLevelEvent::new( - WorldEvent::JukeboxStopsPlaying as i32, - position, - 0, - false, - )) - .await; - } - pub async fn tick(self: &Arc, server: &Server) { self.flush_block_updates().await; @@ -1536,6 +1516,11 @@ impl World { } } + pub async fn sync_world_event(&self, world_event: WorldEvent, position: BlockPos, data: i32) { + self.broadcast_packet_all(&CWorldEvent::new(world_event as i32, position, data, false)) + .await; + } + pub async fn get_chunk(&self, position: &BlockPos) -> Arc> { let (chunk_coordinate, _) = position.chunk_and_chunk_relative_position();