Revert "Backport InventoryTransactionPacket impl from PM4"

This reverts commit cb06be615a.

we can't push this to stable because it would break plugins without any
way to know (no protocol or API change).

At most, this should have been wrapped into a protocol change.
This commit is contained in:
Dylan K. Taylor
2021-03-22 21:21:11 +00:00
parent cb06be615a
commit c7cdaeae85
9 changed files with 392 additions and 850 deletions

View File

@ -152,12 +152,7 @@ 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;
@ -416,7 +411,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var float */
protected $lastRightClickTime = 0.0;
/** @var UseItemTransactionData|null */
/** @var \stdClass|null */
protected $lastRightClickData = null;
/**
@ -2431,7 +2426,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var InventoryAction[] $actions */
$actions = [];
$isCraftingPart = false;
foreach($packet->trData->getActions() as $networkInventoryAction){
foreach($packet->actions as $networkInventoryAction){
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and (
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
@ -2489,313 +2484,321 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->craftingTransaction = null;
}
if($packet->trData instanceof NormalTransactionData){
$this->setUsingItem(false);
$transaction = new InventoryTransaction($this, $actions);
switch($packet->transactionType){
case InventoryTransactionPacket::TYPE_NORMAL:
$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->trData->getActions()));
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));
return false;
}
return false;
}
//TODO: fix achievement for getting iron from furnace
//TODO: fix achievement for getting iron from furnace
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_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 UseItemTransactionData){
return true;
case InventoryTransactionPacket::TYPE_USE_ITEM:
$blockVector = new Vector3($packet->trData->x, $packet->trData->y, $packet->trData->z);
$face = $packet->trData->face;
$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;
}
//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)){
$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;
}
}elseif(!$this->inventory->getItemInHand()->equals($packet->trData->getItemInHand())){
$this->inventory->sendHeldItem($this);
}else{
$item = $this->inventory->getItemInHand();
$oldItem = clone $item;
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);
}
//TODO: end hack for client spam bug
return true;
}
}
$this->setUsingItem(false);
$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);
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);
}
$this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING);
}
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);
}
}
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);
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;
}
$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()){
}elseif(!$this->inventory->getItemInHand()->equals($packet->trData->itemInHand)){
$this->inventory->sendHeldItem($this);
}else{
$item = $this->inventory->getItemInHand();
if($this->hasItemCooldown($item)){
$this->inventory->sendContents($this);
return false;
$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;
}
if($item->onReleaseUsing($this)){
$this->resetItemCooldown($item);
$this->inventory->setItemInHand($item);
}
$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);
return true;
case InventoryTransactionPacket::USE_ITEM_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);
}
$this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING);
}
return true;
}
break;
$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);
}
}
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);
}
}
$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();
}
$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;
}
}finally{
$this->setUsingItem(false);
}
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;
$this->inventory->sendContents($this);
return false;
}else{
$this->inventory->sendContents($this);
return false;
}
return false; //TODO
}
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{