Drop item when destroying blocks

Also make EntityType consts
This commit is contained in:
Alexander Medvedev 2025-02-07 21:20:03 +01:00
parent 2e2fe83720
commit 8a5a0cdee8
32 changed files with 236 additions and 254 deletions

View File

@ -1,9 +1,8 @@
use quote::quote; use quote::{format_ident, quote};
use std::{env, fs, path::Path, process::Command}; use std::{env, fs, path::Path, process::Command};
use heck::ToPascalCase; use heck::ToPascalCase;
use proc_macro2::{Span, TokenStream}; use proc_macro2::TokenStream;
use syn::Ident;
mod biome; mod biome;
mod chunk_status; mod chunk_status;
@ -43,7 +42,7 @@ pub fn array_to_tokenstream(array: Vec<String>) -> TokenStream {
let mut variants = TokenStream::new(); let mut variants = TokenStream::new();
for item in array.iter() { for item in array.iter() {
let name = ident(item.to_pascal_case()); let name = format_ident!("{}", item.to_pascal_case());
variants.extend([quote! { variants.extend([quote! {
#name, #name,
}]); }]);
@ -62,12 +61,3 @@ pub fn write_generated_file(content: TokenStream, out_file: &str) {
// Doesn't matter if rustfmt is unavailable. // Doesn't matter if rustfmt is unavailable.
let _ = Command::new("rustfmt").arg(path).output(); let _ = Command::new("rustfmt").arg(path).output();
} }
pub fn ident<I: AsRef<str>>(s: I) -> Ident {
let s = s.as_ref().trim();
// Parse the ident from a str. If the string is a Rust keyword, stick an
// underscore in front.
syn::parse_str::<Ident>(s)
.unwrap_or_else(|_| Ident::new(format!("_{s}").as_str(), Span::call_site()))
}

View File

@ -1,8 +1,6 @@
use heck::ToPascalCase; use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use crate::ident;
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/chunk_status.json"); println!("cargo:rerun-if-changed=../assets/chunk_status.json");
@ -14,7 +12,7 @@ pub(crate) fn build() -> TokenStream {
for status in chunk_status.iter() { for status in chunk_status.iter() {
let full_name = format!("minecraft:{status}"); let full_name = format!("minecraft:{status}");
let name = ident(status.to_pascal_case()); let name = format_ident!("{}", status.to_pascal_case());
variants.extend([quote! { variants.extend([quote! {
#[serde(rename = #full_name)] #[serde(rename = #full_name)]
#name, #name,

View File

@ -1,6 +1,6 @@
use heck::{ToPascalCase, ToShoutySnakeCase}; use heck::{ToPascalCase, ToShoutySnakeCase};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
@ -29,8 +29,8 @@ pub(crate) fn build() -> TokenStream {
let mut enum_variants = Vec::new(); let mut enum_variants = Vec::new();
for (name, entry) in damage_types { for (name, entry) in damage_types {
let const_ident = crate::ident(name.to_shouty_snake_case()); let const_ident = format_ident!("{}", name.to_shouty_snake_case());
let enum_ident = crate::ident(name.to_pascal_case()); let enum_ident = format_ident!("{}", name.to_pascal_case());
enum_variants.push(enum_ident.clone()); enum_variants.push(enum_ident.clone());
@ -58,7 +58,7 @@ pub(crate) fn build() -> TokenStream {
let enum_arms = enum_variants.iter().map(|variant| { let enum_arms = enum_variants.iter().map(|variant| {
let const_name = variant.to_string().to_shouty_snake_case(); let const_name = variant.to_string().to_shouty_snake_case();
let const_ident = crate::ident(&const_name); let const_ident = format_ident!("{}", &const_name);
quote! { quote! {
DamageType::#variant => &#const_ident, DamageType::#variant => &#const_ident,
} }

View File

@ -1,66 +1,101 @@
use std::collections::HashMap; use std::collections::HashMap;
use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote, ToTokens};
use serde::Deserialize; use serde::Deserialize;
use syn::LitInt; use syn::LitInt;
use crate::ident;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct JSONStruct { pub struct EntityType {
id: u16, pub id: u16,
pub max_health: Option<f32>,
pub attackable: Option<bool>,
pub summonable: bool,
pub fire_immune: bool,
pub dimension: [f32; 2],
pub eye_height: f32,
}
impl ToTokens for EntityType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let id = LitInt::new(&self.id.to_string(), proc_macro2::Span::call_site());
let max_health = match self.max_health {
Some(mh) => quote! { Some(#mh) },
None => quote! { None },
};
let attackable = match self.attackable {
Some(a) => quote! { Some(#a) },
None => quote! { None },
};
let summonable = self.summonable;
let fire_immune = self.fire_immune;
let eye_height = self.eye_height;
let dimension0 = self.dimension[0];
let dimension1 = self.dimension[1];
tokens.extend(quote! {
EntityType {
id: #id,
max_health: #max_health,
attackable: #attackable,
summonable: #summonable,
fire_immune: #fire_immune,
dimension: [#dimension0, #dimension1], // Correctly construct the array
eye_height: #eye_height,
}
});
}
} }
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/entities.json"); println!("cargo:rerun-if-changed=../assets/entities.json");
let json: HashMap<String, JSONStruct> = let json: HashMap<String, EntityType> =
serde_json::from_str(include_str!("../../assets/entities.json")) serde_json::from_str(include_str!("../../assets/entities.json"))
.expect("Failed to parse sound_category.json"); .expect("Failed to parse entities.json");
let mut variants = TokenStream::new();
for (item, id) in json.iter() { let mut consts = TokenStream::new();
let id = id.id as u8; let mut type_from_raw_id_arms = TokenStream::new();
let mut type_from_name = TokenStream::new();
for (name, entity) in json.iter() {
let id = entity.id as u8;
let id_lit = LitInt::new(&id.to_string(), proc_macro2::Span::call_site()); let id_lit = LitInt::new(&id.to_string(), proc_macro2::Span::call_site());
let name = ident(item.to_pascal_case()); let upper_name = format_ident!("{}", name.to_uppercase());
variants.extend([quote! {
#name = #id_lit, let entity_tokens = entity.to_token_stream();
}]);
consts.extend(quote! {
pub const #upper_name: EntityType = #entity_tokens;
});
type_from_raw_id_arms.extend(quote! {
#id_lit => Some(Self::#upper_name),
});
type_from_name.extend(quote! {
#name => Some(Self::#upper_name),
});
} }
let type_from_raw_id_arms = json
.iter()
.map(|sound| {
let id = &sound.1.id;
let name = ident(sound.0.to_pascal_case());
quote! {
#id => Some(Self::#name),
}
})
.collect::<TokenStream>();
let type_from_name = json
.iter()
.map(|sound| {
let id = &sound.0;
let name = ident(sound.0.to_pascal_case());
quote! {
#id => Some(Self::#name),
}
})
.collect::<TokenStream>();
quote! { quote! {
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum EntityType { pub struct EntityType {
#variants pub id: u16,
pub max_health: Option<f32>,
pub attackable: Option<bool>,
pub summonable: bool,
pub fire_immune: bool,
pub dimension: [f32; 2],
pub eye_height: f32,
} }
impl EntityType { impl EntityType {
#consts
pub const fn from_raw(id: u16) -> Option<Self> { pub const fn from_raw(id: u16) -> Option<Self> {
match id { match id {
#type_from_raw_id_arms #type_from_raw_id_arms

View File

@ -2,11 +2,9 @@ use std::collections::HashMap;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use pumpkin_util::text::style::Style; use pumpkin_util::text::style::Style;
use quote::quote; use quote::{format_ident, quote};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::ident;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RawChatType { pub struct RawChatType {
id: u32, id: u32,
@ -37,7 +35,7 @@ pub(crate) fn build() -> TokenStream {
for (name, typee) in json.iter() { for (name, typee) in json.iter() {
let i = typee.id; let i = typee.id;
let name = ident(name.to_uppercase()); let name = format_ident!("{}", name.to_uppercase());
variants.extend([quote! { variants.extend([quote! {
pub const #name: u32 = #i; pub const #name: u32 = #i;
}]); }]);

View File

@ -1,11 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use crate::ident;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DoublePerlinNoiseParameters { pub struct DoublePerlinNoiseParameters {
#[serde(rename = "firstOctave")] #[serde(rename = "firstOctave")]
@ -23,7 +21,7 @@ pub(crate) fn build() -> TokenStream {
for (name, parameter) in json.iter() { for (name, parameter) in json.iter() {
let raw_name = format!("minecraft:{name}"); let raw_name = format!("minecraft:{name}");
let name = ident(name.to_uppercase()); let name = format_ident!("{}", name.to_uppercase());
let first_octave = parameter.first_octave; let first_octave = parameter.first_octave;
let amplitudes = &parameter.amplitudes; let amplitudes = &parameter.amplitudes;
variants.extend([quote! { variants.extend([quote! {

View File

@ -1,11 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use crate::ident;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Packets { pub struct Packets {
serverbound: HashMap<String, Vec<String>>, serverbound: HashMap<String, Vec<String>>,
@ -40,7 +38,7 @@ pub(crate) fn parse_packets(packets: HashMap<String, Vec<String>>) -> proc_macro
for (id, packet_name) in packet.1.iter().enumerate() { for (id, packet_name) in packet.1.iter().enumerate() {
let packet_id = id as i32; let packet_id = id as i32;
let name = format!("{phase}_{packet_name}").to_uppercase(); let name = format!("{phase}_{packet_name}").to_uppercase();
let name = ident(name); let name = format_ident!("{}", name);
consts.extend([quote! { consts.extend([quote! {
pub const #name: i32 = #packet_id; pub const #name: i32 = #packet_id;
}]); }]);

View File

@ -1,8 +1,8 @@
use heck::ToPascalCase; use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use crate::{array_to_tokenstream, ident}; use crate::array_to_tokenstream;
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/sounds.json"); println!("cargo:rerun-if-changed=../assets/sounds.json");
@ -15,7 +15,7 @@ pub(crate) fn build() -> TokenStream {
.iter() .iter()
.map(|sound| { .map(|sound| {
let id = &sound; let id = &sound;
let name = ident(sound.to_pascal_case()); let name = format_ident!("{}", sound.to_pascal_case());
quote! { quote! {
#id => Some(Self::#name), #id => Some(Self::#name),
@ -27,7 +27,7 @@ pub(crate) fn build() -> TokenStream {
.iter() .iter()
.map(|sound| { .map(|sound| {
let id = &sound; let id = &sound;
let name = ident(sound.to_pascal_case()); let name = format_ident!("{}", sound.to_pascal_case());
quote! { quote! {
Self::#name => #id, Self::#name => #id,

View File

@ -2,9 +2,7 @@ use std::collections::HashMap;
use heck::ToPascalCase; use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{format_ident, quote};
use crate::ident;
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/world_event.json"); println!("cargo:rerun-if-changed=../assets/world_event.json");
@ -15,7 +13,7 @@ pub(crate) fn build() -> TokenStream {
let mut variants = TokenStream::new(); let mut variants = TokenStream::new();
for (event, id) in events.iter() { for (event, id) in events.iter() {
let name = ident(event.to_pascal_case()); let name = format_ident!("{}", event.to_pascal_case());
variants.extend([quote! { variants.extend([quote! {
#name = #id, #name = #id,
}]); }]);

View File

@ -1,5 +1,6 @@
use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION; use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION;
use pumpkin_macros::client_packet; use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use serde::Serialize; use serde::Serialize;
use crate::VarInt; use crate::VarInt;
@ -8,18 +9,18 @@ use crate::VarInt;
#[client_packet(PLAY_SET_ENTITY_MOTION)] #[client_packet(PLAY_SET_ENTITY_MOTION)]
pub struct CEntityVelocity<'a> { pub struct CEntityVelocity<'a> {
entity_id: &'a VarInt, entity_id: &'a VarInt,
velocity_x: i16, velocity: Vector3<i16>,
velocity_y: i16,
velocity_z: i16,
} }
impl<'a> CEntityVelocity<'a> { impl<'a> CEntityVelocity<'a> {
pub fn new(entity_id: &'a VarInt, velocity_x: f64, velocity_y: f64, velocity_z: f64) -> Self { pub fn new(entity_id: &'a VarInt, velocity_x: f64, velocity_y: f64, velocity_z: f64) -> Self {
Self { Self {
entity_id, entity_id,
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16, velocity: Vector3::new(
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16, (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16, (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16,
(velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16,
),
} }
} }
} }

View File

@ -1,6 +1,7 @@
use bytes::BufMut; use bytes::BufMut;
use pumpkin_data::{packet::clientbound::PLAY_SOUND, sound::SoundCategory}; use pumpkin_data::{packet::clientbound::PLAY_SOUND, sound::SoundCategory};
use pumpkin_macros::client_packet; use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundEvent, VarInt}; use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundEvent, VarInt};
@ -8,23 +9,18 @@ use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundEvent, VarIn
pub struct CSoundEffect { pub struct CSoundEffect {
sound_event: IDOrSoundEvent, sound_event: IDOrSoundEvent,
sound_category: VarInt, sound_category: VarInt,
effect_position_x: i32, position: Vector3<i32>,
effect_position_y: i32,
effect_position_z: i32,
volume: f32, volume: f32,
pitch: f32, pitch: f32,
seed: f64, seed: f64,
} }
impl CSoundEffect { impl CSoundEffect {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
sound_id: VarInt, sound_id: VarInt,
sound_event: Option<SoundEvent>, sound_event: Option<SoundEvent>,
sound_category: SoundCategory, sound_category: SoundCategory,
effect_position_x: f64, position: &Vector3<f64>,
effect_position_y: f64,
effect_position_z: f64,
volume: f32, volume: f32,
pitch: f32, pitch: f32,
seed: f64, seed: f64,
@ -35,9 +31,11 @@ impl CSoundEffect {
sound_event, sound_event,
}, },
sound_category: VarInt(sound_category as i32), sound_category: VarInt(sound_category as i32),
effect_position_x: (effect_position_x * 8.0) as i32, position: Vector3::new(
effect_position_y: (effect_position_y * 8.0) as i32, (position.x * 8.0) as i32,
effect_position_z: (effect_position_z * 8.0) as i32, (position.y * 8.0) as i32,
(position.z * 8.0) as i32,
),
volume, volume,
pitch, pitch,
seed, seed,
@ -58,9 +56,9 @@ impl ClientPacket for CSoundEffect {
} }
} }
bytebuf.put_var_int(&self.sound_category); bytebuf.put_var_int(&self.sound_category);
bytebuf.put_i32(self.effect_position_x); bytebuf.put_i32(self.position.x);
bytebuf.put_i32(self.effect_position_y); bytebuf.put_i32(self.position.y);
bytebuf.put_i32(self.effect_position_z); bytebuf.put_i32(self.position.z);
bytebuf.put_f32(self.volume); bytebuf.put_f32(self.volume);
bytebuf.put_f32(self.pitch); bytebuf.put_f32(self.pitch);
bytebuf.put_f64(self.seed); bytebuf.put_f64(self.seed);

View File

@ -1,5 +1,6 @@
use pumpkin_data::packet::clientbound::PLAY_ADD_ENTITY; use pumpkin_data::packet::clientbound::PLAY_ADD_ENTITY;
use pumpkin_macros::client_packet; use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use serde::Serialize; use serde::Serialize;
use crate::VarInt; use crate::VarInt;
@ -11,16 +12,12 @@ pub struct CSpawnEntity {
#[serde(with = "uuid::serde::compact")] #[serde(with = "uuid::serde::compact")]
entity_uuid: uuid::Uuid, entity_uuid: uuid::Uuid,
typ: VarInt, typ: VarInt,
x: f64, position: Vector3<f64>,
y: f64,
z: f64,
pitch: u8, // angle pitch: u8, // angle
yaw: u8, // angle yaw: u8, // angle
head_yaw: u8, // angle head_yaw: u8, // angle
data: VarInt, data: VarInt,
velocity_x: i16, velocity: Vector3<i16>,
velocity_y: i16,
velocity_z: i16,
} }
impl CSpawnEntity { impl CSpawnEntity {
@ -29,31 +26,27 @@ impl CSpawnEntity {
entity_id: VarInt, entity_id: VarInt,
entity_uuid: uuid::Uuid, entity_uuid: uuid::Uuid,
typ: VarInt, typ: VarInt,
x: f64, position: Vector3<f64>,
y: f64,
z: f64,
pitch: f32, // angle pitch: f32, // angle
yaw: f32, // angle yaw: f32, // angle
head_yaw: f32, // angle head_yaw: f32, // angle
data: VarInt, data: VarInt,
velocity_x: f32, velocity: Vector3<f64>,
velocity_y: f32,
velocity_z: f32,
) -> Self { ) -> Self {
Self { Self {
entity_id, entity_id,
entity_uuid, entity_uuid,
typ, typ,
x, position,
y,
z,
pitch: (pitch * 256.0 / 360.0).floor() as u8, pitch: (pitch * 256.0 / 360.0).floor() as u8,
yaw: (yaw * 256.0 / 360.0).floor() as u8, yaw: (yaw * 256.0 / 360.0).floor() as u8,
head_yaw: (head_yaw * 256.0 / 360.0).floor() as u8, head_yaw: (head_yaw * 256.0 / 360.0).floor() as u8,
data, data,
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16, velocity: Vector3::new(
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16, (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16, (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16,
(velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16,
),
} }
} }
} }

View File

@ -7,15 +7,15 @@ pub struct BoundingBox {
} }
impl BoundingBox { impl BoundingBox {
pub fn new_default(size: &BoundingBoxSize) -> Self { pub fn new_default(size: &EntityDimensions) -> Self {
Self::new_from_pos(0., 0., 0., size) Self::new_from_pos(0., 0., 0., size)
} }
pub fn new_from_pos(x: f64, y: f64, z: f64, size: &BoundingBoxSize) -> Self { pub fn new_from_pos(x: f64, y: f64, z: f64, size: &EntityDimensions) -> Self {
let f = size.width / 2.; let f = size.width as f64 / 2.;
Self { Self {
min: Vector3::new(x - f, y, z - f), min: Vector3::new(x - f, y, z - f),
max: Vector3::new(x + f, y + size.height, z + f), max: Vector3::new(x + f, y + size.height as f64, z + f),
} }
} }
@ -75,7 +75,7 @@ impl BoundingBox {
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct BoundingBoxSize { pub struct EntityDimensions {
pub width: f64, pub width: f32,
pub height: f64, pub height: f32,
} }

View File

@ -1 +0,0 @@
pub mod registry;

View File

@ -1,35 +0,0 @@
use std::{collections::HashMap, sync::LazyLock};
use serde::Deserialize;
const ENTITIES_JSON: &str = include_str!("../../../assets/entities.json");
pub static ENTITIES: LazyLock<HashMap<String, Entity>> = LazyLock::new(|| {
serde_json::from_str(ENTITIES_JSON).expect("Could not parse entity.json registry.")
});
pub static ENTITIES_BY_ID: LazyLock<HashMap<String, u16>> = LazyLock::new(|| {
let mut map = HashMap::new();
for (entity_name, entity) in ENTITIES.iter() {
map.insert(entity_name.clone(), entity.id);
}
map
});
pub fn get_entity_id(name: &str) -> Option<&u16> {
ENTITIES_BY_ID.get(&name.replace("minecraft:", ""))
}
pub fn get_entity_by_id<'a>(entity_id: u16) -> Option<&'a Entity> {
ENTITIES.values().find(|&entity| entity.id == entity_id)
}
#[derive(Deserialize, Clone, Debug)]
pub struct Entity {
pub id: u16,
pub max_health: Option<f32>,
pub attackable: Option<bool>,
pub summonable: bool,
pub fire_immune: bool,
pub dimension: [f32; 2],
}

View File

@ -13,7 +13,6 @@ pub mod chunk;
pub mod coordinates; pub mod coordinates;
pub mod cylindrical_chunk_iterator; pub mod cylindrical_chunk_iterator;
pub mod dimension; pub mod dimension;
pub mod entity;
mod generation; mod generation;
pub mod item; pub mod item;
pub mod level; pub mod level;

View File

@ -1,10 +1,19 @@
use blocks::chest::ChestBlock; use blocks::chest::ChestBlock;
use blocks::furnace::FurnaceBlock; use blocks::furnace::FurnaceBlock;
use properties::BlockPropertiesManager; use properties::BlockPropertiesManager;
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::block::registry::Block;
use pumpkin_world::item::ItemStack;
use rand::Rng;
use crate::block::blocks::crafting_table::CraftingTableBlock; use crate::block::blocks::crafting_table::CraftingTableBlock;
use crate::block::blocks::jukebox::JukeboxBlock; use crate::block::blocks::jukebox::JukeboxBlock;
use crate::block::registry::BlockRegistry; use crate::block::registry::BlockRegistry;
use crate::entity::item::ItemEntity;
use crate::server::Server;
use crate::world::World;
use std::sync::Arc; use std::sync::Arc;
mod blocks; mod blocks;
@ -24,6 +33,21 @@ pub fn default_registry() -> Arc<BlockRegistry> {
Arc::new(manager) Arc::new(manager)
} }
pub async fn drop_loot(server: &Server, world: &Arc<World>, block: &Block, pos: &BlockPos) {
// TODO: Currently only the item block is droped, We should drop the loop table
let height = EntityType::ITEM.dimension[1] / 2.0;
let pos = Vector3::new(
f64::from(pos.0.x) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25),
f64::from(pos.0.y) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25) - f64::from(height),
f64::from(pos.0.z) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25),
);
let entity = server.add_entity(pos, EntityType::ITEM, world);
let item_entity = Arc::new(ItemEntity::new(entity, &ItemStack::new(1, block.item_id)));
world.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await;
}
#[must_use] #[must_use]
pub fn default_block_properties_manager() -> Arc<BlockPropertiesManager> { pub fn default_block_properties_manager() -> Arc<BlockPropertiesManager> {
let mut manager = BlockPropertiesManager::default(); let mut manager = BlockPropertiesManager::default();

View File

@ -41,7 +41,7 @@ impl CommandExecutor for SetblockExecutor {
async fn execute<'a>( async fn execute<'a>(
&self, &self,
sender: &mut CommandSender<'a>, sender: &mut CommandSender<'a>,
_server: &crate::server::Server, server: &crate::server::Server,
args: &ConsumedArgs<'a>, args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
@ -67,7 +67,9 @@ impl CommandExecutor for SetblockExecutor {
for y in start_y..=end_y { for y in start_y..=end_y {
for z in start_z..=end_z { for z in start_z..=end_z {
let block_position = BlockPos(Vector3 { x, y, z }); let block_position = BlockPos(Vector3 { x, y, z });
world.break_block(&block_position, None).await; world
.break_block(server, &block_position, None, false)
.await;
world.set_block_state(&block_position, block_state_id).await; world.set_block_state(&block_position, block_state_id).await;
placed_blocks += 1; placed_blocks += 1;
} }

View File

@ -46,7 +46,7 @@ impl CommandExecutor for KillExecutor {
Some(TextComponent::text(name.clone())), Some(TextComponent::text(name.clone())),
)); ));
if entity.entity_type == entity::EntityType::Player { if entity.entity_type == entity::EntityType::PLAYER {
entity_display = entity_display.click_event(ClickEvent::SuggestCommand( entity_display = entity_display.click_event(ClickEvent::SuggestCommand(
format!("/tell {} ", name.clone()).into(), format!("/tell {} ", name.clone()).into(),
)); ));

View File

@ -34,7 +34,7 @@ impl CommandExecutor for SetblockExecutor {
async fn execute<'a>( async fn execute<'a>(
&self, &self,
sender: &mut CommandSender<'a>, sender: &mut CommandSender<'a>,
_server: &crate::server::Server, server: &crate::server::Server,
args: &ConsumedArgs<'a>, args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
@ -46,7 +46,7 @@ impl CommandExecutor for SetblockExecutor {
let success = match mode { let success = match mode {
Mode::Destroy => { Mode::Destroy => {
world.break_block(&pos, None).await; world.clone().break_block(server, &pos, None, false).await;
world.set_block_state(&pos, block_state_id).await; world.set_block_state(&pos, block_state_id).await;
true true
} }

View File

@ -43,8 +43,8 @@ fn yaw_pitch_facing_position(
let yaw_radians = -direction_vector.x.atan2(direction_vector.z); let yaw_radians = -direction_vector.x.atan2(direction_vector.z);
let pitch_radians = (-direction_vector.y).asin(); let pitch_radians = (-direction_vector.y).asin();
let yaw_degrees = yaw_radians * 180.0 / std::f64::consts::PI; let yaw_degrees = yaw_radians.to_degrees();
let pitch_degrees = pitch_radians * 180.0 / std::f64::consts::PI; let pitch_degrees = pitch_radians.to_degrees();
(yaw_degrees as f32, pitch_degrees as f32) (yaw_degrees as f32, pitch_degrees as f32)
} }

View File

@ -97,7 +97,7 @@ impl CommandSender<'_> {
} }
#[must_use] #[must_use]
pub fn world(&self) -> Option<&World> { pub fn world(&self) -> Option<&Arc<World>> {
match self { match self {
// TODO: maybe return first world when console // TODO: maybe return first world when console
CommandSender::Console | CommandSender::Rcon(..) => None, CommandSender::Console | CommandSender::Rcon(..) => None,

View File

@ -1,5 +1,3 @@
use std::f32::consts::PI;
use pumpkin_data::{ use pumpkin_data::{
particle::Particle, particle::Particle,
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
@ -74,8 +72,8 @@ pub async fn handle_knockback(
let saved_velo = victim_entity.velocity.load(); let saved_velo = victim_entity.velocity.load();
victim_entity.knockback( victim_entity.knockback(
strength * 0.5, strength * 0.5,
f64::from((yaw * (PI / 180.0)).sin()), f64::from((yaw.to_radians()).sin()),
f64::from(-(yaw * (PI / 180.0)).cos()), f64::from(-(yaw.to_radians()).cos()),
); );
let entity_id = VarInt(victim_entity.entity_id); let entity_id = VarInt(victim_entity.entity_id);
@ -98,8 +96,8 @@ pub async fn handle_knockback(
pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) { pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) {
let yaw = attacker_entity.yaw.load(); let yaw = attacker_entity.yaw.load();
let d = -f64::from((yaw * (PI / 180.0)).sin()); let d = -f64::from((yaw.to_radians()).sin());
let e = f64::from((yaw * (PI / 180.0)).cos()); let e = f64::from((yaw.to_radians()).cos());
let scale = 0.5; let scale = 0.5;
// TODO: use entity height // TODO: use entity height

View File

@ -64,7 +64,7 @@ pub async fn from_type(
}; };
#[expect(clippy::single_match)] #[expect(clippy::single_match)]
match entity_type { match entity_type {
EntityType::Zombie => Zombie::make(&mob).await, EntityType::ZOMBIE => Zombie::make(&mob).await,
// TODO // TODO
_ => (), _ => (),
} }

View File

@ -1,8 +1,5 @@
use core::f32; use core::f32;
use std::{ use std::sync::{atomic::AtomicBool, Arc};
f32::consts::PI,
sync::{atomic::AtomicBool, Arc},
};
use async_trait::async_trait; use async_trait::async_trait;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
@ -22,7 +19,7 @@ use pumpkin_protocol::{
codec::var_int::VarInt, codec::var_int::VarInt,
}; };
use pumpkin_util::math::{ use pumpkin_util::math::{
boundingbox::{BoundingBox, BoundingBoxSize}, boundingbox::{BoundingBox, EntityDimensions},
get_section_cord, get_section_cord,
position::BlockPos, position::BlockPos,
vector2::Vector2, vector2::Vector2,
@ -94,7 +91,7 @@ pub struct Entity {
/// The bounding box of an entity (hitbox) /// The bounding box of an entity (hitbox)
pub bounding_box: AtomicCell<BoundingBox>, pub bounding_box: AtomicCell<BoundingBox>,
///The size (width and height) of the bounding box ///The size (width and height) of the bounding box
pub bounding_box_size: AtomicCell<BoundingBoxSize>, pub bounding_box_size: AtomicCell<EntityDimensions>,
/// Whether this entity is invulnerable to all damage /// Whether this entity is invulnerable to all damage
pub invulnerable: AtomicBool, pub invulnerable: AtomicBool,
/// List of damage types this entity is immune to /// List of damage types this entity is immune to
@ -111,7 +108,7 @@ impl Entity {
entity_type: EntityType, entity_type: EntityType,
standing_eye_height: f32, standing_eye_height: f32,
bounding_box: AtomicCell<BoundingBox>, bounding_box: AtomicCell<BoundingBox>,
bounding_box_size: AtomicCell<BoundingBoxSize>, bounding_box_size: AtomicCell<EntityDimensions>,
invulnerable: bool, invulnerable: bool,
) -> Self { ) -> Self {
let floor_x = position.x.floor() as i32; let floor_x = position.x.floor() as i32;
@ -187,8 +184,8 @@ impl Entity {
/// Returns entity rotation as vector /// Returns entity rotation as vector
pub fn rotation(&self) -> Vector3<f32> { pub fn rotation(&self) -> Vector3<f32> {
// Convert degrees to radians if necessary // Convert degrees to radians if necessary
let yaw_rad = self.yaw.load() * (PI / 180.0); let yaw_rad = self.yaw.load().to_radians();
let pitch_rad = self.pitch.load() * (PI / 180.0); let pitch_rad = self.pitch.load().to_radians();
Vector3::new( Vector3::new(
yaw_rad.cos() * pitch_rad.cos(), yaw_rad.cos() * pitch_rad.cos(),
@ -260,17 +257,13 @@ impl Entity {
CSpawnEntity::new( CSpawnEntity::new(
VarInt(self.entity_id), VarInt(self.entity_id),
self.entity_uuid, self.entity_uuid,
VarInt((self.entity_type) as i32), VarInt(i32::from(self.entity_type.id)),
entity_loc.x, entity_loc,
entity_loc.y,
entity_loc.z,
self.pitch.load(), self.pitch.load(),
self.yaw.load(), self.yaw.load(),
self.head_yaw.load(), // todo: head_yaw and yaw are swapped, find out why self.head_yaw.load(), // todo: head_yaw and yaw are swapped, find out why
0.into(), 0.into(),
entity_vel.x as f32, entity_vel,
entity_vel.y as f32,
entity_vel.z as f32,
) )
} }

View File

@ -47,7 +47,7 @@ use pumpkin_protocol::{
}; };
use pumpkin_util::{ use pumpkin_util::{
math::{ math::{
boundingbox::{BoundingBox, BoundingBoxSize}, boundingbox::{BoundingBox, EntityDimensions},
experience, experience,
position::BlockPos, position::BlockPos,
vector2::Vector2, vector2::Vector2,
@ -169,9 +169,9 @@ impl Player {
let gameprofile_clone = gameprofile.clone(); let gameprofile_clone = gameprofile.clone();
let config = client.config.lock().await.clone().unwrap_or_default(); let config = client.config.lock().await.clone().unwrap_or_default();
let bounding_box_size = BoundingBoxSize { let bounding_box_size = EntityDimensions {
width: 0.6, width: EntityType::PLAYER.dimension[0],
height: 1.8, height: EntityType::PLAYER.dimension[1],
}; };
Self { Self {
@ -180,8 +180,8 @@ impl Player {
player_uuid, player_uuid,
world, world,
Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0),
EntityType::Player, EntityType::PLAYER,
1.62, EntityType::PLAYER.eye_height,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)), AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size), AtomicCell::new(bounding_box_size),
matches!(gamemode, GameMode::Creative | GameMode::Spectator), matches!(gamemode, GameMode::Creative | GameMode::Spectator),
@ -404,9 +404,7 @@ impl Player {
VarInt(i32::from(sound_id)), VarInt(i32::from(sound_id)),
None, None,
category, category,
position.x, position,
position.y,
position.z,
volume, volume,
pitch, pitch,
seed, seed,
@ -791,7 +789,7 @@ impl Player {
if let Some(item) = inv.held_item_mut() { if let Some(item) = inv.held_item_mut() {
let entity = server.add_entity( let entity = server.add_entity(
self.living_entity.entity.pos.load(), self.living_entity.entity.pos.load(),
EntityType::Item, EntityType::ITEM,
self.world(), self.world(),
); );
let item_entity = Arc::new(ItemEntity::new(entity, &item.clone())); let item_entity = Arc::new(ItemEntity::new(entity, &item.clone()));

View File

@ -1,4 +1,4 @@
use std::f32::{self, consts::PI}; use std::f32::{self};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -8,8 +8,6 @@ pub struct ThrownItemEntity {
entity: Entity, entity: Entity,
} }
const DEG_PER_RAD_F32: f32 = 180.0 / PI;
impl ThrownItemEntity { impl ThrownItemEntity {
pub fn new(entity: Entity, owner: &Entity) -> Self { pub fn new(entity: Entity, owner: &Entity) -> Self {
let mut owner_pos = owner.pos.load(); let mut owner_pos = owner.pos.load();
@ -69,8 +67,8 @@ impl ThrownItemEntity {
self.entity.velocity.store(velocity); self.entity.velocity.store(velocity);
let len = velocity.horizontal_length(); let len = velocity.horizontal_length();
self.entity.set_rotation( self.entity.set_rotation(
velocity.x.atan2(velocity.z) as f32 * DEG_PER_RAD_F32, velocity.x.atan2(velocity.z).to_degrees() as f32,
velocity.y.atan2(len) as f32 * DEG_PER_RAD_F32, velocity.y.atan2(len).to_degrees() as f32,
); );
} }
} }

View File

@ -27,7 +27,7 @@ impl PumpkinItem for EggItem {
) )
.await; .await;
// TODO: Implement eggs the right way, so there is a chance of spawning chickens // TODO: Implement eggs the right way, so there is a chance of spawning chickens
let entity = server.add_entity(position, EntityType::Egg, world); let entity = server.add_entity(position, EntityType::EGG, world);
let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load(); let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load(); let pitch = player.living_entity.entity.pitch.load();

View File

@ -26,7 +26,7 @@ impl PumpkinItem for SnowBallItem {
&position, &position,
) )
.await; .await;
let entity = server.add_entity(position, EntityType::Snowball, world); let entity = server.add_entity(position, EntityType::SNOWBALL, world);
let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load(); let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load(); let pitch = player.living_entity.entity.pitch.load();

View File

@ -53,7 +53,6 @@ use pumpkin_world::item::registry::{get_item_by_id, get_name_by_id};
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use pumpkin_world::{ use pumpkin_world::{
block::{registry::get_block_by_item, BlockDirection}, block::{registry::get_block_by_item, BlockDirection},
entity::registry::get_entity_id,
item::registry::get_spawn_egg, item::registry::get_spawn_egg,
}; };
@ -820,8 +819,9 @@ impl Player {
let world = &entity.world; let world = &entity.world;
let block = world.get_block(&location).await; let block = world.get_block(&location).await;
world.break_block(&location, Some(self.clone())).await; world
.break_block(server, &location, Some(self.clone()), false)
.await;
if let Ok(block) = block { if let Ok(block) = block {
server server
.block_registry .block_registry
@ -864,7 +864,14 @@ impl Player {
let world = &entity.world; let world = &entity.world;
let block = world.get_block(&location).await; let block = world.get_block(&location).await;
world.break_block(&location, Some(self.clone())).await; world
.break_block(
server,
&location,
Some(self.clone()),
self.gamemode.load() != GameMode::Creative,
)
.await;
if let Ok(block) = block { if let Ok(block) = block {
server server
@ -1171,7 +1178,7 @@ impl Player {
face: &BlockDirection, face: &BlockDirection,
) -> Result<bool, Box<dyn PumpkinError>> { ) -> Result<bool, Box<dyn PumpkinError>> {
// checks if spawn egg has a corresponding entity name // checks if spawn egg has a corresponding entity name
if let Some(spawn_item_id) = get_entity_id(&item_t) { if let Some(entity_type) = EntityType::from_name(&item_t) {
let world_pos = BlockPos(location.0 + face.to_offset()); let world_pos = BlockPos(location.0 + face.to_offset());
// align position like Vanilla does // align position like Vanilla does
let pos = Vector3::new( let pos = Vector3::new(
@ -1185,7 +1192,7 @@ impl Player {
let world = self.world(); let world = self.world();
// create new mob and uuid based on spawn egg id // create new mob and uuid based on spawn egg id
let mob = mob::from_type( let mob = mob::from_type(
EntityType::from_raw(*spawn_item_id).unwrap(), EntityType::from_raw(entity_type.id).unwrap(),
server, server,
pos, pos,
world, world,

View File

@ -8,14 +8,13 @@ use pumpkin_inventory::{Container, OpenContainer};
use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::client::login::CEncryptionRequest;
use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket};
use pumpkin_registry::{DimensionType, Registry}; use pumpkin_registry::{DimensionType, Registry};
use pumpkin_util::math::boundingbox::{BoundingBox, BoundingBoxSize}; use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::dimension::Dimension; use pumpkin_world::dimension::Dimension;
use pumpkin_world::entity::registry::get_entity_by_id;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
@ -221,16 +220,10 @@ impl Server {
let entity_id = self.new_entity_id(); let entity_id = self.new_entity_id();
// TODO: this should be resolved to a integer using a macro when calling this function // TODO: this should be resolved to a integer using a macro when calling this function
let bounding_box_size = get_entity_by_id(entity_type as u16).map_or( let bounding_box_size = EntityDimensions {
BoundingBoxSize { width: entity_type.dimension[0],
width: 0.6, height: entity_type.dimension[1],
height: 1.8, };
},
|entity| BoundingBoxSize {
width: f64::from(entity.dimension[0]),
height: f64::from(entity.dimension[1]),
},
);
// TODO: standing eye height should be per mob // TODO: standing eye height should be per mob
let new_uuid = uuid::Uuid::new_v4(); let new_uuid = uuid::Uuid::new_v4();
@ -240,7 +233,7 @@ impl Server {
world.clone(), world.clone(),
position, position,
entity_type, entity_type,
1.62, entity_type.eye_height,
AtomicCell::new(BoundingBox::new_from_pos( AtomicCell::new(BoundingBox::new_from_pos(
position.x, position.x,
position.y, position.y,

View File

@ -7,6 +7,7 @@ pub mod chunker;
pub mod time; pub mod time;
use crate::{ use crate::{
block,
command::client_suggestions, command::client_suggestions,
entity::{player::Player, Entity, EntityBase, EntityId}, entity::{player::Player, Entity, EntityBase, EntityId},
error::PumpkinError, error::PumpkinError,
@ -423,17 +424,13 @@ impl World {
&CSpawnEntity::new( &CSpawnEntity::new(
entity_id.into(), entity_id.into(),
gameprofile.id, gameprofile.id,
(EntityType::Player as i32).into(), i32::from(EntityType::PLAYER.id).into(),
position.x, position,
position.y,
position.z,
pitch, pitch,
yaw, yaw,
yaw, yaw,
0.into(), 0.into(),
0.0, Vector3::new(0.0, 0.0, 0.0),
0.0,
0.0,
), ),
) )
.await; .await;
@ -449,17 +446,13 @@ impl World {
.send_packet(&CSpawnEntity::new( .send_packet(&CSpawnEntity::new(
existing_player.entity_id().into(), existing_player.entity_id().into(),
gameprofile.id, gameprofile.id,
(EntityType::Player as i32).into(), i32::from(EntityType::PLAYER.id).into(),
pos.x, pos,
pos.y,
pos.z,
entity.yaw.load(), entity.yaw.load(),
entity.pitch.load(), entity.pitch.load(),
entity.head_yaw.load(), entity.head_yaw.load(),
0.into(), 0.into(),
0.0, Vector3::new(0.0, 0.0, 0.0),
0.0,
0.0,
)) ))
.await; .await;
} }
@ -594,17 +587,13 @@ impl World {
&CSpawnEntity::new( &CSpawnEntity::new(
entity.entity_id.into(), entity.entity_id.into(),
player.gameprofile.id, player.gameprofile.id,
(EntityType::Player as i32).into(), i32::from(EntityType::PLAYER.id).into(),
position.x, position,
position.y,
position.z,
pitch, pitch,
yaw, yaw,
yaw, yaw,
0.into(), 0.into(),
0.0, Vector3::new(0.0, 0.0, 0.0),
0.0,
0.0,
), ),
) )
.await; .await;
@ -1025,7 +1014,13 @@ impl World {
chunk chunk
} }
pub async fn break_block(&self, position: &BlockPos, cause: Option<Arc<Player>>) { pub async fn break_block(
self: &Arc<Self>,
server: &Server,
position: &BlockPos,
cause: Option<Arc<Player>>,
drop: bool,
) {
let block = self.get_block(position).await.unwrap(); let block = self.get_block(position).await.unwrap();
let event = BlockBreakEvent::new(cause.clone(), block.clone(), 0, false); let event = BlockBreakEvent::new(cause.clone(), block.clone(), 0, false);
@ -1045,6 +1040,10 @@ impl World {
false, false,
); );
if drop {
block::drop_loot(server, self, block, position).await;
}
match cause { match cause {
Some(player) => { Some(player) => {
self.broadcast_packet_except(&[player.gameprofile.id], &particles_packet) self.broadcast_packet_except(&[player.gameprofile.id], &particles_packet)