Make Snowballs and Eggs work

This commit is contained in:
Alexander Medvedev 2025-02-01 21:17:37 +01:00
parent 186e846858
commit 1729f58a6f
21 changed files with 264 additions and 79 deletions

View File

@ -231,7 +231,7 @@ pub fn pumpkin_item(input: TokenStream, item: TokenStream) -> TokenStream {
let gen = quote! {
#item
impl #impl_generics crate::block::pumpkin_block::BlockMetadata for #name #ty_generics {
impl #impl_generics crate::item::pumpkin_item::ItemMetadata for #name #ty_generics {
const NAMESPACE: &'static str = #namespace;
const ID: &'static str = #id;
}

View File

@ -22,6 +22,10 @@ pub fn get_item(name: &str) -> Option<&Item> {
ITEMS.get(&name.replace("minecraft:", ""))
}
pub fn get_name_by_id<'a>(item_id: u16) -> Option<&'a String> {
ITEMS_REGISTRY_NAME_BY_ID.get(&item_id)
}
pub fn get_item_by_id<'a>(item_id: u16) -> Option<&'a Item> {
ITEMS.values().find(|&item| item.id == item_id)
}

View File

@ -29,7 +29,7 @@ pub struct ChestBlock;
#[async_trait]
impl PumpkinBlock for ChestBlock {
async fn on_use<'a>(
async fn normal_use(
&self,
block: &Block,
player: &Player,
@ -40,7 +40,7 @@ impl PumpkinBlock for ChestBlock {
.await;
}
async fn on_use_with_item<'a>(
async fn use_with_item(
&self,
block: &Block,
player: &Player,
@ -53,17 +53,11 @@ impl PumpkinBlock for ChestBlock {
BlockActionResult::Consume
}
async fn on_broken<'a>(
&self,
block: &Block,
player: &Player,
location: BlockPos,
server: &Server,
) {
async fn broken(&self, block: &Block, player: &Player, location: BlockPos, server: &Server) {
super::standard_on_broken_with_container(block, player, location, server).await;
}
async fn on_close<'a>(
async fn close(
&self,
_block: &Block,
player: &Player,

View File

@ -14,7 +14,7 @@ pub struct CraftingTableBlock;
#[async_trait]
impl PumpkinBlock for CraftingTableBlock {
async fn on_use<'a>(
async fn normal_use(
&self,
block: &Block,
player: &Player,
@ -25,7 +25,7 @@ impl PumpkinBlock for CraftingTableBlock {
.await;
}
async fn on_use_with_item<'a>(
async fn use_with_item(
&self,
block: &Block,
player: &Player,
@ -38,17 +38,11 @@ impl PumpkinBlock for CraftingTableBlock {
BlockActionResult::Consume
}
async fn on_broken<'a>(
&self,
block: &Block,
player: &Player,
location: BlockPos,
server: &Server,
) {
async fn broken(&self, block: &Block, player: &Player, location: BlockPos, server: &Server) {
super::standard_on_broken_with_container(block, player, location, server).await;
}
async fn on_close<'a>(
async fn close(
&self,
_block: &Block,
player: &Player,

View File

@ -15,7 +15,7 @@ pub struct FurnaceBlock;
#[async_trait]
impl PumpkinBlock for FurnaceBlock {
async fn on_use<'a>(
async fn normal_use(
&self,
block: &Block,
player: &Player,
@ -26,7 +26,7 @@ impl PumpkinBlock for FurnaceBlock {
.await;
}
async fn on_use_with_item<'a>(
async fn use_with_item(
&self,
block: &Block,
player: &Player,
@ -39,13 +39,7 @@ impl PumpkinBlock for FurnaceBlock {
BlockActionResult::Consume
}
async fn on_broken<'a>(
&self,
block: &Block,
player: &Player,
location: BlockPos,
server: &Server,
) {
async fn broken(&self, block: &Block, player: &Player, location: BlockPos, server: &Server) {
super::standard_on_broken_with_container(block, player, location, server).await;
}
}

View File

@ -14,7 +14,7 @@ pub struct JukeboxBlock;
#[async_trait]
impl PumpkinBlock for JukeboxBlock {
async fn on_use<'a>(
async fn normal_use(
&self,
_block: &Block,
player: &Player,
@ -27,7 +27,7 @@ impl PumpkinBlock for JukeboxBlock {
world.stop_record(location).await;
}
async fn on_use_with_item<'a>(
async fn use_with_item(
&self,
_block: &Block,
player: &Player,
@ -57,13 +57,7 @@ impl PumpkinBlock for JukeboxBlock {
BlockActionResult::Consume
}
async fn on_broken<'a>(
&self,
_block: &Block,
player: &Player,
location: BlockPos,
_server: &Server,
) {
async fn broken(&self, _block: &Block, player: &Player, location: BlockPos, _server: &Server) {
// For now just stop the music at this position
let world = &player.living_entity.entity.world;

View File

@ -17,7 +17,7 @@ pub trait BlockMetadata {
#[async_trait]
pub trait PumpkinBlock: Send + Sync {
async fn on_use<'a>(
async fn normal_use(
&self,
_block: &Block,
_player: &Player,
@ -25,7 +25,7 @@ pub trait PumpkinBlock: Send + Sync {
_server: &Server,
) {
}
async fn on_use_with_item<'a>(
async fn use_with_item(
&self,
_block: &Block,
_player: &Player,
@ -36,7 +36,7 @@ pub trait PumpkinBlock: Send + Sync {
BlockActionResult::Continue
}
async fn on_placed<'a>(
async fn placed(
&self,
_block: &Block,
_player: &Player,
@ -45,7 +45,7 @@ pub trait PumpkinBlock: Send + Sync {
) {
}
async fn on_broken<'a>(
async fn broken(
&self,
_block: &Block,
_player: &Player,
@ -54,7 +54,7 @@ pub trait PumpkinBlock: Send + Sync {
) {
}
async fn on_close<'a>(
async fn close(
&self,
_block: &Block,
_player: &Player,

View File

@ -34,11 +34,13 @@ impl BlockRegistry {
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.on_use(block, player, location, server).await;
pumpkin_block
.normal_use(block, player, location, server)
.await;
}
}
pub async fn on_use_with_item(
pub async fn use_with_item(
&self,
block: &Block,
player: &Player,
@ -49,7 +51,7 @@ impl BlockRegistry {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block
.on_use_with_item(block, player, location, item, server)
.use_with_item(block, player, location, item, server)
.await;
}
BlockActionResult::Continue
@ -64,13 +66,11 @@ impl BlockRegistry {
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block
.on_placed(block, player, location, server)
.await;
pumpkin_block.placed(block, player, location, server).await;
}
}
pub async fn on_broken(
pub async fn broken(
&self,
block: &Block,
player: &Player,
@ -79,13 +79,11 @@ impl BlockRegistry {
) {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block
.on_broken(block, player, location, server)
.await;
pumpkin_block.broken(block, player, location, server).await;
}
}
pub async fn on_close(
pub async fn close(
&self,
block: &Block,
player: &Player,
@ -96,7 +94,7 @@ impl BlockRegistry {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block
.on_close(block, player, location, server, container)
.close(block, player, location, server, container)
.await;
}
}

View File

@ -235,7 +235,7 @@ impl Entity {
pub fn set_rotation(&self, yaw: f32, pitch: f32) {
// TODO
self.yaw.store(yaw);
self.pitch.store(pitch);
self.pitch.store(pitch.clamp(-90.0, 90.0) % 360.0);
}
/// Removes the Entity from their current World

View File

@ -484,6 +484,10 @@ impl Player {
&self.living_entity.entity.world
}
pub fn position(&self) -> Vector3<f64> {
self.living_entity.entity.pos.load()
}
/// Updates the current abilities the Player has
pub async fn send_abilities_update(&self) {
let mut b = 0i8;
@ -984,7 +988,10 @@ impl Player {
self.handle_use_item_on(SUseItemOn::read(bytebuf)?, server)
.await?;
}
SUseItem::PACKET_ID => self.handle_use_item(&SUseItem::read(bytebuf)?),
SUseItem::PACKET_ID => {
self.handle_use_item(&SUseItem::read(bytebuf)?, server)
.await;
}
SCommandSuggestion::PACKET_ID => {
self.handle_command_suggestion(SCommandSuggestion::read(bytebuf)?, server)
.await;

View File

@ -2,15 +2,21 @@ use std::f32::{self, consts::PI};
use pumpkin_util::math::vector3::Vector3;
use super::Entity;
use super::{living::LivingEntity, Entity, EntityBase};
pub struct ProjectileEntity {
pub struct ThrownItem {
entity: Entity,
}
const DEG_PER_RAD_F32: f32 = 180.0 / PI;
impl ProjectileEntity {
impl ThrownItem {
pub fn new(entity: Entity, owner: &Entity) -> Self {
let mut owner_pos = owner.pos.load();
owner_pos.y = (owner_pos.y + f64::from(owner.standing_eye_height)) - 0.1;
entity.pos.store(owner_pos);
Self { entity }
}
pub fn set_velocity_from(
&self,
shooter: &Entity,
@ -20,9 +26,15 @@ impl ProjectileEntity {
speed: f32,
divergence: f32,
) {
let x = -(yaw * PI / 180.0).sin() * (pitch * PI / 180.0).cos();
let y = -((pitch + roll) * PI / 180.0).sin();
let z = -(yaw * PI / 180.0).cos() * (pitch * PI / 180.0).cos();
let to_radians = |degrees: f32| degrees * PI / 180.0;
let yaw_rad = to_radians(yaw);
let pitch_rad = to_radians(pitch);
let roll_rad = to_radians(pitch + roll);
let x = -yaw_rad.sin() * pitch_rad.cos();
let y = -roll_rad.sin();
let z = yaw_rad.cos() * pitch_rad.cos();
self.set_velocity(
f64::from(x),
f64::from(y),
@ -64,3 +76,13 @@ impl ProjectileEntity {
);
}
}
impl EntityBase for ThrownItem {
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}
}

View File

@ -0,0 +1,37 @@
use std::sync::Arc;
use crate::entity::player::Player;
use crate::entity::projectile::ThrownItem;
use crate::item::pumpkin_item::PumpkinItem;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item;
use pumpkin_world::item::registry::Item;
#[pumpkin_item("minecraft:egg")]
pub struct EggItem;
const POWER: f32 = 1.5;
#[async_trait]
impl PumpkinItem for EggItem {
async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) {
let position = player.position();
let world = player.world();
world
.play_sound(
Sound::EntityEggThrow,
pumpkin_data::sound::SoundCategory::Players,
&position,
)
.await;
// TODO: Implement eggs the right way, so there is a chance of spawning chickens
let entity = server.add_entity(position, EntityType::Egg, world);
let snowball = ThrownItem::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();
snowball.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(snowball)).await;
}
}

View File

@ -0,0 +1,2 @@
pub mod egg;
pub mod snowball;

View File

@ -0,0 +1,36 @@
use std::sync::Arc;
use crate::entity::player::Player;
use crate::entity::projectile::ThrownItem;
use crate::item::pumpkin_item::PumpkinItem;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item;
use pumpkin_world::item::registry::Item;
#[pumpkin_item("minecraft:snowball")]
pub struct SnowBallItem;
const POWER: f32 = 1.5;
#[async_trait]
impl PumpkinItem for SnowBallItem {
async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) {
let position = player.position();
let world = player.world();
world
.play_sound(
Sound::EntitySnowballThrow,
pumpkin_data::sound::SoundCategory::Neutral,
&position,
)
.await;
let entity = server.add_entity(position, EntityType::Snowball, world);
let snowball = ThrownItem::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();
snowball.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(snowball)).await;
}
}

18
pumpkin/src/item/mod.rs Normal file
View File

@ -0,0 +1,18 @@
use items::{egg::EggItem, snowball::SnowBallItem};
use registry::ItemRegistry;
use std::sync::Arc;
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

@ -0,0 +1,28 @@
use crate::entity::player::Player;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
pub trait ItemMetadata {
const NAMESPACE: &'static str;
const ID: &'static str;
fn name(&self) -> String {
format!("{}:{}", Self::NAMESPACE, Self::ID)
}
}
#[async_trait]
pub trait PumpkinItem: Send + Sync {
async fn normal_use(&self, _block: &Item, _player: &Player, _server: &Server) {}
async fn use_on_block(
&self,
_item: &Item,
_player: &Player,
_location: BlockPos,
_block: &Block,
_server: &Server,
) {
}
}

View File

@ -0,0 +1,49 @@
use crate::entity::player::Player;
use crate::server::Server;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
use std::collections::HashMap;
use std::sync::Arc;
use super::pumpkin_item::{ItemMetadata, PumpkinItem};
#[derive(Default)]
pub struct ItemRegistry {
blocks: HashMap<String, Arc<dyn PumpkinItem>>,
}
impl ItemRegistry {
pub fn register<T: PumpkinItem + ItemMetadata + 'static>(&mut self, block: T) {
self.blocks.insert(block.name(), Arc::new(block));
}
pub async fn on_use(&self, item_name: &str, item: &Item, player: &Player, server: &Server) {
let pumpkin_block = self.get_pumpkin_item(item_name);
if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.normal_use(item, player, server).await;
}
}
pub async fn use_on_block(
&self,
item_name: &str,
item: &Item,
player: &Player,
location: BlockPos,
block: &Block,
server: &Server,
) {
let pumpkin_block = self.get_pumpkin_item(item_name);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block
.use_on_block(item, player, location, block, server)
.await;
}
}
#[must_use]
pub fn get_pumpkin_item(&self, item_name: &str) -> Option<&Arc<dyn PumpkinItem>> {
self.blocks.get(format!("minecraft:{item_name}").as_str())
}
}

View File

@ -24,6 +24,7 @@ pub mod command;
pub mod data;
pub mod entity;
pub mod error;
pub mod item;
pub mod net;
pub mod plugin;
pub mod server;

View File

@ -58,6 +58,7 @@ pub mod command;
pub mod data;
pub mod entity;
pub mod error;
pub mod item;
pub mod net;
pub mod plugin;
pub mod server;

View File

@ -45,7 +45,7 @@ use pumpkin_util::{
};
use pumpkin_world::block::registry::get_block_collision_shapes;
use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::get_item_by_id;
use pumpkin_world::item::registry::{get_item_by_id, get_name_by_id};
use pumpkin_world::item::ItemStack;
use pumpkin_world::{
block::{registry::get_block_by_item, BlockDirection},
@ -236,10 +236,7 @@ impl Player {
.on_ground
.store(packet.ground, std::sync::atomic::Ordering::Relaxed);
entity.set_rotation(
wrap_degrees(packet.yaw) % 360.0,
wrap_degrees(packet.pitch).clamp(-90.0, 90.0) % 360.0,
);
entity.set_rotation(wrap_degrees(packet.yaw) % 360.0, wrap_degrees(packet.pitch));
let entity_id = entity.entity_id;
let Vector3 { x, y, z } = position;
@ -316,7 +313,7 @@ impl Player {
.store(rotation.ground, std::sync::atomic::Ordering::Relaxed);
entity.set_rotation(
wrap_degrees(rotation.yaw) % 360.0,
wrap_degrees(rotation.pitch).clamp(-90.0, 90.0) % 360.0,
wrap_degrees(rotation.pitch),
);
// send new position to all other players
let entity_id = entity.entity_id;
@ -799,7 +796,7 @@ impl Player {
if let Ok(block) = block {
server
.block_registry
.on_broken(block, &self, location, server)
.broken(block, &self, location, server)
.await;
}
}
@ -837,7 +834,7 @@ impl Player {
if let Ok(block) = block {
server
.block_registry
.on_broken(block, &self, location, server)
.broken(block, &self, location, server)
.await;
}
}
@ -950,7 +947,7 @@ impl Player {
{
let action_result = server
.block_registry
.on_use_with_item(block, self, location, item, server)
.use_with_item(block, self, location, item, server)
.await;
match action_result {
BlockActionResult::Continue => {}
@ -995,12 +992,17 @@ impl Player {
Ok(())
}
pub fn handle_use_item(&self, _use_item: &SUseItem) {
pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Server) {
if !self.has_client_loaded() {
return;
}
// TODO: handle packet correctly
log::error!("An item was used(SUseItem), but the packet is not implemented yet");
if let Some(held) = self.inventory().lock().await.held_item() {
// this is so ugly and slow brooo...
if let Some(item) = get_item_by_id(held.item_id) {
let name = get_name_by_id(item.id).unwrap();
server.item_registry.on_use(name, item, self, server).await;
}
}
}
pub async fn handle_set_held_item(&self, held: SSetHeldItem) {
@ -1054,7 +1056,7 @@ impl Player {
if let Some(block) = container.get_block() {
server
.block_registry
.on_close(&block, self, pos, server, container) //block, self, location, server)
.close(&block, self, pos, server, container) //block, self, location, server)
.await;
}
}

View File

@ -30,10 +30,11 @@ use std::{
};
use tokio::sync::{Mutex, RwLock};
use crate::block::default_block_properties_manager;
use crate::block::properties::BlockPropertiesManager;
use crate::block::registry::BlockRegistry;
use crate::block::{self, default_block_properties_manager};
use crate::entity::{Entity, EntityId};
use crate::item::registry::ItemRegistry;
use crate::net::EncryptionError;
use crate::world::custom_bossbar::CustomBossbars;
use crate::{
@ -61,6 +62,8 @@ pub struct Server {
pub command_dispatcher: RwLock<CommandDispatcher>,
/// Block Behaviour
pub block_registry: Arc<BlockRegistry>,
/// Item Behaviour
pub item_registry: Arc<ItemRegistry>,
/// Creates and stores block property registry and managed behaviours.
pub block_properties_manager: Arc<BlockPropertiesManager>,
/// Manages multiple worlds within the server.
@ -132,7 +135,8 @@ impl Server {
DimensionType::TheEnd,
],
command_dispatcher,
block_registry: block::default_registry(),
block_registry: super::block::default_registry(),
item_registry: super::item::default_registry(),
block_properties_manager: default_block_properties_manager(),
auth_client,
key_store: KeyStore::new(),