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.")
}
#[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> {
match name {
#type_from_name
_ => 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> {
match id {
#type_from_raw_id_arms

View File

@ -1,7 +1,7 @@
use heck::{ToPascalCase, ToSnakeCase};
use proc_macro::TokenStream;
use pumpkin_data::item::Item;
use quote::quote;
use syn::{self};
use syn::{
Block, Expr, Field, Fields, ItemStruct, Stmt,
parse::{Nothing, Parser},
@ -198,29 +198,6 @@ pub fn pumpkin_block(input: TokenStream, item: TokenStream) -> TokenStream {
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]
pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream {
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 {
Self {
min: self.min.add(&other.min),

View File

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

View File

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

View File

@ -41,14 +41,7 @@ impl LootPool {
for _ in 0..i {
for entry in &self.entries {
if let Some(conditions) = &entry.conditions {
let mut conditions_met = true;
for condition in conditions {
if !condition.test() {
conditions_met = false;
break;
}
}
if !conditions_met {
if !conditions.iter().all(|condition| condition.test()) {
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 item_entity = Arc::new(ItemEntity::new(
entity,
stack.item.id,
u32::from(stack.item_count),
));
let item_entity =
Arc::new(ItemEntity::new(entity, stack.item.id, u32::from(stack.item_count)).await);
world.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await;
}

View File

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

View File

@ -6,6 +6,7 @@ use pumpkin_protocol::{
client::play::{CTakeItemEntity, MetaDataType, Metadata},
codec::slot::Slot,
};
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
use tokio::sync::Mutex;
@ -24,7 +25,14 @@ pub struct 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);
Self {
entity,
@ -44,7 +52,8 @@ impl ItemEntity {
#[async_trait]
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;
*delay = delay.saturating_sub(1);

View File

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

View File

@ -427,6 +427,33 @@ impl Entity {
self.invulnerable.load(std::sync::atomic::Ordering::Relaxed)
|| 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]
@ -435,7 +462,9 @@ impl EntityBase for Entity {
false
}
async fn tick(&self, _: &Server) {}
async fn tick(&self, _: &Server) {
self.tick_move().await;
}
fn get_entity(&self) -> &Entity {
self

View File

@ -1009,7 +1009,7 @@ impl Player {
.create_entity(self.living_entity.entity.pos.load(), EntityType::ITEM);
// 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;
item_entity.send_meta_packet().await;
}

View File

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

View File

@ -1,2 +1,24 @@
pub mod egg;
pub mod snowball;
mod egg;
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::projectile::ThrownItemEntity;
use crate::item::pumpkin_item::PumpkinItem;
use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem};
use async_trait::async_trait;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item;
#[pumpkin_item("snowball")]
pub struct SnowBallItem;
impl ItemMetadata for SnowBallItem {
const IDS: &'static [u16] = &[Item::SNOWBALL.id];
}
const POWER: f32 = 1.5;
#[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};
use registry::ItemRegistry;
use std::sync::Arc;
mod items;
pub mod items;
pub mod pumpkin_item;
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;
pub trait ItemMetadata {
const ID: u16;
const IDS: &'static [u16];
}
#[async_trait]
@ -21,4 +21,8 @@ pub trait PumpkinItem: Send + Sync {
_server: &Server,
) {
}
fn can_mine(&self, _player: &Player) -> bool {
true
}
}

View File

@ -10,12 +10,12 @@ use super::pumpkin_item::{ItemMetadata, PumpkinItem};
#[derive(Default)]
pub struct ItemRegistry {
items: HashMap<u16, Arc<dyn PumpkinItem>>,
items: HashMap<&'static [u16], Arc<dyn PumpkinItem>>,
}
impl ItemRegistry {
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) {
@ -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]
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_protocol::client::play::{
CBlockEntityData, COpenSignEditor, CSetContainerSlot, CSetHeldItem, EquipmentSlot,
CBlockEntityData, CBlockUpdate, COpenSignEditor, CSetContainerSlot, CSetHeldItem, EquipmentSlot,
};
use pumpkin_protocol::codec::slot::Slot;
use pumpkin_protocol::codec::var_int::VarInt;
@ -835,9 +835,22 @@ impl Player {
let location = player_action.location;
let entity = &self.living_entity.entity;
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();
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: Config
if self.gamemode.load() == GameMode::Creative {
@ -846,19 +859,16 @@ impl Player {
world
.break_block(&location, Some(self.clone()), false)
.await;
if let Ok(block) = block {
server
.block_registry
.broken(block, &self, location, server)
.await;
}
return;
}
self.start_mining_time.store(
self.tick_counter.load(std::sync::atomic::Ordering::Relaxed),
std::sync::atomic::Ordering::Relaxed,
);
if let Ok(block) = block {
if !state.air {
let speed = block::calc_block_breaking(&self, state, &block.name).await;
// Instant break
@ -878,7 +888,6 @@ impl Player {
.store(progress, std::sync::atomic::Ordering::Relaxed);
}
}
}
self.update_sequence(player_action.sequence.0);
}
Status::CancelledDigging => {
@ -1022,14 +1031,13 @@ impl Player {
let Ok(block) = world.get_block(&location).await else {
return Err(BlockPlacingError::NoBaseBlock.into());
};
let Some(stack) = held_item else {
if !self
let sneaking = self
.living_entity
.entity
.sneaking
.load(std::sync::atomic::Ordering::Relaxed)
{
.load(std::sync::atomic::Ordering::Relaxed);
let Some(stack) = held_item else {
if !sneaking {
// Using block with empty hand
server
.block_registry
@ -1044,12 +1052,7 @@ impl Player {
}
return Ok(());
};
if !self
.living_entity
.entity
.sneaking
.load(std::sync::atomic::Ordering::Relaxed)
{
if !sneaking {
let action_result = server
.block_registry
.use_with_item(block, self, location, &stack.item, server)

View File

@ -121,7 +121,7 @@ impl Server {
],
command_dispatcher,
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(),
auth_client,
key_store: KeyStore::new(),

View File

@ -306,6 +306,8 @@ impl World {
.entity
.bounding_box
.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())
{
collied_player = Some(player.clone());
@ -402,7 +404,7 @@ impl World {
// also send his info to everyone else
log::debug!("Broadcasting player info for {}", player.gameprofile.name);
self.broadcast_packet_all(&CPlayerInfoUpdate::new(
0x01 | 0x08,
0x01 | 0x04 | 0x08,
&[pumpkin_protocol::client::play::Player {
uuid: gameprofile.id,
actions: vec![
@ -411,6 +413,7 @@ impl World {
properties: &gameprofile.properties,
},
PlayerAction::UpdateListed(true),
PlayerAction::UpdateGameMode(VarInt(gamemode as i32)),
],
}],
))