Refactor World Entity System

Also support Item drops (WIP)
This commit is contained in:
Alexander Medvedev 2025-01-31 21:45:20 +01:00
parent 8bb47b6ec7
commit ef497fb435
23 changed files with 297 additions and 183 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,46 @@ pub struct Metadata<T> {
} }
impl<T> Metadata<T> { impl<T> Metadata<T> {
pub fn new(index: u8, typ: VarInt, value: T) -> Self { pub fn new(index: u8, typ: MetaDataType, value: T) -> Self {
Self { index, typ, value } Self {
index,
typ: VarInt(typ as i32),
value,
}
} }
} }
#[repr(u8)]
pub enum MetaDataType {
Byte,
Integer,
Long,
Float,
String,
TextComponent,
OptionalTextComponent,
ItemStack,
Boolean,
Rotation,
BlockPos,
OptionalBlockPos,
Facing,
OptionalUuid,
BlockState,
OptionalBlockState,
NbtCompound,
Particle,
ParticleList,
VillagerData,
OptionalInt,
EntityPose,
CatVariant,
WolfVariant,
FrogVariant,
OptionalGlobalPos,
PaintingVariant,
SnifferState,
ArmadilloState,
Vector3f,
QuaternionF,
}

View File

@ -43,8 +43,7 @@ struct Particle {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct ParticleOptions { struct ParticleOptions {
#[serde(rename = "type")] r#type: String,
typee: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
value: Option<VarInt>, value: Option<VarInt>,
} }

View File

@ -45,8 +45,7 @@ pub enum MonsterSpawnLightLevel {
pub struct MonsterSpawnLightLevelTagged { pub struct MonsterSpawnLightLevelTagged {
min_inclusive: i32, min_inclusive: i32,
max_inclusive: i32, max_inclusive: i32,
#[serde(rename = "type")] r#type: String,
typee: String,
} }
impl From<i32> for MonsterSpawnLightLevel { impl From<i32> for MonsterSpawnLightLevel {

View File

@ -26,9 +26,8 @@ pub enum HoverEvent {
/// The UUID cannot use uuid::Uuid because its serialization parses it into bytes, so its double bytes serialized /// The UUID cannot use uuid::Uuid because its serialization parses it into bytes, so its double bytes serialized
id: Cow<'static, str>, id: Cow<'static, str>,
/// Resource identifier of the entity /// Resource identifier of the entity
#[serde(rename = "type")]
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
kind: Option<Cow<'static, str>>, r#type: Option<Cow<'static, str>>,
/// Optional custom name for the entity /// Optional custom name for the entity
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Vec<TextComponentBase>>, name: Option<Vec<TextComponentBase>>,
@ -45,7 +44,7 @@ impl HoverEvent {
{ {
Self::ShowEntity { Self::ShowEntity {
id: id.into(), id: id.into(),
kind: kind.map(|kind| kind.into()), r#type: kind.map(|kind| kind.into()),
name: match name { name: match name {
Some(name) => Some(vec![name.0]), Some(name) => Some(vec![name.0]),
None => None, None => None,

View File

@ -3,6 +3,8 @@ use std::sync::LazyLock;
use serde::Deserialize; use serde::Deserialize;
use crate::loot::LootTable;
pub static BLOCKS: LazyLock<TopLevel> = LazyLock::new(|| { pub static BLOCKS: LazyLock<TopLevel> = LazyLock::new(|| {
serde_json::from_str(include_str!("../../../assets/blocks.json")) serde_json::from_str(include_str!("../../../assets/blocks.json"))
.expect("Could not parse blocks.json registry.") .expect("Could not parse blocks.json registry.")
@ -103,13 +105,13 @@ pub fn get_block_collision_shapes(block_id: u16) -> Option<Vec<Shape>> {
} }
#[expect(dead_code)] #[expect(dead_code)]
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone)]
pub struct TopLevel { pub struct TopLevel {
block_entity_types: Vec<String>, block_entity_types: Vec<String>,
shapes: Vec<Shape>, shapes: Vec<Shape>,
pub blocks: Vec<Block>, pub blocks: Vec<Block>,
} }
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone)]
pub struct Block { pub struct Block {
pub id: u16, pub id: u16,
pub item_id: u16, pub item_id: u16,
@ -117,6 +119,7 @@ pub struct Block {
pub wall_variant_id: Option<u16>, pub wall_variant_id: Option<u16>,
pub translation_key: String, pub translation_key: String,
pub name: String, pub name: String,
pub loot_table: Option<LootTable>,
pub properties: Vec<Property>, pub properties: Vec<Property>,
pub default_state_id: u16, pub default_state_id: u16,
pub states: Vec<State>, pub states: Vec<State>,

View File

@ -77,8 +77,7 @@ pub struct AttributeModifiers {
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
pub struct Modifier { pub struct Modifier {
#[serde(rename = "type")] pub r#type: String,
pub type_val: String,
pub id: String, pub id: String,
pub amount: f64, pub amount: f64,
pub operation: Operation, pub operation: Operation,

View File

@ -18,6 +18,7 @@ mod generation;
pub mod item; pub mod item;
pub mod level; pub mod level;
mod lock; mod lock;
pub mod loot;
pub mod world_info; pub mod world_info;
pub const WORLD_HEIGHT: usize = 384; pub const WORLD_HEIGHT: usize = 384;
pub const WORLD_LOWEST_Y: i16 = -64; pub const WORLD_LOWEST_Y: i16 = -64;

View File

@ -0,0 +1,37 @@
use serde::Deserialize;
#[expect(dead_code)]
#[derive(Deserialize, Clone)]
pub struct LootTable {
r#type: LootTableType,
pools: Option<Vec<LootPool>>,
}
#[expect(dead_code)]
#[derive(Deserialize, Clone)]
pub struct LootPool {
entries: Vec<LootPoolEntry>,
}
#[expect(dead_code)]
#[derive(Deserialize, Clone)]
pub struct LootPoolEntry {
// TODO
r#type: Option<String>,
// TODO
name: Option<String>,
}
#[derive(Deserialize, Clone)]
#[serde(rename = "snake_case")]
pub enum LootTableType {
#[serde(rename = "minecraft:empty")]
/// Nothing will be droped
Empty,
#[serde(rename = "minecraft:block")]
/// A Block will be droped
Block,
#[serde(rename = "minecraft:chest")]
/// A Item will be droped
Chest,
}

View File

@ -112,7 +112,7 @@ pub async fn standard_open_container_unique<C: Container + Default + 'static>(
pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { pub async fn close_all_in_container(player: &Player, container: &OpenContainer) {
for id in container.all_player_ids() { for id in container.all_player_ids() {
if let Some(remote_player) = player.world().get_player_by_entityid(id).await { if let Some(remote_player) = player.world().get_player_by_id(id).await {
remote_player.close_container().await; remote_player.close_container().await;
} }
} }

View File

@ -38,10 +38,7 @@ impl CommandExecutor for SummonExecutor {
if let Some(player) = sender.as_player() { if let Some(player) = sender.as_player() {
let pos = pos.unwrap_or(player.living_entity.entity.pos.load()); let pos = pos.unwrap_or(player.living_entity.entity.pos.load());
let (mob, uuid) = mob::from_type(entity, server, pos, player.world()).await; let (mob, uuid) = mob::from_type(entity, server, pos, player.world()).await;
player player.world().spawn_entity(uuid, mob).await;
.world()
.broadcast_packet_all(&mob.living_entity.entity.create_spawn_packet(uuid))
.await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.summon.success", "commands.summon.success",

View File

@ -0,0 +1,38 @@
use async_trait::async_trait;
use pumpkin_protocol::{
client::play::{MetaDataType, Metadata},
codec::slot::Slot,
};
use pumpkin_world::item::ItemStack;
use super::{living::LivingEntity, Entity, EntityBase};
pub struct ItemEntity {
entity: Entity,
item: Slot,
}
impl ItemEntity {
pub fn new(entity: Entity, stack: &ItemStack) -> Self {
let slot = Slot::from(stack);
Self { entity, item: slot }
}
pub async fn send_meta_packet(&self) {
self.entity
.send_meta_data(Metadata::new(8, MetaDataType::ItemStack, self.item.clone()))
.await;
}
}
#[async_trait]
impl EntityBase for ItemEntity {
async fn tick(&self) {}
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}
}

View File

@ -4,7 +4,7 @@ use async_trait::async_trait;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_nbt::tag::NbtTag; use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::client::play::{CDamageEvent, CEntityStatus, CSetEntityMetadata, Metadata}; use pumpkin_protocol::client::play::{CDamageEvent, CEntityStatus, MetaDataType, Metadata};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use super::{Entity, EntityId, NBTStorage}; use super::{Entity, EntityId, NBTStorage};
@ -58,11 +58,7 @@ impl LivingEntity {
self.health.store(health); self.health.store(health);
// tell everyone entities health changed // tell everyone entities health changed
self.entity self.entity
.world .send_meta_data(Metadata::new(9, MetaDataType::Float, health))
.broadcast_packet_all(&CSetEntityMetadata::new(
self.entity.entity_id.into(),
Metadata::new(9, 3.into(), health),
))
.await; .await;
} }

View File

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -11,18 +12,20 @@ use crate::{server::Server, world::World};
use super::{ use super::{
ai::{goal::Goal, path::Navigator}, ai::{goal::Goal, path::Navigator},
living::LivingEntity, living::LivingEntity,
Entity, EntityBase,
}; };
pub mod zombie; pub mod zombie;
pub struct MobEntity { pub struct MobEntity {
pub living_entity: Arc<LivingEntity>, pub living_entity: LivingEntity,
pub goals: Mutex<Vec<(Arc<dyn Goal>, bool)>>, pub goals: Mutex<Vec<(Arc<dyn Goal>, bool)>>,
pub navigator: Mutex<Navigator>, pub navigator: Mutex<Navigator>,
} }
impl MobEntity { #[async_trait]
pub async fn tick(&self) { impl EntityBase for MobEntity {
async fn tick(&self) {
let mut goals = self.goals.lock().await; let mut goals = self.goals.lock().await;
for (goal, running) in goals.iter_mut() { for (goal, running) in goals.iter_mut() {
if *running { if *running {
@ -38,6 +41,14 @@ impl MobEntity {
let mut navigator = self.navigator.lock().await; let mut navigator = self.navigator.lock().await;
navigator.tick(&self.living_entity).await; navigator.tick(&self.living_entity).await;
} }
fn get_entity(&self) -> &Entity {
&self.living_entity.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
Some(&self.living_entity)
}
} }
pub async fn from_type( pub async fn from_type(
@ -45,15 +56,20 @@ pub async fn from_type(
server: &Server, server: &Server,
position: Vector3<f64>, position: Vector3<f64>,
world: &Arc<World>, world: &Arc<World>,
) -> (Arc<MobEntity>, Uuid) { ) -> (Arc<dyn EntityBase>, Uuid) {
let entity = server.add_mob_entity(entity_type, position, world).await; let (entity, uuid) = server.add_entity(position, entity_type, world);
let mob = MobEntity {
living_entity: LivingEntity::new(entity),
goals: Mutex::new(vec![]),
navigator: Mutex::new(Navigator::default()),
};
#[expect(clippy::single_match)] #[expect(clippy::single_match)]
match entity_type { match entity_type {
EntityType::Zombie => Zombie::make(&entity.0).await, EntityType::Zombie => Zombie::make(&mob).await,
// TODO // TODO
_ => (), _ => (),
} }
entity (Arc::new(mob), uuid)
} }
impl MobEntity { impl MobEntity {

View File

@ -3,6 +3,7 @@ use std::sync::{atomic::AtomicBool, Arc};
use async_trait::async_trait; use async_trait::async_trait;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use living::LivingEntity;
use pumpkin_data::{ use pumpkin_data::{
entity::{EntityPose, EntityType}, entity::{EntityPose, EntityType},
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
@ -10,7 +11,8 @@ use pumpkin_data::{
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{ client::play::{
CHeadRot, CSetEntityMetadata, CSpawnEntity, CTeleportEntity, CUpdateEntityRot, Metadata, CHeadRot, CSetEntityMetadata, CSpawnEntity, CTeleportEntity, CUpdateEntityRot,
MetaDataType, Metadata,
}, },
codec::var_int::VarInt, codec::var_int::VarInt,
}; };
@ -22,18 +24,26 @@ use pumpkin_util::math::{
vector3::Vector3, vector3::Vector3,
wrap_degrees, wrap_degrees,
}; };
use serde::Serialize;
use uuid::Uuid; use uuid::Uuid;
use crate::world::World; use crate::world::World;
pub mod ai; pub mod ai;
pub mod mob; pub mod item;
pub mod living; pub mod living;
pub mod mob;
pub mod player; pub mod player;
pub type EntityId = i32; pub type EntityId = i32;
#[async_trait]
pub trait EntityBase: Send + Sync {
async fn tick(&self);
fn get_entity(&self) -> &Entity;
fn get_living_entity(&self) -> Option<&LivingEntity>;
}
/// Represents a not living Entity (e.g. Item, Egg, Snowball...) /// Represents a not living Entity (e.g. Item, Egg, Snowball...)
pub struct Entity { pub struct Entity {
/// A unique identifier for the entity /// A unique identifier for the entity
@ -295,8 +305,8 @@ impl Entity {
} else { } else {
b &= !(1 << index); b &= !(1 << index);
} }
let packet = CSetEntityMetadata::new(self.entity_id.into(), Metadata::new(0, 0.into(), b)); self.send_meta_data(Metadata::new(0, MetaDataType::Byte, b))
self.world.broadcast_packet_all(&packet).await; .await;
} }
/// Plays sound at this entity's position with the entity's sound category /// Plays sound at this entity's position with the entity's sound category
@ -306,14 +316,33 @@ impl Entity {
.await; .await;
} }
pub async fn send_meta_data<T>(&self, meta: Metadata<T>)
where
T: Serialize,
{
self.world
.broadcast_packet_all(&CSetEntityMetadata::new(self.entity_id.into(), meta))
.await;
}
pub async fn set_pose(&self, pose: EntityPose) { pub async fn set_pose(&self, pose: EntityPose) {
self.pose.store(pose); self.pose.store(pose);
let pose = pose as i32; let pose = pose as i32;
let packet = CSetEntityMetadata::<VarInt>::new( self.send_meta_data(Metadata::new(6, MetaDataType::EntityPose, pose))
self.entity_id.into(), .await;
Metadata::new(6, 21.into(), pose.into()), }
); }
self.world.broadcast_packet_all(&packet).await;
#[async_trait]
impl EntityBase for Entity {
async fn tick(&self) {}
fn get_entity(&self) -> &Entity {
self
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
} }
} }

View File

@ -21,7 +21,8 @@ use pumpkin_protocol::{
client::play::{ client::play::{
CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus, CGameEvent, CHurtAnimation, CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus, CGameEvent, CHurtAnimation,
CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition,
CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, GameEvent, PlayerAction, CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, GameEvent, MetaDataType,
PlayerAction,
}, },
server::play::{ server::play::{
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd, SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
@ -40,7 +41,7 @@ use pumpkin_protocol::{
}; };
use pumpkin_protocol::{client::play::CUpdateTime, codec::var_int::VarInt}; use pumpkin_protocol::{client::play::CUpdateTime, codec::var_int::VarInt};
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{CSetEntityMetadata, Metadata}, client::play::Metadata,
server::play::{SClickContainer, SKeepAlive}, server::play::{SClickContainer, SKeepAlive},
}; };
use pumpkin_util::{ use pumpkin_util::{
@ -63,7 +64,7 @@ use pumpkin_world::{
}; };
use tokio::sync::{Mutex, Notify, RwLock}; use tokio::sync::{Mutex, Notify, RwLock};
use super::{Entity, EntityId, NBTStorage}; use super::{item::ItemEntity, Entity, EntityId, NBTStorage};
use crate::{ use crate::{
command::{client_cmd_suggestions, dispatcher::CommandDispatcher}, command::{client_cmd_suggestions, dispatcher::CommandDispatcher},
data::op_data::OPERATOR_CONFIG, data::op_data::OPERATOR_CONFIG,
@ -535,10 +536,10 @@ impl Player {
/// Sends the mobs to just the player. /// Sends the mobs to just the player.
// TODO: This should be optimized for larger servers based on current player chunk // TODO: This should be optimized for larger servers based on current player chunk
pub async fn send_mobs(&self, world: &World) { pub async fn send_mobs(&self, world: &World) {
let mobs = world.current_living_mobs.lock().await.clone(); let entities = world.entities.lock().await.clone();
for (uuid, mob) in mobs { for (uuid, entity) in entities {
self.client self.client
.send_packet(&mob.living_entity.entity.create_spawn_packet(uuid)) .send_packet(&entity.get_entity().create_spawn_packet(uuid))
.await; .await;
} }
} }
@ -681,19 +682,18 @@ impl Player {
} }
/// Send skin layers and used hand to all players /// Send skin layers and used hand to all players
pub async fn update_client_information(&self) { pub async fn send_client_information(&self) {
let config = self.config.lock().await; let config = self.config.lock().await;
let world = self.world(); self.living_entity
world .entity
.broadcast_packet_all(&CSetEntityMetadata::new( .send_meta_data(Metadata::new(17, MetaDataType::Byte, config.skin_parts))
self.entity_id().into(),
Metadata::new(17, 0.into(), config.skin_parts),
))
.await; .await;
world self.living_entity
.broadcast_packet_all(&CSetEntityMetadata::new( .entity
self.entity_id().into(), .send_meta_data(Metadata::new(
Metadata::new(18, 0.into(), config.main_hand as u8), 18,
MetaDataType::Byte,
config.main_hand as u8,
)) ))
.await; .await;
} }
@ -715,6 +715,20 @@ impl Player {
.await; .await;
} }
pub async fn drop_item(&self, server: &Server) {
let inv = self.inventory.lock().await;
if let Some(item) = inv.held_item() {
let (entity, uuid) = server.add_entity(
self.living_entity.entity.pos.load(),
EntityType::Item,
self.world(),
);
let item_entity = Arc::new(ItemEntity::new(entity, item));
self.world().spawn_entity(uuid, item_entity.clone()).await;
item_entity.send_meta_packet().await;
}
}
pub async fn send_system_message(&self, text: &TextComponent) { pub async fn send_system_message(&self, text: &TextComponent) {
self.send_system_message_raw(text, false).await; self.send_system_message_raw(text, false).await;
} }

View File

@ -490,7 +490,7 @@ impl Player {
.living_entity .living_entity
.entity .entity
.world .world
.current_players .players
.lock() .lock()
.await .await
.iter() .iter()

View File

@ -13,7 +13,7 @@ use crate::{
world::player_chunker, world::player_chunker,
}; };
use pumpkin_config::ADVANCED_CONFIG; use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::{EntityPose, EntityType};
use pumpkin_data::world::CHAT; use pumpkin_data::world::CHAT;
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
use pumpkin_inventory::InventoryError; use pumpkin_inventory::InventoryError;
@ -660,7 +660,7 @@ impl Player {
self.gameprofile.name, self.gameprofile.name,
self.client.id, self.client.id,
); );
self.update_client_information().await; self.send_client_information().await;
} }
} else { } else {
self.kick(TextComponent::text("Invalid hand or chat type")) self.kick(TextComponent::text("Invalid hand or chat type"))
@ -719,8 +719,8 @@ impl Player {
} }
let world = &entity.world; let world = &entity.world;
let player_victim = world.get_player_by_entityid(entity_id.0).await; let player_victim = world.get_player_by_id(entity_id.0).await;
let entity_victim = world.get_living_entity_by_entityid(entity_id.0).await; let entity_victim = world.get_entity_by_id(entity_id.0).await;
if let Some(player_victim) = player_victim { if let Some(player_victim) = player_victim {
if player_victim.living_entity.health.load() <= 0.0 { if player_victim.living_entity.health.load() <= 0.0 {
// you can trigger this from a non-modded / innocent client client, // you can trigger this from a non-modded / innocent client client,
@ -729,11 +729,15 @@ impl Player {
} }
self.attack(&player_victim).await; self.attack(&player_victim).await;
} else if let Some(entity_victim) = entity_victim { } else if let Some(entity_victim) = entity_victim {
if entity_victim.health.load() <= 0.0 { // Checks if victim is a living entity
return; if let Some(entity_victim) = entity_victim.get_living_entity() {
if entity_victim.health.load() <= 0.0 {
return;
}
entity_victim.entity.set_pose(EntityPose::Dying).await;
entity_victim.kill().await;
world.clone().remove_entity(&entity_victim.entity).await;
} }
entity_victim.kill().await;
world.clone().remove_mob_entity(entity_victim).await;
// TODO: block entities should be checked here (signs) // TODO: block entities should be checked here (signs)
} else { } else {
log::error!( log::error!(
@ -836,10 +840,10 @@ impl Player {
.await; .await;
} }
} }
Status::DropItemStack Status::DropItemStack | Status::DropItem => {
| Status::DropItem self.drop_item(server).await;
| Status::ShootArrowOrFinishEating }
| Status::SwapItem => { Status::ShootArrowOrFinishEating | Status::SwapItem => {
log::debug!("todo"); log::debug!("todo");
} }
}, },
@ -1130,12 +1134,10 @@ impl Player {
.await; .await;
// set the rotation // set the rotation
mob.living_entity.entity.set_rotation(yaw, 0.0); mob.get_entity().set_rotation(yaw, 0.0);
// broadcast new mob to all players // broadcast new mob to all players
world world.spawn_entity(uuid, mob).await;
.broadcast_packet_all(&mob.living_entity.entity.create_spawn_packet(uuid))
.await;
// TODO: send/configure additional commands/data based on type of entity (horse, slime, etc) // TODO: send/configure additional commands/data based on type of entity (horse, slime, etc)
} else { } else {

View File

@ -115,7 +115,7 @@ async fn handle_packet(
let mut players: Vec<CString> = Vec::new(); let mut players: Vec<CString> = Vec::new();
for world in server.worlds.read().await.iter() { for world in server.worlds.read().await.iter() {
let mut world_players = world let mut world_players = world
.current_players .players
.lock() .lock()
.await .await
// Although there is no documented limit, we will limit to 4 players // Although there is no documented limit, we will limit to 4 players

View File

@ -55,7 +55,7 @@ impl Context {
}; };
for world in self.server.worlds.read().await.iter() { for world in self.server.worlds.read().await.iter() {
for player in world.current_players.lock().await.values() { for player in world.players.lock().await.values() {
client_cmd_suggestions::send_c_commands_packet( client_cmd_suggestions::send_c_commands_packet(
player, player,
&self.server.command_dispatcher, &self.server.command_dispatcher,
@ -72,7 +72,7 @@ impl Context {
}; };
for world in self.server.worlds.read().await.iter() { for world in self.server.worlds.read().await.iter() {
for player in world.current_players.lock().await.values() { for player in world.players.lock().await.values() {
client_cmd_suggestions::send_c_commands_packet( client_cmd_suggestions::send_c_commands_packet(
player, player,
&self.server.command_dispatcher, &self.server.command_dispatcher,

View File

@ -34,9 +34,6 @@ use uuid::Uuid;
use crate::block::block_manager::BlockManager; use crate::block::block_manager::BlockManager;
use crate::block::properties::BlockPropertiesManager; use crate::block::properties::BlockPropertiesManager;
use crate::block::{default_block_manager, default_block_properties_manager}; use crate::block::{default_block_manager, default_block_properties_manager};
use crate::entity::ai::path::Navigator;
use crate::entity::living::LivingEntity;
use crate::entity::mob::MobEntity;
use crate::entity::{Entity, EntityId}; use crate::entity::{Entity, EntityId};
use crate::net::EncryptionError; use crate::net::EncryptionError;
use crate::world::custom_bossbar::CustomBossbars; use crate::world::custom_bossbar::CustomBossbars;
@ -207,22 +204,6 @@ impl Server {
} }
} }
pub async fn add_mob_entity(
&self,
entity_type: EntityType,
position: Vector3<f64>,
world: &Arc<World>,
) -> (Arc<MobEntity>, Uuid) {
let (living_entity, uuid) = self.add_living_entity(position, entity_type, world);
let mob = Arc::new(MobEntity {
living_entity,
goals: Mutex::new(vec![]),
navigator: Mutex::new(Navigator::default()),
});
world.add_mob_entity(uuid, mob.clone()).await;
(mob, uuid)
}
/// Adds a new living entity to the server. This does not Spawn the entity /// Adds a new living entity to the server. This does not Spawn the entity
/// ///
/// # Returns /// # Returns
@ -232,12 +213,12 @@ impl Server {
/// - `Arc<LivingEntity>`: A reference to the newly created living entity. /// - `Arc<LivingEntity>`: A reference to the newly created living entity.
/// - `Arc<World>`: A reference to the world that the living entity was added to. /// - `Arc<World>`: A reference to the world that the living entity was added to.
/// - `Uuid`: The uuid of the newly created living entity to be used to send to the client. /// - `Uuid`: The uuid of the newly created living entity to be used to send to the client.
fn add_living_entity( pub fn add_entity(
&self, &self,
position: Vector3<f64>, position: Vector3<f64>,
entity_type: EntityType, entity_type: EntityType,
world: &Arc<World>, world: &Arc<World>,
) -> (Arc<LivingEntity>, Uuid) { ) -> (Entity, Uuid) {
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
@ -254,7 +235,7 @@ impl Server {
// 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();
let mob = Arc::new(LivingEntity::new(Entity::new( let entity = Entity::new(
entity_id, entity_id,
new_uuid, new_uuid,
world.clone(), world.clone(),
@ -263,9 +244,9 @@ impl Server {
1.62, 1.62,
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),
))); );
(mob, new_uuid) (entity, new_uuid)
} }
pub async fn try_get_container( pub async fn try_get_container(
@ -380,7 +361,7 @@ impl Server {
let mut players = Vec::<Arc<Player>>::new(); let mut players = Vec::<Arc<Player>>::new();
for world in self.worlds.read().await.iter() { for world in self.worlds.read().await.iter() {
for (_, player) in world.current_players.lock().await.iter() { for (_, player) in world.players.lock().await.iter() {
if player.client.address.lock().await.ip() == ip { if player.client.address.lock().await.ip() == ip {
players.push(player.clone()); players.push(player.clone());
} }
@ -395,7 +376,7 @@ impl Server {
let mut players = Vec::<Arc<Player>>::new(); let mut players = Vec::<Arc<Player>>::new();
for world in self.worlds.read().await.iter() { for world in self.worlds.read().await.iter() {
for (_, player) in world.current_players.lock().await.iter() { for (_, player) in world.players.lock().await.iter() {
players.push(player.clone()); players.push(player.clone());
} }
} }
@ -441,7 +422,7 @@ impl Server {
pub async fn get_player_count(&self) -> usize { pub async fn get_player_count(&self) -> usize {
let mut count = 0; let mut count = 0;
for world in self.worlds.read().await.iter() { for world in self.worlds.read().await.iter() {
count += world.current_players.lock().await.len(); count += world.players.lock().await.len();
} }
count count
} }
@ -450,7 +431,7 @@ impl Server {
pub async fn has_n_players(&self, n: usize) -> bool { pub async fn has_n_players(&self, n: usize) -> bool {
let mut count = 0; let mut count = 0;
for world in self.worlds.read().await.iter() { for world in self.worlds.read().await.iter() {
count += world.current_players.lock().await.len(); count += world.players.lock().await.len();
if count >= n { if count >= n {
return true; return true;
} }

View File

@ -5,7 +5,7 @@ pub mod player_chunker;
use crate::{ use crate::{
command::client_cmd_suggestions, command::client_cmd_suggestions,
entity::{living::LivingEntity, mob::MobEntity, player::Player, Entity, EntityId}, entity::{player::Player, Entity, EntityBase, EntityId},
error::PumpkinError, error::PumpkinError,
plugin::{ plugin::{
block::BlockBreakEvent, block::BlockBreakEvent,
@ -17,19 +17,16 @@ use crate::{
use level_time::LevelTime; use level_time::LevelTime;
use pumpkin_config::BasicConfiguration; use pumpkin_config::BasicConfiguration;
use pumpkin_data::{ use pumpkin_data::{
entity::{EntityPose, EntityType}, entity::EntityType,
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
world::WorldEvent, world::WorldEvent,
}; };
use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}; use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent};
use pumpkin_protocol::{ use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier};
client::play::CLevelEvent,
codec::{identifier::Identifier, var_int::VarInt},
};
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{ client::play::{
CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, CSpawnEntity, GameEvent, PlayerAction,
}, },
ClientPacket, ClientPacket,
}; };
@ -99,9 +96,10 @@ pub struct World {
/// The underlying level, responsible for chunk management and terrain generation. /// The underlying level, responsible for chunk management and terrain generation.
pub level: Arc<Level>, pub level: Arc<Level>,
/// A map of active players within the world, keyed by their unique UUID. /// A map of active players within the world, keyed by their unique UUID.
pub current_players: Arc<Mutex<HashMap<uuid::Uuid, Arc<Player>>>>, pub players: Arc<Mutex<HashMap<uuid::Uuid, Arc<Player>>>>,
/// A map of active mob entities within the world, keyed by their unique UUID. /// A map of active entities within the world, keyed by their unique UUID.
pub current_living_mobs: Arc<Mutex<HashMap<uuid::Uuid, Arc<MobEntity>>>>, /// This does not include Players
pub entities: Arc<Mutex<HashMap<uuid::Uuid, Arc<dyn EntityBase>>>>,
/// The world's scoreboard, used for tracking scores, objectives, and display information. /// The world's scoreboard, used for tracking scores, objectives, and display information.
pub scoreboard: Mutex<Scoreboard>, pub scoreboard: Mutex<Scoreboard>,
/// The world's worldborder, defining the playable area and controlling its expansion or contraction. /// The world's worldborder, defining the playable area and controlling its expansion or contraction.
@ -118,8 +116,8 @@ impl World {
pub fn load(level: Level, dimension_type: DimensionType) -> Self { pub fn load(level: Level, dimension_type: DimensionType) -> Self {
Self { Self {
level: Arc::new(level), level: Arc::new(level),
current_players: Arc::new(Mutex::new(HashMap::new())), players: Arc::new(Mutex::new(HashMap::new())),
current_living_mobs: Arc::new(Mutex::new(HashMap::new())), entities: Arc::new(Mutex::new(HashMap::new())),
scoreboard: Mutex::new(Scoreboard::new()), scoreboard: Mutex::new(Scoreboard::new()),
worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)), worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)),
level_time: Mutex::new(LevelTime::new()), level_time: Mutex::new(LevelTime::new()),
@ -140,7 +138,7 @@ impl World {
where where
P: ClientPacket, P: ClientPacket,
{ {
let current_players = self.current_players.lock().await; let current_players = self.players.lock().await;
for player in current_players.values() { for player in current_players.values() {
player.client.send_packet(packet).await; player.client.send_packet(packet).await;
} }
@ -171,7 +169,7 @@ impl World {
where where
P: ClientPacket, P: ClientPacket,
{ {
let current_players = self.current_players.lock().await; let current_players = self.players.lock().await;
for (_, player) in current_players.iter().filter(|c| !except.contains(c.0)) { for (_, player) in current_players.iter().filter(|c| !except.contains(c.0)) {
player.client.send_packet(packet).await; player.client.send_packet(packet).await;
} }
@ -191,7 +189,7 @@ impl World {
pitch: f32, pitch: f32,
) { ) {
let seed = thread_rng().gen::<f64>(); let seed = thread_rng().gen::<f64>();
let players = self.current_players.lock().await; let players = self.players.lock().await;
for (_, player) in players.iter() { for (_, player) in players.iter() {
player player
.play_sound(sound_id, category, position, volume, pitch, seed) .play_sound(sound_id, category, position, volume, pitch, seed)
@ -233,11 +231,11 @@ impl World {
} }
} }
// player ticks // player ticks
for player in self.current_players.lock().await.values() { for player in self.players.lock().await.values() {
player.tick().await; player.tick().await;
} }
// entities tick // entities tick
for entity in self.current_living_mobs.lock().await.values() { for entity in self.entities.lock().await.values() {
entity.tick().await; entity.tick().await;
} }
} }
@ -339,12 +337,12 @@ impl World {
}], }],
)) ))
.await; .await;
player.update_client_information().await; player.send_client_information().await;
// here we send all the infos of already joined players // here we send all the infos of already joined players
let mut entries = Vec::new(); let mut entries = Vec::new();
{ {
let current_players = self.current_players.lock().await; let current_players = self.players.lock().await;
for (_, playerr) in current_players for (_, playerr) in current_players
.iter() .iter()
.filter(|(c, _)| **c != player.gameprofile.id) .filter(|(c, _)| **c != player.gameprofile.id)
@ -394,13 +392,7 @@ impl World {
.await; .await;
// spawn players for our client // spawn players for our client
let id = player.gameprofile.id; let id = player.gameprofile.id;
for (_, existing_player) in self for (_, existing_player) in self.players.lock().await.iter().filter(|c| c.0 != &id) {
.current_players
.lock()
.await
.iter()
.filter(|c| c.0 != &id)
{
let entity = &existing_player.living_entity.entity; let entity = &existing_player.living_entity.entity;
let pos = entity.pos.load(); let pos = entity.pos.load();
let gameprofile = &existing_player.gameprofile; let gameprofile = &existing_player.gameprofile;
@ -426,14 +418,7 @@ impl World {
} }
// entity meta data // entity meta data
// set skin parts // set skin parts
if let Some(config) = player.client.config.lock().await.as_ref() { player.send_client_information().await;
let packet = CSetEntityMetadata::new(
entity_id.into(),
Metadata::new(17, VarInt(0), config.skin_parts),
);
log::debug!("Broadcasting skin for {}", player.gameprofile.name);
self.broadcast_packet_all(&packet).await;
}
// Start waiting for level chunks, Sets the "Loading Terrain" screen // Start waiting for level chunks, Sets the "Loading Terrain" screen
log::debug!("Sending waiting chunks to {}", player.gameprofile.name); log::debug!("Sending waiting chunks to {}", player.gameprofile.name);
@ -530,11 +515,6 @@ impl World {
.await; .await;
let entity = &player.living_entity.entity; let entity = &player.living_entity.entity;
let entity_id = entity.entity_id;
let skin_parts = player.config.lock().await.skin_parts;
let entity_metadata_packet =
CSetEntityMetadata::new(entity_id.into(), Metadata::new(17, VarInt(0), &skin_parts));
self.broadcast_packet_except( self.broadcast_packet_except(
&[player.gameprofile.id], &[player.gameprofile.id],
@ -556,9 +536,9 @@ impl World {
), ),
) )
.await; .await;
player.send_client_information().await;
player_chunker::player_join(player).await; player_chunker::player_join(player).await;
self.broadcast_packet_all(&entity_metadata_packet).await;
// update commands // update commands
player.set_health(20.0, 20, 20.0).await; player.set_health(20.0, 20, 20.0).await;
@ -634,8 +614,8 @@ impl World {
} }
/// Gets a Player by entity id /// Gets a Player by entity id
pub async fn get_player_by_entityid(&self, id: EntityId) -> Option<Arc<Player>> { pub async fn get_player_by_id(&self, id: EntityId) -> Option<Arc<Player>> {
for player in self.current_players.lock().await.values() { for player in self.players.lock().await.values() {
if player.entity_id() == id { if player.entity_id() == id {
return Some(player.clone()); return Some(player.clone());
} }
@ -643,12 +623,11 @@ impl World {
None None
} }
/// Gets a Living Entity by entity id /// Gets a Entity by entity id
pub async fn get_living_entity_by_entityid(&self, id: EntityId) -> Option<Arc<LivingEntity>> { pub async fn get_entity_by_id(&self, id: EntityId) -> Option<Arc<dyn EntityBase>> {
for mob_entity in self.current_living_mobs.lock().await.values() { for entity in self.entities.lock().await.values() {
let living_entity = &mob_entity.living_entity; if entity.get_entity().entity_id == id {
if living_entity.entity_id() == id { return Some(entity.clone());
return Some(living_entity.clone());
} }
} }
None None
@ -656,7 +635,7 @@ impl World {
/// Gets a Player by username /// Gets a Player by username
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> { pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
for player in self.current_players.lock().await.values() { for player in self.players.lock().await.values() {
if player.gameprofile.name.to_lowercase() == name.to_lowercase() { if player.gameprofile.name.to_lowercase() == name.to_lowercase() {
return Some(player.clone()); return Some(player.clone());
} }
@ -677,7 +656,7 @@ impl World {
/// ///
/// An `Option<Arc<Player>>` containing the player if found, or `None` if not. /// An `Option<Arc<Player>>` containing the player if found, or `None` if not.
pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option<Arc<Player>> { pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option<Arc<Player>> {
return self.current_players.lock().await.get(&id).cloned(); return self.players.lock().await.get(&id).cloned();
} }
/// Gets a list of players who's location equals the given position in the world. /// Gets a list of players who's location equals the given position in the world.
@ -690,7 +669,7 @@ impl World {
/// ///
/// * `position`: The position the function will check. /// * `position`: The position the function will check.
pub async fn get_players_by_pos(&self, position: BlockPos) -> HashMap<uuid::Uuid, Arc<Player>> { pub async fn get_players_by_pos(&self, position: BlockPos) -> HashMap<uuid::Uuid, Arc<Player>> {
self.current_players self.players
.lock() .lock()
.await .await
.iter() .iter()
@ -720,7 +699,7 @@ impl World {
) -> HashMap<uuid::Uuid, Arc<Player>> { ) -> HashMap<uuid::Uuid, Arc<Player>> {
let radius_squared = radius.powi(2); let radius_squared = radius.powi(2);
self.current_players self.players
.lock() .lock()
.await .await
.iter() .iter()
@ -766,11 +745,11 @@ impl World {
/// * `player`: An `Arc<Player>` reference to the player object. /// * `player`: An `Arc<Player>` reference to the player object.
pub async fn add_player(&self, uuid: uuid::Uuid, player: Arc<Player>) { pub async fn add_player(&self, uuid: uuid::Uuid, player: Arc<Player>) {
{ {
let mut current_players = self.current_players.lock().await; let mut current_players = self.players.lock().await;
current_players.insert(uuid, player.clone()) current_players.insert(uuid, player.clone())
}; };
let current_players = self.current_players.clone(); let current_players = self.players.clone();
tokio::spawn(async move { tokio::spawn(async move {
let msg_comp = TextComponent::translate( let msg_comp = TextComponent::translate(
"multiplayer.player.joined", "multiplayer.player.joined",
@ -815,7 +794,7 @@ impl World {
/// - This function assumes `broadcast_packet_expect` and `remove_entity` are defined elsewhere. /// - This function assumes `broadcast_packet_expect` and `remove_entity` are defined elsewhere.
/// - The disconnect message sending is currently optional. Consider making it a configurable option. /// - The disconnect message sending is currently optional. Consider making it a configurable option.
pub async fn remove_player(&self, player: Arc<Player>) { pub async fn remove_player(&self, player: Arc<Player>) {
self.current_players self.players
.lock() .lock()
.await .await
.remove(&player.gameprofile.id) .remove(&player.gameprofile.id)
@ -842,7 +821,7 @@ impl World {
.await; .await;
if !event.cancelled { if !event.cancelled {
let players = self.current_players.lock().await; let players = self.players.lock().await;
for player in players.values() { for player in players.values() {
player.send_system_message(&event.leave_message).await; player.send_system_message(&event.leave_message).await;
} }
@ -859,25 +838,11 @@ impl World {
/// ///
/// * `uuid`: The unique UUID of the living entity to add. /// * `uuid`: The unique UUID of the living entity to add.
/// * `living_entity`: A `Arc<LivingEntity>` reference to the living entity object. /// * `living_entity`: A `Arc<LivingEntity>` reference to the living entity object.
pub async fn add_mob_entity(&self, uuid: uuid::Uuid, living_entity: Arc<MobEntity>) { pub async fn spawn_entity(&self, uuid: uuid::Uuid, entity: Arc<dyn EntityBase>) {
let mut current_living_entities = self.current_living_mobs.lock().await; self.broadcast_packet_all(&entity.get_entity().create_spawn_packet(uuid))
current_living_entities.insert(uuid, living_entity); .await;
} let mut current_living_entities = self.entities.lock().await;
current_living_entities.insert(uuid, entity);
pub async fn remove_mob_entity(self: Arc<Self>, living_entity: Arc<LivingEntity>) {
let mut current_living_entities = self.current_living_mobs.lock().await;
current_living_entities
.remove(&living_entity.entity.entity_uuid)
.unwrap();
// TODO: does this work with collisions?
living_entity.entity.set_pose(EntityPose::Dying).await;
let world = self.clone();
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await;
world.remove_entity(&living_entity.entity).await;
});
} }
pub async fn remove_entity(&self, entity: &Entity) { pub async fn remove_entity(&self, entity: &Entity) {