fix: close container screens when their block is destroyed (#1539)

Track the block position of open container screens on the player and
close them when the underlying block is broken or exploded, preventing
ghost UIs and item duplication.

Closes #1512
This commit is contained in:
Richard Marshall
2026-02-13 11:00:38 +00:00
committed by GitHub
parent ab1926f3a9
commit ba30b4e9a2
13 changed files with 34 additions and 11 deletions

View File

@@ -60,7 +60,7 @@ impl BlockBehaviour for BarrelBlock {
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&BarrelScreenFactory(inventory))
.open_handled_screen(&BarrelScreenFactory(inventory), Some(*args.position))
.await;
}

View File

@@ -96,7 +96,7 @@ impl BlockBehaviour for BlastFurnaceBlock {
experience_container,
);
args.player
.open_handled_screen(&blasting_furnace_screen_factory)
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position))
.await;
}
crate::block::registry::BlockActionResult::Consume

View File

@@ -182,7 +182,7 @@ impl BlockBehaviour for ChestBlock {
};
args.player
.open_handled_screen(&ChestScreenFactory(inventory))
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position))
.await;
BlockActionResult::Success
@@ -330,7 +330,7 @@ impl BlockBehaviour for CopperChestBlock {
};
args.player
.open_handled_screen(&ChestScreenFactory(inventory))
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position))
.await;
BlockActionResult::Success

View File

@@ -19,7 +19,7 @@ impl BlockBehaviour for CraftingTableBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.player
.open_handled_screen(&CraftingTableScreenFactory)
.open_handled_screen(&CraftingTableScreenFactory, Some(*args.position))
.await;
BlockActionResult::Success

View File

@@ -83,7 +83,10 @@ impl BlockBehaviour for EnderChestBlock {
let inventory = args.player.ender_chest_inventory();
inventory.set_tracker(block_entity.get_tracker()).await;
args.player
.open_handled_screen(&EnderChestScreenFactory(inventory.clone()))
.open_handled_screen(
&EnderChestScreenFactory(inventory.clone()),
Some(*args.position),
)
.await;
// TODO: player.incrementStat(Stats.OPEN_ENDERCHEST);

View File

@@ -93,7 +93,7 @@ impl BlockBehaviour for FurnaceBlock {
let furnace_screen_factory =
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
args.player
.open_handled_screen(&furnace_screen_factory)
.open_handled_screen(&furnace_screen_factory, Some(*args.position))
.await;
}
crate::block::registry::BlockActionResult::Consume

View File

@@ -59,7 +59,7 @@ impl BlockBehaviour for HopperBlock {
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&HopperBlockScreenFactory(inventory))
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(*args.position))
.await;
}

View File

@@ -89,7 +89,7 @@ impl BlockBehaviour for DropperBlock {
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&DropperScreenFactory(inventory))
.open_handled_screen(&DropperScreenFactory(inventory), Some(*args.position))
.await;
}
BlockActionResult::Success

View File

@@ -87,7 +87,7 @@ impl BlockBehaviour for ShulkerBoxBlock {
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&ShulkerBoxScreenFactory(inventory))
.open_handled_screen(&ShulkerBoxScreenFactory(inventory), Some(*args.position))
.await;
}

View File

@@ -91,7 +91,7 @@ impl BlockBehaviour for SmokerBlock {
let smoker_screen_factory =
SmokerScreenFactory::new(inventory, property_delegate, experience_container);
args.player
.open_handled_screen(&smoker_screen_factory)
.open_handled_screen(&smoker_screen_factory, Some(*args.position))
.await;
}
crate::block::registry::BlockActionResult::Consume

View File

@@ -374,6 +374,8 @@ pub struct Player {
pub hunger_manager: HungerManager,
/// The ID of the currently open container (if any).
pub open_container: AtomicCell<Option<u64>>,
/// The block position of the currently open container screen (if any).
pub open_container_pos: AtomicCell<Option<BlockPos>>,
/// The item currently being held by the player.
pub carried_item: Mutex<Option<ItemStack>>,
/// The player's abilities and special powers.
@@ -490,6 +492,7 @@ impl Player {
hunger_manager: HungerManager::default(),
current_block_destroy_stage: AtomicI32::new(-1),
open_container: AtomicCell::new(None),
open_container_pos: AtomicCell::new(None),
tick_counter: AtomicI32::new(0),
packet_sequence: AtomicI32::new(-1),
start_mining_time: AtomicI32::new(0),
@@ -2577,6 +2580,7 @@ impl Player {
}
*self.current_screen_handler.lock().await = self.player_screen_handler.clone();
self.open_container_pos.store(None);
}
pub async fn on_screen_handler_opened(&self, screen_handler: Arc<Mutex<dyn ScreenHandler>>) {
@@ -2594,6 +2598,7 @@ impl Player {
pub async fn open_handled_screen(
&self,
screen_handler_factory: &dyn ScreenHandlerFactory,
block_pos: Option<BlockPos>,
) -> Option<u8> {
if !self
.current_screen_handler
@@ -2631,6 +2636,7 @@ impl Player {
drop(screen_handler_temp);
self.on_screen_handler_opened(screen_handler.clone()).await;
*self.current_screen_handler.lock().await = screen_handler;
self.open_container_pos.store(block_pos);
Some(self.screen_handler_sync_id.load(Ordering::Relaxed))
} else {
//TODO: Send message if spectator

View File

@@ -207,6 +207,7 @@ impl Explosion {
self.damage_entities(world).await;
for (pos, (block, state)) in &blocks {
world.set_block_state(pos, 0, BlockFlags::NOTIFY_ALL).await;
world.close_container_screens_at(pos).await;
let pumpkin_block = world.block_registry.get_pumpkin_block(block.id);

View File

@@ -2930,6 +2930,9 @@ impl World {
let broken_state_id = self.set_block_state(position, new_state_id, flags).await;
// Close container screens for any players viewing this block
self.close_container_screens_at(position).await;
if Block::from_state_id(broken_state_id) != &Block::FIRE {
let particles_packet = CWorldEvent::new(
WorldEvent::BlockBroken as i32,
@@ -2958,6 +2961,16 @@ impl World {
None
}
/// Close container screens for all players who have a container open at the given block position.
pub async fn close_container_screens_at(&self, position: &BlockPos) {
let players = self.players.load();
for player in players.iter() {
if player.open_container_pos.load() == Some(*position) {
player.close_handled_screen().await;
}
}
}
pub async fn drop_stack(self: &Arc<Self>, pos: &BlockPos, stack: ItemStack) {
let height = EntityType::ITEM.dimension[1] / 2.0;
let spawn_pos = {