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.
|
||||
- **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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct AuthenticationConfig {
|
||||
/// Whether to use Mojang authentication.
|
||||
pub enabled: bool,
|
||||
pub auth_url: Option<String>,
|
||||
pub url: Option<String>,
|
||||
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,
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum Biome {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -102,7 +102,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
#(#constants)*
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum DamageType {
|
||||
#(#enum_variants,)*
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum EntityPose {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
let variants = array_to_tokenstream(game_events);
|
||||
|
||||
quote! {
|
||||
#[repr(u8)]
|
||||
pub enum GameEvent {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum Particle {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
let variants = array_to_tokenstream(sound_categories);
|
||||
|
||||
quote! {
|
||||
#[repr(u8)]
|
||||
pub enum ScoreboardDisplaySlot {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum WindowType {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ pub(crate) fn build() -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum SoundCategory {
|
||||
#variants
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ impl<T: WindowPropertyTrait> WindowProperty<T> {
|
||||
(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,
|
||||
}
|
||||
|
||||
@ -22,7 +22,6 @@ impl CEntityAnimation {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Animation {
|
||||
SwingMainArm,
|
||||
LeaveBed = 2,
|
||||
|
||||
@ -39,7 +39,6 @@ impl<T> Metadata<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum MetaDataType {
|
||||
Byte,
|
||||
Integer,
|
||||
|
||||
@ -19,7 +19,6 @@ impl CGameEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum GameEvent {
|
||||
NoRespawnBlockAvailable,
|
||||
BeginRaining,
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
@ -59,14 +59,12 @@ impl ClientPacket for CUpdateObjectives<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum Mode {
|
||||
Add,
|
||||
Remove,
|
||||
Update,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
pub enum RenderType {
|
||||
Integer,
|
||||
Hearts,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -63,7 +63,6 @@ pub struct SyncedRegistry {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum DimensionType {
|
||||
Overworld,
|
||||
OverworldCaves,
|
||||
|
||||
@ -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<i8> for GameMode {
|
||||
fn from(value: i8) -> Self {
|
||||
impl TryFrom<i8> for GameMode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i8) -> Result<Self, Self::Error> {
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -34,16 +34,12 @@ impl ArgumentConsumer for GamemodeArgumentConsumer {
|
||||
let s = args.pop()?;
|
||||
|
||||
if let Ok(id) = s.parse::<i8>() {
|
||||
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>(
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<PlayerConfig>,
|
||||
/// The player's current gamemode (e.g., Survival, Creative, Adventure).
|
||||
pub gamemode: AtomicCell<GameMode>,
|
||||
/// The player's hunger level.
|
||||
pub food: AtomicI32,
|
||||
/// The player's food saturation level.
|
||||
pub food_saturation: AtomicCell<f32>,
|
||||
/// The Hunger Manager manages Players hunger level
|
||||
pub hunger_manager: HungerManager,
|
||||
/// The ID of the currently open container (if any).
|
||||
pub open_container: AtomicCell<Option<u64>>,
|
||||
/// 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<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 {
|
||||
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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<Self>, 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) {
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -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<u8> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Client>) -> (Arc<Player>, Arc<World>) {
|
||||
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];
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user