Add Hunger

This commit is contained in:
Alexander Medvedev 2025-02-07 00:22:52 +01:00
parent 5c31e1ff62
commit 2e2fe83720
35 changed files with 212 additions and 103 deletions

View File

@ -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) ![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. - **Performance**: Leveraging multi-threading for maximum speed and efficiency.
- **Compatibility**: Supports the latest Minecraft server version and adheres to vanilla game mechanics. - **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. - **Flexibility**: Highly configurable, with the ability to disable unnecessary features.
- **Extensibility**: Provides a foundation for plugin development. - **Extensibility**: Provides a foundation for plugin development.
## What Pumpkin will not
- Be compatible with plugins or mods for other servers
> [!IMPORTANT] > [!IMPORTANT]
> Pumpkin is currently under heavy development. > Pumpkin is currently under heavy development.
@ -48,6 +44,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [x] World Loading - [x] World Loading
- [x] Lighting - [x] Lighting
- [x] Entity Spawning - [x] Entity Spawning
- [x] Item drops
- [x] Bossbar - [x] Bossbar
- [x] Chunk Loading (Vanilla, Linear) - [x] Chunk Loading (Vanilla, Linear)
- [x] Chunk Generation - [x] Chunk Generation
@ -59,13 +56,15 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- [ ] Redstone - [ ] Redstone
- [ ] Liquid Physics - [ ] Liquid Physics
- Player - Player
- [x] Player Skins - [x] Skins
- [x] Player Client brand - [x] Client brand
- [x] Player Teleport - [x] Teleport
- [x] Player Movement - [x] Movement
- [x] Player Animation - [x] Animation
- [x] Player Inventory - [x] Inventory
- [x] Player Combat - [x] Combat
- [x] Experience
- [x] Hunger
- Entities - Entities
- [x] Non-Living (Minecart, Eggs...) - [x] Non-Living (Minecart, Eggs...)
- [x] Players - [x] Players

View File

@ -25,7 +25,6 @@ impl Default for ChunkCompression {
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
#[repr(u8)]
pub enum Compression { pub enum Compression {
/// GZip Compression /// GZip Compression
GZip, GZip,
@ -38,7 +37,6 @@ pub enum Compression {
} }
#[derive(Deserialize, Serialize, Clone, Default)] #[derive(Deserialize, Serialize, Clone, Default)]
#[repr(u8)]
pub enum ChunkFormat { pub enum ChunkFormat {
#[default] #[default]
Anvil, Anvil,

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
pub struct AuthenticationConfig { pub struct AuthenticationConfig {
/// Whether to use Mojang authentication. /// Whether to use Mojang authentication.
pub enabled: bool, pub enabled: bool,
pub auth_url: Option<String>, pub url: Option<String>,
pub connect_timeout: u32, pub connect_timeout: u32,
pub read_timeout: u32, pub read_timeout: u32,
pub prevent_proxy_connections: bool, pub prevent_proxy_connections: bool,
@ -24,7 +24,7 @@ impl Default for AuthenticationConfig {
prevent_proxy_connections: false, prevent_proxy_connections: false,
player_profile: Default::default(), player_profile: Default::default(),
textures: Default::default(), textures: Default::default(),
auth_url: None, url: None,
prevent_proxy_connection_auth_url: None, prevent_proxy_connection_auth_url: None,
connect_timeout: 5000, connect_timeout: 5000,
read_timeout: 5000, read_timeout: 5000,

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(u8)]
pub enum Biome { pub enum Biome {
#variants #variants
} }

View File

@ -102,7 +102,6 @@ pub(crate) fn build() -> TokenStream {
#(#constants)* #(#constants)*
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum DamageType { pub enum DamageType {
#(#enum_variants,)* #(#enum_variants,)*
} }

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(u8)]
pub enum EntityPose { pub enum EntityPose {
#variants #variants
} }

View File

@ -4,6 +4,7 @@ use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use serde::Deserialize; use serde::Deserialize;
use syn::LitInt;
use crate::ident; use crate::ident;
@ -22,9 +23,10 @@ pub(crate) fn build() -> TokenStream {
for (item, id) in json.iter() { for (item, id) in json.iter() {
let id = id.id as u8; 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()); let name = ident(item.to_pascal_case());
variants.extend([quote! { variants.extend([quote! {
#name = #id, #name = #id_lit,
}]); }]);
} }
@ -54,7 +56,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum EntityType { pub enum EntityType {
#variants #variants
} }

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
let variants = array_to_tokenstream(game_events); let variants = array_to_tokenstream(game_events);
quote! { quote! {
#[repr(u8)]
pub enum GameEvent { pub enum GameEvent {
#variants #variants
} }

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Particle { pub enum Particle {
#variants #variants
} }

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
let variants = array_to_tokenstream(sound_categories); let variants = array_to_tokenstream(sound_categories);
quote! { quote! {
#[repr(u8)]
pub enum ScoreboardDisplaySlot { pub enum ScoreboardDisplaySlot {
#variants #variants
} }

View File

@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum WindowType { pub enum WindowType {
#variants #variants
} }

View File

@ -13,7 +13,6 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(u8)]
pub enum SoundCategory { pub enum SoundCategory {
#variants #variants
} }

View File

@ -19,7 +19,6 @@ impl<T: WindowPropertyTrait> WindowProperty<T> {
(self.window_property.to_id(), self.value) (self.window_property.to_id(), self.value)
} }
} }
#[repr(u8)]
pub enum Furnace { pub enum Furnace {
FireIcon, FireIcon,
MaximumFuelBurnTime, MaximumFuelBurnTime,
@ -46,35 +45,29 @@ impl WindowPropertyTrait for EnchantmentTable {
}) as i16 }) as i16
} }
} }
#[repr(u8)]
pub enum Beacon { pub enum Beacon {
PowerLevel, PowerLevel,
FirstPotionEffect, FirstPotionEffect,
SecondPotionEffect, SecondPotionEffect,
} }
#[repr(u8)]
pub enum Anvil { pub enum Anvil {
RepairCost, RepairCost,
} }
#[repr(u8)]
pub enum BrewingStand { pub enum BrewingStand {
BrewTime, BrewTime,
FuelTime, FuelTime,
} }
#[repr(u8)]
pub enum Stonecutter { pub enum Stonecutter {
SelectedRecipe, SelectedRecipe,
} }
#[repr(u8)]
pub enum Loom { pub enum Loom {
SelectedPattern, SelectedPattern,
} }
#[repr(u8)]
pub enum Lectern { pub enum Lectern {
PageNumber, PageNumber,
} }

View File

@ -22,7 +22,6 @@ impl CEntityAnimation {
} }
#[derive(Debug)] #[derive(Debug)]
#[repr(u8)]
pub enum Animation { pub enum Animation {
SwingMainArm, SwingMainArm,
LeaveBed = 2, LeaveBed = 2,

View File

@ -39,7 +39,6 @@ impl<T> Metadata<T> {
} }
} }
#[repr(u8)]
pub enum MetaDataType { pub enum MetaDataType {
Byte, Byte,
Integer, Integer,

View File

@ -19,7 +19,6 @@ impl CGameEvent {
} }
} }
#[repr(u8)]
pub enum GameEvent { pub enum GameEvent {
NoRespawnBlockAvailable, NoRespawnBlockAvailable,
BeginRaining, BeginRaining,

View File

@ -67,12 +67,11 @@ pub struct PreviousMessage<'a> {
} }
#[derive(Serialize)] #[derive(Serialize)]
#[repr(i32)]
pub enum FilterType { pub enum FilterType {
/// Message is not filtered at all /// Message is not filtered at all
PassThrough = 0, PassThrough,
/// Message is fully filtered /// Message is fully filtered
FullyFiltered = 1, FullyFiltered,
/// Only some characters in the message are filtered /// Only some characters in the message are filtered
PartiallyFiltered(BitSet) = 2, PartiallyFiltered(BitSet),
} }

