mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: Support for eating food
This commit is contained in:
@@ -35,6 +35,10 @@ pub struct ItemComponents {
|
||||
pub food: Option<FoodComponent>,
|
||||
#[serde(rename = "minecraft:equippable")]
|
||||
pub equippable: Option<EquippableComponent>,
|
||||
#[serde(rename = "minecraft:consumable")]
|
||||
pub consumable: Option<Consumable>,
|
||||
#[serde(rename = "minecraft:blocks_attacks")]
|
||||
pub blocks_attacks: Option<BlocksAttacks>,
|
||||
}
|
||||
|
||||
impl ToTokens for ItemComponents {
|
||||
@@ -218,6 +222,21 @@ impl ToTokens for ItemComponents {
|
||||
}), });
|
||||
};
|
||||
|
||||
if let Some(consumable) = &self.consumable {
|
||||
let consume_seconds = LitFloat::new(
|
||||
&format!("{:.1}", consumable.consume_seconds.unwrap_or(1.6)),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
tokens.extend(quote! { (Consumable, &ConsumableImpl {
|
||||
consume_seconds: #consume_seconds,
|
||||
}), });
|
||||
};
|
||||
|
||||
if self.blocks_attacks.is_some() {
|
||||
tokens.extend(quote! { (Consumable, &BlocksAttacksImpl), });
|
||||
};
|
||||
|
||||
if let Some(equippable) = &self.equippable {
|
||||
let slot = match equippable.slot.as_str() {
|
||||
"mainhand" => quote! { &EquipmentSlot::MAIN_HAND },
|
||||
@@ -373,6 +392,16 @@ fn _true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct Consumable {
|
||||
consume_seconds: Option<f32>, // TODO
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct BlocksAttacks {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct EquippableComponent {
|
||||
|
||||
@@ -288,8 +288,26 @@ impl Hash for FoodImpl {
|
||||
self.can_always_eat.hash(state);
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct ConsumableImpl;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ConsumableImpl {
|
||||
pub consume_seconds: f32,
|
||||
// TODO: more
|
||||
}
|
||||
|
||||
impl ConsumableImpl {
|
||||
pub fn consume_ticks(&self) -> i32 {
|
||||
(self.consume_seconds * 20.0) as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentImpl for ConsumableImpl {
|
||||
default_impl!(Consumable);
|
||||
}
|
||||
impl Hash for ConsumableImpl {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
unsafe { (*(&self.consume_seconds as *const f32 as *const u32)).hash(state) };
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct UseRemainderImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
@@ -545,6 +563,10 @@ pub struct TooltipStyleImpl;
|
||||
pub struct DeathProtectionImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct BlocksAttacksImpl;
|
||||
|
||||
impl DataComponentImpl for BlocksAttacksImpl {
|
||||
default_impl!(BlocksAttacks);
|
||||
}
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct StoredEnchantmentsImpl;
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
|
||||
@@ -26,7 +26,7 @@ pub enum Status {
|
||||
DropItem,
|
||||
/// I didn't make that up
|
||||
/// Indicates that the currently held item should have its state updated such as eating food, pulling back bows, using buckets, etc. Location is always set to 0/0/0, Face is always set to -Y. Sequence is always set to 0.
|
||||
ShootArrowOrFinishEating,
|
||||
ReleaseItemInUse,
|
||||
/// Used to swap or assign an item to the second hand. Location is always set to 0/0/0, Face is always set to -Y. Sequence is always set to 0.
|
||||
SwapItem,
|
||||
}
|
||||
@@ -43,7 +43,7 @@ impl TryFrom<i32> for Status {
|
||||
2 => Ok(Self::FinishedDigging),
|
||||
3 => Ok(Self::DropItemStack),
|
||||
4 => Ok(Self::DropItem),
|
||||
5 => Ok(Self::ShootArrowOrFinishEating),
|
||||
5 => Ok(Self::ReleaseItemInUse),
|
||||
6 => Ok(Self::SwapItem),
|
||||
_ => Err(InvalidStatus),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{
|
||||
DataComponentImpl, IDSet, MaxStackSizeImpl, ToolImpl, get, read_data,
|
||||
ConsumableImpl, DataComponentImpl, IDSet, MaxStackSizeImpl, ToolImpl, get, read_data,
|
||||
};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::recipes::RecipeResultStruct;
|
||||
@@ -87,6 +87,17 @@ impl ItemStack {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_max_use_time(&self) -> i32 {
|
||||
if let Some(value) = self.get_data_component::<ConsumableImpl>() {
|
||||
return value.consume_ticks();
|
||||
}
|
||||
// TODO: this causes a panic
|
||||
// if self.get_data_component::<BlocksAttacksImpl>().is_some() {
|
||||
// return 72000;
|
||||
// }
|
||||
0
|
||||
}
|
||||
|
||||
pub fn get_item(&self) -> &Item {
|
||||
if self.is_empty() {
|
||||
&Item::AIR
|
||||
@@ -129,6 +140,12 @@ impl ItemStack {
|
||||
self.item_count = count;
|
||||
}
|
||||
|
||||
pub fn decrement_unless_creative(&mut self, gamemode: GameMode, amount: u8) {
|
||||
if gamemode != GameMode::Creative {
|
||||
self.item_count = self.item_count.saturating_sub(amount);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrement(&mut self, amount: u8) {
|
||||
self.item_count = self.item_count.saturating_sub(amount);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,20 @@ impl HungerManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_modifier(&self, player: &Player, food: u8, saturation_modifier: f32) {
|
||||
let saturation = f32::from(food) * saturation_modifier * 2.0;
|
||||
self.level.store(food + self.level.load());
|
||||
self.saturation.store(saturation + self.saturation.load());
|
||||
player.send_health().await;
|
||||
}
|
||||
|
||||
pub async fn eat(&self, player: &Player, food: u8, saturation: f32) {
|
||||
self.level.store(food + self.level.load());
|
||||
self.saturation.store(saturation + self.saturation.load());
|
||||
|
||||
player.send_health().await;
|
||||
}
|
||||
|
||||
pub fn add_exhaustion(&self, exhaustion: f32) {
|
||||
self.exhaustion
|
||||
.store((self.exhaustion.load() + exhaustion).min(40.0));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use pumpkin_data::potion::Effect;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{
|
||||
AtomicBool, AtomicU8,
|
||||
Ordering::{Relaxed, SeqCst},
|
||||
@@ -9,6 +10,7 @@ use std::{collections::HashMap, sync::atomic::AtomicI32};
|
||||
|
||||
use super::{Entity, NBTStorage};
|
||||
use super::{EntityBase, NBTStorageInit};
|
||||
use crate::entity::player::Hand;
|
||||
use crate::server::Server;
|
||||
use crate::world::loot::{LootContextParameters, LootTableExt};
|
||||
use async_trait::async_trait;
|
||||
@@ -16,7 +18,7 @@ use crossbeam::atomic::AtomicCell;
|
||||
use pumpkin_config::advanced_config;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::damage::DeathMessageType;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::data_component_impl::{EquipmentSlot, FoodImpl};
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType};
|
||||
use pumpkin_data::sound::SoundCategory;
|
||||
@@ -47,6 +49,8 @@ pub struct LivingEntity {
|
||||
pub last_damage_taken: AtomicCell<f32>,
|
||||
/// The current health level of the entity.
|
||||
pub health: AtomicCell<f32>,
|
||||
pub item_use_time: AtomicI32,
|
||||
pub item_in_use: Mutex<Option<ItemStack>>,
|
||||
pub death_time: AtomicU8,
|
||||
/// Indicates whether the entity is dead. (`on_death` called)
|
||||
pub dead: AtomicBool,
|
||||
@@ -68,6 +72,7 @@ pub struct LivingEntity {
|
||||
pub climbing_pos: AtomicCell<Option<BlockPos>>,
|
||||
|
||||
water_movement_speed_multiplier: f32,
|
||||
livings_flags: AtomicU8,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -78,6 +83,11 @@ pub trait LivingEntityTrait: EntityBase {
|
||||
}
|
||||
|
||||
impl LivingEntity {
|
||||
const USING_ITEM_FLAG: i32 = 1;
|
||||
const OFF_HAND_ACTIVE_FLAG: i32 = 2;
|
||||
#[allow(dead_code)]
|
||||
const USING_RIPTIDE_FLAG: i32 = 4;
|
||||
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
let water_movement_speed_multiplier = if entity.entity_type == &EntityType::POLAR_BEAR {
|
||||
0.98
|
||||
@@ -97,6 +107,9 @@ impl LivingEntity {
|
||||
fall_distance: AtomicCell::new(0.0),
|
||||
death_time: AtomicU8::new(0),
|
||||
dead: AtomicBool::new(false),
|
||||
item_use_time: AtomicI32::new(0),
|
||||
item_in_use: Mutex::new(None),
|
||||
livings_flags: AtomicU8::new(0),
|
||||
active_effects: Mutex::new(HashMap::new()),
|
||||
entity_equipment: Arc::new(Mutex::new(EntityEquipment::new())),
|
||||
jumping: AtomicBool::new(false),
|
||||
@@ -141,6 +154,35 @@ impl LivingEntity {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Sends the Hand animation to all others, used when Eating for example
|
||||
pub async fn set_active_hand(&self, hand: Hand, stack: ItemStack) {
|
||||
self.item_use_time
|
||||
.store(stack.get_max_use_time(), Ordering::Relaxed);
|
||||
*self.item_in_use.lock().await = Some(stack);
|
||||
self.set_living_flag(Self::USING_ITEM_FLAG, true).await;
|
||||
self.set_living_flag(Self::OFF_HAND_ACTIVE_FLAG, hand == Hand::Left)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn set_living_flag(&self, flag: i32, value: bool) {
|
||||
let index = flag as u8;
|
||||
let mut b = self.livings_flags.load(Ordering::Relaxed);
|
||||
if value {
|
||||
b |= index;
|
||||
} else {
|
||||
b &= !index;
|
||||
}
|
||||
self.livings_flags.store(b, Ordering::Relaxed);
|
||||
self.entity
|
||||
.send_meta_data(&[Metadata::new(8, MetaDataType::Byte, b)])
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn clear_active_hand(&self) {
|
||||
self.set_living_flag(Self::USING_ITEM_FLAG, false).await;
|
||||
self.item_use_time.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub async fn heal(&self, additional_health: f32) {
|
||||
assert!(additional_health > 0.0);
|
||||
self.set_health(self.health.load() + additional_health)
|
||||
@@ -973,6 +1015,35 @@ impl EntityBase for LivingEntity {
|
||||
self.entity.send_velocity().await;
|
||||
}
|
||||
self.tick_effects().await;
|
||||
// Current active item
|
||||
{
|
||||
let mut item_in_use = self.item_in_use.lock().await;
|
||||
if let Some(item) = item_in_use.as_ref()
|
||||
&& self.item_use_time.fetch_sub(1, Ordering::Relaxed) <= 0
|
||||
{
|
||||
// Consume item
|
||||
if let Some(food) = item.get_data_component::<FoodImpl>()
|
||||
&& let Some(player) = caller.get_player()
|
||||
{
|
||||
player
|
||||
.hunger_manager
|
||||
.eat(player, food.nutrition as u8, food.saturation)
|
||||
.await;
|
||||
}
|
||||
if let Some(player) = caller.get_player() {
|
||||
player
|
||||
.inventory
|
||||
.held_item()
|
||||
.lock()
|
||||
.await
|
||||
.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
|
||||
self.clear_active_hand().await;
|
||||
*item_in_use = None;
|
||||
}
|
||||
}
|
||||
|
||||
if self.hurt_cooldown.load(Relaxed) > 0 {
|
||||
self.hurt_cooldown.fetch_sub(1, Relaxed);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::server::{Server, seasonal_events};
|
||||
use crate::world::{World, chunker};
|
||||
use pumpkin_config::{BASIC_CONFIG, advanced_config};
|
||||
use pumpkin_data::block_properties::{BlockProperties, WaterLikeProperties};
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::data_component_impl::{ConsumableImpl, EquipmentSlot, FoodImpl};
|
||||
use pumpkin_data::entity::{EntityType, entity_from_egg};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
@@ -1296,8 +1296,8 @@ impl JavaClient {
|
||||
Status::DropItemStack => {
|
||||
player.drop_held_item(true).await;
|
||||
}
|
||||
Status::ShootArrowOrFinishEating => {
|
||||
log::debug!("todo");
|
||||
Status::ReleaseItemInUse => {
|
||||
player.living_entity.clear_active_hand().await;
|
||||
}
|
||||
Status::SwapItem => {
|
||||
player.swap_item().await;
|
||||
@@ -1603,11 +1603,30 @@ impl JavaClient {
|
||||
None,
|
||||
)
|
||||
};
|
||||
let held = item_in_hand.lock().await;
|
||||
if held.get_data_component::<ConsumableImpl>().is_some() {
|
||||
// If its food we want to make sure we can actually consume it
|
||||
if let Some(food) = held.get_data_component::<FoodImpl>() {
|
||||
if player.abilities.lock().await.invulnerable
|
||||
|| food.can_always_eat
|
||||
|| player.hunger_manager.level.load() < 20
|
||||
{
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(hand, held.clone())
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
player
|
||||
.living_entity
|
||||
.set_active_hand(hand, held.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
send_cancellable! {{
|
||||
event;
|
||||
'after: {
|
||||
let held = item_in_hand.lock().await;
|
||||
let item = held.item;
|
||||
drop(held);
|
||||
server.item_registry.on_use(item, player).await;
|
||||
|
||||
Reference in New Issue
Block a user