Backport InventoryTransactionPacket impl from PM4

this version is far better, and we're going to need it to deal with the PlayerAuthInputPacket bullshit.
This commit is contained in:
Dylan K. Taylor 2021-03-19 22:16:30 +00:00
parent 0d3c11699c
commit cb06be615a
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
9 changed files with 840 additions and 382 deletions

View File

@ -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{

View File

@ -25,9 +25,15 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
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);
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use UnexpectedValueException as PacketDecodeException;
use function count;
class MismatchTransactionData extends TransactionData{
public function getTypeId() : int{
return InventoryTransactionPacket::TYPE_MISMATCH;
}
protected function decodeData(PacketSerializer $stream) : void{
if(count($this->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
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
class NormalTransactionData extends TransactionData{
public function getTypeId() : int{
return InventoryTransactionPacket::TYPE_NORMAL;
}
protected function decodeData(PacketSerializer $stream) : void{
}
protected function encodeData(PacketSerializer $stream) : void{
}
/**
* @param NetworkInventoryAction[] $actions
*
* @return NormalTransactionData
*/
public static function new(array $actions) : self{
$result = new self();
$result->actions = $actions;
return $result;
}
}

View File

@ -0,0 +1,92 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item as ItemStack;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
class ReleaseItemTransactionData extends TransactionData{
public const ACTION_RELEASE = 0; //bow shoot
public const ACTION_CONSUME = 1; //eat food, drink potion
/** @var int */
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $headPos;
public function getActionType() : int{
return $this->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;
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
use pocketmine\utils\BinaryDataException;
use UnexpectedValueException as PacketDecodeException;
use function count;
abstract class TransactionData{
/** @var NetworkInventoryAction[] */
protected $actions = [];
/**
* @return NetworkInventoryAction[]
*/
final public function getActions() : array{
return $this->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;
}

View File

@ -0,0 +1,109 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item as ItemStack;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
class UseItemOnEntityTransactionData extends TransactionData{
public const ACTION_INTERACT = 0;
public const ACTION_ATTACK = 1;
/** @var int */
private $entityRuntimeId;
/** @var int */
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
/** @var Vector3 */
private $clickPos;
public function getEntityRuntimeId() : int{
return $this->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;
}
}

View File

@ -0,0 +1,130 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item as ItemStack;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream as PacketSerializer;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
class UseItemTransactionData extends TransactionData{
public const ACTION_CLICK_BLOCK = 0;
public const ACTION_CLICK_AIR = 1;
public const ACTION_BREAK_BLOCK = 2;
/** @var int */
private $actionType;
/** @var Vector3 */
private $blockPos;
/** @var int */
private $face;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
/** @var Vector3 */
private $clickPos;
/** @var int */
private $blockRuntimeId;
public function getActionType() : int{
return $this->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;
}
}

View File

@ -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