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> {
pub fn new(index: u8, typ: VarInt, value: T) -> Self {
Self { index, typ, value }
pub fn new(index: u8, typ: MetaDataType, value: T) -> Self {
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)]
struct ParticleOptions {
#[serde(rename = "type")]
typee: String,
r#type: String,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<VarInt>,
}

View File

@ -45,8 +45,7 @@ pub enum MonsterSpawnLightLevel {
pub struct MonsterSpawnLightLevelTagged {
min_inclusive: i32,
max_inclusive: i32,
#[serde(rename = "type")]
typee: String,
r#type: String,
}
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
id: Cow<'static, str>,
/// Resource identifier of the entity
#[serde(rename = "type")]
#[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
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Vec<TextComponentBase>>,
@ -45,7 +44,7 @@ impl HoverEvent {
{
Self::ShowEntity {
id: id.into(),
kind: kind.map(|kind| kind.into()),
r#type: kind.map(|kind| kind.into()),
name: match name {
Some(name) => Some(vec![name.0]),
None => None,

View File

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

View File

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

View File

@ -18,6 +18,7 @@ mod generation;
pub mod item;
pub mod level;
mod lock;
pub mod loot;
pub mod world_info;
pub const WORLD_HEIGHT: usize = 384;
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) {
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;
}
}

View File

@ -38,10 +38,7 @@ impl CommandExecutor for SummonExecutor {
if let Some(player) = sender.as_player() {
let pos = pos.unwrap_or(player.living_entity.entity.pos.load());
let (mob, uuid) = mob::from_type(entity, server, pos, player.world()).await;
player
.world()
.broadcast_packet_all(&mob.living_entity.entity.create_spawn_packet(uuid))
.await;
player.world().spawn_entity(uuid, mob).await;
sender
.send_message(TextComponent::translate(
"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 pumpkin_data::sound::Sound;
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 super::{Entity, EntityId, NBTStorage};
@ -58,11 +58,7 @@ impl LivingEntity {
self.health.store(health);
// tell everyone entities health changed
self.entity
.world
.broadcast_packet_all(&CSetEntityMetadata::new(
self.entity.entity_id.into(),
Metadata::new(9, 3.into(), health),
))
.send_meta_data(Metadata::new(9, MetaDataType::Float, health))
.await;
}

View File

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

View File

@ -3,6 +3,7 @@ use std::sync::{atomic::AtomicBool, Arc};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use living::LivingEntity;
use pumpkin_data::{
entity::{EntityPose, EntityType},
sound::{Sound, SoundCategory},
@ -10,7 +11,8 @@ use pumpkin_data::{
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_protocol::{
client::play::{
CHeadRot, CSetEntityMetadata, CSpawnEntity, CTeleportEntity, CUpdateEntityRot, Metadata,
CHeadRot, CSetEntityMetadata, CSpawnEntity, CTeleportEntity, CUpdateEntityRot,
MetaDataType, Metadata,
},
codec::var_int::VarInt,
};
@ -22,18 +24,26 @@ use pumpkin_util::math::{
vector3::Vector3,
wrap_degrees,
};
use serde::Serialize;
use uuid::Uuid;
use crate::world::World;
pub mod ai;
pub mod mob;
pub mod item;
pub mod living;
pub mod mob;
pub mod player;
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...)
pub struct Entity {
/// A unique identifier for the entity
@ -295,8 +305,8 @@ impl Entity {
} else {
b &= !(1 << index);
}
let packet = CSetEntityMetadata::new(self.entity_id.into(), Metadata::new(0, 0.into(), b));
self.world.broadcast_packet_all(&packet).await;
self.send_meta_data(Metadata::new(0, MetaDataType::Byte, b))
.await;
}
/// Plays sound at this entity's position with the entity's sound category
@ -306,14 +316,33 @@ impl Entity {
.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) {
self.pose.store(pose);
let pose = pose as i32;
let packet = CSetEntityMetadata::<VarInt>::new(
self.entity_id.into(),
Metadata::new(6, 21.into(), pose.into()),
);
self.world.broadcast_packet_all(&packet).await;
self.send_meta_data(Metadata::new(6, MetaDataType::EntityPose, pose))
.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::{
CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus, CGameEvent, CHurtAnimation,
CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition,
CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, GameEvent, PlayerAction,
CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, GameEvent, MetaDataType,
PlayerAction,
},
server::play::{
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::{CSetEntityMetadata, Metadata},
client::play::Metadata,
server::play::{SClickContainer, SKeepAlive},
};
use pumpkin_util::{
@ -63,7 +64,7 @@ use pumpkin_world::{
};
use tokio::sync::{Mutex, Notify, RwLock};
use super::{Entity, EntityId, NBTStorage};
use super::{item::ItemEntity, Entity, EntityId, NBTStorage};
use crate::{
command::{client_cmd_suggestions, dispatcher::CommandDispatcher},
data::op_data::OPERATOR_CONFIG,
@ -535,10 +536,10 @@ impl Player {
/// Sends the mobs to just the player.
// TODO: This should be optimized for larger servers based on current player chunk
pub async fn send_mobs(&self, world: &World) {
let mobs = world.current_living_mobs.lock().await.clone();
for (uuid, mob) in mobs {
let entities = world.entities.lock().await.clone();
for (uuid, entity) in entities {
self.client
.send_packet(&mob.living_entity.entity.create_spawn_packet(uuid))
.send_packet(&entity.get_entity().create_spawn_packet(uuid))
.await;
}
}
@ -681,19 +682,18 @@ impl Player {
}
/// 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 world = self.world();
world
.broadcast_packet_all(&CSetEntityMetadata::new(
self.entity_id().into(),
Metadata::new(17, 0.into(), config.skin_parts),
))
self.living_entity
.entity
.send_meta_data(Metadata::new(17, MetaDataType::Byte, config.skin_parts))
.await;
world
.broadcast_packet_all(&CSetEntityMetadata::new(
self.entity_id().into(),
Metadata::new(18, 0.into(), config.main_hand as u8),
self.living_entity
.entity
.send_meta_data(Metadata::new(
18,
MetaDataType::Byte,
config.main_hand as u8,
))
.await;
}
@ -715,6 +715,20 @@ impl Player {
.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) {
self.send_system_message_raw(text, false).await;
}

View File

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

View File

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

View File

@ -115,7 +115,7 @@ async fn handle_packet(
let mut players: Vec<CString> = Vec::new();
for world in server.worlds.read().await.iter() {
let mut world_players = world
.current_players
.players
.lock()
.await
// 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 player in world.current_players.lock().await.values() {
for player in world.players.lock().await.values() {
client_cmd_suggestions::send_c_commands_packet(
player,
&self.server.command_dispatcher,
@ -72,7 +72,7 @@ impl Context {
};
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(
player,
&self.server.command_dispatcher,

View File

@ -34,9 +34,6 @@ use uuid::Uuid;
use crate::block::block_manager::BlockManager;
use crate::block::properties::BlockPropertiesManager;
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::net::EncryptionError;
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
///
/// # Returns
@ -232,12 +213,12 @@ impl Server {
/// - `Arc<LivingEntity>`: A reference to the newly created living entity.
/// - `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.
fn add_living_entity(
pub fn add_entity(
&self,
position: Vector3<f64>,
entity_type: EntityType,
world: &Arc<World>,
) -> (Arc<LivingEntity>, Uuid) {
) -> (Entity, Uuid) {
let entity_id = self.new_entity_id();
// 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
let new_uuid = uuid::Uuid::new_v4();
let mob = Arc::new(LivingEntity::new(Entity::new(
let entity = Entity::new(
entity_id,
new_uuid,
world.clone(),
@ -263,9 +244,9 @@ impl Server {
1.62,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size),
)));
);
(mob, new_uuid)
(entity, new_uuid)
}
pub async fn try_get_container(
@ -380,7 +361,7 @@ impl Server {
let mut players = Vec::<Arc<Player>>::new();
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 {
players.push(player.clone());
}
@ -395,7 +376,7 @@ impl Server {
let mut players = Vec::<Arc<Player>>::new();
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());
}
}
@ -441,7 +422,7 @@ impl Server {
pub async fn get_player_count(&self) -> usize {
let mut count = 0;
for world in self.worlds.read().await.iter() {
count += world.current_players.lock().await.len();
count += world.players.lock().await.len();
}
count
}
@ -450,7 +431,7 @@ impl Server {
pub async fn has_n_players(&self, n: usize) -> bool {
let mut count = 0;
for world in self.worlds.read().await.iter() {
count += world.current_players.lock().await.len();
count += world.players.lock().await.len();
if count >= n {
return true;
}

View File

@ -5,7 +5,7 @@ pub mod player_chunker;
use crate::{
command::client_cmd_suggestions,
entity::{living::LivingEntity, mob::MobEntity, player::Player, Entity, EntityId},
entity::{player::Player, Entity, EntityBase, EntityId},
error::PumpkinError,
plugin::{
block::BlockBreakEvent,
@ -17,19 +17,16 @@ use crate::{
use level_time::LevelTime;
use pumpkin_config::BasicConfiguration;
use pumpkin_data::{
entity::{EntityPose, EntityType},
entity::EntityType,
sound::{Sound, SoundCategory},
world::WorldEvent,
};
use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent};
use pumpkin_protocol::{
client::play::CLevelEvent,
codec::{identifier::Identifier, var_int::VarInt},
};
use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier};
use pumpkin_protocol::{
client::play::{
CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction,
CSpawnEntity, GameEvent, PlayerAction,
},
ClientPacket,
};
@ -99,9 +96,10 @@ pub struct World {
/// The underlying level, responsible for chunk management and terrain generation.
pub level: Arc<Level>,
/// A map of active players within the world, keyed by their unique UUID.
pub current_players: Arc<Mutex<HashMap<uuid::Uuid, Arc<Player>>>>,
/// A map of active mob entities within the world, keyed by their unique UUID.
pub current_living_mobs: Arc<Mutex<HashMap<uuid::Uuid, Arc<MobEntity>>>>,
pub players: Arc<Mutex<HashMap<uuid::Uuid, Arc<Player>>>>,
/// A map of active entities within the world, keyed by their unique UUID.
/// 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.
pub scoreboard: Mutex<Scoreboard>,
/// 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 {
Self {
level: Arc::new(level),
current_players: Arc::new(Mutex::new(HashMap::new())),
current_living_mobs: Arc::new(Mutex::new(HashMap::new())),
players: Arc::new(Mutex::new(HashMap::new())),
entities: Arc::new(Mutex::new(HashMap::new())),
scoreboard: Mutex::new(Scoreboard::new()),
worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)),
level_time: Mutex::new(LevelTime::new()),
@ -140,7 +138,7 @@ impl World {
where
P: ClientPacket,
{
let current_players = self.current_players.lock().await;
let current_players = self.players.lock().await;
for player in current_players.values() {
player.client.send_packet(packet).await;
}
@ -171,7 +169,7 @@ impl World {
where
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)) {
player.client.send_packet(packet).await;
}
@ -191,7 +189,7 @@ impl World {
pitch: f32,
) {
let seed = thread_rng().gen::<f64>();
let players = self.current_players.lock().await;
let players = self.players.lock().await;
for (_, player) in players.iter() {
player
.play_sound(sound_id, category, position, volume, pitch, seed)
@ -233,11 +231,11 @@ impl World {
}
}
// player ticks
for player in self.current_players.lock().await.values() {
for player in self.players.lock().await.values() {
player.tick().await;
}
// entities tick
for entity in self.current_living_mobs.lock().await.values() {
for entity in self.entities.lock().await.values() {
entity.tick().await;
}
}
@ -339,12 +337,12 @@ impl World {
}],
))
.await;
player.update_client_information().await;
player.send_client_information().await;
// here we send all the infos of already joined players
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
.iter()
.filter(|(c, _)| **c != player.gameprofile.id)
@ -394,13 +392,7 @@ impl World {
.await;
// spawn players for our client
let id = player.gameprofile.id;
for (_, existing_player) in self
.current_players
.lock()
.await
.iter()
.filter(|c| c.0 != &id)
{
for (_, existing_player) in self.players.lock().await.iter().filter(|c| c.0 != &id) {
let entity = &existing_player.living_entity.entity;
let pos = entity.pos.load();
let gameprofile = &existing_player.gameprofile;
@ -426,14 +418,7 @@ impl World {
}
// entity meta data
// set skin parts
if let Some(config) = player.client.config.lock().await.as_ref() {
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;
}
player.send_client_information().await;
// Start waiting for level chunks, Sets the "Loading Terrain" screen
log::debug!("Sending waiting chunks to {}", player.gameprofile.name);
@ -530,11 +515,6 @@ impl World {
.await;
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(
&[player.gameprofile.id],
@ -556,9 +536,9 @@ impl World {
),
)
.await;
player.send_client_information().await;
player_chunker::player_join(player).await;
self.broadcast_packet_all(&entity_metadata_packet).await;
// update commands
player.set_health(20.0, 20, 20.0).await;
@ -634,8 +614,8 @@ impl World {
}
/// Gets a Player by entity id
pub async fn get_player_by_entityid(&self, id: EntityId) -> Option<Arc<Player>> {
for player in self.current_players.lock().await.values() {
pub async fn get_player_by_id(&self, id: EntityId) -> Option<Arc<Player>> {
for player in self.players.lock().await.values() {
if player.entity_id() == id {
return Some(player.clone());
}
@ -643,12 +623,11 @@ impl World {
None
}
/// Gets a Living Entity by entity id
pub async fn get_living_entity_by_entityid(&self, id: EntityId) -> Option<Arc<LivingEntity>> {
for mob_entity in self.current_living_mobs.lock().await.values() {
let living_entity = &mob_entity.living_entity;
if living_entity.entity_id() == id {
return Some(living_entity.clone());
/// Gets a Entity by entity id
pub async fn get_entity_by_id(&self, id: EntityId) -> Option<Arc<dyn EntityBase>> {
for entity in self.entities.lock().await.values() {
if entity.get_entity().entity_id == id {
return Some(entity.clone());
}
}
None
@ -656,7 +635,7 @@ impl World {
/// Gets a Player by username
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() {
return Some(player.clone());
}
@ -677,7 +656,7 @@ impl World {
///
/// 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>> {
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.
@ -690,7 +669,7 @@ impl World {
///
/// * `position`: The position the function will check.
pub async fn get_players_by_pos(&self, position: BlockPos) -> HashMap<uuid::Uuid, Arc<Player>> {
self.current_players
self.players
.lock()
.await
.iter()
@ -720,7 +699,7 @@ impl World {
) -> HashMap<uuid::Uuid, Arc<Player>> {
let radius_squared = radius.powi(2);
self.current_players
self.players
.lock()
.await
.iter()
@ -766,11 +745,11 @@ impl World {
/// * `player`: An `Arc<Player>` reference to the player object.
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())
};
let current_players = self.current_players.clone();
let current_players = self.players.clone();
tokio::spawn(async move {
let msg_comp = TextComponent::translate(
"multiplayer.player.joined",
@ -815,7 +794,7 @@ impl World {
/// - 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.
pub async fn remove_player(&self, player: Arc<Player>) {
self.current_players
self.players
.lock()
.await
.remove(&player.gameprofile.id)
@ -842,7 +821,7 @@ impl World {
.await;
if !event.cancelled {
let players = self.current_players.lock().await;
let players = self.players.lock().await;
for player in players.values() {
player.send_system_message(&event.leave_message).await;
}
@ -859,25 +838,11 @@ impl World {
///
/// * `uuid`: The unique UUID of the living entity to add.
/// * `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>) {
let mut current_living_entities = self.current_living_mobs.lock().await;
current_living_entities.insert(uuid, living_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 spawn_entity(&self, uuid: uuid::Uuid, entity: Arc<dyn EntityBase>) {
self.broadcast_packet_all(&entity.get_entity().create_spawn_packet(uuid))
.await;
let mut current_living_entities = self.entities.lock().await;
current_living_entities.insert(uuid, entity);
}
pub async fn remove_entity(&self, entity: &Entity) {