View File

@ -59,14 +59,12 @@ impl ClientPacket for CUpdateObjectives<'_> {
} }
} }
#[repr(u8)]
pub enum Mode { pub enum Mode {
Add, Add,
Remove, Remove,
Update, Update,
} }
#[repr(i32)]
pub enum RenderType { pub enum RenderType {
Integer, Integer,
Hearts, Hearts,

View File

@ -2,7 +2,6 @@ use std::{ffi::CString, io::Cursor};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[repr(u8)]
pub enum PacketType { pub enum PacketType {
// There could be other types but they are not documented // There could be other types but they are not documented
// Besides these types are enough to get server status // Besides these types are enough to get server status

View File

@ -63,7 +63,6 @@ pub struct SyncedRegistry {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum DimensionType { pub enum DimensionType {
Overworld, Overworld,
OverworldCaves, OverworldCaves,

View File

@ -6,24 +6,23 @@ use serde::{Deserialize, Serialize};
pub struct ParseGameModeError; pub struct ParseGameModeError;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[repr(i8)]
pub enum GameMode { pub enum GameMode {
Undefined = -1,
Survival, Survival,
Creative, Creative,
Adventure, Adventure,
Spectator, Spectator,
} }
impl From<i8> for GameMode { impl TryFrom<i8> for GameMode {
fn from(value: i8) -> Self { type Error = ();
fn try_from(value: i8) -> Result<Self, Self::Error> {
match value { match value {
-1 => Self::Undefined, 0 => Ok(Self::Survival),
0 => Self::Survival, 1 => Ok(Self::Creative),
1 => Self::Creative, 2 => Ok(Self::Adventure),
2 => Self::Adventure, 3 => Ok(Self::Spectator),
3 => Self::Spectator, _ => Err(()),
_ => Self::Undefined,
} }
} }
} }

View File

@ -10,7 +10,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// - `Three`: `admin`: Player or executor can use commands related to multiplayer management. /// - `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. /// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management.
#[derive(Clone, Copy, Default, PartialEq, Eq)] #[derive(Clone, Copy, Default, PartialEq, Eq)]
#[repr(i8)]
pub enum PermissionLvl { pub enum PermissionLvl {
#[default] #[default]
Zero = 0, Zero = 0,

View File

@ -27,7 +27,6 @@ const WORLD_DATA_VERSION: i32 = 4189;
pub struct AnvilChunkFormat; pub struct AnvilChunkFormat;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Compression { pub enum Compression {
/// GZip Compression /// GZip Compression
GZip = 1, GZip = 1,

View File

@ -34,16 +34,12 @@ impl ArgumentConsumer for GamemodeArgumentConsumer {
let s = args.pop()?; let s = args.pop()?;
if let Ok(id) = s.parse::<i8>() { if let Ok(id) = s.parse::<i8>() {
match GameMode::from(id) { if let Ok(gamemode) = GameMode::try_from(id) {
GameMode::Undefined => {} return Some(Arg::GameMode(gamemode));
gamemode => return Some(Arg::GameMode(gamemode)), }
};
}; };
match GameMode::from_str(s) { GameMode::from_str(s).map_or_else(|_| None, |gamemode| Some(Arg::GameMode(gamemode)))
Err(_) | Ok(GameMode::Undefined) => None,
Ok(gamemode) => Some(Arg::GameMode(gamemode)),
}
} }
async fn suggest<'a>( async fn suggest<'a>(

View File

@ -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<u32>,
/// The food saturation level.
pub saturation: AtomicCell<f32>,
exhaustion: AtomicCell<f32>,
tick_timer: AtomicCell<u32>,
}
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));
}
}

View File

@ -54,6 +54,12 @@ impl LivingEntity {
self.entity.set_pos(position); 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) { pub async fn set_health(&self, health: f32) {
self.health.store(health); self.health.store(health);
// tell everyone entities health changed // tell everyone entities health changed

View File

@ -34,6 +34,7 @@ use serde::Serialize;
use crate::world::World; use crate::world::World;
pub mod ai; pub mod ai;
pub mod hunger;
pub mod item; pub mod item;
pub mod living; pub mod living;
pub mod mob; pub mod mob;
@ -447,7 +448,6 @@ pub trait NBTStorage: Send + Sync {
/// **Purpose:** /// **Purpose:**
/// ///
/// This enum provides a more type-safe and readable way to represent entity flags compared to using raw integer values. /// 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 { pub enum Flag {
/// Indicates if the entity is on fire. /// Indicates if the entity is on fire.
OnFire = 0, OnFire = 0,

View File

@ -68,6 +68,7 @@ use tokio::sync::{Mutex, Notify, RwLock};
use super::{ use super::{
combat::{self, player_attack_sound, AttackType}, combat::{self, player_attack_sound, AttackType},
hunger::HungerManager,
item::ItemEntity, item::ItemEntity,
Entity, EntityId, NBTStorage, Entity, EntityId, NBTStorage,
}; };
@ -98,10 +99,8 @@ pub struct Player {
pub config: Mutex<PlayerConfig>, pub config: Mutex<PlayerConfig>,
/// The player's current gamemode (e.g., Survival, Creative, Adventure). /// The player's current gamemode (e.g., Survival, Creative, Adventure).
pub gamemode: AtomicCell<GameMode>, pub gamemode: AtomicCell<GameMode>,
/// The player's hunger level. /// The Hunger Manager manages Players hunger level
pub food: AtomicI32, pub hunger_manager: HungerManager,
/// The player's food saturation level.
pub food_saturation: AtomicCell<f32>,
/// The ID of the currently open container (if any). /// The ID of the currently open container (if any).
pub open_container: AtomicCell<Option<u64>>, pub open_container: AtomicCell<Option<u64>>,
/// The item currently being held by the player. /// The item currently being held by the player.
@ -192,8 +191,7 @@ impl Player {
client, client,
awaiting_teleport: Mutex::new(None), awaiting_teleport: Mutex::new(None),
// TODO: Load this from previous instance // TODO: Load this from previous instance
food: AtomicI32::new(20), hunger_manager: HungerManager::default(),
food_saturation: AtomicCell::new(20.0),
current_block_destroy_stage: AtomicU8::new(0), current_block_destroy_stage: AtomicU8::new(0),
open_container: AtomicCell::new(None), open_container: AtomicCell::new(None),
carried_item: AtomicCell::new(None), carried_item: AtomicCell::new(None),
@ -433,8 +431,10 @@ impl Player {
.fetch_add(1, std::sync::atomic::Ordering::Relaxed); .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.living_entity.tick(); 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) { 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 // We never got a response from our last keep alive we send
if self 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<f64>) {
// 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 { pub fn has_client_loaded(&self) -> bool {
self.client_loaded.load(Ordering::Relaxed) self.client_loaded.load(Ordering::Relaxed)
|| self.client_loaded_timeout.load(Ordering::Relaxed) == 0 || self.client_loaded_timeout.load(Ordering::Relaxed) == 0
@ -636,15 +659,40 @@ impl Player {
self.client.close(); self.client.close();
} }
pub async fn set_health(&self, health: f32, food: i32, food_saturation: f32) { pub fn can_food_heal(&self) -> bool {
self.living_entity.set_health(health).await; let health = self.living_entity.health.load();
self.food.store(food, std::sync::atomic::Ordering::Relaxed); let max_health = 20.0; // TODO
self.food_saturation.store(food_saturation); 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 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; .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) { pub fn tick_client_load_timeout(&self) {
if !self.client_loaded.load(Ordering::Relaxed) { if !self.client_loaded.load(Ordering::Relaxed) {
let timeout = self.client_loaded_timeout.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) { pub fn set_for_gamemode(&mut self, gamemode: GameMode) {
match gamemode { match gamemode {
GameMode::Creative => { GameMode::Creative => {
self.flying = false; // Start not flying // self.flying = false; // Start not flying
self.allow_flying = true; self.allow_flying = true;
self.creative = true; self.creative = true;
self.invulnerable = true; self.invulnerable = true;
@ -1108,7 +1156,7 @@ impl Abilities {
self.creative = false; self.creative = false;
self.invulnerable = true; self.invulnerable = true;
} }
GameMode::Survival | GameMode::Adventure | GameMode::Undefined => { _ => {
self.flying = false; self.flying = false;
self.allow_flying = false; self.allow_flying = false;
self.creative = false; self.creative = false;
@ -1120,7 +1168,6 @@ impl Abilities {
/// Represents the player's dominant hand. /// Represents the player's dominant hand.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Hand { pub enum Hand {
/// Usually the player's off-hand. /// Usually the player's off-hand.
Left, Left,

View File

@ -26,11 +26,9 @@ impl ThrownItemEntity {
speed: f32, speed: f32,
divergence: f32, divergence: f32,
) { ) {
let to_radians = |degrees: f32| degrees * PI / 180.0; let yaw_rad = yaw.to_radians();
let pitch_rad = pitch.to_radians();
let yaw_rad = to_radians(yaw); let roll_rad = (pitch + roll).to_radians();
let pitch_rad = to_radians(pitch);
let roll_rad = to_radians(pitch + roll);
let x = -yaw_rad.sin() * pitch_rad.cos(); let x = -yaw_rad.sin() * pitch_rad.cos();
let y = -roll_rad.sin(); let y = -roll_rad.sin();

View File

@ -70,7 +70,7 @@ pub async fn authenticate(
let auth_url = ADVANCED_CONFIG let auth_url = ADVANCED_CONFIG
.networking .networking
.authentication .authentication
.auth_url .url
.as_deref() .as_deref()
.unwrap_or(MOJANG_AUTHENTICATION_URL); .unwrap_or(MOJANG_AUTHENTICATION_URL);

View File

@ -159,6 +159,14 @@ impl Player {
let last_pos = entity.pos.load(); let last_pos = entity.pos.load();
self.living_entity.set_pos(position); 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 entity
.on_ground .on_ground
.store(packet.ground, std::sync::atomic::Ordering::Relaxed); .store(packet.ground, std::sync::atomic::Ordering::Relaxed);
@ -195,7 +203,6 @@ impl Player {
) )
.await; .await;
if !self.abilities.lock().await.flying { if !self.abilities.lock().await.flying {
let height_difference = position.y - last_pos.y;
self.living_entity self.living_entity
.update_fall_distance( .update_fall_distance(
height_difference, height_difference,
@ -205,6 +212,12 @@ impl Player {
.await; .await;
} }
chunker::update_position(self).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<Self>, packet: SPlayerPositionRotation) { pub async fn handle_position_rotation(self: &Arc<Self>, packet: SPlayerPositionRotation) {
@ -236,6 +249,13 @@ impl Player {
let last_pos = entity.pos.load(); let last_pos = entity.pos.load();
self.living_entity.set_pos(position); 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 entity
.on_ground .on_ground
.store(packet.ground, std::sync::atomic::Ordering::Relaxed); .store(packet.ground, std::sync::atomic::Ordering::Relaxed);
@ -287,7 +307,6 @@ impl Player {
) )
.await; .await;
if !self.abilities.lock().await.flying { if !self.abilities.lock().await.flying {
let height_difference = position.y - last_pos.y;
self.living_entity self.living_entity
.update_fall_distance( .update_fall_distance(
height_difference, height_difference,
@ -297,6 +316,12 @@ impl Player {
.await; .await;
} }
chunker::update_position(self).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) { pub async fn handle_rotation(&self, rotation: SPlayerRotation) {
@ -804,6 +829,9 @@ impl Player {
.await; .await;
} }
} }
self.client
.send_packet(&CAcknowledgeBlockChange::new(player_action.sequence))
.await;
} }
Status::CancelledDigging => { Status::CancelledDigging => {
if !self.can_interact_with_block_at(&player_action.location, 1.0) { if !self.can_interact_with_block_at(&player_action.location, 1.0) {
@ -816,6 +844,9 @@ impl Player {
} }
self.current_block_destroy_stage self.current_block_destroy_stage
.store(0, std::sync::atomic::Ordering::Relaxed); .store(0, std::sync::atomic::Ordering::Relaxed);
self.client
.send_packet(&CAcknowledgeBlockChange::new(player_action.sequence))
.await;
} }
Status::FinishedDigging => { Status::FinishedDigging => {
// TODO: do validation // TODO: do validation
@ -841,6 +872,9 @@ impl Player {
.broken(block, &self, location, server) .broken(block, &self, location, server)
.await; .await;
} }
self.client
.send_packet(&CAcknowledgeBlockChange::new(player_action.sequence))
.await;
} }
Status::DropItemStack | Status::DropItem => { Status::DropItemStack | Status::DropItem => {
self.drop_item(server).await; self.drop_item(server).await;
@ -851,10 +885,6 @@ impl Player {
}, },
Err(_) => self.kick(TextComponent::text("Invalid status")).await, 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) { pub async fn handle_keep_alive(&self, keep_alive: SKeepAlive) {

View File

@ -9,7 +9,6 @@ use tokio::io::AsyncReadExt;
/// Client -> Server /// Client -> Server
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum ServerboundPacket { pub enum ServerboundPacket {
/// Typically, the first packet sent by the client, which is used to authenticate the connection with the server. /// Typically, the first packet sent by the client, which is used to authenticate the connection with the server.
Auth = 2, Auth = 2,
@ -29,7 +28,6 @@ impl ServerboundPacket {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
/// Server -> Client /// Server -> Client
pub enum ClientboundPacket { 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`, /// 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`,

View File

@ -61,11 +61,12 @@ impl CachedBranding {
pub fn get_branding(&self) -> CPluginMessage { pub fn get_branding(&self) -> CPluginMessage {
CPluginMessage::new("minecraft:brand", &self.cached_server_brand) CPluginMessage::new("minecraft:brand", &self.cached_server_brand)
} }
const BRAND: &str = "Pumpkin";
const BRAND_BYTES: &[u8] = Self::BRAND.as_bytes();
fn build_brand() -> Vec<u8> { fn build_brand() -> Vec<u8> {
let brand = "Pumpkin";
let mut buf = Vec::new(); let mut buf = Vec::new();
VarInt(brand.len() as i32).encode(&mut buf); VarInt(Self::BRAND.len() as i32).encode(&mut buf);
buf.extend_from_slice(brand.as_bytes()); buf.extend_from_slice(Self::BRAND_BYTES);
buf buf
} }
} }

View File

@ -13,7 +13,6 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::GameMode;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::dimension::Dimension; use pumpkin_world::dimension::Dimension;
use pumpkin_world::entity::registry::get_entity_by_id; 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 /// 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<Client>) -> (Arc<Player>, Arc<World>) { pub async fn add_player(&self, client: Arc<Client>) -> (Arc<Player>, Arc<World>) {
let entity_id = self.new_entity_id(); let entity_id = self.new_entity_id();
let gamemode = match BASIC_CONFIG.default_gamemode { let gamemode = BASIC_CONFIG.default_gamemode;
GameMode::Undefined => GameMode::Survival,
game_mode => game_mode,
};
// Basically the default world // Basically the default world
// TODO: select default from config // TODO: select default from config
let world = &self.worlds.read().await[0]; let world = &self.worlds.read().await[0];

View File

@ -613,7 +613,7 @@ impl World {
chunker::player_join(player).await; chunker::player_join(player).await;
// update commands // update commands
player.set_health(20.0, 20, 20.0).await; player.set_health(20.0).await;
} }
/// IMPORTANT: Chunks have to be non-empty /// IMPORTANT: Chunks have to be non-empty