Check if player can destroy block using an tool

Prevents players to destroy blocks when holding a sword or trident in creative
This commit is contained in:
Alexander Medvedev 2025-03-04 19:45:39 +01:00
parent cccdf53e7c
commit e055dd740c
24 changed files with 3497 additions and 123 deletions

File diff suppressed because it is too large Load Diff

View File

@ -315,14 +315,15 @@ pub(crate) fn build() -> TokenStream {
serde_json::from_str(self.components.item_name.unwrap()).expect("Could not parse item name.") serde_json::from_str(self.components.item_name.unwrap()).expect("Could not parse item name.")
} }
#[doc = r" Try to parse a Item from a resource location string"] #[doc = "Try to parse a Item from a resource location string"]
pub fn from_registry_key(name: &str) -> Option<Self> { pub fn from_registry_key(name: &str) -> Option<Self> {
match name { match name {
#type_from_name #type_from_name
_ => None _ => None
} }
} }
#[doc = r" Try to parse a Item from a raw id"]
#[doc = "Try to parse a Item from a raw id"]
pub const fn from_id(id: u16) -> Option<Self> { pub const fn from_id(id: u16) -> Option<Self> {
match id { match id {
#type_from_raw_id_arms #type_from_raw_id_arms

View File

@ -1,7 +1,7 @@
use heck::{ToPascalCase, ToSnakeCase}; use heck::{ToPascalCase, ToSnakeCase};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use pumpkin_data::item::Item;
use quote::quote; use quote::quote;
use syn::{self};
use syn::{ use syn::{
Block, Expr, Field, Fields, ItemStruct, Stmt, Block, Expr, Field, Fields, ItemStruct, Stmt,
parse::{Nothing, Parser}, parse::{Nothing, Parser},
@ -198,29 +198,6 @@ pub fn pumpkin_block(input: TokenStream, item: TokenStream) -> TokenStream {
code.into() code.into()
} }
#[proc_macro_attribute]
pub fn pumpkin_item(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 item_id = Item::from_registry_key(packet_name).unwrap();
let id = item_id.id;
let item: proc_macro2::TokenStream = item.into();
let code = quote! {
#item
impl #impl_generics crate::item::pumpkin_item::ItemMetadata for #name #ty_generics {
const ID: u16 = #id;
}
};
code.into()
}
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream { pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap(); let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap();

View File

@ -19,6 +19,13 @@ impl BoundingBox {
} }
} }
pub fn expand(&self, x: f64, y: f64, z: f64) -> Self {
Self {
min: Vector3::new(self.min.x - x, self.min.y - y, self.min.z - z),
max: Vector3::new(self.max.x + x, self.max.y + y, self.max.z + z),
}
}
pub fn offset(&self, other: Self) -> Self { pub fn offset(&self, other: Self) -> Self {
Self { Self {
min: self.min.add(&other.min), min: self.min.add(&other.min),

View File

@ -122,6 +122,9 @@ pub struct Block {
pub experience: Option<Experience>, pub experience: Option<Experience>,
pub blast_resistance: f32, pub blast_resistance: f32,
pub wall_variant_id: Option<u16>, pub wall_variant_id: Option<u16>,
pub slipperiness: f32,
pub velocity_multiplier: f32,
pub jump_velocity_multiplier: f32,
pub translation_key: String, pub translation_key: String,
pub name: String, pub name: String,
pub loot_table: Option<LootTable>, pub loot_table: Option<LootTable>,

View File

@ -12,14 +12,7 @@ impl AlternativeEntry {
let mut items = vec![]; let mut items = vec![];
for child in &self.children { for child in &self.children {
if let Some(conditions) = &child.conditions { if let Some(conditions) = &child.conditions {
let mut conditions_met = true; if !conditions.iter().all(|condition| condition.test()) {
for condition in conditions {
if !condition.test() {
conditions_met = false;
break;
}
}
if !conditions_met {
continue; continue;
} }
} }

View File

@ -41,14 +41,7 @@ impl LootPool {
for _ in 0..i { for _ in 0..i {
for entry in &self.entries { for entry in &self.entries {
if let Some(conditions) = &entry.conditions { if let Some(conditions) = &entry.conditions {
let mut conditions_met = true; if !conditions.iter().all(|condition| condition.test()) {
for condition in conditions {
if !condition.test() {
conditions_met = false;
break;
}
}
if !conditions_met {
continue; continue;
} }
} }

View File

@ -80,11 +80,8 @@ async fn drop_stack(world: &Arc<World>, pos: &BlockPos, stack: ItemStack) {
); );
let entity = world.create_entity(pos, EntityType::ITEM); let entity = world.create_entity(pos, EntityType::ITEM);
let item_entity = Arc::new(ItemEntity::new( let item_entity =
entity, Arc::new(ItemEntity::new(entity, stack.item.id, u32::from(stack.item_count)).await);
stack.item.id,
u32::from(stack.item_count),
));
world.spawn_entity(item_entity.clone()).await; world.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await; item_entity.send_meta_packet().await;
} }

View File

@ -64,7 +64,9 @@ impl ExperienceOrbEntity {
#[async_trait] #[async_trait]
impl EntityBase for ExperienceOrbEntity { impl EntityBase for ExperienceOrbEntity {
async fn tick(&self, _: &Server) { async fn tick(&self, server: &Server) {
self.entity.tick(server).await;
let age = self let age = self
.orb_age .orb_age
.fetch_add(1, std::sync::atomic::Ordering::Relaxed); .fetch_add(1, std::sync::atomic::Ordering::Relaxed);

View File

@ -6,6 +6,7 @@ use pumpkin_protocol::{
client::play::{CTakeItemEntity, MetaDataType, Metadata}, client::play::{CTakeItemEntity, MetaDataType, Metadata},
codec::slot::Slot, codec::slot::Slot,
}; };
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -24,7 +25,14 @@ pub struct ItemEntity {
} }
impl ItemEntity { impl ItemEntity {
pub fn new(entity: Entity, item_id: u16, count: u32) -> Self { pub async fn new(entity: Entity, item_id: u16, count: u32) -> Self {
entity
.set_velocity(Vector3::new(
rand::random::<f64>() * 0.2 - 0.1,
0.2,
rand::random::<f64>() * 0.2 - 0.1,
))
.await;
entity.yaw.store(rand::random::<f32>() * 360.0); entity.yaw.store(rand::random::<f32>() * 360.0);
Self { Self {
entity, entity,
@ -44,7 +52,8 @@ impl ItemEntity {
#[async_trait] #[async_trait]
impl EntityBase for ItemEntity { impl EntityBase for ItemEntity {
async fn tick(&self, _server: &Server) { async fn tick(&self, server: &Server) {
self.entity.tick(server).await;
{ {
let mut delay = self.pickup_delay.lock().await; let mut delay = self.pickup_delay.lock().await;
*delay = delay.saturating_sub(1); *delay = delay.saturating_sub(1);

View File

@ -242,7 +242,9 @@ impl LivingEntity {
#[async_trait] #[async_trait]
impl EntityBase for LivingEntity { impl EntityBase for LivingEntity {
async fn tick(&self, _server: &Server) { async fn tick(&self, server: &Server) {
self.entity.tick(server).await;
if self if self
.time_until_regen .time_until_regen
.load(std::sync::atomic::Ordering::Relaxed) .load(std::sync::atomic::Ordering::Relaxed)

View File

@ -427,6 +427,33 @@ impl Entity {
self.invulnerable.load(std::sync::atomic::Ordering::Relaxed) self.invulnerable.load(std::sync::atomic::Ordering::Relaxed)
|| self.damage_immunities.contains(damage_type) || self.damage_immunities.contains(damage_type)
} }
async fn velocity_multiplier(&self, _pos: Vector3<f64>) -> f32 {
let world = self.world.read().await;
let block = world.get_block(&self.block_pos.load()).await.unwrap();
block.velocity_multiplier
// if velo_multiplier == 1.0 {
// const VELOCITY_OFFSET: f64 = 0.500001; // Vanilla
// let pos_with_y_offset = BlockPos(Vector3::new(
// pos.x.floor() as i32,
// (pos.y - VELOCITY_OFFSET).floor() as i32,
// pos.z.floor() as i32,
// ));
// let block = world.get_block(&pos_with_y_offset).await.unwrap();
// block.velocity_multiplier
// } else {
// }
}
async fn tick_move(&self) {
let velo = self.velocity.load();
let pos = self.pos.load();
self.pos
.store(Vector3::new(pos.x + velo.x, pos.y + velo.y, pos.z + velo.z));
let multiplier = f64::from(self.velocity_multiplier(pos).await);
self.velocity
.store(velo.multiply(multiplier, 1.0, multiplier));
}
} }
#[async_trait] #[async_trait]
@ -435,7 +462,9 @@ impl EntityBase for Entity {
false false
} }
async fn tick(&self, _: &Server) {} async fn tick(&self, _: &Server) {
self.tick_move().await;
}
fn get_entity(&self) -> &Entity { fn get_entity(&self) -> &Entity {
self self

View File

@ -1009,7 +1009,7 @@ impl Player {
.create_entity(self.living_entity.entity.pos.load(), EntityType::ITEM); .create_entity(self.living_entity.entity.pos.load(), EntityType::ITEM);
// TODO: Merge stacks together // TODO: Merge stacks together
let item_entity = Arc::new(ItemEntity::new(entity, item_id, count)); let item_entity = Arc::new(ItemEntity::new(entity, item_id, count).await);
self.world().await.spawn_entity(item_entity.clone()).await; self.world().await.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await; item_entity.send_meta_packet().await;
} }

View File

@ -2,16 +2,18 @@ use std::sync::Arc;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::projectile::ThrownItemEntity; use crate::entity::projectile::ThrownItemEntity;
use crate::item::pumpkin_item::PumpkinItem; use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem};
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item;
#[pumpkin_item("egg")]
pub struct EggItem; pub struct EggItem;
impl ItemMetadata for EggItem {
const IDS: &'static [u16] = &[Item::EGG.id];
}
const POWER: f32 = 1.5; const POWER: f32 = 1.5;
#[async_trait] #[async_trait]

View File

@ -1,2 +1,24 @@
pub mod egg; mod egg;
pub mod snowball; mod snowball;
mod sword;
mod trident;
use std::sync::Arc;
use egg::EggItem;
use snowball::SnowBallItem;
use sword::SwordItem;
use trident::TridentItem;
use super::registry::ItemRegistry;
#[must_use]
pub fn default_registry() -> Arc<ItemRegistry> {
let mut manager = ItemRegistry::default();
manager.register(SnowBallItem);
manager.register(EggItem);
manager.register(SwordItem);
manager.register(TridentItem);
Arc::new(manager)
}

View File

@ -2,16 +2,18 @@ use std::sync::Arc;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::projectile::ThrownItemEntity; use crate::entity::projectile::ThrownItemEntity;
use crate::item::pumpkin_item::PumpkinItem; use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem};
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item;
#[pumpkin_item("snowball")]
pub struct SnowBallItem; pub struct SnowBallItem;
impl ItemMetadata for SnowBallItem {
const IDS: &'static [u16] = &[Item::SNOWBALL.id];
}
const POWER: f32 = 1.5; const POWER: f32 = 1.5;
#[async_trait] #[async_trait]

View File

@ -0,0 +1,24 @@
use crate::entity::player::Player;
use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem};
use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_util::GameMode;
pub struct SwordItem;
impl ItemMetadata for SwordItem {
const IDS: &'static [u16] = &[
Item::WOODEN_SWORD.id,
Item::STONE_SWORD.id,
Item::GOLDEN_SWORD.id,
Item::DIAMOND_SWORD.id,
Item::NETHERITE_SWORD.id,
];
}
#[async_trait]
impl PumpkinItem for SwordItem {
fn can_mine(&self, player: &Player) -> bool {
player.gamemode.load() != GameMode::Creative
}
}

View File

@ -0,0 +1,21 @@
use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_util::GameMode;
use crate::{
entity::player::Player,
item::pumpkin_item::{ItemMetadata, PumpkinItem},
};
pub struct TridentItem;
impl ItemMetadata for TridentItem {
const IDS: &'static [u16] = &[Item::TRIDENT.id];
}
#[async_trait]
impl PumpkinItem for TridentItem {
fn can_mine(&self, player: &Player) -> bool {
player.gamemode.load() != GameMode::Creative
}
}

View File

@ -1,18 +1,3 @@
use items::{egg::EggItem, snowball::SnowBallItem}; pub mod items;
use registry::ItemRegistry;
use std::sync::Arc;
mod items;
pub mod pumpkin_item; pub mod pumpkin_item;
pub mod registry; pub mod registry;
#[must_use]
pub fn default_registry() -> Arc<ItemRegistry> {
let mut manager = ItemRegistry::default();
manager.register(SnowBallItem);
manager.register(EggItem);
Arc::new(manager)
}

View File

@ -6,7 +6,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
pub trait ItemMetadata { pub trait ItemMetadata {
const ID: u16; const IDS: &'static [u16];
} }
#[async_trait] #[async_trait]
@ -21,4 +21,8 @@ pub trait PumpkinItem: Send + Sync {
_server: &Server, _server: &Server,
) { ) {
} }
fn can_mine(&self, _player: &Player) -> bool {
true
}
} }

View File

@ -10,12 +10,12 @@ use super::pumpkin_item::{ItemMetadata, PumpkinItem};
#[derive(Default)] #[derive(Default)]
pub struct ItemRegistry { pub struct ItemRegistry {
items: HashMap<u16, Arc<dyn PumpkinItem>>, items: HashMap<&'static [u16], Arc<dyn PumpkinItem>>,
} }
impl ItemRegistry { impl ItemRegistry {
pub fn register<T: PumpkinItem + ItemMetadata + 'static>(&mut self, item: T) { pub fn register<T: PumpkinItem + ItemMetadata + 'static>(&mut self, item: T) {
self.items.insert(T::ID, Arc::new(item)); self.items.insert(T::IDS, Arc::new(item));
} }
pub async fn on_use(&self, item: &Item, player: &Player) { pub async fn on_use(&self, item: &Item, player: &Player) {
@ -41,8 +41,18 @@ impl ItemRegistry {
} }
} }
pub fn can_mine(&self, item: &Item, player: &Player) -> bool {
let pumpkin_block = self.get_pumpkin_item(item.id);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block.can_mine(player);
}
true
}
#[must_use] #[must_use]
pub fn get_pumpkin_item(&self, item_id: u16) -> Option<&Arc<dyn PumpkinItem>> { pub fn get_pumpkin_item(&self, item_id: u16) -> Option<&Arc<dyn PumpkinItem>> {
self.items.get(&item_id) self.items
.iter()
.find_map(|(ids, item)| ids.contains(&item_id).then_some(item))
} }
} }

View File

@ -25,7 +25,7 @@ use pumpkin_inventory::player::{
}; };
use pumpkin_macros::block_entity; use pumpkin_macros::block_entity;
use pumpkin_protocol::client::play::{ use pumpkin_protocol::client::play::{
CBlockEntityData, COpenSignEditor, CSetContainerSlot, CSetHeldItem, EquipmentSlot, CBlockEntityData, CBlockUpdate, COpenSignEditor, CSetContainerSlot, CSetHeldItem, EquipmentSlot,
}; };
use pumpkin_protocol::codec::slot::Slot; use pumpkin_protocol::codec::slot::Slot;
use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::codec::var_int::VarInt;
@ -835,9 +835,22 @@ impl Player {
let location = player_action.location; let location = player_action.location;
let entity = &self.living_entity.entity; let entity = &self.living_entity.entity;
let world = &entity.world.read().await; let world = &entity.world.read().await;
let block = world.get_block(&location).await; let block = world.get_block(&location).await.unwrap();
let state = world.get_block_state(&location).await.unwrap(); let state = world.get_block_state(&location).await.unwrap();
if let Some(held) = self.inventory.lock().await.held_item() {
if !server.item_registry.can_mine(&held.item, &self) {
self.client
.send_packet(&CBlockUpdate::new(
&location,
VarInt(i32::from(state.id)),
))
.await;
self.update_sequence(player_action.sequence.0);
return;
}
}
// TODO: do validation // TODO: do validation
// TODO: Config // TODO: Config
if self.gamemode.load() == GameMode::Creative { if self.gamemode.load() == GameMode::Creative {
@ -846,37 +859,33 @@ impl Player {
world world
.break_block(&location, Some(self.clone()), false) .break_block(&location, Some(self.clone()), false)
.await; .await;
if let Ok(block) = block { server
server .block_registry
.block_registry .broken(block, &self, location, server)
.broken(block, &self, location, server) .await;
.await;
}
return; return;
} }
self.start_mining_time.store( self.start_mining_time.store(
self.tick_counter.load(std::sync::atomic::Ordering::Relaxed), self.tick_counter.load(std::sync::atomic::Ordering::Relaxed),
std::sync::atomic::Ordering::Relaxed, std::sync::atomic::Ordering::Relaxed,
); );
if let Ok(block) = block { if !state.air {
if !state.air { let speed = block::calc_block_breaking(&self, state, &block.name).await;
let speed = block::calc_block_breaking(&self, state, &block.name).await; // Instant break
// Instant break if speed >= 1.0 {
if speed >= 1.0 { world.break_block(&location, Some(self.clone()), true).await;
world.break_block(&location, Some(self.clone()), true).await; server
server .block_registry
.block_registry .broken(block, &self, location, server)
.broken(block, &self, location, server) .await;
.await; } else {
} else { self.mining
self.mining .store(true, std::sync::atomic::Ordering::Relaxed);
.store(true, std::sync::atomic::Ordering::Relaxed); *self.mining_pos.lock().await = location;
*self.mining_pos.lock().await = location; let progress = (speed * 10.0) as i32;
let progress = (speed * 10.0) as i32; world.set_block_breaking(entity, location, progress).await;
world.set_block_breaking(entity, location, progress).await; self.current_block_destroy_stage
self.current_block_destroy_stage .store(progress, std::sync::atomic::Ordering::Relaxed);
.store(progress, std::sync::atomic::Ordering::Relaxed);
}
} }
} }
self.update_sequence(player_action.sequence.0); self.update_sequence(player_action.sequence.0);
@ -1022,14 +1031,13 @@ impl Player {
let Ok(block) = world.get_block(&location).await else { let Ok(block) = world.get_block(&location).await else {
return Err(BlockPlacingError::NoBaseBlock.into()); return Err(BlockPlacingError::NoBaseBlock.into());
}; };
let sneaking = self
.living_entity
.entity
.sneaking
.load(std::sync::atomic::Ordering::Relaxed);
let Some(stack) = held_item else { let Some(stack) = held_item else {
if !self if !sneaking {
.living_entity
.entity
.sneaking
.load(std::sync::atomic::Ordering::Relaxed)
{
// Using block with empty hand // Using block with empty hand
server server
.block_registry .block_registry
@ -1044,12 +1052,7 @@ impl Player {
} }
return Ok(()); return Ok(());
}; };
if !self if !sneaking {
.living_entity
.entity
.sneaking
.load(std::sync::atomic::Ordering::Relaxed)
{
let action_result = server let action_result = server
.block_registry .block_registry
.use_with_item(block, self, location, &stack.item, server) .use_with_item(block, self, location, &stack.item, server)

View File

@ -121,7 +121,7 @@ impl Server {
], ],
command_dispatcher, command_dispatcher,
block_registry: super::block::default_registry(), block_registry: super::block::default_registry(),
item_registry: super::item::default_registry(), item_registry: super::item::items::default_registry(),
block_properties_manager: default_block_properties_manager(), block_properties_manager: default_block_properties_manager(),
auth_client, auth_client,
key_store: KeyStore::new(), key_store: KeyStore::new(),

View File

@ -306,6 +306,8 @@ impl World {
.entity .entity
.bounding_box .bounding_box
.load() .load()
// This is vanilla, but TODO: change this when is in a Vehicle
.expand(1.0, 0.5, 1.0)
.intersects(&entity.get_entity().bounding_box.load()) .intersects(&entity.get_entity().bounding_box.load())
{ {
collied_player = Some(player.clone()); collied_player = Some(player.clone());
@ -402,7 +404,7 @@ impl World {
// also send his info to everyone else // also send his info to everyone else
log::debug!("Broadcasting player info for {}", player.gameprofile.name); log::debug!("Broadcasting player info for {}", player.gameprofile.name);
self.broadcast_packet_all(&CPlayerInfoUpdate::new( self.broadcast_packet_all(&CPlayerInfoUpdate::new(
0x01 | 0x08, 0x01 | 0x04 | 0x08,
&[pumpkin_protocol::client::play::Player { &[pumpkin_protocol::client::play::Player {
uuid: gameprofile.id, uuid: gameprofile.id,
actions: vec![ actions: vec![
@ -411,6 +413,7 @@ impl World {
properties: &gameprofile.properties, properties: &gameprofile.properties,
}, },
PlayerAction::UpdateListed(true), PlayerAction::UpdateListed(true),
PlayerAction::UpdateGameMode(VarInt(gamemode as i32)),
], ],
}], }],
)) ))