From 2e2fe837205b68683ae4caaaf9344b723a3151a7 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 7 Feb 2025 00:22:52 +0100 Subject: [PATCH] Add Hunger --- README.md | 23 +++--- pumpkin-config/src/chunk.rs | 2 - pumpkin-config/src/networking/auth.rs | 4 +- pumpkin-data/build/biome.rs | 1 - pumpkin-data/build/damage_type.rs | 1 - pumpkin-data/build/entity_pose.rs | 1 - pumpkin-data/build/entity_type.rs | 5 +- pumpkin-data/build/game_event.rs | 1 - pumpkin-data/build/particle.rs | 1 - pumpkin-data/build/scoreboard_slot.rs | 1 - pumpkin-data/build/screen.rs | 1 - pumpkin-data/build/sound_category.rs | 1 - pumpkin-inventory/src/window_property.rs | 7 -- .../src/client/play/entity_animation.rs | 1 - .../src/client/play/entity_metadata.rs | 1 - .../src/client/play/game_event.rs | 1 - .../src/client/play/player_chat_message.rs | 7 +- .../src/client/play/update_objectives.rs | 2 - pumpkin-protocol/src/query.rs | 1 - pumpkin-registry/src/lib.rs | 1 - pumpkin-util/src/gamemode.rs | 19 +++-- pumpkin-util/src/permission.rs | 1 - pumpkin-world/src/chunk/anvil.rs | 1 - pumpkin/src/command/args/gamemode.rs | 12 +-- pumpkin/src/entity/hunger.rs | 65 ++++++++++++++++ pumpkin/src/entity/living.rs | 6 ++ pumpkin/src/entity/mod.rs | 2 +- pumpkin/src/entity/player.rs | 77 +++++++++++++++---- pumpkin/src/entity/projectile/mod.rs | 8 +- pumpkin/src/net/authentication.rs | 2 +- pumpkin/src/net/packet/play.rs | 42 ++++++++-- pumpkin/src/net/rcon/packet.rs | 2 - pumpkin/src/server/connection_cache.rs | 7 +- pumpkin/src/server/mod.rs | 6 +- pumpkin/src/world/mod.rs | 2 +- 35 files changed, 212 insertions(+), 103 deletions(-) create mode 100644 pumpkin/src/entity/hunger.rs diff --git a/README.md b/README.md index 5e198f10d..8e2ec1fb1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi ![image](https://github.com/user-attachments/assets/7e2e865e-b150-4675-a2d5-b52f9900378e) -## What Pumpkin wants to achieve +## Goals - **Performance**: Leveraging multi-threading for maximum speed and efficiency. - **Compatibility**: Supports the latest Minecraft server version and adheres to vanilla game mechanics. @@ -22,10 +22,6 @@ and customizable experience. It prioritizes performance and player enjoyment whi - **Flexibility**: Highly configurable, with the ability to disable unnecessary features. - **Extensibility**: Provides a foundation for plugin development. -## What Pumpkin will not - -- Be compatible with plugins or mods for other servers - > [!IMPORTANT] > Pumpkin is currently under heavy development. @@ -48,6 +44,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [x] World Loading - [x] Lighting - [x] Entity Spawning + - [x] Item drops - [x] Bossbar - [x] Chunk Loading (Vanilla, Linear) - [x] Chunk Generation @@ -59,13 +56,15 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [ ] Redstone - [ ] Liquid Physics - Player - - [x] Player Skins - - [x] Player Client brand - - [x] Player Teleport - - [x] Player Movement - - [x] Player Animation - - [x] Player Inventory - - [x] Player Combat + - [x] Skins + - [x] Client brand + - [x] Teleport + - [x] Movement + - [x] Animation + - [x] Inventory + - [x] Combat + - [x] Experience + - [x] Hunger - Entities - [x] Non-Living (Minecart, Eggs...) - [x] Players diff --git a/pumpkin-config/src/chunk.rs b/pumpkin-config/src/chunk.rs index 8c20f4343..eb000c91e 100644 --- a/pumpkin-config/src/chunk.rs +++ b/pumpkin-config/src/chunk.rs @@ -25,7 +25,6 @@ impl Default for ChunkCompression { } #[derive(Deserialize, Serialize, Clone)] -#[repr(u8)] pub enum Compression { /// GZip Compression GZip, @@ -38,7 +37,6 @@ pub enum Compression { } #[derive(Deserialize, Serialize, Clone, Default)] -#[repr(u8)] pub enum ChunkFormat { #[default] Anvil, diff --git a/pumpkin-config/src/networking/auth.rs b/pumpkin-config/src/networking/auth.rs index cdfe1e7e5..2a0dbf367 100644 --- a/pumpkin-config/src/networking/auth.rs +++ b/pumpkin-config/src/networking/auth.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; pub struct AuthenticationConfig { /// Whether to use Mojang authentication. pub enabled: bool, - pub auth_url: Option, + pub url: Option, pub connect_timeout: u32, pub read_timeout: u32, pub prevent_proxy_connections: bool, @@ -24,7 +24,7 @@ impl Default for AuthenticationConfig { prevent_proxy_connections: false, player_profile: Default::default(), textures: Default::default(), - auth_url: None, + url: None, prevent_proxy_connection_auth_url: None, connect_timeout: 5000, read_timeout: 5000, diff --git a/pumpkin-data/build/biome.rs b/pumpkin-data/build/biome.rs index 0dcefbfdb..12977b57f 100644 --- a/pumpkin-data/build/biome.rs +++ b/pumpkin-data/build/biome.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Clone, Copy)] - #[repr(u8)] pub enum Biome { #variants } diff --git a/pumpkin-data/build/damage_type.rs b/pumpkin-data/build/damage_type.rs index 910eec5c5..18526e90d 100644 --- a/pumpkin-data/build/damage_type.rs +++ b/pumpkin-data/build/damage_type.rs @@ -102,7 +102,6 @@ pub(crate) fn build() -> TokenStream { #(#constants)* #[derive(Clone, Copy, Debug, PartialEq, Eq)] - #[repr(u8)] pub enum DamageType { #(#enum_variants,)* } diff --git a/pumpkin-data/build/entity_pose.rs b/pumpkin-data/build/entity_pose.rs index 8a829f82e..c7a9b8b3a 100644 --- a/pumpkin-data/build/entity_pose.rs +++ b/pumpkin-data/build/entity_pose.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Clone, Copy)] - #[repr(u8)] pub enum EntityPose { #variants } diff --git a/pumpkin-data/build/entity_type.rs b/pumpkin-data/build/entity_type.rs index 9557e6312..56c33e0fe 100644 --- a/pumpkin-data/build/entity_type.rs +++ b/pumpkin-data/build/entity_type.rs @@ -4,6 +4,7 @@ use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; +use syn::LitInt; use crate::ident; @@ -22,9 +23,10 @@ pub(crate) fn build() -> TokenStream { for (item, id) in json.iter() { let id = id.id as u8; + let id_lit = LitInt::new(&id.to_string(), proc_macro2::Span::call_site()); let name = ident(item.to_pascal_case()); variants.extend([quote! { - #name = #id, + #name = #id_lit, }]); } @@ -54,7 +56,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[repr(u8)] pub enum EntityType { #variants } diff --git a/pumpkin-data/build/game_event.rs b/pumpkin-data/build/game_event.rs index 2d5912deb..d3b6c68c0 100644 --- a/pumpkin-data/build/game_event.rs +++ b/pumpkin-data/build/game_event.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { let variants = array_to_tokenstream(game_events); quote! { - #[repr(u8)] pub enum GameEvent { #variants } diff --git a/pumpkin-data/build/particle.rs b/pumpkin-data/build/particle.rs index c4f16cb2b..fb22ba1b1 100644 --- a/pumpkin-data/build/particle.rs +++ b/pumpkin-data/build/particle.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - #[repr(u8)] pub enum Particle { #variants } diff --git a/pumpkin-data/build/scoreboard_slot.rs b/pumpkin-data/build/scoreboard_slot.rs index 907641cbe..b8146d7ee 100644 --- a/pumpkin-data/build/scoreboard_slot.rs +++ b/pumpkin-data/build/scoreboard_slot.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { let variants = array_to_tokenstream(sound_categories); quote! { - #[repr(u8)] pub enum ScoreboardDisplaySlot { #variants } diff --git a/pumpkin-data/build/screen.rs b/pumpkin-data/build/screen.rs index e224891f5..5fb826dc0 100644 --- a/pumpkin-data/build/screen.rs +++ b/pumpkin-data/build/screen.rs @@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - #[repr(u8)] pub enum WindowType { #variants } diff --git a/pumpkin-data/build/sound_category.rs b/pumpkin-data/build/sound_category.rs index 069413f05..cf10c80f2 100644 --- a/pumpkin-data/build/sound_category.rs +++ b/pumpkin-data/build/sound_category.rs @@ -13,7 +13,6 @@ pub(crate) fn build() -> TokenStream { quote! { #[derive(Clone, Copy)] - #[repr(u8)] pub enum SoundCategory { #variants } diff --git a/pumpkin-inventory/src/window_property.rs b/pumpkin-inventory/src/window_property.rs index 9bca2c984..9c7a1cc24 100644 --- a/pumpkin-inventory/src/window_property.rs +++ b/pumpkin-inventory/src/window_property.rs @@ -19,7 +19,6 @@ impl WindowProperty { (self.window_property.to_id(), self.value) } } -#[repr(u8)] pub enum Furnace { FireIcon, MaximumFuelBurnTime, @@ -46,35 +45,29 @@ impl WindowPropertyTrait for EnchantmentTable { }) as i16 } } -#[repr(u8)] pub enum Beacon { PowerLevel, FirstPotionEffect, SecondPotionEffect, } -#[repr(u8)] pub enum Anvil { RepairCost, } -#[repr(u8)] pub enum BrewingStand { BrewTime, FuelTime, } -#[repr(u8)] pub enum Stonecutter { SelectedRecipe, } -#[repr(u8)] pub enum Loom { SelectedPattern, } -#[repr(u8)] pub enum Lectern { PageNumber, } diff --git a/pumpkin-protocol/src/client/play/entity_animation.rs b/pumpkin-protocol/src/client/play/entity_animation.rs index 9e2a216c2..c4a163ee2 100644 --- a/pumpkin-protocol/src/client/play/entity_animation.rs +++ b/pumpkin-protocol/src/client/play/entity_animation.rs @@ -22,7 +22,6 @@ impl CEntityAnimation { } #[derive(Debug)] -#[repr(u8)] pub enum Animation { SwingMainArm, LeaveBed = 2, diff --git a/pumpkin-protocol/src/client/play/entity_metadata.rs b/pumpkin-protocol/src/client/play/entity_metadata.rs index cf5d9c9f6..1ce7bc5e8 100644 --- a/pumpkin-protocol/src/client/play/entity_metadata.rs +++ b/pumpkin-protocol/src/client/play/entity_metadata.rs @@ -39,7 +39,6 @@ impl Metadata { } } -#[repr(u8)] pub enum MetaDataType { Byte, Integer, diff --git a/pumpkin-protocol/src/client/play/game_event.rs b/pumpkin-protocol/src/client/play/game_event.rs index 82064b685..375b8e1b9 100644 --- a/pumpkin-protocol/src/client/play/game_event.rs +++ b/pumpkin-protocol/src/client/play/game_event.rs @@ -19,7 +19,6 @@ impl CGameEvent { } } -#[repr(u8)] pub enum GameEvent { NoRespawnBlockAvailable, BeginRaining, diff --git a/pumpkin-protocol/src/client/play/player_chat_message.rs b/pumpkin-protocol/src/client/play/player_chat_message.rs index 0cced313d..76573f752 100644 --- a/pumpkin-protocol/src/client/play/player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/player_chat_message.rs @@ -67,12 +67,11 @@ pub struct PreviousMessage<'a> { } #[derive(Serialize)] -#[repr(i32)] pub enum FilterType { /// Message is not filtered at all - PassThrough = 0, + PassThrough, /// Message is fully filtered - FullyFiltered = 1, + FullyFiltered, /// Only some characters in the message are filtered - PartiallyFiltered(BitSet) = 2, + PartiallyFiltered(BitSet), } diff --git a/pumpkin-protocol/src/client/play/update_objectives.rs b/pumpkin-protocol/src/client/play/update_objectives.rs index 5835528f3..e74b0e447 100644 --- a/pumpkin-protocol/src/client/play/update_objectives.rs +++ b/pumpkin-protocol/src/client/play/update_objectives.rs @@ -59,14 +59,12 @@ impl ClientPacket for CUpdateObjectives<'_> { } } -#[repr(u8)] pub enum Mode { Add, Remove, Update, } -#[repr(i32)] pub enum RenderType { Integer, Hearts, diff --git a/pumpkin-protocol/src/query.rs b/pumpkin-protocol/src/query.rs index ef505fd0f..aca8ec714 100644 --- a/pumpkin-protocol/src/query.rs +++ b/pumpkin-protocol/src/query.rs @@ -2,7 +2,6 @@ use std::{ffi::CString, io::Cursor}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -#[repr(u8)] pub enum PacketType { // There could be other types but they are not documented // Besides these types are enough to get server status diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index 80786b17a..3ac8faaa8 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -63,7 +63,6 @@ pub struct SyncedRegistry { } #[derive(Debug, Clone, Copy)] -#[repr(u8)] pub enum DimensionType { Overworld, OverworldCaves, diff --git a/pumpkin-util/src/gamemode.rs b/pumpkin-util/src/gamemode.rs index 4ac2e121a..66cc5bba4 100644 --- a/pumpkin-util/src/gamemode.rs +++ b/pumpkin-util/src/gamemode.rs @@ -6,24 +6,23 @@ use serde::{Deserialize, Serialize}; pub struct ParseGameModeError; #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -#[repr(i8)] pub enum GameMode { - Undefined = -1, Survival, Creative, Adventure, Spectator, } -impl From for GameMode { - fn from(value: i8) -> Self { +impl TryFrom for GameMode { + type Error = (); + + fn try_from(value: i8) -> Result { match value { - -1 => Self::Undefined, - 0 => Self::Survival, - 1 => Self::Creative, - 2 => Self::Adventure, - 3 => Self::Spectator, - _ => Self::Undefined, + 0 => Ok(Self::Survival), + 1 => Ok(Self::Creative), + 2 => Ok(Self::Adventure), + 3 => Ok(Self::Spectator), + _ => Err(()), } } } diff --git a/pumpkin-util/src/permission.rs b/pumpkin-util/src/permission.rs index e9ef6e36f..8ecfa589e 100644 --- a/pumpkin-util/src/permission.rs +++ b/pumpkin-util/src/permission.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// - `Three`: `admin`: Player or executor can use commands related to multiplayer management. /// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. #[derive(Clone, Copy, Default, PartialEq, Eq)] -#[repr(i8)] pub enum PermissionLvl { #[default] Zero = 0, diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index 766237ac1..43c853c70 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -27,7 +27,6 @@ const WORLD_DATA_VERSION: i32 = 4189; pub struct AnvilChunkFormat; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] pub enum Compression { /// GZip Compression GZip = 1, diff --git a/pumpkin/src/command/args/gamemode.rs b/pumpkin/src/command/args/gamemode.rs index ac16ea38b..49b2127d9 100644 --- a/pumpkin/src/command/args/gamemode.rs +++ b/pumpkin/src/command/args/gamemode.rs @@ -34,16 +34,12 @@ impl ArgumentConsumer for GamemodeArgumentConsumer { let s = args.pop()?; if let Ok(id) = s.parse::() { - match GameMode::from(id) { - GameMode::Undefined => {} - gamemode => return Some(Arg::GameMode(gamemode)), - }; + if let Ok(gamemode) = GameMode::try_from(id) { + return Some(Arg::GameMode(gamemode)); + } }; - match GameMode::from_str(s) { - Err(_) | Ok(GameMode::Undefined) => None, - Ok(gamemode) => Some(Arg::GameMode(gamemode)), - } + GameMode::from_str(s).map_or_else(|_| None, |gamemode| Some(Arg::GameMode(gamemode))) } async fn suggest<'a>( diff --git a/pumpkin/src/entity/hunger.rs b/pumpkin/src/entity/hunger.rs new file mode 100644 index 000000000..a8cf814d8 --- /dev/null +++ b/pumpkin/src/entity/hunger.rs @@ -0,0 +1,65 @@ +use crossbeam::atomic::AtomicCell; +use pumpkin_data::damage::DamageType; + +use super::player::Player; + +pub struct HungerManager { + /// The current hunger level. + pub level: AtomicCell, + /// The food saturation level. + pub saturation: AtomicCell, + exhaustion: AtomicCell, + tick_timer: AtomicCell, +} + +impl Default for HungerManager { + fn default() -> Self { + Self { + level: AtomicCell::new(20), + saturation: AtomicCell::new(5.0), + exhaustion: AtomicCell::new(0.0), + tick_timer: AtomicCell::new(0), + } + } +} + +impl HungerManager { + pub async fn tick(&self, player: &Player) { + let saturation = self.saturation.load(); + let level = self.level.load(); + let exhaustion = self.exhaustion.load(); + // Decrease hunger level on exhaustion + if level != 0 && exhaustion > 4.0 { + self.exhaustion.store(exhaustion - 4.0); + if saturation > 0.0 { + self.saturation.store((saturation - 1.0).max(0.0)); + } else { + self.level.store(level - 1); + player.send_health().await; + } + } + // Heal when hunger is full + if saturation > 0.0 && player.can_food_heal() && level >= 20 { + self.tick_timer.fetch_add(1); + if self.tick_timer.load() >= 10 { + let saturation = saturation.min(6.0); + player.heal(saturation / 6.0).await; + self.add_exhausten(saturation); + self.tick_timer.store(0); + } + } else if level == 0 { + self.tick_timer.fetch_add(1); + if self.tick_timer.load() >= 80 { + player.living_entity.damage(1.0, DamageType::Starve).await; + self.tick_timer.store(0); + } + } else { + self.tick_timer.store(0); + } + } + + pub fn add_exhausten(&self, exhaustion: f32) { + self.exhaustion + .store((self.exhaustion.load() + exhaustion).min(40.0)); + } +} diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index c7c1f3b0c..3f9fda702 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -54,6 +54,12 @@ impl LivingEntity { self.entity.set_pos(position); } + pub async fn heal(&self, additional_health: f32) { + assert!(additional_health > 0.0); + self.set_health(self.health.load() + additional_health) + .await; + } + pub async fn set_health(&self, health: f32) { self.health.store(health); // tell everyone entities health changed diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 2069ae6ce..8b3e0e517 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -34,6 +34,7 @@ use serde::Serialize; use crate::world::World; pub mod ai; +pub mod hunger; pub mod item; pub mod living; pub mod mob; @@ -447,7 +448,6 @@ pub trait NBTStorage: Send + Sync { /// **Purpose:** /// /// This enum provides a more type-safe and readable way to represent entity flags compared to using raw integer values. -#[repr(u8)] pub enum Flag { /// Indicates if the entity is on fire. OnFire = 0, diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 2df1da321..93310e183 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -68,6 +68,7 @@ use tokio::sync::{Mutex, Notify, RwLock}; use super::{ combat::{self, player_attack_sound, AttackType}, + hunger::HungerManager, item::ItemEntity, Entity, EntityId, NBTStorage, }; @@ -98,10 +99,8 @@ pub struct Player { pub config: Mutex, /// The player's current gamemode (e.g., Survival, Creative, Adventure). pub gamemode: AtomicCell, - /// The player's hunger level. - pub food: AtomicI32, - /// The player's food saturation level. - pub food_saturation: AtomicCell, + /// The Hunger Manager manages Players hunger level + pub hunger_manager: HungerManager, /// The ID of the currently open container (if any). pub open_container: AtomicCell>, /// The item currently being held by the player. @@ -192,8 +191,7 @@ impl Player { client, awaiting_teleport: Mutex::new(None), // TODO: Load this from previous instance - food: AtomicI32::new(20), - food_saturation: AtomicCell::new(20.0), + hunger_manager: HungerManager::default(), current_block_destroy_stage: AtomicU8::new(0), open_container: AtomicCell::new(None), carried_item: AtomicCell::new(None), @@ -433,8 +431,10 @@ impl Player { .fetch_add(1, std::sync::atomic::Ordering::Relaxed); self.living_entity.tick(); - self.tick_client_load_timeout(); + self.hunger_manager.tick(self).await; + // timeout/keep alive handling + self.tick_client_load_timeout(); if now.duration_since(self.last_keep_alive_time.load()) >= Duration::from_secs(15) { // We never got a response from our last keep alive we send if self @@ -455,6 +455,29 @@ impl Player { } } + pub async fn jump(&self) { + if self.living_entity.entity.sprinting.load(Ordering::Relaxed) { + self.add_exhaustion(0.2).await; + } else { + self.add_exhaustion(0.05).await; + } + } + + #[expect(clippy::cast_precision_loss)] + pub async fn progress_motion(&self, delta_pos: Vector3) { + // TODO: Swming, Glding... + if self.living_entity.entity.on_ground.load(Ordering::Relaxed) { + let delta = (delta_pos.horizontal_length() * 100.0).round() as i32; + if delta > 0 { + if self.living_entity.entity.sprinting.load(Ordering::Relaxed) { + self.add_exhaustion(0.1 * delta as f32 * 0.01).await; + } else { + self.add_exhaustion(0.0 * delta as f32 * 0.01).await; + } + } + } + } + pub fn has_client_loaded(&self) -> bool { self.client_loaded.load(Ordering::Relaxed) || self.client_loaded_timeout.load(Ordering::Relaxed) == 0 @@ -636,15 +659,40 @@ impl Player { self.client.close(); } - pub async fn set_health(&self, health: f32, food: i32, food_saturation: f32) { - self.living_entity.set_health(health).await; - self.food.store(food, std::sync::atomic::Ordering::Relaxed); - self.food_saturation.store(food_saturation); + pub fn can_food_heal(&self) -> bool { + let health = self.living_entity.health.load(); + let max_health = 20.0; // TODO + health > 0.0 && health < max_health + } + + pub async fn add_exhaustion(&self, exhaustion: f32) { + let abilities = self.abilities.lock().await; + if abilities.invulnerable { + return; + } + self.hunger_manager.add_exhausten(exhaustion); + } + + pub async fn heal(&self, additional_health: f32) { + self.living_entity.heal(additional_health).await; + self.send_health().await; + } + + pub async fn send_health(&self) { self.client - .send_packet(&CSetHealth::new(health, food.into(), food_saturation)) + .send_packet(&CSetHealth::new( + self.living_entity.health.load(), + self.hunger_manager.level.load().into(), + self.hunger_manager.saturation.load(), + )) .await; } + pub async fn set_health(&self, health: f32) { + self.living_entity.set_health(health).await; + self.send_health().await; + } + pub fn tick_client_load_timeout(&self) { if !self.client_loaded.load(Ordering::Relaxed) { let timeout = self.client_loaded_timeout.load(Ordering::Relaxed); @@ -1097,7 +1145,7 @@ impl Abilities { pub fn set_for_gamemode(&mut self, gamemode: GameMode) { match gamemode { GameMode::Creative => { - self.flying = false; // Start not flying + // self.flying = false; // Start not flying self.allow_flying = true; self.creative = true; self.invulnerable = true; @@ -1108,7 +1156,7 @@ impl Abilities { self.creative = false; self.invulnerable = true; } - GameMode::Survival | GameMode::Adventure | GameMode::Undefined => { + _ => { self.flying = false; self.allow_flying = false; self.creative = false; @@ -1120,7 +1168,6 @@ impl Abilities { /// Represents the player's dominant hand. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] pub enum Hand { /// Usually the player's off-hand. Left, diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index 25087edfb..5319e2a5e 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -26,11 +26,9 @@ impl ThrownItemEntity { speed: f32, divergence: f32, ) { - let to_radians = |degrees: f32| degrees * PI / 180.0; - - let yaw_rad = to_radians(yaw); - let pitch_rad = to_radians(pitch); - let roll_rad = to_radians(pitch + roll); + let yaw_rad = yaw.to_radians(); + let pitch_rad = pitch.to_radians(); + let roll_rad = (pitch + roll).to_radians(); let x = -yaw_rad.sin() * pitch_rad.cos(); let y = -roll_rad.sin(); diff --git a/pumpkin/src/net/authentication.rs b/pumpkin/src/net/authentication.rs index c485a9502..ba871c9c1 100644 --- a/pumpkin/src/net/authentication.rs +++ b/pumpkin/src/net/authentication.rs @@ -70,7 +70,7 @@ pub async fn authenticate( let auth_url = ADVANCED_CONFIG .networking .authentication - .auth_url + .url .as_deref() .unwrap_or(MOJANG_AUTHENTICATION_URL); diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 1e174d5bb..3ca631c4a 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -159,6 +159,14 @@ impl Player { let last_pos = entity.pos.load(); self.living_entity.set_pos(position); + let height_difference = position.y - last_pos.y; + if entity.on_ground.load(std::sync::atomic::Ordering::Relaxed) + && !packet.ground + && height_difference > 0.0 + { + self.jump().await; + } + entity .on_ground .store(packet.ground, std::sync::atomic::Ordering::Relaxed); @@ -195,7 +203,6 @@ impl Player { ) .await; if !self.abilities.lock().await.flying { - let height_difference = position.y - last_pos.y; self.living_entity .update_fall_distance( height_difference, @@ -205,6 +212,12 @@ impl Player { .await; } chunker::update_position(self).await; + self.progress_motion(Vector3::new( + position.x - last_pos.x, + position.y - last_pos.y, + position.z - last_pos.z, + )) + .await; } pub async fn handle_position_rotation(self: &Arc, packet: SPlayerPositionRotation) { @@ -236,6 +249,13 @@ impl Player { let last_pos = entity.pos.load(); self.living_entity.set_pos(position); + let height_difference = position.y - last_pos.y; + if entity.on_ground.load(std::sync::atomic::Ordering::Relaxed) + && !packet.ground + && height_difference > 0.0 + { + self.jump().await; + } entity .on_ground .store(packet.ground, std::sync::atomic::Ordering::Relaxed); @@ -287,7 +307,6 @@ impl Player { ) .await; if !self.abilities.lock().await.flying { - let height_difference = position.y - last_pos.y; self.living_entity .update_fall_distance( height_difference, @@ -297,6 +316,12 @@ impl Player { .await; } chunker::update_position(self).await; + self.progress_motion(Vector3::new( + position.x - last_pos.x, + position.y - last_pos.y, + position.z - last_pos.z, + )) + .await; } pub async fn handle_rotation(&self, rotation: SPlayerRotation) { @@ -804,6 +829,9 @@ impl Player { .await; } } + self.client + .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)) + .await; } Status::CancelledDigging => { if !self.can_interact_with_block_at(&player_action.location, 1.0) { @@ -816,6 +844,9 @@ impl Player { } self.current_block_destroy_stage .store(0, std::sync::atomic::Ordering::Relaxed); + self.client + .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)) + .await; } Status::FinishedDigging => { // TODO: do validation @@ -841,6 +872,9 @@ impl Player { .broken(block, &self, location, server) .await; } + self.client + .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)) + .await; } Status::DropItemStack | Status::DropItem => { self.drop_item(server).await; @@ -851,10 +885,6 @@ impl Player { }, Err(_) => self.kick(TextComponent::text("Invalid status")).await, } - - self.client - .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)) - .await; } pub async fn handle_keep_alive(&self, keep_alive: SKeepAlive) { diff --git a/pumpkin/src/net/rcon/packet.rs b/pumpkin/src/net/rcon/packet.rs index 6e976b4e5..16912390c 100644 --- a/pumpkin/src/net/rcon/packet.rs +++ b/pumpkin/src/net/rcon/packet.rs @@ -9,7 +9,6 @@ use tokio::io::AsyncReadExt; /// Client -> Server #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] pub enum ServerboundPacket { /// Typically, the first packet sent by the client, which is used to authenticate the connection with the server. Auth = 2, @@ -29,7 +28,6 @@ impl ServerboundPacket { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] /// Server -> Client pub enum ClientboundPacket { /// This packet is a notification of the connection's current auth status. When the server receives an auth request, it will respond with an empty `SERVERDATA_RESPONSE_VALUE`, diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index ccc8e9dd7..329cef19a 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -61,11 +61,12 @@ impl CachedBranding { pub fn get_branding(&self) -> CPluginMessage { CPluginMessage::new("minecraft:brand", &self.cached_server_brand) } + const BRAND: &str = "Pumpkin"; + const BRAND_BYTES: &[u8] = Self::BRAND.as_bytes(); fn build_brand() -> Vec { - let brand = "Pumpkin"; let mut buf = Vec::new(); - VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); + VarInt(Self::BRAND.len() as i32).encode(&mut buf); + buf.extend_from_slice(Self::BRAND_BYTES); buf } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index be773416d..2cb2b3538 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -13,7 +13,6 @@ use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::text::TextComponent; -use pumpkin_util::GameMode; use pumpkin_world::block::registry::Block; use pumpkin_world::dimension::Dimension; use pumpkin_world::entity::registry::get_entity_by_id; @@ -173,10 +172,7 @@ impl Server { /// You still have to spawn the Player in the World to make then to let them Join and make them Visible pub async fn add_player(&self, client: Arc) -> (Arc, Arc) { let entity_id = self.new_entity_id(); - let gamemode = match BASIC_CONFIG.default_gamemode { - GameMode::Undefined => GameMode::Survival, - game_mode => game_mode, - }; + let gamemode = BASIC_CONFIG.default_gamemode; // Basically the default world // TODO: select default from config let world = &self.worlds.read().await[0]; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 815e3beb9..25fe824b2 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -613,7 +613,7 @@ impl World { chunker::player_join(player).await; // update commands - player.set_health(20.0, 20, 20.0).await; + player.set_health(20.0).await; } /// IMPORTANT: Chunks have to be non-empty