Add Hunger
This commit is contained in:
parent
5c31e1ff62
commit
2e2fe83720
23
README.md
23
README.md
@ -14,7 +14,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,)*
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ impl CEntityAnimation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Animation {
|
pub enum Animation {
|
||||||
SwingMainArm,
|
SwingMainArm,
|
||||||
LeaveBed = 2,
|
LeaveBed = 2,
|
||||||
|
|||||||
@ -39,7 +39,6 @@ impl<T> Metadata<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum MetaDataType {
|
pub enum MetaDataType {
|
||||||
Byte,
|
Byte,
|
||||||
Integer,
|
Integer,
|
||||||
|
|||||||
@ -19,7 +19,6 @@ impl CGameEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum GameEvent {
|
pub enum GameEvent {
|
||||||
NoRespawnBlockAvailable,
|
NoRespawnBlockAvailable,
|
||||||
BeginRaining,
|
BeginRaining,
|
||||||
|
|||||||
@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>(
|
||||||
|
|||||||
65
pumpkin/src/entity/hunger.rs
Normal file
65
pumpkin/src/entity/hunger.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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`,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user