From cb06be615aa3780d4c83a947520fa55c0d908618 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 19 Mar 2021 22:16:30 +0000 Subject: [PATCH] Backport InventoryTransactionPacket impl from PM4 this version is far better, and we're going to need it to deal with the PlayerAuthInputPacket bullshit. --- src/pocketmine/Player.php | 567 +++++++++--------- .../protocol/InventoryTransactionPacket.php | 133 ++-- .../inventory/MismatchTransactionData.php | 50 ++ .../types/inventory/NormalTransactionData.php | 54 ++ .../inventory/ReleaseItemTransactionData.php | 92 +++ .../types/inventory/TransactionData.php | 72 +++ .../UseItemOnEntityTransactionData.php | 109 ++++ .../inventory/UseItemTransactionData.php | 130 ++++ tests/phpstan/configs/l7-baseline.neon | 15 + 9 files changed, 840 insertions(+), 382 deletions(-) create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/MismatchTransactionData.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/NormalTransactionData.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/TransactionData.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index dcec663c4..a61d435cd 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -152,7 +152,12 @@ use pocketmine\network\mcpe\protocol\types\ContainerIds; use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\network\mcpe\protocol\types\GameMode; +use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\NormalTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\ReleaseItemTransactionData; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; +use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData; use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction; use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor; use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece; @@ -411,7 +416,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ /** @var float */ protected $lastRightClickTime = 0.0; - /** @var \stdClass|null */ + /** @var UseItemTransactionData|null */ protected $lastRightClickData = null; /** @@ -2426,7 +2431,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ /** @var InventoryAction[] $actions */ $actions = []; $isCraftingPart = false; - foreach($packet->actions as $networkInventoryAction){ + foreach($packet->trData->getActions() as $networkInventoryAction){ if( $networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and ( $networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or @@ -2484,321 +2489,313 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->craftingTransaction = null; } - switch($packet->transactionType){ - case InventoryTransactionPacket::TYPE_NORMAL: - $this->setUsingItem(false); - $transaction = new InventoryTransaction($this, $actions); + if($packet->trData instanceof NormalTransactionData){ + $this->setUsingItem(false); + $transaction = new InventoryTransaction($this, $actions); - try{ - $transaction->execute(); - }catch(TransactionValidationException $e){ - $this->server->getLogger()->debug("Failed to execute inventory transaction from " . $this->getName() . ": " . $e->getMessage()); - $this->server->getLogger()->debug("Actions: " . json_encode($packet->actions)); + try{ + $transaction->execute(); + }catch(TransactionValidationException $e){ + $this->server->getLogger()->debug("Failed to execute inventory transaction from " . $this->getName() . ": " . $e->getMessage()); + $this->server->getLogger()->debug("Actions: " . json_encode($packet->trData->getActions())); - return false; - } + return false; + } - //TODO: fix achievement for getting iron from furnace + //TODO: fix achievement for getting iron from furnace - return true; - case InventoryTransactionPacket::TYPE_MISMATCH: - if(count($packet->actions) > 0){ - $this->server->getLogger()->debug("Expected 0 actions for mismatch, got " . count($packet->actions) . ", " . json_encode($packet->actions)); - } - $this->setUsingItem(false); - $this->sendAllInventories(); + return true; + }elseif($packet->trData instanceof MismatchTransactionData){ + if(count($packet->trData->getActions()) > 0){ + $this->server->getLogger()->debug("Expected 0 actions for mismatch, got " . count($packet->trData->getActions()) . ", " . json_encode($packet->trData->getActions())); + } + $this->setUsingItem(false); + $this->sendAllInventories(); - return true; - case InventoryTransactionPacket::TYPE_USE_ITEM: - $blockVector = new Vector3($packet->trData->x, $packet->trData->y, $packet->trData->z); - $face = $packet->trData->face; + return true; + }elseif($packet->trData instanceof UseItemTransactionData){ - $type = $packet->trData->actionType; - switch($type){ - case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_BLOCK: - //TODO: start hack for client spam bug - $spamBug = ($this->lastRightClickData !== null and - microtime(true) - $this->lastRightClickTime < 0.1 and //100ms - $this->lastRightClickData->playerPos->distanceSquared($packet->trData->playerPos) < 0.00001 and - $this->lastRightClickData->x === $packet->trData->x and - $this->lastRightClickData->y === $packet->trData->y and - $this->lastRightClickData->z === $packet->trData->z and - $this->lastRightClickData->clickPos->distanceSquared($packet->trData->clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error - ); - //get rid of continued spam if the player clicks and holds right-click - $this->lastRightClickData = $packet->trData; - $this->lastRightClickTime = microtime(true); - if($spamBug){ - return true; - } - //TODO: end hack for client spam bug - - $this->setUsingItem(false); - - if(!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13)){ - }elseif($this->isCreative()){ - $item = $this->inventory->getItemInHand(); - if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->clickPos, $this, true)){ - return true; - } - }elseif(!$this->inventory->getItemInHand()->equals($packet->trData->itemInHand)){ - $this->inventory->sendHeldItem($this); - }else{ - $item = $this->inventory->getItemInHand(); - $oldItem = clone $item; - if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->clickPos, $this, true)){ - if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){ - $this->inventory->setItemInHand($item); - $this->inventory->sendHeldItem($this->hasSpawned); - } - - return true; - } - } - - $this->inventory->sendHeldItem($this); - - if($blockVector->distanceSquared($this) > 10000){ - return true; - } - - $target = $this->level->getBlock($blockVector); - $block = $target->getSide($face); - - /** @var Block[] $blocks */ - $blocks = array_merge($target->getAllSides(), $block->getAllSides()); //getAllSides() on each of these will include $target and $block because they are next to each other - - $this->level->sendBlocks([$this], $blocks, UpdateBlockPacket::FLAG_ALL_PRIORITY); + $blockVector = $packet->trData->getBlockPos(); + $face = $packet->trData->getFace(); + switch($packet->trData->getActionType()){ + case UseItemTransactionData::ACTION_CLICK_BLOCK: + //TODO: start hack for client spam bug + $spamBug = ($this->lastRightClickData !== null and + microtime(true) - $this->lastRightClickTime < 0.1 and //100ms + $this->lastRightClickData->getPlayerPos()->distanceSquared($packet->trData->getPlayerPos()) < 0.00001 and + $this->lastRightClickData->getBlockPos()->equals($packet->trData->getBlockPos()) and + $this->lastRightClickData->getClickPos()->distanceSquared($packet->trData->getClickPos()) < 0.00001 //signature spam bug has 0 distance, but allow some error + ); + //get rid of continued spam if the player clicks and holds right-click + $this->lastRightClickData = $packet->trData; + $this->lastRightClickTime = microtime(true); + if($spamBug){ return true; - case InventoryTransactionPacket::USE_ITEM_ACTION_BREAK_BLOCK: - $this->doCloseInventory(); + } + //TODO: end hack for client spam bug + $this->setUsingItem(false); + + if(!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13)){ + }elseif($this->isCreative()){ + $item = $this->inventory->getItemInHand(); + if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->getClickPos(), $this, true)){ + return true; + } + }elseif(!$this->inventory->getItemInHand()->equals($packet->trData->getItemInHand())){ + $this->inventory->sendHeldItem($this); + }else{ $item = $this->inventory->getItemInHand(); $oldItem = clone $item; - - if($this->canInteract($blockVector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7) and $this->level->useBreakOn($blockVector, $item, $this, true)){ - if($this->isSurvival()){ - if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){ - $this->inventory->setItemInHand($item); - $this->inventory->sendHeldItem($this->hasSpawned); - } - - $this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING); + if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->getClickPos(), $this, true)){ + if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){ + $this->inventory->setItemInHand($item); + $this->inventory->sendHeldItem($this->hasSpawned); } + return true; } + } - $this->inventory->sendContents($this); - $this->inventory->sendHeldItem($this); - - $target = $this->level->getBlock($blockVector); - /** @var Block[] $blocks */ - $blocks = $target->getAllSides(); - $blocks[] = $target; - - $this->level->sendBlocks([$this], $blocks, UpdateBlockPacket::FLAG_ALL_PRIORITY); - - foreach($blocks as $b){ - $tile = $this->level->getTile($b); - if($tile instanceof Spawnable){ - $tile->spawnTo($this); - } - } + $this->inventory->sendHeldItem($this); + if($blockVector->distanceSquared($this) > 10000){ return true; - case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_AIR: - if($this->isUsingItem()){ - $slot = $this->inventory->getItemInHand(); - if($slot instanceof Consumable and !($slot instanceof MaybeConsumable and !$slot->canBeConsumed())){ - $ev = new PlayerItemConsumeEvent($this, $slot); - if($this->hasItemCooldown($slot)){ - $ev->setCancelled(); - } - $ev->call(); - if($ev->isCancelled() or !$this->consumeObject($slot)){ - $this->inventory->sendContents($this); - return true; - } - $this->resetItemCooldown($slot); - if($this->isSurvival()){ - $slot->pop(); - $this->inventory->setItemInHand($slot); - $this->inventory->addItem($slot->getResidue()); - } - $this->setUsingItem(false); + } + + $target = $this->level->getBlock($blockVector); + $block = $target->getSide($face); + + /** @var Block[] $blocks */ + $blocks = array_merge($target->getAllSides(), $block->getAllSides()); //getAllSides() on each of these will include $target and $block because they are next to each other + + $this->level->sendBlocks([$this], $blocks, UpdateBlockPacket::FLAG_ALL_PRIORITY); + + return true; + case UseItemTransactionData::ACTION_BREAK_BLOCK: + $this->doCloseInventory(); + + $item = $this->inventory->getItemInHand(); + $oldItem = clone $item; + + if($this->canInteract($blockVector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7) and $this->level->useBreakOn($blockVector, $item, $this, true)){ + if($this->isSurvival()){ + if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){ + $this->inventory->setItemInHand($item); + $this->inventory->sendHeldItem($this->hasSpawned); } - } - $directionVector = $this->getDirectionVector(); - if($this->isCreative()){ - $item = $this->inventory->getItemInHand(); - }elseif(!$this->inventory->getItemInHand()->equals($packet->trData->itemInHand)){ - $this->inventory->sendHeldItem($this); - return true; - }else{ - $item = $this->inventory->getItemInHand(); + $this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING); } + return true; + } - $ev = new PlayerInteractEvent($this, $item, null, $directionVector, $face, PlayerInteractEvent::RIGHT_CLICK_AIR); - if($this->hasItemCooldown($item) or $this->isSpectator()){ - $ev->setCancelled(); + $this->inventory->sendContents($this); + $this->inventory->sendHeldItem($this); + + $target = $this->level->getBlock($blockVector); + /** @var Block[] $blocks */ + $blocks = $target->getAllSides(); + $blocks[] = $target; + + $this->level->sendBlocks([$this], $blocks, UpdateBlockPacket::FLAG_ALL_PRIORITY); + + foreach($blocks as $b){ + $tile = $this->level->getTile($b); + if($tile instanceof Spawnable){ + $tile->spawnTo($this); } + } - $ev->call(); - if($ev->isCancelled()){ - $this->inventory->sendHeldItem($this); - return true; - } - - if($item->onClickAir($this, $directionVector)){ - $this->resetItemCooldown($item); + return true; + case UseItemTransactionData::ACTION_CLICK_AIR: + if($this->isUsingItem()){ + $slot = $this->inventory->getItemInHand(); + if($slot instanceof Consumable and !($slot instanceof MaybeConsumable and !$slot->canBeConsumed())){ + $ev = new PlayerItemConsumeEvent($this, $slot); + if($this->hasItemCooldown($slot)){ + $ev->setCancelled(); + } + $ev->call(); + if($ev->isCancelled() or !$this->consumeObject($slot)){ + $this->inventory->sendContents($this); + return true; + } + $this->resetItemCooldown($slot); if($this->isSurvival()){ + $slot->pop(); + $this->inventory->setItemInHand($slot); + $this->inventory->addItem($slot->getResidue()); + } + $this->setUsingItem(false); + } + } + $directionVector = $this->getDirectionVector(); + + if($this->isCreative()){ + $item = $this->inventory->getItemInHand(); + }elseif(!$this->inventory->getItemInHand()->equals($packet->trData->getItemInHand())){ + $this->inventory->sendHeldItem($this); + return true; + }else{ + $item = $this->inventory->getItemInHand(); + } + + $ev = new PlayerInteractEvent($this, $item, null, $directionVector, $face, PlayerInteractEvent::RIGHT_CLICK_AIR); + if($this->hasItemCooldown($item) or $this->isSpectator()){ + $ev->setCancelled(); + } + + $ev->call(); + if($ev->isCancelled()){ + $this->inventory->sendHeldItem($this); + return true; + } + + if($item->onClickAir($this, $directionVector)){ + $this->resetItemCooldown($item); + if($this->isSurvival()){ + $this->inventory->setItemInHand($item); + } + } + + $this->setUsingItem(true); + + return true; + default: + //unknown + break; + } + + $this->inventory->sendContents($this); + return false; + }elseif($packet->trData instanceof UseItemOnEntityTransactionData){ + $target = $this->level->getEntity($packet->trData->getEntityRuntimeId()); + if($target === null){ + return false; + } + + switch($packet->trData->getActionType()){ + case UseItemOnEntityTransactionData::ACTION_INTERACT: + break; //TODO + case UseItemOnEntityTransactionData::ACTION_ATTACK: + if(!$target->isAlive()){ + return true; + } + if($target instanceof ItemEntity or $target instanceof Arrow){ + $this->kick("Attempting to attack an invalid entity"); + $this->server->getLogger()->warning($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidEntity", [$this->getName()])); + return false; + } + + $cancelled = false; + + $heldItem = $this->inventory->getItemInHand(); + $oldItem = clone $heldItem; + + if(!$this->canInteract($target, 8) or $this->isSpectator()){ + $cancelled = true; + }elseif($target instanceof Player){ + if(!$this->server->getConfigBool("pvp")){ + $cancelled = true; + } + } + + $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints()); + + $meleeEnchantmentDamage = 0; + /** @var EnchantmentInstance[] $meleeEnchantments */ + $meleeEnchantments = []; + foreach($heldItem->getEnchantments() as $enchantment){ + $type = $enchantment->getType(); + if($type instanceof MeleeWeaponEnchantment and $type->isApplicableTo($target)){ + $meleeEnchantmentDamage += $type->getDamageBonus($enchantment->getLevel()); + $meleeEnchantments[] = $enchantment; + } + } + $ev->setModifier($meleeEnchantmentDamage, EntityDamageEvent::MODIFIER_WEAPON_ENCHANTMENTS); + + if($cancelled){ + $ev->setCancelled(); + } + + if(!$this->isSprinting() and !$this->isFlying() and $this->fallDistance > 0 and !$this->hasEffect(Effect::BLINDNESS) and !$this->isUnderwater()){ + $ev->setModifier($ev->getFinalDamage() / 2, EntityDamageEvent::MODIFIER_CRITICAL); + } + + $target->attack($ev); + + if($ev->isCancelled()){ + if($heldItem instanceof Durable and $this->isSurvival()){ + $this->inventory->sendContents($this); + } + return true; + } + + if($ev->getModifier(EntityDamageEvent::MODIFIER_CRITICAL) > 0){ + $pk = new AnimatePacket(); + $pk->action = AnimatePacket::ACTION_CRITICAL_HIT; + $pk->entityRuntimeId = $target->getId(); + $this->server->broadcastPacket($target->getViewers(), $pk); + if($target instanceof Player){ + $target->dataPacket($pk); + } + } + + foreach($meleeEnchantments as $enchantment){ + $type = $enchantment->getType(); + assert($type instanceof MeleeWeaponEnchantment); + $type->onPostAttack($this, $target, $enchantment->getLevel()); + } + + if($this->isAlive()){ + //reactive damage like thorns might cause us to be killed by attacking another mob, which + //would mean we'd already have dropped the inventory by the time we reached here + if($heldItem->onAttackEntity($target) and $this->isSurvival() and $oldItem->equalsExact($this->inventory->getItemInHand())){ //always fire the hook, even if we are survival + $this->inventory->setItemInHand($heldItem); + } + + $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK); + } + + return true; + default: + break; //unknown + } + + $this->inventory->sendContents($this); + return false; + }elseif($packet->trData instanceof ReleaseItemTransactionData){ + try{ + switch($packet->trData->getActionType()){ + case ReleaseItemTransactionData::ACTION_RELEASE: + if($this->isUsingItem()){ + $item = $this->inventory->getItemInHand(); + if($this->hasItemCooldown($item)){ + $this->inventory->sendContents($this); + return false; + } + if($item->onReleaseUsing($this)){ + $this->resetItemCooldown($item); $this->inventory->setItemInHand($item); } + return true; } - - $this->setUsingItem(true); - - return true; + break; default: - //unknown break; } - break; - case InventoryTransactionPacket::TYPE_USE_ITEM_ON_ENTITY: - $target = $this->level->getEntity($packet->trData->entityRuntimeId); - if($target === null){ - return false; - } - - $type = $packet->trData->actionType; - - switch($type){ - case InventoryTransactionPacket::USE_ITEM_ON_ENTITY_ACTION_INTERACT: - break; //TODO - case InventoryTransactionPacket::USE_ITEM_ON_ENTITY_ACTION_ATTACK: - if(!$target->isAlive()){ - return true; - } - if($target instanceof ItemEntity or $target instanceof Arrow){ - $this->kick("Attempting to attack an invalid entity"); - $this->server->getLogger()->warning($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidEntity", [$this->getName()])); - return false; - } - - $cancelled = false; - - $heldItem = $this->inventory->getItemInHand(); - $oldItem = clone $heldItem; - - if(!$this->canInteract($target, 8) or $this->isSpectator()){ - $cancelled = true; - }elseif($target instanceof Player){ - if(!$this->server->getConfigBool("pvp")){ - $cancelled = true; - } - } - - $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints()); - - $meleeEnchantmentDamage = 0; - /** @var EnchantmentInstance[] $meleeEnchantments */ - $meleeEnchantments = []; - foreach($heldItem->getEnchantments() as $enchantment){ - $type = $enchantment->getType(); - if($type instanceof MeleeWeaponEnchantment and $type->isApplicableTo($target)){ - $meleeEnchantmentDamage += $type->getDamageBonus($enchantment->getLevel()); - $meleeEnchantments[] = $enchantment; - } - } - $ev->setModifier($meleeEnchantmentDamage, EntityDamageEvent::MODIFIER_WEAPON_ENCHANTMENTS); - - if($cancelled){ - $ev->setCancelled(); - } - - if(!$this->isSprinting() and !$this->isFlying() and $this->fallDistance > 0 and !$this->hasEffect(Effect::BLINDNESS) and !$this->isUnderwater()){ - $ev->setModifier($ev->getFinalDamage() / 2, EntityDamageEvent::MODIFIER_CRITICAL); - } - - $target->attack($ev); - - if($ev->isCancelled()){ - if($heldItem instanceof Durable and $this->isSurvival()){ - $this->inventory->sendContents($this); - } - return true; - } - - if($ev->getModifier(EntityDamageEvent::MODIFIER_CRITICAL) > 0){ - $pk = new AnimatePacket(); - $pk->action = AnimatePacket::ACTION_CRITICAL_HIT; - $pk->entityRuntimeId = $target->getId(); - $this->server->broadcastPacket($target->getViewers(), $pk); - if($target instanceof Player){ - $target->dataPacket($pk); - } - } - - foreach($meleeEnchantments as $enchantment){ - $type = $enchantment->getType(); - assert($type instanceof MeleeWeaponEnchantment); - $type->onPostAttack($this, $target, $enchantment->getLevel()); - } - - if($this->isAlive()){ - //reactive damage like thorns might cause us to be killed by attacking another mob, which - //would mean we'd already have dropped the inventory by the time we reached here - if($heldItem->onAttackEntity($target) and $this->isSurvival() and $oldItem->equalsExact($this->inventory->getItemInHand())){ //always fire the hook, even if we are survival - $this->inventory->setItemInHand($heldItem); - } - - $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK); - } - - return true; - default: - break; //unknown - } - - break; - case InventoryTransactionPacket::TYPE_RELEASE_ITEM: - try{ - $type = $packet->trData->actionType; - switch($type){ - case InventoryTransactionPacket::RELEASE_ITEM_ACTION_RELEASE: - if($this->isUsingItem()){ - $item = $this->inventory->getItemInHand(); - if($this->hasItemCooldown($item)){ - $this->inventory->sendContents($this); - return false; - } - if($item->onReleaseUsing($this)){ - $this->resetItemCooldown($item); - $this->inventory->setItemInHand($item); - } - }else{ - break; - } - - return true; - default: - break; - } - }finally{ - $this->setUsingItem(false); - } - - $this->inventory->sendContents($this); - break; - default: - $this->inventory->sendContents($this); - break; + }finally{ + $this->setUsingItem(false); + } + $this->inventory->sendContents($this); + return false; + }else{ + $this->inventory->sendContents($this); + return false; } - - return false; //TODO } public function handleMobEquipment(MobEquipmentPacket $packet) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php b/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php index 1c8662fd8..0fa447ed6 100644 --- a/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php +++ b/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php @@ -25,9 +25,15 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\NetworkSession as PacketHandlerInterface; use pocketmine\network\mcpe\protocol\types\inventory\InventoryTransactionChangedSlotsHack; -use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction; +use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\NormalTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\ReleaseItemTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\TransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData; +use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData; +use UnexpectedValueException as PacketDecodeException; use function count; class InventoryTransactionPacket extends DataPacket{ @@ -39,137 +45,70 @@ class InventoryTransactionPacket extends DataPacket{ public const TYPE_USE_ITEM_ON_ENTITY = 3; public const TYPE_RELEASE_ITEM = 4; - public const USE_ITEM_ACTION_CLICK_BLOCK = 0; - public const USE_ITEM_ACTION_CLICK_AIR = 1; - public const USE_ITEM_ACTION_BREAK_BLOCK = 2; - - public const RELEASE_ITEM_ACTION_RELEASE = 0; //bow shoot - public const RELEASE_ITEM_ACTION_CONSUME = 1; //eat food, drink potion - - public const USE_ITEM_ON_ENTITY_ACTION_INTERACT = 0; - public const USE_ITEM_ON_ENTITY_ACTION_ATTACK = 1; - /** @var int */ public $requestId; /** @var InventoryTransactionChangedSlotsHack[] */ public $requestChangedSlots; - - /** @var int */ - public $transactionType; /** @var bool */ public $hasItemStackIds; - - /** @var NetworkInventoryAction[] */ - public $actions = []; - - /** @var \stdClass */ + /** @var TransactionData */ public $trData; - protected function decodePayload(){ - $this->requestId = $this->readGenericTypeNetworkId(); + protected function decodePayload() : void{ + $in = $this; + $this->requestId = $in->readGenericTypeNetworkId(); $this->requestChangedSlots = []; if($this->requestId !== 0){ - for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){ - $this->requestChangedSlots[] = InventoryTransactionChangedSlotsHack::read($this); + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $this->requestChangedSlots[] = InventoryTransactionChangedSlotsHack::read($in); } } - $this->transactionType = $this->getUnsignedVarInt(); + $transactionType = $in->getUnsignedVarInt(); - $this->hasItemStackIds = $this->getBool(); + $this->hasItemStackIds = $in->getBool(); - for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $this->actions[] = $action = (new NetworkInventoryAction())->read($this, $this->hasItemStackIds); - } - - $this->trData = new \stdClass(); - - switch($this->transactionType){ + switch($transactionType){ case self::TYPE_NORMAL: + $this->trData = new NormalTransactionData(); + break; case self::TYPE_MISMATCH: - //Regular ComplexInventoryTransaction doesn't read any extra data + $this->trData = new MismatchTransactionData(); break; case self::TYPE_USE_ITEM: - $this->trData->actionType = $this->getUnsignedVarInt(); - $this->getBlockPosition($this->trData->x, $this->trData->y, $this->trData->z); - $this->trData->face = $this->getVarInt(); - $this->trData->hotbarSlot = $this->getVarInt(); - $this->trData->itemInHand = $this->getSlot(); - $this->trData->playerPos = $this->getVector3(); - $this->trData->clickPos = $this->getVector3(); - $this->trData->blockRuntimeId = $this->getUnsignedVarInt(); + $this->trData = new UseItemTransactionData(); break; case self::TYPE_USE_ITEM_ON_ENTITY: - $this->trData->entityRuntimeId = $this->getEntityRuntimeId(); - $this->trData->actionType = $this->getUnsignedVarInt(); - $this->trData->hotbarSlot = $this->getVarInt(); - $this->trData->itemInHand = $this->getSlot(); - $this->trData->playerPos = $this->getVector3(); - $this->trData->clickPos = $this->getVector3(); + $this->trData = new UseItemOnEntityTransactionData(); break; case self::TYPE_RELEASE_ITEM: - $this->trData->actionType = $this->getUnsignedVarInt(); - $this->trData->hotbarSlot = $this->getVarInt(); - $this->trData->itemInHand = $this->getSlot(); - $this->trData->headPos = $this->getVector3(); + $this->trData = new ReleaseItemTransactionData(); break; default: - throw new \UnexpectedValueException("Unknown transaction type $this->transactionType"); + throw new PacketDecodeException("Unknown transaction type $transactionType"); } + + $this->trData->decode($in, $this->hasItemStackIds); } - protected function encodePayload(){ - $this->writeGenericTypeNetworkId($this->requestId); + protected function encodePayload() : void{ + $out = $this; + $out->writeGenericTypeNetworkId($this->requestId); if($this->requestId !== 0){ - $this->putUnsignedVarInt(count($this->requestChangedSlots)); + $out->putUnsignedVarInt(count($this->requestChangedSlots)); foreach($this->requestChangedSlots as $changedSlots){ - $changedSlots->write($this); + $changedSlots->write($out); } } - $this->putUnsignedVarInt($this->transactionType); + $out->putUnsignedVarInt($this->trData->getTypeId()); - $this->putBool($this->hasItemStackIds); + $out->putBool($this->hasItemStackIds); - $this->putUnsignedVarInt(count($this->actions)); - foreach($this->actions as $action){ - $action->write($this, $this->hasItemStackIds); - } - - switch($this->transactionType){ - case self::TYPE_NORMAL: - case self::TYPE_MISMATCH: - break; - case self::TYPE_USE_ITEM: - $this->putUnsignedVarInt($this->trData->actionType); - $this->putBlockPosition($this->trData->x, $this->trData->y, $this->trData->z); - $this->putVarInt($this->trData->face); - $this->putVarInt($this->trData->hotbarSlot); - $this->putSlot($this->trData->itemInHand); - $this->putVector3($this->trData->playerPos); - $this->putVector3($this->trData->clickPos); - $this->putUnsignedVarInt($this->trData->blockRuntimeId); - break; - case self::TYPE_USE_ITEM_ON_ENTITY: - $this->putEntityRuntimeId($this->trData->entityRuntimeId); - $this->putUnsignedVarInt($this->trData->actionType); - $this->putVarInt($this->trData->hotbarSlot); - $this->putSlot($this->trData->itemInHand); - $this->putVector3($this->trData->playerPos); - $this->putVector3($this->trData->clickPos); - break; - case self::TYPE_RELEASE_ITEM: - $this->putUnsignedVarInt($this->trData->actionType); - $this->putVarInt($this->trData->hotbarSlot); - $this->putSlot($this->trData->itemInHand); - $this->putVector3($this->trData->headPos); - break; - default: - throw new \InvalidArgumentException("Unknown transaction type $this->transactionType"); - } + $this->trData->encode($out, $this->hasItemStackIds); } - public function handle(NetworkSession $session) : bool{ - return $session->handleInventoryTransaction($this); + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handleInventoryTransaction($this); } } diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/MismatchTransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/MismatchTransactionData.php new file mode 100644 index 000000000..2ee19c64d --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/MismatchTransactionData.php @@ -0,0 +1,50 @@ +actions) > 0){ + throw new PacketDecodeException("Mismatch transaction type should not have any actions associated with it, but got " . count($this->actions)); + } + } + + protected function encodeData(PacketSerializer $stream) : void{ + + } + + public static function new() : self{ + return new self; //no arguments + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/NormalTransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/NormalTransactionData.php new file mode 100644 index 000000000..757bf9d62 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/NormalTransactionData.php @@ -0,0 +1,54 @@ +actions = $actions; + return $result; + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php new file mode 100644 index 000000000..96b359d17 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php @@ -0,0 +1,92 @@ +actionType; + } + + public function getHotbarSlot() : int{ + return $this->hotbarSlot; + } + + public function getItemInHand() : ItemStack{ + return $this->itemInHand; + } + + public function getHeadPos() : Vector3{ + return $this->headPos; + } + + public function getTypeId() : int{ + return InventoryTransactionPacket::TYPE_RELEASE_ITEM; + } + + protected function decodeData(PacketSerializer $stream) : void{ + $this->actionType = $stream->getUnsignedVarInt(); + $this->hotbarSlot = $stream->getVarInt(); + $this->itemInHand = $stream->getSlot(); + $this->headPos = $stream->getVector3(); + } + + protected function encodeData(PacketSerializer $stream) : void{ + $stream->putUnsignedVarInt($this->actionType); + $stream->putVarInt($this->hotbarSlot); + $stream->putSlot($this->itemInHand); + $stream->putVector3($this->headPos); + } + + /** + * @param NetworkInventoryAction[] $actions + */ + public static function new(array $actions, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $headPos) : self{ + $result = new self; + $result->actions = $actions; + $result->actionType = $actionType; + $result->hotbarSlot = $hotbarSlot; + $result->itemInHand = $itemInHand; + $result->headPos = $headPos; + + return $result; + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/TransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/TransactionData.php new file mode 100644 index 000000000..23089c71b --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/TransactionData.php @@ -0,0 +1,72 @@ +actions; + } + + abstract public function getTypeId() : int; + + /** + * @throws BinaryDataException + * @throws PacketDecodeException + */ + final public function decode(PacketSerializer $stream, bool $hasItemStackIds) : void{ + $actionCount = $stream->getUnsignedVarInt(); + for($i = 0; $i < $actionCount; ++$i){ + $this->actions[] = (new NetworkInventoryAction())->read($stream, $hasItemStackIds); + } + $this->decodeData($stream); + } + + /** + * @throws BinaryDataException + * @throws PacketDecodeException + */ + abstract protected function decodeData(PacketSerializer $stream) : void; + + final public function encode(PacketSerializer $stream, bool $hasItemStackIds) : void{ + $stream->putUnsignedVarInt(count($this->actions)); + foreach($this->actions as $action){ + $action->write($stream, $hasItemStackIds); + } + $this->encodeData($stream); + } + + abstract protected function encodeData(PacketSerializer $stream) : void; +} diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php new file mode 100644 index 000000000..f88bbca73 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php @@ -0,0 +1,109 @@ +entityRuntimeId; + } + + public function getActionType() : int{ + return $this->actionType; + } + + public function getHotbarSlot() : int{ + return $this->hotbarSlot; + } + + public function getItemInHand() : ItemStack{ + return $this->itemInHand; + } + + public function getPlayerPos() : Vector3{ + return $this->playerPos; + } + + public function getClickPos() : Vector3{ + return $this->clickPos; + } + + public function getTypeId() : int{ + return InventoryTransactionPacket::TYPE_USE_ITEM_ON_ENTITY; + } + + protected function decodeData(PacketSerializer $stream) : void{ + $this->entityRuntimeId = $stream->getEntityRuntimeId(); + $this->actionType = $stream->getUnsignedVarInt(); + $this->hotbarSlot = $stream->getVarInt(); + $this->itemInHand = $stream->getSlot(); + $this->playerPos = $stream->getVector3(); + $this->clickPos = $stream->getVector3(); + } + + protected function encodeData(PacketSerializer $stream) : void{ + $stream->putEntityRuntimeId($this->entityRuntimeId); + $stream->putUnsignedVarInt($this->actionType); + $stream->putVarInt($this->hotbarSlot); + $stream->putSlot($this->itemInHand); + $stream->putVector3($this->playerPos); + $stream->putVector3($this->clickPos); + } + + /** + * @param NetworkInventoryAction[] $actions + */ + public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{ + $result = new self; + $result->actions = $actions; + $result->entityRuntimeId = $entityRuntimeId; + $result->actionType = $actionType; + $result->hotbarSlot = $hotbarSlot; + $result->itemInHand = $itemInHand; + $result->playerPos = $playerPos; + $result->clickPos = $clickPos; + return $result; + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php b/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php new file mode 100644 index 000000000..5c1872bea --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php @@ -0,0 +1,130 @@ +actionType; + } + + public function getBlockPos() : Vector3{ + return $this->blockPos; + } + + public function getFace() : int{ + return $this->face; + } + + public function getHotbarSlot() : int{ + return $this->hotbarSlot; + } + + public function getItemInHand() : ItemStack{ + return $this->itemInHand; + } + + public function getPlayerPos() : Vector3{ + return $this->playerPos; + } + + public function getClickPos() : Vector3{ + return $this->clickPos; + } + + public function getBlockRuntimeId() : int{ + return $this->blockRuntimeId; + } + + public function getTypeId() : int{ + return InventoryTransactionPacket::TYPE_USE_ITEM; + } + + protected function decodeData(PacketSerializer $stream) : void{ + $this->actionType = $stream->getUnsignedVarInt(); + $x = $y = $z = 0; + $stream->getBlockPosition($x, $y, $z); + $this->blockPos = new Vector3($x, $y, $z); + $this->face = $stream->getVarInt(); + $this->hotbarSlot = $stream->getVarInt(); + $this->itemInHand = $stream->getSlot(); + $this->playerPos = $stream->getVector3(); + $this->clickPos = $stream->getVector3(); + $this->blockRuntimeId = $stream->getUnsignedVarInt(); + } + + protected function encodeData(PacketSerializer $stream) : void{ + $stream->putUnsignedVarInt($this->actionType); + $stream->putBlockPosition($this->blockPos->x, $this->blockPos->y, $this->blockPos->z); + $stream->putVarInt($this->face); + $stream->putVarInt($this->hotbarSlot); + $stream->putSlot($this->itemInHand); + $stream->putVector3($this->playerPos); + $stream->putVector3($this->clickPos); + $stream->putUnsignedVarInt($this->blockRuntimeId); + } + + /** + * @param NetworkInventoryAction[] $actions + */ + public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{ + $result = new self; + $result->actions = $actions; + $result->actionType = $actionType; + $result->blockPos = $blockPos; + $result->face = $face; + $result->hotbarSlot = $hotbarSlot; + $result->itemInHand = $itemInHand; + $result->playerPos = $playerPos; + $result->clickPos = $clickPos; + $result->blockRuntimeId = $blockRuntimeId; + return $result; + } +} diff --git a/tests/phpstan/configs/l7-baseline.neon b/tests/phpstan/configs/l7-baseline.neon index 5b8a923c8..3fb827c3d 100644 --- a/tests/phpstan/configs/l7-baseline.neon +++ b/tests/phpstan/configs/l7-baseline.neon @@ -695,6 +695,21 @@ parameters: count: 1 path: ../../../src/pocketmine/network/mcpe/protocol/types/LegacySkinAdapter.php + - + message: "#^Parameter \\#1 \\$x of method pocketmine\\\\network\\\\mcpe\\\\NetworkBinaryStream\\:\\:putBlockPosition\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php + + - + message: "#^Parameter \\#2 \\$y of method pocketmine\\\\network\\\\mcpe\\\\NetworkBinaryStream\\:\\:putBlockPosition\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php + + - + message: "#^Parameter \\#3 \\$z of method pocketmine\\\\network\\\\mcpe\\\\NetworkBinaryStream\\:\\:putBlockPosition\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/protocol/types/inventory/UseItemTransactionData.php + - message: "#^Parameter \\#1 \\$string of function strlen expects string, string\\|false given\\.$#" count: 2