mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix(player): apply food exhaustion when attacking and mining (#2578)
Vanilla Player#attack ends its successful-hit branch with causeFoodExhaustion(0.1F), and Block#playerDestroy calls player.causeFoodExhaustion(0.005F) for every block destroyed. Pumpkin never called add_exhaustion from either path: the only callers were the Hunger status effect in entity/living.rs and jump/sprint movement in entity/player.rs. Combat and mining were therefore free, so the hunger bar was effectively cosmetic for a player who was not sprinting. Player::attack now adds 0.1 exhaustion at the end of the hit path, after the damage_with_context early-return, so only hits that actually deal damage cost hunger. The Java and Bedrock block-break handlers now add MINE_BLOCK_EXHAUSTION (0.005) per block broken. Mining exhaustion is gated exactly where vanilla gates Block#playerDestroy: ServerPlayerGameMode#destroyBlock skips it in creative and when the block was broken without a tool that can harvest it, so the call sites reuse the existing can_harvest / block_drop checks rather than adding new conditions. Creative and spectator players are additionally covered by the existing abilities.invulnerable guard in Player::add_exhaustion, which mirrors vanilla Player#causeFoodExhaustion. Limitation: swimming exhaustion (0.01 per metre) is still missing. Player::progress_motion only implements the onGround branch of vanilla ServerPlayer#checkMovementStatistics; adding the swimming and in-water branches requires the whole else-if chain plus movement-state plumbing (Player::is_swimming is itself still an inferred approximation carrying a TODO), so it is deliberately left out of this change.
This commit is contained in:
@@ -122,6 +122,14 @@ const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
|
||||
|
||||
pub const DATA_VERSION: i32 = 4903; // 26.2
|
||||
|
||||
/// Food exhaustion applied for every block a player mines.
|
||||
///
|
||||
/// Vanilla: `Block#playerDestroy` calls `player.causeFoodExhaustion(0.005F)`.
|
||||
/// `ServerPlayerGameMode#destroyBlock` only reaches `playerDestroy` for
|
||||
/// non-creative players holding a tool that can harvest the block, so callers
|
||||
/// must apply the same gating.
|
||||
pub const MINE_BLOCK_EXHAUSTION: f32 = 0.005; // Vanilla: 0.005F
|
||||
|
||||
struct HeapNode(i32, Vector2<i32>, Weak<ChunkData>);
|
||||
|
||||
impl Eq for HeapNode {}
|
||||
@@ -1164,6 +1172,11 @@ impl Player {
|
||||
})
|
||||
.await;
|
||||
|
||||
// Vanilla `Player#attack` ends the successful-hit branch with
|
||||
// `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage
|
||||
// case returned early above.
|
||||
self.add_exhaustion(0.1).await;
|
||||
|
||||
if config.swing {}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,10 @@ use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::{
|
||||
block::{BlockHitResult, registry::BlockActionResult},
|
||||
entity::{EntityBase, player::Player},
|
||||
entity::{
|
||||
EntityBase,
|
||||
player::{MINE_BLOCK_EXHAUSTION, Player},
|
||||
},
|
||||
net::{DisconnectReason, bedrock::BedrockClient},
|
||||
plugin::player::{
|
||||
item_held::PlayerItemHeldEvent,
|
||||
@@ -1099,6 +1102,7 @@ impl BedrockClient {
|
||||
let speed = crate::block::calc_block_breaking(player, state, block).await;
|
||||
if speed >= 1.0 {
|
||||
let broken_state = world.get_block_state(&location);
|
||||
let can_harvest = player.can_harvest(broken_state, block).await;
|
||||
let new_state = world
|
||||
.break_block(
|
||||
&location,
|
||||
@@ -1112,6 +1116,9 @@ impl BedrockClient {
|
||||
.broken(&world, block, player, &location, server, broken_state)
|
||||
.await;
|
||||
player.apply_tool_damage_for_block_break(broken_state).await;
|
||||
if can_harvest {
|
||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player.mining.store(true, Ordering::Relaxed);
|
||||
@@ -1157,6 +1164,9 @@ impl BedrockClient {
|
||||
.broken(&world, block, player, &location, server, state)
|
||||
.await;
|
||||
player.apply_tool_damage_for_block_break(state).await;
|
||||
if block_drop {
|
||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::block::{self};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::equipment_break_status;
|
||||
use crate::entity::player::statistics::{CustomStatistic, StatisticCategory};
|
||||
use crate::entity::player::{ChatMode, ChatSession, Player};
|
||||
use crate::entity::player::{ChatMode, ChatSession, MINE_BLOCK_EXHAUSTION, Player};
|
||||
use crate::error::PumpkinError;
|
||||
use crate::log_at_level;
|
||||
use crate::net::PlayerConfig;
|
||||
@@ -1993,6 +1993,7 @@ impl JavaClient {
|
||||
// Instant break
|
||||
if speed >= 1.0 {
|
||||
let broken_state = world.get_block_state(&position);
|
||||
let can_harvest = player.can_harvest(broken_state, block).await;
|
||||
let new_state = world
|
||||
.break_block(
|
||||
&position,
|
||||
@@ -2006,6 +2007,9 @@ impl JavaClient {
|
||||
.broken(&world, block, player, &position, server, broken_state)
|
||||
.await;
|
||||
player.apply_tool_damage_for_block_break(broken_state).await;
|
||||
if can_harvest {
|
||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
||||
}
|
||||
let item_id = player.inventory().held_item().lock().await.item.id;
|
||||
player
|
||||
.increment_stat(StatisticCategory::Used, item_id as i32, 1)
|
||||
@@ -2090,6 +2094,9 @@ impl JavaClient {
|
||||
.await;
|
||||
|
||||
player.apply_tool_damage_for_block_break(state).await;
|
||||
if block_drop {
|
||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
||||
}
|
||||
let item_id = player.inventory().held_item().lock().await.item.id;
|
||||
player
|
||||
.increment_stat(StatisticCategory::Used, item_id as i32, 1)
|
||||
|
||||
Reference in New Issue
Block a user