203 lines
6.3 KiB
Rust
203 lines
6.3 KiB
Rust
use std::sync::Arc;
|
|
|
|
use super::{NBTStorage, NBTStorageInit, player::Player};
|
|
use crate::entity::NbtFuture;
|
|
use crossbeam::atomic::AtomicCell;
|
|
use pumpkin_data::damage::DamageType;
|
|
use pumpkin_nbt::compound::NbtCompound;
|
|
use pumpkin_util::Difficulty;
|
|
|
|
const MAX_FOOD: u8 = 20;
|
|
const EXHAUSTION_COST: f32 = 4.0;
|
|
const MAX_EXHAUSTION: f32 = 40.0;
|
|
|
|
pub struct HungerManager {
|
|
pub level: AtomicCell<u8>,
|
|
pub saturation: AtomicCell<f32>,
|
|
pub exhaustion: AtomicCell<f32>,
|
|
pub tick_timer: AtomicCell<u32>,
|
|
}
|
|
|
|
impl Default for HungerManager {
|
|
fn default() -> Self {
|
|
Self {
|
|
level: AtomicCell::new(MAX_FOOD),
|
|
saturation: AtomicCell::new(5.0),
|
|
exhaustion: AtomicCell::new(0.0),
|
|
tick_timer: AtomicCell::new(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HungerManager {
|
|
pub async fn tick(&self, player: &Arc<Player>) {
|
|
let mut level = self.level.load();
|
|
let mut saturation = self.saturation.load();
|
|
let mut exhaustion = self.exhaustion.load();
|
|
let mut timer = self.tick_timer.load();
|
|
|
|
let level_info = player.world().level_info.load();
|
|
let difficulty = level_info.difficulty;
|
|
let natural_regen = level_info.game_rules.natural_health_regeneration;
|
|
let health = player.living_entity.health.load();
|
|
let can_heal = player.can_food_heal();
|
|
|
|
let mut needs_sync = false;
|
|
let mut heal_amount = 0.0;
|
|
let mut damage_amount = 0.0;
|
|
|
|
if exhaustion > EXHAUSTION_COST {
|
|
exhaustion -= EXHAUSTION_COST;
|
|
if saturation > 0.0 {
|
|
saturation = (saturation - 1.0).max(0.0);
|
|
} else if difficulty != Difficulty::Peaceful {
|
|
level = level.saturating_sub(1);
|
|
}
|
|
needs_sync = true;
|
|
}
|
|
|
|
if natural_regen && saturation > 0.0 && can_heal && level >= 20 {
|
|
timer += 1;
|
|
if timer >= 10 {
|
|
let cost = saturation.min(6.0);
|
|
saturation -= cost;
|
|
exhaustion += cost;
|
|
heal_amount = cost / 6.0;
|
|
timer = 0;
|
|
needs_sync = true;
|
|
}
|
|
} else if natural_regen && level >= 18 && can_heal {
|
|
timer += 1;
|
|
if timer >= 80 {
|
|
heal_amount = 1.0;
|
|
exhaustion += 6.0;
|
|
timer = 0;
|
|
needs_sync = true;
|
|
}
|
|
} else if level == 0 {
|
|
timer += 1;
|
|
if timer >= 80 {
|
|
timer = 0;
|
|
let should_starve = match difficulty {
|
|
Difficulty::Peaceful => false,
|
|
Difficulty::Easy => health > 10.0,
|
|
Difficulty::Normal => health > 1.0,
|
|
Difficulty::Hard => true,
|
|
};
|
|
|
|
if should_starve {
|
|
damage_amount = 1.0;
|
|
}
|
|
self.tick_timer.store(0);
|
|
}
|
|
} else {
|
|
timer = 0;
|
|
}
|
|
|
|
if needs_sync || timer != self.tick_timer.load() {
|
|
self.level.store(level);
|
|
self.saturation.store(saturation);
|
|
self.exhaustion.store(exhaustion);
|
|
self.tick_timer.store(timer);
|
|
}
|
|
|
|
if needs_sync {
|
|
player.send_health().await;
|
|
}
|
|
if heal_amount > 0.0 {
|
|
player.heal(heal_amount).await;
|
|
}
|
|
if damage_amount > 0.0 {
|
|
player
|
|
.damage(&**player, damage_amount, DamageType::STARVE)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
pub async fn eat(&self, player: &Player, food: u8, saturation_modifier: f32) {
|
|
let added_saturation = f32::from(food) * saturation_modifier * 2.0;
|
|
|
|
let current_level = self.level.load();
|
|
let current_sat = self.saturation.load();
|
|
|
|
let new_level = (current_level + food).min(MAX_FOOD);
|
|
|
|
let new_sat = (current_sat + added_saturation).min(f32::from(new_level));
|
|
|
|
self.level.store(new_level);
|
|
self.saturation.store(new_sat);
|
|
|
|
player.send_health().await;
|
|
}
|
|
|
|
/// Add exhaustion to trigger hunger decrease
|
|
pub fn add_exhaustion(&self, exhaustion: f32) {
|
|
let current = self.exhaustion.load();
|
|
self.exhaustion
|
|
.store((current + exhaustion).min(MAX_EXHAUSTION));
|
|
}
|
|
|
|
/// Add hunger manually
|
|
pub fn add_hunger(&self, hunger: u8) {
|
|
let current = self.level.load();
|
|
self.level.store((current + hunger).min(MAX_FOOD));
|
|
}
|
|
|
|
/// Add saturation manually
|
|
pub fn add_saturation(&self, saturation: f32) {
|
|
let current = self.saturation.load();
|
|
self.saturation
|
|
.store((current + saturation).min(f32::from(self.level.load())));
|
|
}
|
|
|
|
pub fn set_level(&self, level: u8) {
|
|
self.level.store(level.min(MAX_FOOD));
|
|
}
|
|
|
|
pub fn set_saturation(&self, saturation: f32) {
|
|
self.saturation
|
|
.store(saturation.min(f32::from(self.level.load())));
|
|
}
|
|
|
|
pub fn get_exhaustion(&self) -> f32 {
|
|
self.exhaustion.load()
|
|
}
|
|
|
|
pub fn set_exhaustion(&self, exhaustion: f32) {
|
|
self.exhaustion.store(exhaustion.min(MAX_EXHAUSTION));
|
|
}
|
|
|
|
pub fn restart(&self) {
|
|
self.level.store(MAX_FOOD);
|
|
self.saturation.store(5.0);
|
|
self.exhaustion.store(0.0);
|
|
self.tick_timer.store(0);
|
|
}
|
|
}
|
|
|
|
impl NBTStorage for HungerManager {
|
|
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
|
Box::pin(async {
|
|
nbt.put_int("foodLevel", self.level.load().into());
|
|
nbt.put_float("foodSaturationLevel", self.saturation.load());
|
|
nbt.put_float("foodExhaustionLevel", self.exhaustion.load());
|
|
nbt.put_int("foodTickTimer", self.tick_timer.load() as i32);
|
|
})
|
|
}
|
|
|
|
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
self.level
|
|
.store(nbt.get_int("foodLevel").unwrap_or(20) as u8);
|
|
self.saturation
|
|
.store(nbt.get_float("foodSaturationLevel").unwrap_or(5.0));
|
|
self.exhaustion
|
|
.store(nbt.get_float("foodExhaustionLevel").unwrap_or(0.0));
|
|
self.tick_timer
|
|
.store(nbt.get_int("foodTickTimer").unwrap_or(0) as u32);
|
|
})
|
|
}
|
|
}
|
|
|
|
impl NBTStorageInit for HungerManager {}
|