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 heck::ToPascalCase;
use proc_macro2::{Span, TokenStream};
use syn::Ident;
use proc_macro2::TokenStream;
mod biome;
mod chunk_status;
@ -43,7 +42,7 @@ pub fn array_to_tokenstream(array: Vec<String>) -> TokenStream {
let mut variants = TokenStream::new();
for item in array.iter() {
let name = ident(item.to_pascal_case());
let name = format_ident!("{}", item.to_pascal_case());
variants.extend([quote! {
#name,
}]);
@ -62,12 +61,3 @@ pub fn write_generated_file(content: TokenStream, out_file: &str) {
// Doesn't matter if rustfmt is unavailable.
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 proc_macro2::TokenStream;
use quote::quote;
use crate::ident;
use quote::{format_ident, quote};
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/chunk_status.json");
@ -14,7 +12,7 @@ pub(crate) fn build() -> TokenStream {
for status in chunk_status.iter() {
let full_name = format!("minecraft:{status}");
let name = ident(status.to_pascal_case());
let name = format_ident!("{}", status.to_pascal_case());
variants.extend([quote! {
#[serde(rename = #full_name)]
#name,

View File

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

View File

@ -1,66 +1,101 @@
use std::collections::HashMap;
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote, ToTokens};
use serde::Deserialize;
use syn::LitInt;
use crate::ident;
#[derive(Deserialize)]
pub struct JSONStruct {
id: u16,
pub struct EntityType {
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 {
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"))
.expect("Failed to parse sound_category.json");
let mut variants = TokenStream::new();
.expect("Failed to parse entities.json");
for (item, id) in json.iter() {
let id = id.id as u8;
let mut consts = TokenStream::new();
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 name = ident(item.to_pascal_case());
variants.extend([quote! {
#name = #id_lit,
}]);
let upper_name = format_ident!("{}", name.to_uppercase());
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! {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EntityType {
#variants
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EntityType {
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 {
#consts
pub const fn from_raw(id: u16) -> Option<Self> {
match id {
#type_from_raw_id_arms

View File

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

View File

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

View File

@ -1,11 +1,9 @@
use std::collections::HashMap;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote};
use serde::Deserialize;
use crate::ident;
#[derive(Deserialize)]
pub struct Packets {
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() {
let packet_id = id as i32;
let name = format!("{phase}_{packet_name}").to_uppercase();
let name = ident(name);
let name = format_ident!("{}", name);
consts.extend([quote! {
pub const #name: i32 = #packet_id;
}]);

View File

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

View File

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

View File

@ -1,5 +1,6 @@
use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION;
use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use serde::Serialize;
use crate::VarInt;
@ -8,18 +9,18 @@ use crate::VarInt;
#[client_packet(PLAY_SET_ENTITY_MOTION)]
pub struct CEntityVelocity<'a> {
entity_id: &'a VarInt,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
velocity: Vector3<i16>,
}
impl<'a> CEntityVelocity<'a> {
pub fn new(entity_id: &'a VarInt, velocity_x: f64, velocity_y: f64, velocity_z: f64) -> Self {
Self {
entity_id,
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity: Vector3::new(
(velocity_x.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 pumpkin_data::{packet::clientbound::PLAY_SOUND, sound::SoundCategory};
use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundEvent, VarInt};
@ -8,23 +9,18 @@ use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundEvent, VarIn
pub struct CSoundEffect {
sound_event: IDOrSoundEvent,
sound_category: VarInt,
effect_position_x: i32,
effect_position_y: i32,
effect_position_z: i32,
position: Vector3<i32>,
volume: f32,
pitch: f32,
seed: f64,
}
impl CSoundEffect {
#[allow(clippy::too_many_arguments)]
pub fn new(
sound_id: VarInt,
sound_event: Option<SoundEvent>,
sound_category: SoundCategory,
effect_position_x: f64,
effect_position_y: f64,
effect_position_z: f64,
position: &Vector3<f64>,
volume: f32,
pitch: f32,
seed: f64,
@ -35,9 +31,11 @@ impl CSoundEffect {
sound_event,
},
sound_category: VarInt(sound_category as i32),
effect_position_x: (effect_position_x * 8.0) as i32,
effect_position_y: (effect_position_y * 8.0) as i32,
effect_position_z: (effect_position_z * 8.0) as i32,
position: Vector3::new(
(position.x * 8.0) as i32,
(position.y * 8.0) as i32,
(position.z * 8.0) as i32,
),
volume,
pitch,
seed,
@ -58,9 +56,9 @@ impl ClientPacket for CSoundEffect {
}
}
bytebuf.put_var_int(&self.sound_category);
bytebuf.put_i32(self.effect_position_x);
bytebuf.put_i32(self.effect_position_y);
bytebuf.put_i32(self.effect_position_z);
bytebuf.put_i32(self.position.x);
bytebuf.put_i32(self.position.y);
bytebuf.put_i32(self.position.z);
bytebuf.put_f32(self.volume);
bytebuf.put_f32(self.pitch);
bytebuf.put_f64(self.seed);

View File

@ -1,5 +1,6 @@
use pumpkin_data::packet::clientbound::PLAY_ADD_ENTITY;
use pumpkin_macros::client_packet;
use pumpkin_util::math::vector3::Vector3;
use serde::Serialize;
use crate::VarInt;
@ -11,16 +12,12 @@ pub struct CSpawnEntity {
#[serde(with = "uuid::serde::compact")]
entity_uuid: uuid::Uuid,
typ: VarInt,
x: f64,
y: f64,
z: f64,
position: Vector3<f64>,
pitch: u8, // angle
yaw: u8, // angle
head_yaw: u8, // angle
data: VarInt,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
velocity: Vector3<i16>,
}
impl CSpawnEntity {
@ -29,31 +26,27 @@ impl CSpawnEntity {
entity_id: VarInt,
entity_uuid: uuid::Uuid,
typ: VarInt,
x: f64,
y: f64,
z: f64,
position: Vector3<f64>,
pitch: f32, // angle
yaw: f32, // angle
head_yaw: f32, // angle
data: VarInt,
velocity_x: f32,
velocity_y: f32,
velocity_z: f32,
velocity: Vector3<f64>,
) -> Self {
Self {
entity_id,
entity_uuid,
typ,
x,
y,
z,
position,
pitch: (pitch * 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,
data,
velocity_x: (velocity_x.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_y: (velocity_y.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity_z: (velocity_z.clamp(-3.9, 3.9) * 8000.0) as i16,
velocity: Vector3::new(
(velocity.x.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 {
pub fn new_default(size: &BoundingBoxSize) -> Self {
pub fn new_default(size: &EntityDimensions) -> Self {
Self::new_from_pos(0., 0., 0., size)
}
pub fn new_from_pos(x: f64, y: f64, z: f64, size: &BoundingBoxSize) -> Self {
let f = size.width / 2.;
pub fn new_from_pos(x: f64, y: f64, z: f64, size: &EntityDimensions) -> Self {
let f = size.width as f64 / 2.;
Self {
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)]
pub struct BoundingBoxSize {
pub width: f64,
pub height: f64,
pub struct EntityDimensions {
pub width: f32,
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 cylindrical_chunk_iterator;
pub mod dimension;
pub mod entity;
mod generation;
pub mod item;
pub mod level;

View File

@ -1,10 +1,19 @@
use blocks::chest::ChestBlock;
use blocks::furnace::FurnaceBlock;
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::jukebox::JukeboxBlock;
use crate::block::registry::BlockRegistry;
use crate::entity::item::ItemEntity;
use crate::server::Server;
use crate::world::World;
use std::sync::Arc;
mod blocks;
@ -24,6 +33,21 @@ pub fn default_registry() -> Arc<BlockRegistry> {
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]
pub fn default_block_properties_manager() -> Arc<BlockPropertiesManager> {
let mut manager = BlockPropertiesManager::default();

View File

@ -41,7 +41,7 @@ impl CommandExecutor for SetblockExecutor {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
_server: &crate::server::Server,
server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
@ -67,7 +67,9 @@ impl CommandExecutor for SetblockExecutor {
for y in start_y..=end_y {
for z in start_z..=end_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;
placed_blocks += 1;
}

View File

@ -46,7 +46,7 @@ impl CommandExecutor for KillExecutor {
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(
format!("/tell {} ", name.clone()).into(),
));

View File

@ -34,7 +34,7 @@ impl CommandExecutor for SetblockExecutor {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
_server: &crate::server::Server,
server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
@ -46,7 +46,7 @@ impl CommandExecutor for SetblockExecutor {
let success = match mode {
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;
true
}

View File

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

View File

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

View File

@ -1,5 +1,3 @@
use std::f32::consts::PI;
use pumpkin_data::{
particle::Particle,
sound::{Sound, SoundCategory},
@ -74,8 +72,8 @@ pub async fn handle_knockback(
let saved_velo = victim_entity.velocity.load();
victim_entity.knockback(
strength * 0.5,
f64::from((yaw * (PI / 180.0)).sin()),
f64::from(-(yaw * (PI / 180.0)).cos()),
f64::from((yaw.to_radians()).sin()),
f64::from(-(yaw.to_radians()).cos()),
);
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>) {
let yaw = attacker_entity.yaw.load();
let d = -f64::from((yaw * (PI / 180.0)).sin());
let e = f64::from((yaw * (PI / 180.0)).cos());
let d = -f64::from((yaw.to_radians()).sin());
let e = f64::from((yaw.to_radians()).cos());
let scale = 0.5;
// TODO: use entity height

View File

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

View File

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

View File

@ -47,7 +47,7 @@ use pumpkin_protocol::{
};
use pumpkin_util::{
math::{
boundingbox::{BoundingBox, BoundingBoxSize},
boundingbox::{BoundingBox, EntityDimensions},
experience,
position::BlockPos,
vector2::Vector2,
@ -169,9 +169,9 @@ impl Player {
let gameprofile_clone = gameprofile.clone();
let config = client.config.lock().await.clone().unwrap_or_default();
let bounding_box_size = BoundingBoxSize {
width: 0.6,
height: 1.8,
let bounding_box_size = EntityDimensions {
width: EntityType::PLAYER.dimension[0],
height: EntityType::PLAYER.dimension[1],
};
Self {
@ -180,8 +180,8 @@ impl Player {
player_uuid,
world,
Vector3::new(0.0, 0.0, 0.0),
EntityType::Player,
1.62,
EntityType::PLAYER,
EntityType::PLAYER.eye_height,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size),
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
@ -404,9 +404,7 @@ impl Player {
VarInt(i32::from(sound_id)),
None,
category,
position.x,
position.y,
position.z,
position,
volume,
pitch,
seed,
@ -791,7 +789,7 @@ impl Player {
if let Some(item) = inv.held_item_mut() {
let entity = server.add_entity(
self.living_entity.entity.pos.load(),
EntityType::Item,
EntityType::ITEM,
self.world(),
);
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;
@ -8,8 +8,6 @@ pub struct ThrownItemEntity {
entity: Entity,
}
const DEG_PER_RAD_F32: f32 = 180.0 / PI;
impl ThrownItemEntity {
pub fn new(entity: Entity, owner: &Entity) -> Self {
let mut owner_pos = owner.pos.load();
@ -69,8 +67,8 @@ impl ThrownItemEntity {
self.entity.velocity.store(velocity);
let len = velocity.horizontal_length();
self.entity.set_rotation(
velocity.x.atan2(velocity.z) as f32 * DEG_PER_RAD_F32,
velocity.y.atan2(len) as f32 * DEG_PER_RAD_F32,
velocity.x.atan2(velocity.z).to_degrees() as f32,
velocity.y.atan2(len).to_degrees() as f32,
);
}
}

View File

@ -27,7 +27,7 @@ impl PumpkinItem for EggItem {
)
.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 entity = server.add_entity(position, EntityType::EGG, world);
let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity);
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();

View File

@ -26,7 +26,7 @@ impl PumpkinItem for SnowBallItem {
&position,
)
.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 yaw = player.living_entity.entity.yaw.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::{
block::{registry::get_block_by_item, BlockDirection},
entity::registry::get_entity_id,
item::registry::get_spawn_egg,
};
@ -820,8 +819,9 @@ impl Player {
let world = &entity.world;
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 {
server
.block_registry
@ -864,7 +864,14 @@ impl Player {
let world = &entity.world;
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 {
server
@ -1171,7 +1178,7 @@ impl Player {
face: &BlockDirection,
) -> Result<bool, Box<dyn PumpkinError>> {
// 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());
// align position like Vanilla does
let pos = Vector3::new(
@ -1185,7 +1192,7 @@ impl Player {
let world = self.world();
// create new mob and uuid based on spawn egg id
let mob = mob::from_type(
EntityType::from_raw(*spawn_item_id).unwrap(),
EntityType::from_raw(entity_type.id).unwrap(),
server,
pos,
world,

View File

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

View File

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