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, 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::() 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, world: Arc, block_entity: Arc, 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::(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, world: Arc, 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::() 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::() 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::() 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> { 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) }, ) }) } }