Refactor World Entity System
Also support Item drops (WIP)
This commit is contained in:
parent
8bb47b6ec7
commit
ef497fb435
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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,
|
||||
}
|
||||
|
||||
@ -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>,
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
37
pumpkin-world/src/loot/mod.rs
Normal file
37
pumpkin-world/src/loot/mod.rs
Normal 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,
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
38
pumpkin/src/entity/item.rs
Normal file
38
pumpkin/src/entity/item.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -490,7 +490,7 @@ impl Player {
|
||||
.living_entity
|
||||
.entity
|
||||
.world
|
||||
.current_players
|
||||
.players
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user