Pumpkin/pumpkin/src/block/blocks/command.rs
Alexander Medvedev 3629f43dcc fix: disable cave carver for now
fixes some issues
2026-05-23 16:36:08 +02:00

376 lines
13 KiB
Rust

use std::sync::{Arc, atomic::Ordering};
use super::redstone::block_receives_redstone_power;
use crate::block::entities::{BlockEntity, command_block::CommandBlockEntity};
use crate::command::CommandSender;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
registry::BlockActionResult,
},
server::Server,
world::World,
};
use pumpkin_data::{
Block, FacingExt,
block_properties::{BlockProperties, CommandBlockLikeProperties, Facing},
};
use pumpkin_util::{GameMode, PermissionLvl, math::position::BlockPos};
use pumpkin_world::{BlockStateId, tick::TickPriority};
use tracing::warn;
pub struct CommandBlock;
impl CommandBlock {
fn get_relative_facing(
world: &World,
pos: &BlockPos,
dir: Facing,
) -> Option<(BlockPos, CommandBlockLikeProperties)> {
let target_pos = pos.offset(dir.to_block_direction().to_offset());
let block = world.get_block(&target_pos);
let allowed_blocks = [
Block::COMMAND_BLOCK.name,
Block::CHAIN_COMMAND_BLOCK.name,
Block::REPEATING_COMMAND_BLOCK.name,
];
if !allowed_blocks.contains(&block.name) {
return None;
}
let state_id = world.get_block_state_id(&target_pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
Some((target_pos, props))
}
fn conditions_met(world: &Arc<World>, pos: &BlockPos, facing: Facing) -> bool {
let (block, state_id) = world.get_block_and_state_id(pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
if !props.conditional {
return true;
}
let Some(before) = Self::get_relative_facing(world, pos, facing.opposite()) else {
return false;
};
let Some(before_entity) = world.get_block_entity(&before.0) else {
warn!("Command block has no matching entity");
return false;
};
let Some(command_entity) = before_entity.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", before.0);
return false;
};
command_entity.success_count.load(Ordering::Relaxed) > 0
}
fn update(
world: &World,
block: &Block,
command_block: &CommandBlockEntity,
pos: &BlockPos,
powered: bool,
) {
let is_auto = command_block.auto.load(Ordering::Relaxed);
if command_block.powered.load(Ordering::Relaxed) == powered && !is_auto {
return;
}
command_block.powered.store(powered, Ordering::Relaxed);
if block.id == Block::CHAIN_COMMAND_BLOCK.id || is_auto || !powered {
return;
}
let state_id = world.get_block_state_id(pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
if !props.conditional {
world.schedule_block_tick(block, *pos, 1, TickPriority::Normal);
return;
}
let Some(behind) = Self::get_relative_facing(world, pos, props.facing.opposite()) else {
return;
};
let Some(behind_entity) = world.get_block_entity(&behind.0) else {
warn!(
"Command Block exists at {} with no matching block entity!",
behind.0
);
return;
};
let behind_entity: &CommandBlockEntity = behind_entity
.as_any()
.downcast_ref()
.expect("behind should always be a command block");
if behind_entity.success_count.load(Ordering::Relaxed) > 0 {
world.schedule_block_tick(block, *pos, 1, TickPriority::Normal);
}
}
async fn execute(
server: &Arc<Server>,
world: Arc<World>,
block_entity: Arc<dyn BlockEntity>,
command: &str,
) {
let command_blocks_work = { world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
let Ok(command_entity) = Arc::downcast::<CommandBlockEntity>(block_entity) else {
warn!("Failed to downcast block entity to CommandBlockEntity");
return;
};
if command.is_empty() {
command_entity.success_count.store(0, Ordering::Release);
} else {
let source = CommandSender::CommandBlock(command_entity, world.clone())
.into_source(server)
.await;
server
.command_dispatcher
.read()
.await
.handle_command(&source, command)
.await;
}
}
async fn chain_execute(
server: &Arc<Server>,
world: Arc<World>,
start: BlockPos,
direction: Facing,
) {
let mut i = u16::MAX;
let mut pos = start;
while i > 0 {
let command_blocks_work = { world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
let block = world.get_block(&pos);
if block.id != Block::CHAIN_COMMAND_BLOCK.id {
break;
}
let Some(block_entity) = world.get_block_entity(&pos) else {
warn!("Missing command block entity");
break;
};
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", pos);
break;
};
let powered = command_entity.powered.load(Ordering::Relaxed);
let auto = command_entity.auto.load(Ordering::Relaxed);
let state_id = world.get_block_state_id(&pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
if powered || auto {
let conditions_met = Self::conditions_met(&world, &pos, direction);
if conditions_met {
let command = command_entity.command.lock().await;
let Some(entity) = world.get_block_entity(&pos) else {
warn!("Command block entity disappeared during execution");
break;
};
Self::execute(server, world.clone(), entity, &command).await;
} else if props.conditional {
command_entity.success_count.store(0, Ordering::Release);
}
}
pos = pos.offset(direction.to_block_direction().to_offset());
i -= 1;
if i == 0 {
warn!(
"Command block chain executed {} times (the maximum)!",
u16::MAX
);
}
}
}
}
impl BlockMetadata for CommandBlock {
fn ids() -> Box<[u16]> {
[
Block::COMMAND_BLOCK.id,
Block::CHAIN_COMMAND_BLOCK.id,
Block::REPEATING_COMMAND_BLOCK.id,
]
.into()
}
}
impl BlockBehaviour for CommandBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CommandBlockLikeProperties::default(args.block);
props.facing = args.player.living_entity.entity.get_facing().opposite();
props.to_state_id(args.block)
})
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if args.player.permission_lvl.load() < PermissionLvl::Two {
return BlockActionResult::Pass;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return BlockActionResult::Pass;
};
args.world.update_block_entity(&block_entity);
BlockActionResult::SuccessServer
})
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let command_blocks_work =
{ args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
if let Some(block_entity) = args.world.get_block_entity(args.position) {
if block_entity.resource_location() != CommandBlockEntity::ID {
return;
}
let Some(command_entity) =
block_entity.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", args.position);
return;
};
Self::update(
args.world,
args.block,
command_entity,
args.position,
block_receives_redstone_power(args.world, args.position).await,
);
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let command_blocks_work =
{ args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return;
};
if block_entity.resource_location() != CommandBlockEntity::ID {
return;
}
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", args.position);
return;
};
let Some(server) = args.world.server.upgrade() else {
return;
};
let props = CommandBlockLikeProperties::from_state_id(
args.world.get_block_state_id(args.position),
args.block,
);
Self::execute(
&server,
args.world.clone(),
block_entity.clone(),
&command_entity.command.lock().await,
)
.await;
Self::chain_execute(
&server,
args.world.clone(),
args.position
.offset(props.facing.to_block_direction().to_offset()),
props.facing,
)
.await;
let block = args.world.get_block(args.position);
let is_auto = command_entity.auto.load(Ordering::Relaxed);
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
args.world
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
}
})
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
if let Some(player) = args.player
&& player.gamemode.load() == GameMode::Creative
{
return true;
}
false
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let send_command_feedback = {
let game_rules = &args.world.level_info.load().game_rules;
game_rules.send_command_feedback
};
let entity = CommandBlockEntity::new(
*args.position,
send_command_feedback,
args.block.id == Block::CHAIN_COMMAND_BLOCK.id,
);
args.world.add_block_entity(Arc::new(entity));
})
}
fn get_comparator_output<'a>(
&'a self,
args: crate::block::GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async {
let entity = args.world.get_block_entity(args.position);
entity.map_or_else(
|| {
warn!("Command block is missing its corresponding block entity");
None
},
|entity| {
let command_block_entity: &CommandBlockEntity =
entity.as_any().downcast_ref().expect(
"Block entity command block's position should be a matching entity",
);
Some(command_block_entity.success_count.load(Ordering::Acquire) as u8)
},
)
})
}
}