mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add: Interactive blocks (#333)
* WIP interactive blocks * Added jukebox and crafting table * Making feature more dynamic allowing every block action to be registered here * Renamed interactive_block_manager to block_manager * Simplifying register functions * Added on placed * Fix master merge * Added pumpkin_block macro * Removed junk * Added the option to block placing in on_use_with_item * Fixed inventory lock * Adding on_broken and implementing it for the jukebox * Fixed merge
This commit is contained in:
@@ -42,6 +42,32 @@ pub fn server_packet(input: TokenStream, item: TokenStream) -> TokenStream {
|
||||
gen.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn pumpkin_block(input: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap();
|
||||
let name = &ast.ident;
|
||||
let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();
|
||||
|
||||
let input_string = input.to_string();
|
||||
let packet_name = input_string.trim_matches('"');
|
||||
let packet_name_split: Vec<&str> = packet_name.split(":").collect();
|
||||
|
||||
let namespace = packet_name_split[0];
|
||||
let id = packet_name_split[1];
|
||||
|
||||
let item: proc_macro2::TokenStream = item.into();
|
||||
|
||||
let gen = quote! {
|
||||
#item
|
||||
impl #impl_generics crate::block::pumpkin_block::BlockMetadata for #name #ty_generics {
|
||||
const NAMESPACE: &'static str = #namespace;
|
||||
const ID: &'static str = #id;
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
|
||||
mod screen;
|
||||
#[proc_macro]
|
||||
pub fn screen(item: TokenStream) -> TokenStream {
|
||||
|
||||
28
pumpkin-protocol/src/client/play/c_level_event.rs
Normal file
28
pumpkin-protocol/src/client/play/c_level_event.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_macros::client_packet;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[client_packet("play:level_event")]
|
||||
pub struct CLevelEvent {
|
||||
event: i32,
|
||||
location: WorldPosition,
|
||||
data: i32,
|
||||
disable_relative_volume: bool,
|
||||
}
|
||||
|
||||
impl CLevelEvent {
|
||||
pub fn new(
|
||||
event: i32,
|
||||
location: WorldPosition,
|
||||
data: i32,
|
||||
disable_relative_volume: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
event,
|
||||
location,
|
||||
data,
|
||||
disable_relative_volume,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ mod c_head_rot;
|
||||
mod c_hurt_animation;
|
||||
mod c_initialize_world_border;
|
||||
mod c_keep_alive;
|
||||
mod c_level_event;
|
||||
mod c_login;
|
||||
mod c_open_screen;
|
||||
mod c_particle;
|
||||
@@ -94,6 +95,7 @@ pub use c_head_rot::*;
|
||||
pub use c_hurt_animation::*;
|
||||
pub use c_initialize_world_border::*;
|
||||
pub use c_keep_alive::*;
|
||||
pub use c_level_event::*;
|
||||
pub use c_login::*;
|
||||
pub use c_open_screen::*;
|
||||
pub use c_particle::*;
|
||||
|
||||
@@ -3,7 +3,12 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JukeboxSong {
|
||||
sound_event: String,
|
||||
// description: TextComponent<'static>,
|
||||
description: Description,
|
||||
length_in_seconds: f32,
|
||||
comparator_output: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct Description {
|
||||
translate: String,
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct SyncedRegistry {
|
||||
damage_type: IndexMap<String, DamageType>,
|
||||
banner_pattern: IndexMap<String, BannerPattern>,
|
||||
enchantment: IndexMap<String, Enchantment>,
|
||||
jukebox_song: IndexMap<String, JukeboxSong>,
|
||||
pub jukebox_song: IndexMap<String, JukeboxSong>,
|
||||
instrument: IndexMap<String, Instrument>,
|
||||
}
|
||||
|
||||
@@ -225,18 +225,18 @@ impl Registry {
|
||||
// registry_entries,
|
||||
// };
|
||||
|
||||
// let registry_entries = SYNCED_REGISTRIES
|
||||
// .jukebox_song
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let jukebox_song = Registry {
|
||||
// registry_id: "minecraft:jukebox_song".to_string(),
|
||||
// registry_entries,
|
||||
// };
|
||||
let registry_entries = SYNCED_REGISTRIES
|
||||
.jukebox_song
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let jukebox_song = Registry {
|
||||
registry_id: "minecraft:jukebox_song".to_string(),
|
||||
registry_entries,
|
||||
};
|
||||
|
||||
// let registry_entries = SYNCED_REGISTRIES
|
||||
// .instrument
|
||||
@@ -262,7 +262,7 @@ impl Registry {
|
||||
damage_type,
|
||||
banner_pattern,
|
||||
// enchantment,
|
||||
// jukebox_song,
|
||||
jukebox_song,
|
||||
// instrument,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -12,6 +13,14 @@ pub fn get_item(name: &str) -> Option<&Item> {
|
||||
ITEMS.get(&name.replace("minecraft:", ""))
|
||||
}
|
||||
|
||||
pub fn get_item_by_id<'a>(id: u16) -> Option<&'a Item> {
|
||||
let item = ITEMS.iter().find(|item| item.1.id == id);
|
||||
if let Some(item) = item {
|
||||
return Some(item.1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct Item {
|
||||
pub id: u16,
|
||||
@@ -22,4 +31,11 @@ pub struct Item {
|
||||
pub struct ItemComponents {
|
||||
#[serde(rename = "minecraft:max_stack_size")]
|
||||
pub max_stack_size: u8,
|
||||
#[serde(rename = "minecraft:jukebox_playable")]
|
||||
pub jukebox_playable: Option<JukeboxPlayable>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct JukeboxPlayable {
|
||||
pub song: String,
|
||||
}
|
||||
|
||||
89
pumpkin/src/block/block_manager.rs
Normal file
89
pumpkin/src/block/block_manager.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock};
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_world::block::block_registry::Block;
|
||||
use pumpkin_world::item::item_registry::Item;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum BlockActionResult {
|
||||
/// Allow other actions to be executed
|
||||
Continue,
|
||||
/// Block other actions
|
||||
Consume,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BlockManager {
|
||||
blocks: HashMap<String, Arc<dyn PumpkinBlock>>,
|
||||
}
|
||||
|
||||
impl BlockManager {
|
||||
pub fn register<T: PumpkinBlock + BlockMetadata + 'static>(&mut self, block: T) {
|
||||
self.blocks
|
||||
.insert(block.name().to_string(), Arc::new(block));
|
||||
}
|
||||
|
||||
pub async fn on_use(
|
||||
&self,
|
||||
block: &Block,
|
||||
player: &Player,
|
||||
location: WorldPosition,
|
||||
server: &Server,
|
||||
) {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
pumpkin_block.on_use(player, location, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn on_use_with_item(
|
||||
&self,
|
||||
block: &Block,
|
||||
player: &Player,
|
||||
location: WorldPosition,
|
||||
item: &Item,
|
||||
server: &Server,
|
||||
) -> BlockActionResult {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
return pumpkin_block
|
||||
.on_use_with_item(player, location, item, server)
|
||||
.await;
|
||||
}
|
||||
BlockActionResult::Continue
|
||||
}
|
||||
|
||||
pub async fn on_placed(
|
||||
&self,
|
||||
block: &Block,
|
||||
player: &Player,
|
||||
location: WorldPosition,
|
||||
server: &Server,
|
||||
) {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
pumpkin_block.on_placed(player, location, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn on_broken(
|
||||
&self,
|
||||
block: &Block,
|
||||
player: &Player,
|
||||
location: WorldPosition,
|
||||
server: &Server,
|
||||
) {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
pumpkin_block.on_broken(player, location, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_pumpkin_block(&self, block: &Block) -> Option<&Arc<dyn PumpkinBlock>> {
|
||||
self.blocks
|
||||
.get(format!("minecraft:{}", block.name).as_str())
|
||||
}
|
||||
}
|
||||
50
pumpkin/src/block/blocks/crafting_table.rs
Normal file
50
pumpkin/src/block/blocks/crafting_table.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::block::block_manager::BlockActionResult;
|
||||
use crate::block::pumpkin_block::PumpkinBlock;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::item::item_registry::Item;
|
||||
|
||||
#[pumpkin_block("minecraft:crafting_table")]
|
||||
pub struct CraftingTableBlock;
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinBlock for CraftingTableBlock {
|
||||
async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) {
|
||||
self.open_crafting_screen(player, server).await;
|
||||
}
|
||||
|
||||
async fn on_use_with_item<'a>(
|
||||
&self,
|
||||
player: &Player,
|
||||
_location: WorldPosition,
|
||||
_item: &Item,
|
||||
server: &Server,
|
||||
) -> BlockActionResult {
|
||||
self.open_crafting_screen(player, server).await;
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
}
|
||||
|
||||
impl CraftingTableBlock {
|
||||
pub async fn open_crafting_screen(&self, player: &Player, server: &Server) {
|
||||
//TODO: Adjust /craft command to real crafting table
|
||||
let entity_id = player.entity_id();
|
||||
player.open_container.store(Some(1));
|
||||
{
|
||||
let mut open_containers = server.open_containers.write().await;
|
||||
if let Some(ender_chest) = open_containers.get_mut(&1) {
|
||||
ender_chest.add_player(entity_id);
|
||||
} else {
|
||||
let open_container = OpenContainer::new_empty_container::<CraftingTable>(entity_id);
|
||||
open_containers.insert(1, open_container);
|
||||
}
|
||||
}
|
||||
player
|
||||
.open_container(server, WindowType::CraftingTable)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
58
pumpkin/src/block/blocks/jukebox.rs
Normal file
58
pumpkin/src/block/blocks/jukebox.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::block::block_manager::BlockActionResult;
|
||||
use crate::block::pumpkin_block::PumpkinBlock;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_registry::SYNCED_REGISTRIES;
|
||||
use pumpkin_world::item::item_registry::Item;
|
||||
|
||||
#[pumpkin_block("minecraft:jukebox")]
|
||||
pub struct JukeboxBlock;
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinBlock for JukeboxBlock {
|
||||
async fn on_use<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) {
|
||||
// For now just stop the music at this position
|
||||
let world = &player.living_entity.entity.world;
|
||||
|
||||
world.stop_record(location).await;
|
||||
}
|
||||
|
||||
async fn on_use_with_item<'a>(
|
||||
&self,
|
||||
player: &Player,
|
||||
location: WorldPosition,
|
||||
item: &Item,
|
||||
_server: &Server,
|
||||
) -> BlockActionResult {
|
||||
let world = &player.living_entity.entity.world;
|
||||
|
||||
let Some(jukebox_playable) = &item.components.jukebox_playable else {
|
||||
return BlockActionResult::Continue;
|
||||
};
|
||||
|
||||
let Some(song) = jukebox_playable.song.split(':').nth(1) else {
|
||||
return BlockActionResult::Continue;
|
||||
};
|
||||
|
||||
let Some(jukebox_song) = SYNCED_REGISTRIES.jukebox_song.get_index_of(song) else {
|
||||
log::error!("Jukebox playable song not registered!");
|
||||
return BlockActionResult::Continue;
|
||||
};
|
||||
|
||||
//TODO: Update block state and block nbt
|
||||
|
||||
world.play_record(jukebox_song as i32, location).await;
|
||||
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
|
||||
async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) {
|
||||
// For now just stop the music at this position
|
||||
let world = &player.living_entity.entity.world;
|
||||
|
||||
world.stop_record(location).await;
|
||||
}
|
||||
}
|
||||
2
pumpkin/src/block/blocks/mod.rs
Normal file
2
pumpkin/src/block/blocks/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod crafting_table;
|
||||
pub(crate) mod jukebox;
|
||||
18
pumpkin/src/block/mod.rs
Normal file
18
pumpkin/src/block/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::block::block_manager::BlockManager;
|
||||
use crate::block::blocks::crafting_table::CraftingTableBlock;
|
||||
use crate::block::blocks::jukebox::JukeboxBlock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod block_manager;
|
||||
mod blocks;
|
||||
pub mod pumpkin_block;
|
||||
|
||||
#[must_use]
|
||||
pub fn default_block_manager() -> Arc<BlockManager> {
|
||||
let mut manager = BlockManager::default();
|
||||
|
||||
manager.register(JukeboxBlock);
|
||||
manager.register(CraftingTableBlock);
|
||||
|
||||
Arc::new(manager)
|
||||
}
|
||||
32
pumpkin/src/block/pumpkin_block.rs
Normal file
32
pumpkin/src/block/pumpkin_block.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use crate::block::block_manager::BlockActionResult;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_world::item::item_registry::Item;
|
||||
|
||||
pub trait BlockMetadata {
|
||||
const NAMESPACE: &'static str;
|
||||
const ID: &'static str;
|
||||
fn name(&self) -> String {
|
||||
format!("{}:{}", Self::NAMESPACE, Self::ID)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait PumpkinBlock: Send + Sync {
|
||||
async fn on_use<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {}
|
||||
async fn on_use_with_item<'a>(
|
||||
&self,
|
||||
_player: &Player,
|
||||
_location: WorldPosition,
|
||||
_item: &Item,
|
||||
_server: &Server,
|
||||
) -> BlockActionResult {
|
||||
BlockActionResult::Continue
|
||||
}
|
||||
|
||||
async fn on_placed<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {}
|
||||
|
||||
async fn on_broken<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::PlayerConfig;
|
||||
use crate::block::block_manager::BlockActionResult;
|
||||
use crate::{
|
||||
command::CommandSender,
|
||||
entity::player::{ChatMode, Hand, Player},
|
||||
@@ -35,10 +37,9 @@ use pumpkin_protocol::{
|
||||
},
|
||||
};
|
||||
use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace};
|
||||
use pumpkin_world::item::item_registry::get_item_by_id;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::PlayerConfig;
|
||||
|
||||
fn modulus(a: f32, b: f32) -> f32 {
|
||||
((a % b) + b) % b
|
||||
}
|
||||
@@ -48,6 +49,7 @@ pub enum BlockPlacingError {
|
||||
BlockOutOfReach,
|
||||
InvalidBlockFace,
|
||||
BlockOutOfWorld,
|
||||
InventoryInvalid,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BlockPlacingError {
|
||||
@@ -60,7 +62,7 @@ impl PumpkinError for BlockPlacingError {
|
||||
fn is_kick(&self) -> bool {
|
||||
match self {
|
||||
Self::BlockOutOfReach | Self::BlockOutOfWorld => false,
|
||||
Self::InvalidBlockFace => true,
|
||||
Self::InvalidBlockFace | Self::InventoryInvalid => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +71,7 @@ impl PumpkinError for BlockPlacingError {
|
||||
Self::BlockOutOfReach | Self::BlockOutOfWorld | Self::InvalidBlockFace => {
|
||||
log::Level::Warn
|
||||
}
|
||||
Self::InventoryInvalid => log::Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +79,7 @@ impl PumpkinError for BlockPlacingError {
|
||||
match self {
|
||||
Self::BlockOutOfReach | Self::BlockOutOfWorld => None,
|
||||
Self::InvalidBlockFace => Some("Invalid block face".into()),
|
||||
Self::InventoryInvalid => Some("Held item invalid".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +527,7 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_player_action(&self, player_action: SPlayerAction) {
|
||||
pub async fn handle_player_action(&self, player_action: SPlayerAction, server: &Server) {
|
||||
match Status::from_i32(player_action.status.0) {
|
||||
Some(status) => match status {
|
||||
Status::StartedDigging => {
|
||||
@@ -542,7 +546,16 @@ impl Player {
|
||||
// Block break & block break sound
|
||||
let entity = &self.living_entity.entity;
|
||||
let world = &entity.world;
|
||||
let block = world.get_block(location).await;
|
||||
|
||||
world.break_block(location, Some(self)).await;
|
||||
|
||||
if let Ok(block) = block {
|
||||
server
|
||||
.block_manager
|
||||
.on_broken(block, self, location, server)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Status::CancelledDigging => {
|
||||
@@ -571,7 +584,16 @@ impl Player {
|
||||
// Block break & block break sound
|
||||
let entity = &self.living_entity.entity;
|
||||
let world = &entity.world;
|
||||
let block = world.get_block(location).await;
|
||||
|
||||
world.break_block(location, Some(self)).await;
|
||||
|
||||
if let Ok(block) = block {
|
||||
server
|
||||
.block_manager
|
||||
.on_broken(block, self, location, server)
|
||||
.await;
|
||||
}
|
||||
// TODO: Send this every tick
|
||||
self.client
|
||||
.send_packet(&CAcknowledgeBlockChange::new(player_action.sequence))
|
||||
@@ -624,6 +646,7 @@ impl Player {
|
||||
pub async fn handle_use_item_on(
|
||||
&self,
|
||||
use_item_on: SUseItemOn,
|
||||
server: &Server,
|
||||
) -> Result<(), Box<dyn PumpkinError>> {
|
||||
let location = use_item_on.location;
|
||||
|
||||
@@ -633,22 +656,46 @@ impl Player {
|
||||
}
|
||||
|
||||
if let Some(face) = BlockFace::from_i32(use_item_on.face.0) {
|
||||
let mut inventory = self.inventory().lock().await;
|
||||
let item_slot = inventory.held_item_mut();
|
||||
if let Some(item) = item_slot {
|
||||
let block = get_block_by_item(item.item_id);
|
||||
// check if item is a block, Because Not every item can be placed :D
|
||||
if let Some(block) = block {
|
||||
let entity = &self.living_entity.entity;
|
||||
let world = &entity.world;
|
||||
let inventory = self.inventory().lock().await;
|
||||
let entity = &self.living_entity.entity;
|
||||
let world = &entity.world;
|
||||
let item_slot = inventory.held_item();
|
||||
|
||||
if let Some(item_stack) = item_slot {
|
||||
let item_stack = *item_stack;
|
||||
drop(inventory);
|
||||
|
||||
if let Some(item) = get_item_by_id(item_stack.item_id) {
|
||||
if let Ok(block) = world.get_block(location).await {
|
||||
let result = server
|
||||
.block_manager
|
||||
.on_use_with_item(block, self, location, item, server)
|
||||
.await;
|
||||
match result {
|
||||
BlockActionResult::Continue => {}
|
||||
BlockActionResult::Consume => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if item is a block, Because Not every item can be placed :D
|
||||
if let Some(block) = get_block_by_item(item_stack.item_id) {
|
||||
// TODO: Config
|
||||
// Decrease Block count
|
||||
if self.gamemode.load() != GameMode::Creative {
|
||||
item.item_count -= 1;
|
||||
if item.item_count == 0 {
|
||||
let mut inventory = self.inventory().lock().await;
|
||||
let item_slot = inventory.held_item_mut();
|
||||
// This should never be possible
|
||||
let Some(item_stack) = item_slot else {
|
||||
return Err(BlockPlacingError::InventoryInvalid.into());
|
||||
};
|
||||
item_stack.item_count -= 1;
|
||||
if item_stack.item_count == 0 {
|
||||
*item_slot = None;
|
||||
}
|
||||
drop(inventory);
|
||||
}
|
||||
|
||||
let clicked_world_pos = WorldPosition(location.0);
|
||||
@@ -682,11 +729,25 @@ impl Player {
|
||||
world
|
||||
.set_block_state(world_pos, block.default_state_id)
|
||||
.await;
|
||||
server
|
||||
.block_manager
|
||||
.on_placed(block, self, world_pos, server)
|
||||
.await;
|
||||
}
|
||||
|
||||
self.client
|
||||
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
drop(inventory);
|
||||
let block = world.get_block(location).await;
|
||||
if let Ok(block) = block {
|
||||
server
|
||||
.block_manager
|
||||
.on_use(block, self, location, server)
|
||||
.await;
|
||||
}
|
||||
self.client
|
||||
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -657,7 +657,7 @@ impl Player {
|
||||
.await;
|
||||
}
|
||||
SPlayerAction::PACKET_ID => {
|
||||
self.handle_player_action(SPlayerAction::read(bytebuf)?)
|
||||
self.handle_player_action(SPlayerAction::read(bytebuf)?, server)
|
||||
.await;
|
||||
}
|
||||
SPlayerCommand::PACKET_ID => {
|
||||
@@ -684,7 +684,8 @@ impl Player {
|
||||
self.handle_swing_arm(SSwingArm::read(bytebuf)?).await;
|
||||
}
|
||||
SUseItemOn::PACKET_ID => {
|
||||
self.handle_use_item_on(SUseItemOn::read(bytebuf)?).await?;
|
||||
self.handle_use_item_on(SUseItemOn::read(bytebuf)?, server)
|
||||
.await?;
|
||||
}
|
||||
SUseItem::PACKET_ID => self.handle_use_item(&SUseItem::read(bytebuf)?),
|
||||
SCommandSuggestion::PACKET_ID => {
|
||||
|
||||
@@ -38,6 +38,7 @@ use rcon::RCONServer;
|
||||
use std::time::Instant;
|
||||
// Setup some tokens to allow us to identify which event is for which socket.
|
||||
|
||||
pub mod block;
|
||||
pub mod client;
|
||||
pub mod command;
|
||||
pub mod entity;
|
||||
|
||||
@@ -21,6 +21,8 @@ use std::{
|
||||
};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
use crate::block::block_manager::BlockManager;
|
||||
use crate::block::default_block_manager;
|
||||
use crate::client::EncryptionError;
|
||||
use crate::world::custom_bossbar::CustomBossbars;
|
||||
use crate::{
|
||||
@@ -46,6 +48,8 @@ pub struct Server {
|
||||
server_branding: CachedBranding,
|
||||
/// Saves and Dispatches commands to appropriate handlers.
|
||||
pub command_dispatcher: Arc<CommandDispatcher<'static>>,
|
||||
/// Saves and calls blocks blocks
|
||||
pub block_manager: Arc<BlockManager>,
|
||||
/// Manages multiple worlds within the server.
|
||||
pub worlds: Vec<Arc<World>>,
|
||||
// All the dimensions that exists on the server,
|
||||
@@ -112,6 +116,7 @@ impl Server {
|
||||
DimensionType::TheEnd,
|
||||
],
|
||||
command_dispatcher,
|
||||
block_manager: default_block_manager(),
|
||||
auth_client,
|
||||
key_store: KeyStore::new(),
|
||||
server_listing: Mutex::new(CachedStatus::new()),
|
||||
|
||||
@@ -16,6 +16,7 @@ use pumpkin_core::math::vector2::Vector2;
|
||||
use pumpkin_core::math::{position::WorldPosition, vector3::Vector3};
|
||||
use pumpkin_core::text::{color::NamedColor, TextComponent};
|
||||
use pumpkin_entity::{entity_type::EntityType, EntityId};
|
||||
use pumpkin_protocol::client::play::CLevelEvent;
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CBlockUpdate, CRespawn, CSoundEffect, CWorldEvent},
|
||||
SoundCategory,
|
||||
@@ -166,6 +167,16 @@ impl World {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn play_record(&self, record_id: i32, position: WorldPosition) {
|
||||
self.broadcast_packet_all(&CLevelEvent::new(1010, position, record_id, false))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn stop_record(&self, position: WorldPosition) {
|
||||
self.broadcast_packet_all(&CLevelEvent::new(1011, position, 0, false))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn tick(&self) {
|
||||
// world ticks
|
||||
let mut level_time = self.level_time.lock().await;
|
||||
|
||||
Reference in New Issue
Block a user