diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index e93f2bc39..225b6cdec 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -59,8 +59,11 @@ use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\ObjectSet; +use function array_keys; use function array_search; +use function count; use function get_class; +use function implode; use function is_int; use function max; use function spl_object_id; @@ -100,6 +103,8 @@ class InventoryManager{ private int $nextItemStackId = 1; private ?int $currentItemStackRequestId = null; + private bool $fullSyncRequested = false; + public function __construct( private Player $player, private NetworkSession $session @@ -400,7 +405,7 @@ class InventoryManager{ if($clientSideItem === null || !$clientSideItem->equals($currentItem)){ //no prediction or incorrect - do not associate this with the currently active itemstack request $this->trackItemStack($inventory, $slot, $currentItem, null); - $this->syncSlot($inventory, $slot); + $inventoryEntry->pendingSyncs[$slot] = $slot; }else{ //correctly predicted - associate the change with the currently active itemstack request $this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId); @@ -452,7 +457,7 @@ class InventoryManager{ } $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper)); } - unset($entry->predictions[$slot]); + unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]); } public function syncContents(Inventory $inventory) : void{ @@ -464,6 +469,7 @@ class InventoryManager{ } if($windowId !== null){ $entry->predictions = []; + $entry->pendingSyncs = []; $contents = []; foreach($inventory->getContents(true) as $slot => $item){ $itemStack = TypeConverter::getInstance()->coreItemStackToNet($item); @@ -494,6 +500,10 @@ class InventoryManager{ } } + public function requestSyncAll() : void{ + $this->fullSyncRequested = true; + } + public function syncMismatchedPredictedSlotChanges() : void{ foreach($this->inventories as $entry){ $inventory = $entry->inventory; @@ -504,13 +514,33 @@ class InventoryManager{ //any prediction that still exists at this point is a slot that was predicted to change but didn't $this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot"); - $this->syncSlot($inventory, $slot); + $entry->pendingSyncs[$slot] = $slot; } $entry->predictions = []; } } + public function flushPendingUpdates() : void{ + if($this->fullSyncRequested){ + $this->fullSyncRequested = false; + $this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->inventories) . " inventories"); + $this->syncAll(); + }else{ + foreach($this->inventories as $entry){ + if(count($entry->pendingSyncs) === 0){ + continue; + } + $inventory = $entry->inventory; + $this->session->getLogger()->debug("Syncing slots " . implode(", ", array_keys($entry->pendingSyncs)) . " in inventory " . get_class($inventory) . "#" . spl_object_id($inventory)); + foreach($entry->pendingSyncs as $slot){ + $this->syncSlot($inventory, $slot); + } + $entry->pendingSyncs = []; + } + } + } + public function syncData(Inventory $inventory, int $propertyId, int $value) : void{ $windowId = $this->getWindowId($inventory); if($windowId !== null){ diff --git a/src/network/mcpe/InventoryManagerEntry.php b/src/network/mcpe/InventoryManagerEntry.php index b87650e29..fa49baf87 100644 --- a/src/network/mcpe/InventoryManagerEntry.php +++ b/src/network/mcpe/InventoryManagerEntry.php @@ -39,6 +39,12 @@ final class InventoryManagerEntry{ */ public array $itemStackInfos = []; + /** + * @var int[] + * @phpstan-var array + */ + public array $pendingSyncs = []; + public function __construct( public Inventory $inventory, public ?ComplexInventoryMapEntry $complexSlotMap = null diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 56b0622d8..697795f1c 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1219,6 +1219,7 @@ class NetworkSession{ $attribute->markSynchronized(); } } + $this->invManager?->flushPendingUpdates(); $this->flushSendBuffer(); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 682cc0d4e..68e939195 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -35,7 +35,8 @@ use pocketmine\event\player\PlayerEditBookEvent; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionBuilder; -use pocketmine\inventory\transaction\TransactionException; +use pocketmine\inventory\transaction\TransactionCancelledException; +use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\VanillaItems; use pocketmine\item\WritableBook; use pocketmine\item\WritableBookPage; @@ -335,7 +336,7 @@ class InGamePacketHandler extends PacketHandler{ $result = $this->handleNormalTransaction($packet->trData, $packet->requestId); }elseif($packet->trData instanceof MismatchTransactionData){ $this->session->getLogger()->debug("Mismatch transaction received"); - $this->inventoryManager->syncAll(); + $this->inventoryManager->requestSyncAll(); $result = true; }elseif($packet->trData instanceof UseItemTransactionData){ $result = $this->handleUseItemTransaction($packet->trData); @@ -357,9 +358,14 @@ class InGamePacketHandler extends PacketHandler{ $this->inventoryManager->addTransactionPredictedSlotChanges($transaction); try{ $transaction->execute(); - }catch(TransactionException $e){ + }catch(TransactionValidationException $e){ + $this->inventoryManager->requestSyncAll(); $logger = $this->session->getLogger(); - $logger->debug("Failed to execute inventory transaction: " . $e->getMessage()); + $logger->debug("Invalid inventory transaction $requestId: " . $e->getMessage()); + + return false; + }catch(TransactionCancelledException){ + $this->session->getLogger()->debug("Inventory transaction $requestId cancelled by a plugin"); return false; }finally{ @@ -538,6 +544,7 @@ class InGamePacketHandler extends PacketHandler{ $result = false; $this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage()); $this->session->getLogger()->debug(implode("\n", Utils::printableExceptionInfo($e))); + $this->inventoryManager->requestSyncAll(); } if(!$result){