diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6aae60745d2..e9cc3408c09 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -498,11 +498,6 @@ private function handleUseItemTransaction(UseItemTransactionData $data) : bool{ $blockPos = $data->getBlockPosition(); $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); $this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos); - //always sync this in case plugins caused a different result than the client expected - //we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating - //more information up the stack. For now I think this is good enough. - //if only the client would tell us what blocks it thinks changed... - $this->syncBlocksNearby($vBlockPos, $data->getFace()); return true; case UseItemTransactionData::ACTION_CLICK_AIR: if($this->player->isUsingItem()){ diff --git a/src/player/Player.php b/src/player/Player.php index aa2d2af8818..c222be9e499 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1938,12 +1938,25 @@ public function interactBlock(Vector3 $pos, int $face, Vector3 $clickOffset) : b return true; } }else{ + $this->syncBlocks([$pos, $pos->getSide($face)]); $this->logger->debug("Cancelled interaction of block at $pos due to not currently being interactable"); } return false; } + /** + * Sync blocks sends block updates to the player at the block positions. + * + * @param Vector3[] $blocks + */ + public function syncBlocks(array $blocks) : void { + $blocks = array_filter($blocks, fn(Vector3 $block) => $block->distanceSquared($this->getLocation()) < 10000); + foreach ($this->getWorld()->createBlockUpdatePackets($blocks) as $packet) { + $this->getNetworkSession()->sendDataPacket($packet); + } + } + /** * Attacks the given entity with the currently-held item. * TODO: move this up the class hierarchy diff --git a/src/world/World.php b/src/world/World.php index 236fd6e5601..f85871056d5 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2238,9 +2238,10 @@ private function destroyBlockInternal(Block $target, Item $item, ?Player $player * @param bool $playSound Whether to play a block-place sound if the block was placed successfully. * @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped if the inventory is full) */ - public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $clickVector = null, ?Player $player = null, bool $playSound = false, array &$returnedItems = []) : bool{ - $blockClicked = $this->getBlock($vector); + public function useItemOn(Vector3 $clickedBlockPos, Item &$item, int $face, ?Vector3 $clickVector = null, ?Player $player = null, bool $playSound = false, array &$returnedItems = []) : bool{ + $blockClicked = $this->getBlock($clickedBlockPos); $blockReplace = $blockClicked->getSide($face); + $mainBlocks = [$clickedBlockPos, ($sideClickPos = $clickedBlockPos->getSide($face))]; if($clickVector === null){ $clickVector = new Vector3(0.0, 0.0, 0.0); @@ -2254,15 +2255,18 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli if(!$this->isInWorld($blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z)){ //TODO: build height limit messages for custom world heights and mcregion cap + $player?->syncBlocks($mainBlocks); return false; } $chunkX = $blockReplace->getPosition()->getFloorX() >> Chunk::COORD_BIT_SIZE; $chunkZ = $blockReplace->getPosition()->getFloorZ() >> Chunk::COORD_BIT_SIZE; if(!$this->isChunkLoaded($chunkX, $chunkZ) || $this->isChunkLocked($chunkX, $chunkZ)){ + $player?->syncBlocks($mainBlocks); return false; } if($blockClicked->getTypeId() === BlockTypeIds::AIR){ + $player?->syncBlocks($mainBlocks); return false; } @@ -2279,6 +2283,18 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli $ev->call(); if(!$ev->isCancelled()){ if($ev->useBlock() && $blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){ + $aroundBlocks = []; + foreach ($clickedBlockPos->sidesArray() as $otherVector) { + if ($sideClickPos !== $otherVector) { + $aroundBlocks[] = $otherVector; + } + } + foreach ($sideClickPos->sidesArray() as $otherVector) { + if ($clickedBlockPos !== $otherVector) { + $aroundBlocks[] = $otherVector; + } + } + $player->syncBlocks($aroundBlocks); return true; } @@ -2289,6 +2305,7 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli } } }else{ + $player->syncBlocks($mainBlocks); return false; } }elseif($blockClicked->onInteract($item, $face, $clickVector, $player, $returnedItems)){ @@ -2296,6 +2313,7 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli } if($item->isNull() || !$item->canBePlaced()){ + $player?->syncBlocks($mainBlocks); return false; } @@ -2307,24 +2325,43 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli $item->getPlacementTransaction($blockReplace, $blockClicked, $face, $clickVector, $player); if($tx === null){ //no placement options available + $player?->syncBlocks($mainBlocks); return false; } - foreach($tx->getBlocks() as [$x, $y, $z, $block]){ + /** @var Vector3[] $placementSyncBlocks */ + $placementSyncBlocks = []; + $allowed = true; + $needsSync = false; + foreach($tx->getBlocks() as [$x, $y, $z, $block]) { + $blockPos = new Vector3($x, $y, $z); + $placementSyncBlocks[] = $blockPos; $block->position($this, $x, $y, $z); - foreach($block->getCollisionBoxes() as $collisionBox){ - if(count($this->getCollidingEntities($collisionBox)) > 0){ - return false; //Entity in block + foreach($block->getCollisionBoxes() as $collisionBox) { + $playerCollision = false; + if ($player !== null && $player->getBoundingBox()->intersectsWith($collisionBox)) { + $allowed = false; + $playerCollision = true; + } + if (count($this->getCollidingEntities($collisionBox)) > 0) { + $allowed = false; + $needsSync = $needsSync || !$playerCollision; } } } + if (!$allowed) { + if ($needsSync) { + $player?->syncBlocks($placementSyncBlocks); + } + return false; + } + if($player !== null){ $ev = new BlockPlaceEvent($player, $tx, $blockClicked, $item); if($player->isSpectator()){ $ev->cancel(); } - if($player->isAdventure(true) && !$ev->isCancelled()){ $canPlace = false; $itemParser = LegacyStringToItemParser::getInstance(); @@ -2343,11 +2380,13 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli $ev->call(); if($ev->isCancelled()){ + $player->syncBlocks($placementSyncBlocks); return false; } } if(!$tx->apply()){ + $player?->syncBlocks($placementSyncBlocks); return false; } $first = true; @@ -2367,7 +2406,8 @@ public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $cli } $item->pop(); - + // As long as we are only syncing the relevant blocks to the actual placement, this should be fine. + $player?->syncBlocks($placementSyncBlocks); return true; }