add basic entity NBT storage

This commit is contained in:
Alexander Medvedev
2025-01-19 19:30:32 +01:00
parent 88576891d9
commit 709d222c23
21 changed files with 228 additions and 83 deletions

1
assets/attributes.json Normal file
View File

@@ -0,0 +1 @@
{"armor":0.0,"armor_toughness":0.0,"attack_damage":2.0,"attack_knockback":0.0,"attack_speed":4.0,"block_break_speed":1.0,"block_interaction_range":4.5,"burning_time":1.0,"explosion_knockback_resistance":0.0,"entity_interaction_range":3.0,"fall_damage_multiplier":1.0,"flying_speed":0.4,"follow_range":32.0,"gravity":0.08,"jump_strength":0.41999998688697815,"knockback_resistance":0.0,"luck":0.0,"max_absorption":0.0,"max_health":20.0,"mining_efficiency":0.0,"movement_efficiency":0.0,"movement_speed":0.7,"oxygen_bonus":0.0,"safe_fall_distance":3.0,"scale":1.0,"sneaking_speed":0.3,"spawn_reinforcements":0.0,"step_height":0.6,"submerged_mining_speed":0.2,"sweeping_damage_ratio":0.0,"tempt_range":10.0,"water_movement_efficiency":0.0}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"chat":{"data":{"chat":{"translation_key":"chat.type.text","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}},"emote_command":{"data":{"chat":{"translation_key":"chat.type.emote","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.emote","parameters":["sender","content"]}}},"msg_command_incoming":{"data":{"chat":{"translation_key":"commands.message.display.incoming","parameters":["sender","content"],"style":{"italic":true,"color":"gray"}},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}},"msg_command_outgoing":{"data":{"chat":{"translation_key":"commands.message.display.outgoing","parameters":["target","content"],"style":{"italic":true,"color":"gray"}},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}},"say_command":{"data":{"chat":{"translation_key":"chat.type.announcement","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}},"team_msg_command_incoming":{"data":{"chat":{"translation_key":"chat.type.team.text","parameters":["target","sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}},"team_msg_command_outgoing":{"data":{"chat":{"translation_key":"chat.type.team.sent","parameters":["target","sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}}}
{"chat":{"chat":{"translation_key":"chat.type.text","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}},"emote_command":{"chat":{"translation_key":"chat.type.emote","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.emote","parameters":["sender","content"]}},"msg_command_incoming":{"chat":{"translation_key":"commands.message.display.incoming","parameters":["sender","content"],"style":{"italic":true,"color":"gray"}},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}},"msg_command_outgoing":{"chat":{"translation_key":"commands.message.display.outgoing","parameters":["target","content"],"style":{"italic":true,"color":"gray"}},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}},"say_command":{"chat":{"translation_key":"chat.type.announcement","parameters":["sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}},"team_msg_command_incoming":{"chat":{"translation_key":"chat.type.team.text","parameters":["target","sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}},"team_msg_command_outgoing":{"chat":{"translation_key":"chat.type.team.sent","parameters":["target","sender","content"]},"narration":{"translation_key":"chat.type.text.narrate","parameters":["sender","content"]}}}

View File

@@ -1 +1 @@
{"aquifer_barrier":{"first_octave":-3,"amplitudes":[1.0]},"aquifer_fluid_level_floodedness":{"first_octave":-7,"amplitudes":[1.0]},"aquifer_fluid_level_spread":{"first_octave":-5,"amplitudes":[1.0]},"aquifer_lava":{"first_octave":-1,"amplitudes":[1.0]},"badlands_pillar":{"first_octave":-2,"amplitudes":[1.0,1.0,1.0,1.0]},"badlands_pillar_roof":{"first_octave":-8,"amplitudes":[1.0]},"badlands_surface":{"first_octave":-6,"amplitudes":[1.0,1.0,1.0]},"calcite":{"first_octave":-9,"amplitudes":[1.0,1.0,1.0,1.0]},"cave_cheese":{"first_octave":-8,"amplitudes":[0.5,1.0,2.0,1.0,2.0,1.0,0.0,2.0,0.0]},"cave_entrance":{"first_octave":-7,"amplitudes":[0.4,0.5,1.0]},"cave_layer":{"first_octave":-8,"amplitudes":[1.0]},"clay_bands_offset":{"first_octave":-8,"amplitudes":[1.0]},"continentalness":{"first_octave":-9,"amplitudes":[1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0]},"continentalness_large":{"first_octave":-11,"amplitudes":[1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0]},"erosion":{"first_octave":-9,"amplitudes":[1.0,1.0,0.0,1.0,1.0]},"erosion_large":{"first_octave":-11,"amplitudes":[1.0,1.0,0.0,1.0,1.0]},"gravel":{"first_octave":-8,"amplitudes":[1.0,1.0,1.0,1.0]},"gravel_layer":{"first_octave":-8,"amplitudes":[1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"ice":{"first_octave":-4,"amplitudes":[1.0,1.0,1.0,1.0]},"iceberg_pillar":{"first_octave":-6,"amplitudes":[1.0,1.0,1.0,1.0]},"iceberg_pillar_roof":{"first_octave":-3,"amplitudes":[1.0]},"iceberg_surface":{"first_octave":-6,"amplitudes":[1.0,1.0,1.0]},"jagged":{"first_octave":-16,"amplitudes":[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]},"nether_state_selector":{"first_octave":-4,"amplitudes":[1.0]},"nether_wart":{"first_octave":-3,"amplitudes":[1.0,0.0,0.0,0.9]},"netherrack":{"first_octave":-3,"amplitudes":[1.0,0.0,0.0,0.35]},"noodle":{"first_octave":-8,"amplitudes":[1.0]},"noodle_ridge_a":{"first_octave":-7,"amplitudes":[1.0]},"noodle_ridge_b":{"first_octave":-7,"amplitudes":[1.0]},"noodle_thickness":{"first_octave":-8,"amplitudes":[1.0]},"offset":{"first_octave":-3,"amplitudes":[1.0,1.0,1.0,0.0]},"ore_gap":{"first_octave":-5,"amplitudes":[1.0]},"ore_vein_a":{"first_octave":-7,"amplitudes":[1.0]},"ore_vein_b":{"first_octave":-7,"amplitudes":[1.0]},"ore_veininess":{"first_octave":-8,"amplitudes":[1.0]},"packed_ice":{"first_octave":-7,"amplitudes":[1.0,1.0,1.0,1.0]},"patch":{"first_octave":-5,"amplitudes":[1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"pillar":{"first_octave":-7,"amplitudes":[1.0,1.0]},"pillar_rareness":{"first_octave":-8,"amplitudes":[1.0]},"pillar_thickness":{"first_octave":-8,"amplitudes":[1.0]},"powder_snow":{"first_octave":-6,"amplitudes":[1.0,1.0,1.0,1.0]},"ridge":{"first_octave":-7,"amplitudes":[1.0,2.0,1.0,0.0,0.0,0.0]},"soul_sand_layer":{"first_octave":-8,"amplitudes":[1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"spaghetti_2d":{"first_octave":-7,"amplitudes":[1.0]},"spaghetti_2d_elevation":{"first_octave":-8,"amplitudes":[1.0]},"spaghetti_2d_modulator":{"first_octave":-11,"amplitudes":[1.0]},"spaghetti_2d_thickness":{"first_octave":-11,"amplitudes":[1.0]},"spaghetti_3d_1":{"first_octave":-7,"amplitudes":[1.0]},"spaghetti_3d_2":{"first_octave":-7,"amplitudes":[1.0]},"spaghetti_3d_rarity":{"first_octave":-11,"amplitudes":[1.0]},"spaghetti_3d_thickness":{"first_octave":-8,"amplitudes":[1.0]},"spaghetti_roughness":{"first_octave":-5,"amplitudes":[1.0]},"spaghetti_roughness_modulator":{"first_octave":-8,"amplitudes":[1.0]},"surface":{"first_octave":-6,"amplitudes":[1.0,1.0,1.0]},"surface_secondary":{"first_octave":-6,"amplitudes":[1.0,1.0,0.0,1.0]},"surface_swamp":{"first_octave":-2,"amplitudes":[1.0]},"temperature":{"first_octave":-10,"amplitudes":[1.5,0.0,1.0,0.0,0.0,0.0]},"temperature_large":{"first_octave":-12,"amplitudes":[1.5,0.0,1.0,0.0,0.0,0.0]},"vegetation":{"first_octave":-8,"amplitudes":[1.0,1.0,0.0,0.0,0.0,0.0]},"vegetation_large":{"first_octave":-10,"amplitudes":[1.0,1.0,0.0,0.0,0.0,0.0]}}
{"aquifer_barrier":{"firstOctave":-3,"amplitudes":[1.0]},"aquifer_fluid_level_floodedness":{"firstOctave":-7,"amplitudes":[1.0]},"aquifer_fluid_level_spread":{"firstOctave":-5,"amplitudes":[1.0]},"aquifer_lava":{"firstOctave":-1,"amplitudes":[1.0]},"badlands_pillar":{"firstOctave":-2,"amplitudes":[1.0,1.0,1.0,1.0]},"badlands_pillar_roof":{"firstOctave":-8,"amplitudes":[1.0]},"badlands_surface":{"firstOctave":-6,"amplitudes":[1.0,1.0,1.0]},"calcite":{"firstOctave":-9,"amplitudes":[1.0,1.0,1.0,1.0]},"cave_cheese":{"firstOctave":-8,"amplitudes":[0.5,1.0,2.0,1.0,2.0,1.0,0.0,2.0,0.0]},"cave_entrance":{"firstOctave":-7,"amplitudes":[0.4,0.5,1.0]},"cave_layer":{"firstOctave":-8,"amplitudes":[1.0]},"clay_bands_offset":{"firstOctave":-8,"amplitudes":[1.0]},"continentalness":{"firstOctave":-9,"amplitudes":[1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0]},"continentalness_large":{"firstOctave":-11,"amplitudes":[1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0]},"erosion":{"firstOctave":-9,"amplitudes":[1.0,1.0,0.0,1.0,1.0]},"erosion_large":{"firstOctave":-11,"amplitudes":[1.0,1.0,0.0,1.0,1.0]},"gravel":{"firstOctave":-8,"amplitudes":[1.0,1.0,1.0,1.0]},"gravel_layer":{"firstOctave":-8,"amplitudes":[1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"ice":{"firstOctave":-4,"amplitudes":[1.0,1.0,1.0,1.0]},"iceberg_pillar":{"firstOctave":-6,"amplitudes":[1.0,1.0,1.0,1.0]},"iceberg_pillar_roof":{"firstOctave":-3,"amplitudes":[1.0]},"iceberg_surface":{"firstOctave":-6,"amplitudes":[1.0,1.0,1.0]},"jagged":{"firstOctave":-16,"amplitudes":[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]},"nether_state_selector":{"firstOctave":-4,"amplitudes":[1.0]},"nether_wart":{"firstOctave":-3,"amplitudes":[1.0,0.0,0.0,0.9]},"netherrack":{"firstOctave":-3,"amplitudes":[1.0,0.0,0.0,0.35]},"noodle":{"firstOctave":-8,"amplitudes":[1.0]},"noodle_ridge_a":{"firstOctave":-7,"amplitudes":[1.0]},"noodle_ridge_b":{"firstOctave":-7,"amplitudes":[1.0]},"noodle_thickness":{"firstOctave":-8,"amplitudes":[1.0]},"offset":{"firstOctave":-3,"amplitudes":[1.0,1.0,1.0,0.0]},"ore_gap":{"firstOctave":-5,"amplitudes":[1.0]},"ore_vein_a":{"firstOctave":-7,"amplitudes":[1.0]},"ore_vein_b":{"firstOctave":-7,"amplitudes":[1.0]},"ore_veininess":{"firstOctave":-8,"amplitudes":[1.0]},"packed_ice":{"firstOctave":-7,"amplitudes":[1.0,1.0,1.0,1.0]},"patch":{"firstOctave":-5,"amplitudes":[1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"pillar":{"firstOctave":-7,"amplitudes":[1.0,1.0]},"pillar_rareness":{"firstOctave":-8,"amplitudes":[1.0]},"pillar_thickness":{"firstOctave":-8,"amplitudes":[1.0]},"powder_snow":{"firstOctave":-6,"amplitudes":[1.0,1.0,1.0,1.0]},"ridge":{"firstOctave":-7,"amplitudes":[1.0,2.0,1.0,0.0,0.0,0.0]},"soul_sand_layer":{"firstOctave":-8,"amplitudes":[1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.013333333333333334]},"spaghetti_2d":{"firstOctave":-7,"amplitudes":[1.0]},"spaghetti_2d_elevation":{"firstOctave":-8,"amplitudes":[1.0]},"spaghetti_2d_modulator":{"firstOctave":-11,"amplitudes":[1.0]},"spaghetti_2d_thickness":{"firstOctave":-11,"amplitudes":[1.0]},"spaghetti_3d_1":{"firstOctave":-7,"amplitudes":[1.0]},"spaghetti_3d_2":{"firstOctave":-7,"amplitudes":[1.0]},"spaghetti_3d_rarity":{"firstOctave":-11,"amplitudes":[1.0]},"spaghetti_3d_thickness":{"firstOctave":-8,"amplitudes":[1.0]},"spaghetti_roughness":{"firstOctave":-5,"amplitudes":[1.0]},"spaghetti_roughness_modulator":{"firstOctave":-8,"amplitudes":[1.0]},"surface":{"firstOctave":-6,"amplitudes":[1.0,1.0,1.0]},"surface_secondary":{"firstOctave":-6,"amplitudes":[1.0,1.0,0.0,1.0]},"surface_swamp":{"firstOctave":-2,"amplitudes":[1.0]},"temperature":{"firstOctave":-10,"amplitudes":[1.5,0.0,1.0,0.0,0.0,0.0]},"temperature_large":{"firstOctave":-12,"amplitudes":[1.5,0.0,1.0,0.0,0.0,0.0]},"vegetation":{"firstOctave":-8,"amplitudes":[1.0,1.0,0.0,0.0,0.0,0.0]},"vegetation_large":{"firstOctave":-10,"amplitudes":[1.0,1.0,0.0,0.0,0.0,0.0]}}

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,7 @@ use crate::ident;
#[derive(Deserialize)]
pub struct DoublePerlinNoiseParameters {
#[serde(rename = "firstOctave")]
first_octave: i32,
amplitudes: Vec<f64>,
}

View File

@@ -70,7 +70,7 @@ impl OpenContainer {
}
pub fn clear_all_players(&mut self) {
self.players = vec![];
self.players.clear();
}
pub fn all_player_ids(&self) -> Vec<i32> {

View File

@@ -13,7 +13,7 @@ pub struct PlayerInventory {
armor: [Option<ItemStack>; 4],
offhand: Option<ItemStack>,
// current selected slot in hotbar
selected: usize,
pub selected: u32,
pub state_id: u32,
// Notchian server wraps this value at 100, we can just keep it as a u8 that automatically wraps
pub total_opened_containers: i32,
@@ -101,23 +101,23 @@ impl PlayerInventory {
_ => Err(InventoryError::InvalidSlot),
}
}
pub fn set_selected(&mut self, slot: usize) {
pub fn set_selected(&mut self, slot: u32) {
assert!((0..9).contains(&slot));
self.selected = slot;
}
pub fn get_selected(&self) -> usize {
pub fn get_selected(&self) -> u32 {
self.selected + 36
}
pub fn held_item(&self) -> Option<&ItemStack> {
debug_assert!((0..9).contains(&self.selected));
self.items[self.selected + 36 - 9].as_ref()
self.items[self.selected as usize + 36 - 9].as_ref()
}
pub fn held_item_mut(&mut self) -> &mut Option<ItemStack> {
debug_assert!((0..9).contains(&self.selected));
&mut self.items[self.selected + 36 - 9]
&mut self.items[self.selected as usize + 36 - 9]
}
pub fn get_slot_with_item(&self, item_id: u16) -> Option<usize> {
@@ -131,14 +131,14 @@ impl PlayerInventory {
None
}
pub fn get_pick_item_hotbar_slot(&self) -> usize {
if self.items[self.selected + 36 - 9].is_none() {
pub fn get_pick_item_hotbar_slot(&self) -> u32 {
if self.items[self.selected as usize + 36 - 9].is_none() {
return self.selected;
}
for slot in 0..9 {
if self.items[slot + 36 - 9].is_none() {
return slot;
return slot as u32;
}
}

View File

@@ -28,7 +28,7 @@ impl NbtCompound {
let name = get_nbt_string(bytes).map_err(|_| Error::Cesu8DecodingError)?;
if let Ok(tag) = NbtTag::deserialize_data(bytes, tag_id) {
compound.put(name, tag);
compound.put(&name, tag);
} else {
break;
}
@@ -59,12 +59,44 @@ impl NbtCompound {
Ok(())
}
pub fn put(&mut self, name: String, value: impl Into<NbtTag>) {
pub fn put(&mut self, name: &str, value: impl Into<NbtTag>) {
let name = name.to_string();
if !self.child_tags.iter().any(|(key, _)| key == &name) {
self.child_tags.push((name, value.into()));
}
}
pub fn put_byte(&mut self, name: &str, value: i8) {
self.put(name, NbtTag::Byte(value));
}
pub fn put_bool(&mut self, name: &str, value: bool) {
self.put(name, NbtTag::Byte(if value { 1 } else { 0 }));
}
pub fn put_short(&mut self, name: &str, value: i16) {
self.put(name, NbtTag::Short(value));
}
pub fn put_int(&mut self, name: &str, value: i32) {
self.put(name, NbtTag::Int(value));
}
pub fn put_long(&mut self, name: &str, value: i64) {
self.put(name, NbtTag::Long(value));
}
pub fn put_float(&mut self, name: &str, value: f32) {
self.put(name, NbtTag::Float(value));
}
pub fn put_double(&mut self, name: &str, value: f64) {
self.put(name, NbtTag::Double(value));
}
pub fn put_component(&mut self, name: &str, value: NbtCompound) {
self.put(name, NbtTag::Compound(value));
}
pub fn get_byte(&self, name: &str) -> Option<i8> {
self.get(name).and_then(|tag| tag.extract_byte())
}
@@ -134,7 +166,7 @@ impl FromIterator<(String, NbtTag)> for NbtCompound {
fn from_iter<T: IntoIterator<Item = (String, NbtTag)>>(iter: T) -> Self {
let mut compound = NbtCompound::new();
for (key, value) in iter {
compound.put(key, value);
compound.put(&key, value);
}
compound
}

View File

@@ -68,7 +68,7 @@ where
value.serialize(&mut serializer)?;
Ok(serializer.output)
}
/// Serializes struct using Serde Serializer to unamed NBT
pub fn to_writer_unnamed<T, W>(value: &T, mut writer: W) -> Result<()>
where
T: Serialize,
@@ -112,7 +112,6 @@ impl ser::Serializer for &mut Serializer {
type SerializeStruct = Self;
type SerializeStructVariant = Impossible<(), Error>;
// NBT doesn't have bool type, but it's most commonly represented as a byte
fn serialize_bool(self, v: bool) -> Result<()> {
self.serialize_i8(v as i8)?;
Ok(())
@@ -202,7 +201,7 @@ impl ser::Serializer for &mut Serializer {
Ok(())
}
// Just skip serializing, if value is none
// Just skip serializing if value is none
fn serialize_none(self) -> Result<()> {
Ok(())
}

View File

@@ -270,6 +270,18 @@ impl From<&[u8]> for NbtTag {
}
}
impl From<f32> for NbtTag {
fn from(value: f32) -> Self {
NbtTag::Float(value)
}
}
impl From<f64> for NbtTag {
fn from(value: f64) -> Self {
NbtTag::Double(value)
}
}
impl From<bool> for NbtTag {
fn from(value: bool) -> Self {
NbtTag::Byte(value as i8)

View File

@@ -28,5 +28,5 @@ bytes.workspace = true
aes = "0.8.4"
cfb8 = "0.8.1"
# decryption
# compression
libdeflater = "1.23.0"

View File

@@ -15,11 +15,11 @@ pub struct CSetContainerSlot<'a> {
}
impl<'a> CSetContainerSlot<'a> {
pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: &'a Slot) -> Self {
pub fn new(window_id: i8, state_id: i32, slot: i16, slot_data: &'a Slot) -> Self {
Self {
window_id,
state_id: state_id.into(),
slot: slot.try_into().unwrap(),
slot,
slot_data,
}
}

View File

@@ -11,5 +11,5 @@ uuid.workspace = true
num-traits = "0.2"
colored = "2"
colored = "3"
md5 = "0.7.0"

View File

@@ -14,6 +14,7 @@ LegalCopyright = "Copyright © 2025 Aleksander Medvedev"
[dependencies]
# pumpkin
pumpkin-util = { path = "../pumpkin-util" }
pumpkin-nbt = { path = "../pumpkin-nbt" }
pumpkin-config = { path = "../pumpkin-config" }
pumpkin-inventory = { path = "../pumpkin-inventory" }
pumpkin-world = { path = "../pumpkin-world" }
@@ -72,4 +73,4 @@ async-trait = "0.1.83"
git-version = "0.3.9"
# This makes it so the entire project doesn't recompile on each build on linux.
[target.'cfg(target_os = "windows")'.build-dependencies]
tauri-winres= "0.1.1"
tauri-winres= "0.2.0"

View File

@@ -1,20 +1,17 @@
use std::sync::atomic::AtomicI32;
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_inventory::{Container, EmptyContainer};
use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::client::play::{CDamageEvent, CEntityStatus, CSetEntityMetadata, Metadata};
use pumpkin_util::math::vector3::Vector3;
use tokio::sync::Mutex;
use super::{Entity, EntityId};
use super::{Entity, EntityId, NBTStorage};
/// Represents a living entity within the game world.
///
/// This struct encapsulates the core properties and behaviors of living entities, including players, mobs, and other creatures.
pub struct LivingEntity<C = EmptyContainer>
where
C: Container,
{
pub struct LivingEntity {
/// The underlying entity object, providing basic entity information and functionality.
pub entity: Entity,
/// Previously last known position of the entity
@@ -27,8 +24,6 @@ where
pub health: AtomicCell<f32>,
/// The distance the entity has been falling
pub fall_distance: AtomicCell<f64>,
/// Inventory if it exists on the entity
pub inventory: Option<Mutex<C>>,
}
impl LivingEntity {
pub const fn new(entity: Entity) -> Self {
@@ -39,21 +34,6 @@ impl LivingEntity {
last_damage_taken: AtomicCell::new(0.0),
health: AtomicCell::new(20.0),
fall_distance: AtomicCell::new(0.0),
// This automatically gets inferred as Option::<EmptyContainer>::None
inventory: None,
}
}
}
impl<C: Container> LivingEntity<C> {
pub fn new_with_container(entity: Entity, inventory: C) -> Self {
Self {
entity,
last_pos: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)),
time_until_regen: AtomicI32::new(0),
last_damage_taken: AtomicCell::new(0.0),
health: AtomicCell::new(20.0),
fall_distance: AtomicCell::new(0.0),
inventory: Some(Mutex::new(inventory)),
}
}
@@ -182,3 +162,17 @@ impl<C: Container> LivingEntity<C> {
.await;
}
}
#[async_trait]
impl NBTStorage for LivingEntity {
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
self.entity.write_nbt(nbt).await;
nbt.put("Health", NbtTag::Float(self.health.load()));
// todo more...
}
async fn read_nbt(&mut self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
self.entity.read_nbt(nbt).await;
self.health.store(nbt.get_float("Health").unwrap_or(0.0));
// todo more...
}
}

View File

@@ -1,8 +1,10 @@
use core::f32;
use std::sync::{atomic::AtomicBool, Arc};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_data::entity::{EntityPose, EntityType};
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_protocol::{
client::play::{CHeadRot, CSetEntityMetadata, CTeleportEntity, CUpdateEntityRot, Metadata},
codec::var_int::VarInt,
@@ -282,6 +284,63 @@ impl Entity {
}
}
#[async_trait]
impl NBTStorage for Entity {
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
let position = self.pos.load();
nbt.put(
"Pos",
NbtTag::List(vec![
position.x.into(),
position.y.into(),
position.z.into(),
]),
);
let velocity = self.velocity.load();
nbt.put(
"Motion",
NbtTag::List(vec![
velocity.x.into(),
velocity.y.into(),
velocity.z.into(),
]),
);
nbt.put(
"Rotation",
NbtTag::List(vec![self.yaw.load().into(), self.pitch.load().into()]),
);
// todo more...
}
async fn read_nbt(&mut self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
let position = nbt.get_list("Pos").unwrap();
let x = position[0].extract_double().unwrap_or(0.0);
let y = position[1].extract_double().unwrap_or(0.0);
let z = position[2].extract_double().unwrap_or(0.0);
self.pos.store(Vector3::new(x, y, z));
let velocity = nbt.get_list("Motion").unwrap();
let x = velocity[0].extract_double().unwrap_or(0.0);
let y = velocity[1].extract_double().unwrap_or(0.0);
let z = velocity[2].extract_double().unwrap_or(0.0);
self.velocity.store(Vector3::new(x, y, z));
let rotation = nbt.get_list("Rotation").unwrap();
let yaw = rotation[0].extract_float().unwrap_or(0.0);
let pitch = rotation[1].extract_float().unwrap_or(0.0);
self.yaw.store(yaw);
self.pitch.store(pitch);
// todo more...
}
}
#[async_trait]
pub trait NBTStorage: Send + Sync {
async fn write_nbt(&self, nbt: &mut NbtCompound);
async fn read_nbt(&mut self, nbt: &mut NbtCompound);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// Represents various entity flags that are sent in entity metadata.
///

View File

@@ -7,6 +7,7 @@ use std::{
time::{Duration, Instant},
};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_data::{
@@ -14,6 +15,7 @@ use pumpkin_data::{
sound::{Sound, SoundCategory},
};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::{
bytebuf::packet_id::Packet,
client::play::{
@@ -61,7 +63,7 @@ use pumpkin_world::{
};
use tokio::sync::{Mutex, Notify, RwLock};
use super::{Entity, EntityId};
use super::{Entity, EntityId, NBTStorage};
use crate::{
command::{client_cmd_suggestions, dispatcher::CommandDispatcher},
data::op_data::OPERATOR_CONFIG,
@@ -81,11 +83,13 @@ use super::living::LivingEntity;
/// A `Player` is a special type of entity that represents a human player connected to the server.
pub struct Player {
/// The underlying living entity object that represents the player.
pub living_entity: LivingEntity<PlayerInventory>,
pub living_entity: LivingEntity,
/// The player's game profile information, including their username and UUID.
pub gameprofile: GameProfile,
/// The client connection associated with the player.
pub client: Arc<Client>,
/// Players Inventory
pub inventory: Mutex<PlayerInventory>,
/// The player's configuration settings. Changes when the Player changes their settings.
pub config: Mutex<PlayerConfig>,
/// The player's current gamemode (e.g., Survival, Creative, Adventure).
@@ -162,19 +166,16 @@ impl Player {
};
Self {
living_entity: LivingEntity::new_with_container(
Entity::new(
entity_id,
player_uuid,
world,
Vector3::new(0.0, 0.0, 0.0),
EntityType::Player,
1.62,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size),
),
PlayerInventory::new(),
),
living_entity: LivingEntity::new(Entity::new(
entity_id,
player_uuid,
world,
Vector3::new(0.0, 0.0, 0.0),
EntityType::Player,
1.62,
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
AtomicCell::new(bounding_box_size),
)),
config: Mutex::new(config),
gameprofile,
client,
@@ -214,14 +215,12 @@ impl Player {
AtomicCell::new(ADVANCED_CONFIG.commands.default_op_level),
|op| AtomicCell::new(op.level),
),
inventory: Mutex::new(PlayerInventory::new()),
}
}
pub fn inventory(&self) -> &Mutex<PlayerInventory> {
self.living_entity
.inventory
.as_ref()
.expect("Player always has inventory")
&self.inventory
}
/// Removes the Player out of the current World
@@ -486,7 +485,7 @@ impl Player {
.send_packet(&CPlayerAbilities::new(
b,
abilities.fly_speed,
abilities.walk_speed_fov,
abilities.walk_speed,
))
.await;
}
@@ -703,6 +702,23 @@ impl Player {
.await;
}
}
#[async_trait]
impl NBTStorage for Player {
async fn write_nbt(&self, nbt: &mut NbtCompound) {
self.living_entity.write_nbt(nbt).await;
nbt.put_int(
"SelectedItemSlot",
self.inventory.lock().await.selected as i32,
);
self.abilities.lock().await.write_nbt(nbt).await;
}
async fn read_nbt(&mut self, nbt: &mut NbtCompound) {
self.living_entity.read_nbt(nbt).await;
self.inventory.lock().await.selected = nbt.get_int("SelectedItemSlot").unwrap_or(0) as u32;
self.abilities.lock().await.read_nbt(nbt).await;
}
}
impl Player {
pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) {
@@ -860,10 +876,39 @@ pub struct Abilities {
pub allow_flying: bool,
/// Indicates whether the player is in creative mode.
pub creative: bool,
/// Indicates whether the player is allowed to modify the world.
pub allow_modify_world: bool,
/// The player's flying speed.
pub fly_speed: f32,
/// The field of view adjustment when the player is walking or sprinting.
pub walk_speed_fov: f32,
pub walk_speed: f32,
}
#[async_trait]
impl NBTStorage for Abilities {
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
let mut component = NbtCompound::new();
component.put_bool("invulnerable", self.invulnerable);
component.put_bool("flying", self.flying);
component.put_bool("mayfly", self.allow_flying);
component.put_bool("instabuild", self.creative);
component.put_bool("mayBuild", self.allow_modify_world);
component.put_float("flySpeed", self.fly_speed);
component.put_float("walkSpeed", self.walk_speed);
nbt.put_component("abilities", component);
}
async fn read_nbt(&mut self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
if let Some(component) = nbt.get_compound("abilities") {
self.invulnerable = component.get_bool("invulnerable").unwrap_or(false);
self.flying = component.get_bool("flying").unwrap_or(false);
self.allow_flying = component.get_bool("mayfly").unwrap_or(false);
self.creative = component.get_bool("instabuild").unwrap_or(false);
self.allow_modify_world = component.get_bool("mayBuild").unwrap_or(false);
self.fly_speed = component.get_float("flySpeed").unwrap_or(0.0);
self.walk_speed = component.get_float("walk_speed").unwrap_or(0.0);
}
}
}
impl Default for Abilities {
@@ -873,8 +918,9 @@ impl Default for Abilities {
flying: false,
allow_flying: false,
creative: false,
allow_modify_world: true,
fly_speed: 0.05,
walk_speed_fov: 0.1,
walk_speed: 0.1,
}
}
}

View File

@@ -204,7 +204,7 @@ impl Player {
pub async fn handle_decrease_item(
&self,
_server: &Server,
slot_index: usize,
slot_index: i16,
item_stack: Option<&ItemStack>,
state_id: &mut u32,
) -> Result<(), InventoryError> {
@@ -521,7 +521,7 @@ impl Player {
let packet = CSetContainerSlot::new(
total_opened_containers as i8,
(inventory.state_id) as i32,
slot_index,
slot_index as i16,
&slot,
);
player.client.send_packet(&packet).await;

View File

@@ -336,7 +336,7 @@ impl Player {
async fn update_single_slot(
&self,
inventory: &mut tokio::sync::MutexGuard<'_, PlayerInventory>,
slot: usize,
slot: i16,
slot_data: Slot,
) {
inventory.state_id += 1;
@@ -344,7 +344,7 @@ impl Player {
self.client.send_packet(&dest_packet).await;
if inventory
.set_slot(slot, slot_data.to_item(), false)
.set_slot(slot as usize, slot_data.to_item(), false)
.is_err()
{
log::error!("Pick item set slot error!");
@@ -368,7 +368,7 @@ impl Player {
let mut inventory = self.inventory().lock().await;
let source_slot = inventory.get_slot_with_item(block.item_id);
let mut dest_slot = inventory.get_pick_item_hotbar_slot();
let mut dest_slot = inventory.get_pick_item_hotbar_slot() as usize;
let dest_slot_data = match inventory.get_slot(dest_slot + 36) {
Ok(Some(stack)) => Slot::from(&*stack),
@@ -393,24 +393,24 @@ impl Player {
Ok(Some(stack)) => Slot::from(&*stack),
_ => return,
};
self.update_single_slot(&mut inventory, dest_slot + 36, source_slot_data)
self.update_single_slot(&mut inventory, dest_slot as i16 + 36, source_slot_data)
.await;
// Update source slot
self.update_single_slot(&mut inventory, slot_index, dest_slot_data)
self.update_single_slot(&mut inventory, slot_index as i16, dest_slot_data)
.await;
}
None if self.gamemode.load() == GameMode::Creative => {
// Case where item is not present, if in creative mode create the item
let item_stack = ItemStack::new(1, block.item_id);
let slot_data = Slot::from(&item_stack);
self.update_single_slot(&mut inventory, dest_slot + 36, slot_data)
self.update_single_slot(&mut inventory, dest_slot as i16 + 36, slot_data)
.await;
// Check if there is any empty slot in the player inventory
if let Some(slot_index) = inventory.get_empty_slot() {
inventory.state_id += 1;
self.update_single_slot(&mut inventory, slot_index, dest_slot_data)
self.update_single_slot(&mut inventory, slot_index as i16, dest_slot_data)
.await;
}
}
@@ -418,7 +418,7 @@ impl Player {
}
// Update held item
inventory.set_selected(dest_slot);
inventory.set_selected(dest_slot as u32);
self.client
.send_packet(&CSetHeldItem::new(dest_slot as i8))
.await;
@@ -908,7 +908,7 @@ impl Player {
let _ = self
.handle_decrease_item(
server,
slot_id,
slot_id as i16,
item_slot.as_ref(),
&mut state_id,
)
@@ -937,7 +937,7 @@ impl Player {
self.kick(TextComponent::text("Invalid held slot")).await;
return;
}
self.inventory().lock().await.set_selected(slot as usize);
self.inventory().lock().await.set_selected(slot as u32);
}
pub async fn handle_set_creative_slot(