mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-17 04:06:54 +00:00
Merge remote-tracking branch 'origin/minor-next' into major-next
This commit is contained in:
@@ -102,6 +102,7 @@ use pocketmine\player\XboxLivePlayerInfo;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\TextFormat;
|
||||
@@ -393,7 +394,7 @@ class NetworkSession{
|
||||
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
|
||||
}
|
||||
}
|
||||
}catch(PacketDecodeException $e){
|
||||
}catch(PacketDecodeException|BinaryDataException $e){
|
||||
$this->logger->logException($e);
|
||||
throw PacketHandlingException::wrap($e, "Packet batch decode error");
|
||||
}
|
||||
|
@@ -383,35 +383,51 @@ class InGamePacketHandler extends PacketHandler{
|
||||
throw new PacketHandlingException("Expected exactly 2 actions for dropping an item");
|
||||
}
|
||||
|
||||
$sourceSlot = null;
|
||||
$clientItemStack = null;
|
||||
$droppedCount = null;
|
||||
|
||||
foreach($data->getActions() as $networkInventoryAction){
|
||||
if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD){
|
||||
//drop item - we don't need to validate this, we only care about the count
|
||||
//if the resulting actions don't match the client for some reason, it will trigger an automatic
|
||||
//prediction rollback anyway.
|
||||
//it's technically possible to see this more than once, but a normal client should never do that.
|
||||
$inventory = $this->player->getInventory();
|
||||
|
||||
$heldItemStack = TypeConverter::getInstance()->coreItemStackToNet($inventory->getItemInHand());
|
||||
$droppedItemStack = $networkInventoryAction->newItem->getItemStack();
|
||||
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
|
||||
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
|
||||
if($heldItemStack->getCount() < $droppedItemStack->getCount() || !$heldItemStack->equalsWithoutCount($droppedItemStack)){
|
||||
return false;
|
||||
if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot == NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){
|
||||
$droppedCount = $networkInventoryAction->newItem->getItemStack()->getCount();
|
||||
if($droppedCount <= 0){
|
||||
throw new PacketHandlingException("Expected positive count for dropped item");
|
||||
}
|
||||
|
||||
$newHeldItem = $inventory->getItemInHand();
|
||||
$droppedItem = $newHeldItem->pop($droppedItemStack->getCount());
|
||||
|
||||
$builder = new TransactionBuilder();
|
||||
$builder->getInventory($inventory)->setItem($inventory->getHeldItemIndex(), $newHeldItem);
|
||||
$builder->addAction(new DropItemAction($droppedItem));
|
||||
|
||||
$transaction = new InventoryTransaction($this->player, $builder->generateActions());
|
||||
return $this->executeInventoryTransaction($transaction, $itemStackRequestId);
|
||||
}elseif($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && $networkInventoryAction->windowId === ContainerIds::INVENTORY){
|
||||
//mobile players can drop an item from a non-selected hotbar slot
|
||||
$sourceSlot = $networkInventoryAction->inventorySlot;
|
||||
$clientItemStack = $networkInventoryAction->oldItem->getItemStack();
|
||||
}else{
|
||||
throw new PacketHandlingException("Unexpected action type in drop item transaction");
|
||||
}
|
||||
}
|
||||
if($sourceSlot === null || $clientItemStack === null || $droppedCount === null){
|
||||
throw new PacketHandlingException("Missing information in drop item transaction, need source slot, client item stack and dropped count");
|
||||
}
|
||||
|
||||
throw new PacketHandlingException("Legacy 'normal' transactions should only be used for dropping items");
|
||||
$inventory = $this->player->getInventory();
|
||||
|
||||
if(!$inventory->slotExists($sourceSlot)){
|
||||
return false; //TODO: size desync??
|
||||
}
|
||||
|
||||
$sourceSlotItem = $inventory->getItem($sourceSlot);
|
||||
$serverItemStack = TypeConverter::getInstance()->coreItemStackToNet($sourceSlotItem);
|
||||
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
|
||||
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
|
||||
if(!$serverItemStack->equals($clientItemStack)){
|
||||
return false;
|
||||
}
|
||||
|
||||
//this modifies $sourceSlotItem
|
||||
$droppedItem = $sourceSlotItem->pop($droppedCount);
|
||||
|
||||
$builder = new TransactionBuilder();
|
||||
$builder->getInventory($inventory)->setItem($sourceSlot, $sourceSlotItem);
|
||||
$builder->addAction(new DropItemAction($droppedItem));
|
||||
|
||||
$transaction = new InventoryTransaction($this->player, $builder->generateActions());
|
||||
return $this->executeInventoryTransaction($transaction, $itemStackRequestId);
|
||||
}
|
||||
|
||||
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
|
||||
|
@@ -58,7 +58,7 @@ use function array_key_first;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
final class ItemStackRequestExecutor{
|
||||
class ItemStackRequestExecutor{
|
||||
private TransactionBuilder $builder;
|
||||
|
||||
/** @var ItemStackRequestSlotInfo[] */
|
||||
@@ -81,7 +81,7 @@ final class ItemStackRequestExecutor{
|
||||
$this->builder = new TransactionBuilder();
|
||||
}
|
||||
|
||||
private function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{
|
||||
protected function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{
|
||||
if($inventory instanceof TransactionBuilderInventory){
|
||||
$inventory = $inventory->getActualInventory();
|
||||
}
|
||||
@@ -111,7 +111,7 @@ final class ItemStackRequestExecutor{
|
||||
*
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
|
||||
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
|
||||
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
|
||||
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $info->getSlotId());
|
||||
if($windowAndSlot === null){
|
||||
@@ -129,7 +129,10 @@ final class ItemStackRequestExecutor{
|
||||
return [$this->builder->getInventory($inventory), $slot];
|
||||
}
|
||||
|
||||
private function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequestSlotInfo $destination, int $count) : void{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
protected function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequestSlotInfo $destination, int $count) : void{
|
||||
$removed = $this->removeItemFromSlot($source, $count);
|
||||
$this->addItemToSlot($destination, $removed, $count);
|
||||
}
|
||||
@@ -138,9 +141,13 @@ final class ItemStackRequestExecutor{
|
||||
* Deducts items from an inventory slot, returning a stack containing the removed items.
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
|
||||
protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
|
||||
$this->requestSlotInfos[] = $slotInfo;
|
||||
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
|
||||
if($count < 1){
|
||||
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
|
||||
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
|
||||
}
|
||||
|
||||
$existingItem = $inventory->getItem($slot);
|
||||
if($existingItem->getCount() < $count){
|
||||
@@ -155,10 +162,15 @@ final class ItemStackRequestExecutor{
|
||||
|
||||
/**
|
||||
* Adds items to the target slot, if they are stackable.
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, int $count) : void{
|
||||
protected function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, int $count) : void{
|
||||
$this->requestSlotInfos[] = $slotInfo;
|
||||
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
|
||||
if($count < 1){
|
||||
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
|
||||
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
|
||||
}
|
||||
|
||||
$existingItem = $inventory->getItem($slot);
|
||||
if(!$existingItem->isNull() && !$existingItem->canStackWith($item)){
|
||||
@@ -174,7 +186,7 @@ final class ItemStackRequestExecutor{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function setNextCreatedItem(?Item $item, bool $creative = false) : void{
|
||||
protected function setNextCreatedItem(?Item $item, bool $creative = false) : void{
|
||||
if($item !== null && $item->isNull()){
|
||||
$item = null;
|
||||
}
|
||||
@@ -196,13 +208,19 @@ final class ItemStackRequestExecutor{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function beginCrafting(int $recipeId, int $repetitions) : void{
|
||||
protected function beginCrafting(int $recipeId, int $repetitions) : void{
|
||||
if($this->specialTransaction !== null){
|
||||
throw new ItemStackRequestProcessException("Another special transaction is already in progress");
|
||||
}
|
||||
if($repetitions < 1){ //TODO: upper bound?
|
||||
if($repetitions < 1){
|
||||
throw new ItemStackRequestProcessException("Cannot craft a recipe less than 1 time");
|
||||
}
|
||||
if($repetitions > 256){
|
||||
//TODO: we can probably lower this limit to 64, but I'm unsure if there are cases where the client may
|
||||
//request more than 64 repetitions of a recipe.
|
||||
//It's already hard-limited to 256 repetitions in the protocol, so this is just a sanity check.
|
||||
throw new ItemStackRequestProcessException("Cannot craft a recipe more than 256 times");
|
||||
}
|
||||
$craftingManager = $this->player->getServer()->getCraftingManager();
|
||||
$recipe = $craftingManager->getCraftingRecipeFromIndex($recipeId);
|
||||
if($recipe === null){
|
||||
@@ -228,7 +246,14 @@ final class ItemStackRequestExecutor{
|
||||
}
|
||||
}
|
||||
|
||||
private function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
protected function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
|
||||
if($count < 1){
|
||||
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
|
||||
throw new ItemStackRequestProcessException("Cannot take less than 1 created item");
|
||||
}
|
||||
$createdItem = $this->nextCreatedItem;
|
||||
if($createdItem === null){
|
||||
throw new ItemStackRequestProcessException("No created item is waiting to be taken");
|
||||
@@ -264,7 +289,7 @@ final class ItemStackRequestExecutor{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function processItemStackRequestAction(ItemStackRequestAction $action) : void{
|
||||
protected function processItemStackRequestAction(ItemStackRequestAction $action) : void{
|
||||
if(
|
||||
$action instanceof TakeStackRequestAction ||
|
||||
$action instanceof PlaceStackRequestAction
|
||||
|
Reference in New Issue
Block a user