Files
Pumpkin/crates/pumpkin/src/net/bedrock/play/player_action.rs
Alexander Medvedev 3d69190a37 feat: leaves
2026-08-26 22:58:01 +02:00

216 lines
9.4 KiB
Rust

#[allow(clippy::wildcard_imports)]
use super::*;
impl BedrockClient {
#[expect(clippy::match_same_arms)]
#[expect(clippy::too_many_lines)]
pub async fn handle_player_action(
&self,
player: &Arc<Player>,
server: &Server,
packet: SPlayerAction,
) {
if !player.has_client_loaded()
|| ((player.living_entity.dead.load(Ordering::Relaxed)
|| player.living_entity.health.load() <= 0.0)
&& !matches!(packet.action, PlayerAction::Respawn))
{
return;
}
player.update_last_action_time();
match packet.action {
PlayerAction::StartDestroyBlock
| PlayerAction::CreativeDestroyBlock
| PlayerAction::ContinueDestroyBlock => {
let location = packet.block_position;
if !player.can_interact_with_block_at(&location, 1.0) {
return;
}
let entity = &player.get_entity();
let world = entity.world.load_full();
let (block, state) = world.get_block_and_state(&location);
if player.mining.load(Ordering::Relaxed)
&& *player
.mining_pos
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
!= location
{
player.stop_mining();
}
if player.gamemode.load() == GameMode::Creative {
let new_state = world.break_block(
&location,
Some(player.clone()),
BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS,
);
if new_state.is_some() {
server
.block_registry
.broken(&world, block, player, &location, server, state);
}
} else if !state.is_air() {
let speed = crate::block::calc_block_breaking(player, state, block);
if speed >= 1.0 {
player.stop_mining();
let broken_state = world.get_block_state(&location);
let can_harvest = player.can_harvest(broken_state, block);
let new_state = world.break_block(
&location,
Some(player.clone()),
BlockFlags::NOTIFY_NEIGHBORS,
);
if new_state.is_some() {
server.block_registry.broken(
&world,
block,
player,
&location,
server,
broken_state,
);
player.apply_tool_damage_for_block_break(broken_state);
if can_harvest {
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
}
}
} else {
let mut mining_pos = player
.mining_pos
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let starts_breaking =
!player.mining.load(Ordering::Relaxed) || *mining_pos != location;
let progress = if starts_breaking {
player.start_mining_time.store(
player.tick_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
player.mining.store(true, Ordering::Relaxed);
*mining_pos = location;
(speed * 10.0) as i32
} else {
player.current_block_destroy_stage.load(Ordering::Relaxed)
};
drop(mining_pos);
let old_speed = player
.current_block_breaking_speed
.swap(speed.to_bits(), Ordering::Relaxed);
if starts_breaking {
world.set_block_breaking(
entity,
location,
BlockBreakingProgress::Start {
stage: progress,
speed,
},
);
player
.current_block_destroy_stage
.store(progress, Ordering::Relaxed);
} else if old_speed != speed.to_bits() {
world.set_block_breaking(
entity,
location,
BlockBreakingProgress::Update {
stage: progress,
speed: Some(speed),
},
);
}
}
}
}
action @ (PlayerAction::PredictDestroyBlock | PlayerAction::StopDestroyBlock) => {
let location = packet.block_position;
if !player.can_interact_with_block_at(&location, 1.0) {
return;
}
let entity = &player.get_entity();
let world = entity.world.load_full();
let (block, state) = world.get_block_and_state(&location);
if player.gamemode.load() != GameMode::Creative && !state.is_air() {
let speed = crate::block::calc_block_breaking(player, state, block);
let elapsed = player.tick_counter.load(Ordering::Relaxed)
- player.start_mining_time.load(Ordering::Relaxed)
+ 1;
let same_block = *player
.mining_pos
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
== location;
if player.mining.load(Ordering::Relaxed)
&& same_block
&& speed * elapsed as f32 >= MIN_PREDICTED_BREAK_PROGRESS
{
player.stop_mining();
let can_harvest = player.can_harvest(state, block);
let flags = if can_harvest {
BlockFlags::NOTIFY_NEIGHBORS
} else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
};
if world
.break_block(&location, Some(player.clone()), flags)
.is_some()
{
server
.block_registry
.broken(&world, block, player, &location, server, state);
player.apply_tool_damage_for_block_break(state);
if can_harvest {
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
}
}
} else {
let runtime_id = pumpkin_data::BlockState::to_be_network_id(state.id);
self.enqueue_client_packet(&CUpdateBlock::new(location, runtime_id as u32))
.await;
if matches!(action, PlayerAction::StopDestroyBlock) {
player.stop_mining();
} else {
world.set_block_breaking(
entity,
location,
BlockBreakingProgress::Update {
stage: player
.current_block_destroy_stage
.load(Ordering::Relaxed),
speed: Some(speed),
},
);
}
}
} else if matches!(action, PlayerAction::StopDestroyBlock) {
player.stop_mining();
}
}
PlayerAction::CrackBlock => {
// Don't do anything for this action. It is no longer used. Block
// cracking is done fully server-side.
}
PlayerAction::AbortDestroyBlock => {
player.stop_mining();
}
PlayerAction::DropItem => {
player.drop_held_item(false).await;
}
PlayerAction::Respawn
if player.living_entity.dead.load(Ordering::Relaxed)
|| player.living_entity.health.load() <= 0.0 =>
{
player.world().respawn_player(player, false).await;
}
// TODO
_ => {}
}
}
}