the great airgapping of recipes and itemstacks

This commit is contained in:
Dylan K. Taylor 2020-04-23 14:11:48 +01:00
parent 843993f02b
commit 18d48869a0
29 changed files with 907 additions and 299 deletions

View File

@ -26,13 +26,22 @@ namespace pocketmine\crafting;
use pocketmine\item\Item;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Zlib;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\PacketBatch;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
use pocketmine\timings\Timings;
use pocketmine\utils\Binary;
use pocketmine\utils\UUID;
use function array_map;
use function file_get_contents;
use function json_decode;
use function json_encode;
use function str_repeat;
use function usort;
use const DIRECTORY_SEPARATOR;
@ -101,19 +110,58 @@ class CraftingManager{
$pk = new CraftingDataPacket();
$pk->cleanRecipes = true;
$counter = 0;
$nullUUID = UUID::fromData(str_repeat("\x00", 16));
$converter = TypeConverter::getInstance();
foreach($this->shapelessRecipes as $list){
foreach($list as $recipe){
$pk->addShapelessRecipe($recipe);
$pk->entries[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
Binary::writeInt($counter++),
array_map(function(Item $item) use ($converter) : RecipeIngredient{
return $converter->coreItemStackToRecipeIngredient($item);
}, $recipe->getIngredientList()),
array_map(function(Item $item) use ($converter) : ItemStack{
return $converter->coreItemStackToNet($item);
}, $recipe->getResults()),
$nullUUID,
"crafting_table",
50
);
}
}
foreach($this->shapedRecipes as $list){
foreach($list as $recipe){
$pk->addShapedRecipe($recipe);
$inputs = [];
for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){
for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){
$inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row));
}
}
$pk->entries[] = $r = new ProtocolShapedRecipe(
CraftingDataPacket::ENTRY_SHAPED,
Binary::writeInt($counter++),
$inputs,
array_map(function(Item $item) use ($converter) : ItemStack{
return $converter->coreItemStackToNet($item);
}, $recipe->getResults()),
$nullUUID,
"crafting_table",
50
);
}
}
foreach($this->furnaceRecipes as $recipe){
$pk->addFurnaceRecipe($recipe);
$input = $converter->coreItemStackToNet($recipe->getInput());
$pk->entries[] = new ProtocolFurnaceRecipe(
CraftingDataPacket::ENTRY_FURNACE_DATA,
$input->getId(),
$input->getMeta(),
$converter->coreItemStackToNet($recipe->getResult()),
"furnace"
);
}
$this->craftingDataCache = new CompressBatchPromise();

View File

@ -41,6 +41,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
@ -403,7 +404,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->pitch = $this->location->pitch;
$pk->item = $this->getInventory()->getItemInHand();
$pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand());
$pk->metadata = $this->getSyncedNetworkData(false);
$player->getNetworkSession()->sendDataPacket($pk);

View File

@ -29,6 +29,7 @@ use pocketmine\event\entity\ItemSpawnEvent;
use pocketmine\event\inventory\InventoryPickupItemEvent;
use pocketmine\item\Item;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\AddItemActorPacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
use pocketmine\network\mcpe\protocol\types\entity\EntityLegacyIds;
@ -210,7 +211,7 @@ class ItemEntity extends Entity{
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->item = $this->getItem();
$pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getItem());
$pk->metadata = $this->getSyncedNetworkData(false);
$player->getNetworkSession()->sendDataPacket($pk);

View File

@ -34,6 +34,7 @@ use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
@ -41,6 +42,7 @@ use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;
use pocketmine\player\Player;
use function array_search;
@ -159,7 +161,7 @@ class InventoryManager{
$currentItem = $inventory->getItem($slot);
$clientSideItem = $this->initiatedSlotChanges[$windowId][$slot] ?? null;
if($clientSideItem === null or !$clientSideItem->equalsExact($currentItem)){
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, $currentItem));
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, TypeConverter::getInstance()->coreItemStackToNet($currentItem)));
}
unset($this->initiatedSlotChanges[$windowId][$slot]);
}
@ -169,7 +171,10 @@ class InventoryManager{
$windowId = $this->getWindowId($inventory);
if($windowId !== null){
unset($this->initiatedSlotChanges[$windowId]);
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, $inventory->getContents(true)));
$typeConverter = TypeConverter::getInstance();
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, array_map(function(Item $itemStack) use ($typeConverter) : ItemStack{
return $typeConverter->coreItemStackToNet($itemStack);
}, $inventory->getContents(true))));
}
}
@ -189,7 +194,7 @@ class InventoryManager{
public function syncSelectedHotbarSlot() : void{
$this->session->sendDataPacket(MobEquipmentPacket::create(
$this->player->getId(),
$this->player->getInventory()->getItemInHand(),
TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand()),
$this->player->getInventory()->getHeldItemIndex(),
ContainerIds::INVENTORY
));
@ -197,9 +202,10 @@ class InventoryManager{
public function syncCreative() : void{
$items = [];
$typeConverter = TypeConverter::getInstance();
if(!$this->player->isSpectator()){ //fill it for all gamemodes except spectator
foreach(CreativeInventory::getAll() as $i => $item){
$items[$i] = clone $item;
$items[$i] = $typeConverter->coreItemStackToNet($item);
}
}

View File

@ -37,6 +37,7 @@ use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\DecompressionException;
use pocketmine\network\mcpe\compression\Zlib;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\encryption\DecryptionException;
use pocketmine\network\mcpe\encryption\NetworkCipher;
use pocketmine\network\mcpe\encryption\PrepareEncryptionTask;
@ -790,12 +791,19 @@ class NetworkSession{
public function onMobEquipmentChange(Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), $inv->getItemInHand(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand()), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
}
public function onMobArmorChange(Living $mob) : void{
$inv = $mob->getArmorInventory();
$this->sendDataPacket(MobArmorEquipmentPacket::create($mob->getId(), $inv->getHelmet(), $inv->getChestplate(), $inv->getLeggings(), $inv->getBoots()));
$converter = TypeConverter::getInstance();
$this->sendDataPacket(MobArmorEquipmentPacket::create(
$mob->getId(),
$converter->coreItemStackToNet($inv->getHelmet()),
$converter->coreItemStackToNet($inv->getChestplate()),
$converter->coreItemStackToNet($inv->getLeggings()),
$converter->coreItemStackToNet($inv->getBoots())
));
}
public function syncPlayerList() : void{

View File

@ -0,0 +1,124 @@
<?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\convert;
use pocketmine\item\Durable;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\ItemIds;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
class TypeConverter{
private const DAMAGE_TAG = "Damage"; //TAG_Int
private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___";
/** @var self|null */
private static $instance;
private function __construct(){
//NOOP
}
public static function getInstance() : self{
if(self::$instance === null){
self::$instance = new self;
}
return self::$instance;
}
public static function setInstance(self $instance) : void{
self::$instance = $instance;
}
public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{
$meta = $itemStack->getMeta();
return new RecipeIngredient($itemStack->getId(), $meta === -1 ? 0x7fff : $meta, $itemStack->getCount());
}
public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{
$meta = $ingredient->getMeta();
return ItemFactory::get($ingredient->getId(), $meta === 0x7fff ? -1 : $meta, $ingredient->getCount());
}
public function coreItemStackToNet(Item $itemStack) : ItemStack{
$nbt = null;
if($itemStack->hasNamedTag()){
$nbt = clone $itemStack->getNamedTag();
}
if($itemStack instanceof Durable and $itemStack->getDamage() > 0){
if($nbt !== null){
if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){
$nbt->removeTag(self::DAMAGE_TAG);
$nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing);
}
}else{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
}
$id = $itemStack->getId();
$meta = $itemStack->getMeta();
return new ItemStack(
$id,
$meta === -1 ? 0x7fff : $meta,
$itemStack->getCount(),
$nbt,
[],
[],
$id === ItemIds::SHIELD ? 0 : null
);
}
public function netItemStackToCore(ItemStack $itemStack) : Item{
$compound = $itemStack->getNbt();
$meta = $itemStack->getMeta();
if($compound !== null){
$compound = clone $compound;
if($compound->hasTag(self::DAMAGE_TAG, IntTag::class)){
$meta = $compound->getInt(self::DAMAGE_TAG);
$compound->removeTag(self::DAMAGE_TAG);
if($compound->count() === 0){
$compound = null;
goto end;
}
}
if(($conflicted = $compound->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){
$compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
$compound->setTag(self::DAMAGE_TAG, $conflicted);
}
}
end:
return ItemFactory::get(
$itemStack->getId(),
$meta !== 0x7fff ? $meta : -1,
$itemStack->getCount(),
$compound
);
}
}

View File

@ -38,6 +38,7 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\ActorFallPacket;
@ -205,14 +206,16 @@ class InGamePacketHandler extends PacketHandler{
$isCrafting = false;
$isFinalCraftingPart = false;
foreach($data->getActions() as $networkInventoryAction){
$old = TypeConverter::getInstance()->netItemStackToCore($networkInventoryAction->oldItem);
$new = TypeConverter::getInstance()->netItemStackToCore($networkInventoryAction->newItem);
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER and
$networkInventoryAction->windowId === ContainerIds::UI and
$networkInventoryAction->inventorySlot === 50 and
!$networkInventoryAction->oldItem->equalsExact($networkInventoryAction->newItem)
!$old->equalsExact($new)
){
$isCrafting = true;
if(!$networkInventoryAction->oldItem->isNull() and $networkInventoryAction->newItem->isNull()){
if(!$old->isNull() and $new->isNull()){
$isFinalCraftingPart = true;
}
}elseif(

View File

@ -43,6 +43,7 @@ use pocketmine\player\Player;
use pocketmine\player\PlayerInfo;
use pocketmine\Server;
use pocketmine\utils\UUID;
use function array_map;
use function base64_decode;
/**

View File

@ -25,10 +25,10 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
class AddItemActorPacket extends DataPacket implements ClientboundPacket{
@ -38,7 +38,7 @@ class AddItemActorPacket extends DataPacket implements ClientboundPacket{
public $entityUniqueId = null; //TODO
/** @var int */
public $entityRuntimeId;
/** @var Item */
/** @var ItemStack */
public $item;
/** @var Vector3 */
public $position;

View File

@ -25,11 +25,11 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\entity\EntityLink;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\utils\UUID;
use function count;
@ -57,7 +57,7 @@ class AddPlayerPacket extends DataPacket implements ClientboundPacket{
public $yaw = 0.0;
/** @var float|null */
public $headYaw = null; //TODO
/** @var Item */
/** @var ItemStack */
public $item;
/**
* @var MetadataProperty[]

View File

@ -25,21 +25,17 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\crafting\FurnaceRecipe;
use pocketmine\crafting\ShapedRecipe;
use pocketmine\crafting\ShapelessRecipe;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe;
use pocketmine\network\mcpe\protocol\types\PotionTypeRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\MultiRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeWithTypeId;
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
#ifndef COMPILE
use pocketmine\utils\Binary;
#endif
use function count;
use function str_repeat;
class CraftingDataPacket extends DataPacket implements ClientboundPacket{
public const NETWORK_ID = ProtocolInfo::CRAFTING_DATA_PACKET;
@ -48,12 +44,12 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{
public const ENTRY_SHAPED = 1;
public const ENTRY_FURNACE = 2;
public const ENTRY_FURNACE_DATA = 3;
public const ENTRY_MULTI = 4; //TODO
public const ENTRY_SHULKER_BOX = 5; //TODO
public const ENTRY_SHAPELESS_CHEMISTRY = 6; //TODO
public const ENTRY_SHAPED_CHEMISTRY = 7; //TODO
public const ENTRY_MULTI = 4;
public const ENTRY_SHULKER_BOX = 5;
public const ENTRY_SHAPELESS_CHEMISTRY = 6;
public const ENTRY_SHAPED_CHEMISTRY = 7;
/** @var object[] */
/** @var RecipeWithTypeId[] */
public $entries = [];
/** @var PotionTypeRecipe[] */
public $potionTypeRecipes = [];
@ -62,88 +58,31 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{
/** @var bool */
public $cleanRecipes = false;
/** @var mixed[][] */
public $decodedEntries = [];
protected function decodePayload(NetworkBinaryStream $in) : void{
$this->decodedEntries = [];
$recipeCount = $in->getUnsignedVarInt();
for($i = 0; $i < $recipeCount; ++$i){
$entry = [];
$entry["type"] = $recipeType = $in->getVarInt();
$recipeType = $in->getVarInt();
switch($recipeType){
case self::ENTRY_SHAPELESS:
case self::ENTRY_SHULKER_BOX:
case self::ENTRY_SHAPELESS_CHEMISTRY:
$entry["recipe_id"] = $in->getString();
$ingredientCount = $in->getUnsignedVarInt();
/** @var Item */
$entry["input"] = [];
for($j = 0; $j < $ingredientCount; ++$j){
$entry["input"][] = $input = $in->getRecipeIngredient();
$input->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system because it isn't always 1
}
$resultCount = $in->getUnsignedVarInt();
$entry["output"] = [];
for($k = 0; $k < $resultCount; ++$k){
$entry["output"][] = $in->getSlot();
}
$entry["uuid"] = $in->getUUID()->toString();
$entry["block"] = $in->getString();
$entry["priority"] = $in->getVarInt();
$this->entries[] = ShapelessRecipe::decode($recipeType, $in);
break;
case self::ENTRY_SHAPED:
case self::ENTRY_SHAPED_CHEMISTRY:
$entry["recipe_id"] = $in->getString();
$entry["width"] = $in->getVarInt();
$entry["height"] = $in->getVarInt();
$count = $entry["width"] * $entry["height"];
$entry["input"] = [];
for($j = 0; $j < $count; ++$j){
$entry["input"][] = $input = $in->getRecipeIngredient();
$input->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system
}
$resultCount = $in->getUnsignedVarInt();
$entry["output"] = [];
for($k = 0; $k < $resultCount; ++$k){
$entry["output"][] = $in->getSlot();
}
$entry["uuid"] = $in->getUUID()->toString();
$entry["block"] = $in->getString();
$entry["priority"] = $in->getVarInt();
$this->entries[] = ShapedRecipe::decode($recipeType, $in);
break;
case self::ENTRY_FURNACE:
case self::ENTRY_FURNACE_DATA:
$inputId = $in->getVarInt();
$inputData = -1;
if($recipeType === self::ENTRY_FURNACE_DATA){
$inputData = $in->getVarInt();
if($inputData === 0x7fff){
$inputData = -1;
}
}
try{
$entry["input"] = ItemFactory::get($inputId, $inputData);
}catch(\InvalidArgumentException $e){
throw BadPacketException::wrap($e);
}
$entry["output"] = $out = $in->getSlot();
if($out->getMeta() === 0x7fff){
$entry["output"] = ItemFactory::get($out->getId(), 0); //TODO HACK: some 1.12 furnace recipe outputs have wildcard damage values
}
$entry["block"] = $in->getString();
$this->entries[] = FurnaceRecipe::decode($recipeType, $in);
break;
case self::ENTRY_MULTI:
$entry["uuid"] = $in->getUUID()->toString();
$this->entries[] = MultiRecipe::decode($recipeType, $in);
break;
default:
throw new BadPacketException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode
}
$this->decodedEntries[] = $entry;
}
for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
$input = $in->getVarInt();
@ -160,105 +99,11 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{
$this->cleanRecipes = $in->getBool();
}
/**
* @param object $entry
*/
private static function writeEntry($entry, NetworkBinaryStream $stream, int $pos) : int{
if($entry instanceof ShapelessRecipe){
return self::writeShapelessRecipe($entry, $stream, $pos);
}elseif($entry instanceof ShapedRecipe){
return self::writeShapedRecipe($entry, $stream, $pos);
}elseif($entry instanceof FurnaceRecipe){
return self::writeFurnaceRecipe($entry, $stream);
}
//TODO: add MultiRecipe
return -1;
}
private static function writeShapelessRecipe(ShapelessRecipe $recipe, NetworkBinaryStream $stream, int $pos) : int{
$stream->putString(Binary::writeInt($pos)); //some kind of recipe ID, doesn't matter what it is as long as it's unique
$stream->putUnsignedVarInt($recipe->getIngredientCount());
foreach($recipe->getIngredientList() as $item){
$stream->putRecipeIngredient($item);
}
$results = $recipe->getResults();
$stream->putUnsignedVarInt(count($results));
foreach($results as $item){
$stream->putSlot($item);
}
$stream->put(str_repeat("\x00", 16)); //Null UUID
$stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks)
$stream->putVarInt(50); //TODO: priority
return CraftingDataPacket::ENTRY_SHAPELESS;
}
private static function writeShapedRecipe(ShapedRecipe $recipe, NetworkBinaryStream $stream, int $pos) : int{
$stream->putString(Binary::writeInt($pos)); //some kind of recipe ID, doesn't matter what it is as long as it's unique
$stream->putVarInt($recipe->getWidth());
$stream->putVarInt($recipe->getHeight());
for($z = 0; $z < $recipe->getHeight(); ++$z){
for($x = 0; $x < $recipe->getWidth(); ++$x){
$stream->putRecipeIngredient($recipe->getIngredient($x, $z));
}
}
$results = $recipe->getResults();
$stream->putUnsignedVarInt(count($results));
foreach($results as $item){
$stream->putSlot($item);
}
$stream->put(str_repeat("\x00", 16)); //Null UUID
$stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks)
$stream->putVarInt(50); //TODO: priority
return CraftingDataPacket::ENTRY_SHAPED;
}
private static function writeFurnaceRecipe(FurnaceRecipe $recipe, NetworkBinaryStream $stream) : int{
$stream->putVarInt($recipe->getInput()->getId());
$result = CraftingDataPacket::ENTRY_FURNACE;
if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe
$stream->putVarInt($recipe->getInput()->getMeta());
$result = CraftingDataPacket::ENTRY_FURNACE_DATA;
}
$stream->putSlot($recipe->getResult());
$stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks)
return $result;
}
public function addShapelessRecipe(ShapelessRecipe $recipe) : void{
$this->entries[] = $recipe;
}
public function addShapedRecipe(ShapedRecipe $recipe) : void{
$this->entries[] = $recipe;
}
public function addFurnaceRecipe(FurnaceRecipe $recipe) : void{
$this->entries[] = $recipe;
}
protected function encodePayload(NetworkBinaryStream $out) : void{
$out->putUnsignedVarInt(count($this->entries));
$writer = new NetworkBinaryStream();
$counter = 0;
foreach($this->entries as $d){
$entryType = self::writeEntry($d, $writer, $counter++);
if($entryType >= 0){
$out->putVarInt($entryType);
$out->put($writer->getBuffer());
}else{
$out->putVarInt(-1);
}
$writer->reset();
$out->putVarInt($d->getTypeId());
$d->encode($out);
}
$out->putUnsignedVarInt(count($this->potionTypeRecipes));
foreach($this->potionTypeRecipes as $recipe){

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\utils\UUID;
use function count;
@ -40,9 +40,9 @@ class CraftingEventPacket extends DataPacket implements ServerboundPacket{
public $type;
/** @var UUID */
public $id;
/** @var Item[] */
/** @var ItemStack[] */
public $input = [];
/** @var Item[] */
/** @var ItemStack[] */
public $output = [];
protected function decodePayload(NetworkBinaryStream $in) : void{

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use function count;
@ -35,11 +35,11 @@ class InventoryContentPacket extends DataPacket implements ClientboundPacket{
/** @var int */
public $windowId;
/** @var Item[] */
/** @var ItemStack[] */
public $items = [];
/**
* @param Item[] $items
* @param ItemStack[] $items
*
* @return InventoryContentPacket
*/

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
class InventorySlotPacket extends DataPacket implements ClientboundPacket{
@ -36,14 +36,15 @@ class InventorySlotPacket extends DataPacket implements ClientboundPacket{
public $windowId;
/** @var int */
public $inventorySlot;
/** @var Item */
/** @var ItemStack */
public $item;
public static function create(int $windowId, int $slot, Item $item) : self{
public static function create(int $windowId, int $slot, ItemStack $item) : self{
$result = new self;
$result->inventorySlot = $slot;
$result->item = $item;
$result->windowId = $windowId;
return $result;
}

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{
@ -37,22 +37,23 @@ class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, S
//this intentionally doesn't use an array because we don't want any implicit dependencies on internal order
/** @var Item */
/** @var ItemStack */
public $head;
/** @var Item */
/** @var ItemStack */
public $chest;
/** @var Item */
/** @var ItemStack */
public $legs;
/** @var Item */
/** @var ItemStack */
public $feet;
public static function create(int $entityRuntimeId, Item $head, Item $chest, Item $legs, Item $feet) : self{
public static function create(int $entityRuntimeId, ItemStack $head, ItemStack $chest, ItemStack $legs, ItemStack $feet) : self{
$result = new self;
$result->entityRuntimeId = $entityRuntimeId;
$result->head = $head;
$result->chest = $chest;
$result->legs = $legs;
$result->feet = $feet;
return $result;
}

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
class MobEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{
@ -34,7 +34,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server
/** @var int */
public $entityRuntimeId;
/** @var Item */
/** @var ItemStack */
public $item;
/** @var int */
public $inventorySlot;
@ -43,7 +43,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server
/** @var int */
public $windowId = 0;
public static function create(int $entityRuntimeId, Item $item, int $inventorySlot, int $windowId) : self{
public static function create(int $entityRuntimeId, ItemStack $item, int $inventorySlot, int $windowId) : self{
$result = new self;
$result->entityRuntimeId = $entityRuntimeId;
$result->item = $item;

View File

@ -0,0 +1,100 @@
<?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\ItemIds;
use pocketmine\nbt\tag\CompoundTag;
final class ItemStack{
/** @var int */
private $id;
/** @var int */
private $meta;
/** @var int */
private $count;
/** @var string[] */
private $canPlaceOn;
/** @var string[] */
private $canDestroy;
/** @var CompoundTag|null */
private $nbt;
/** @var int|null */
private $shieldBlockingTick;
/**
* @param string[] $canPlaceOn
* @param string[] $canDestroy
*/
public function __construct(int $id, int $meta, int $count, ?CompoundTag $nbt, array $canPlaceOn, array $canDestroy, ?int $shieldBlockingTick = null){
if(($shieldBlockingTick !== null) !== ($id === ItemIds::SHIELD)){
throw new \InvalidArgumentException("Blocking tick must only be provided for shield items");
}
$this->id = $id;
$this->meta = $meta;
$this->count = $count;
$this->canPlaceOn = $canPlaceOn;
$this->canDestroy = $canDestroy;
$this->nbt = $nbt;
$this->shieldBlockingTick = $shieldBlockingTick;
}
public static function null() : self{
return new self(0, 0, 0, null, [], [], null);
}
public function getId() : int{
return $this->id;
}
public function getMeta() : int{
return $this->meta;
}
public function getCount() : int{
return $this->count;
}
/**
* @return string[]
*/
public function getCanPlaceOn() : array{
return $this->canPlaceOn;
}
/**
* @return string[]
*/
public function getCanDestroy() : array{
return $this->canDestroy;
}
public function getNbt() : ?CompoundTag{
return $this->nbt;
}
public function getShieldBlockingTick() : ?int{
return $this->shieldBlockingTick;
}
}

View File

@ -31,8 +31,8 @@ use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\player\Player;
use pocketmine\utils\BinaryDataException;
@ -82,9 +82,9 @@ class NetworkInventoryAction{
public $sourceFlags = 0;
/** @var int */
public $inventorySlot;
/** @var Item */
/** @var ItemStack */
public $oldItem;
/** @var Item */
/** @var ItemStack */
public $newItem;
/**
@ -150,7 +150,9 @@ class NetworkInventoryAction{
* @throws \UnexpectedValueException
*/
public function createInventoryAction(Player $player) : ?InventoryAction{
if($this->oldItem->equalsExact($this->newItem)){
$old = TypeConverter::getInstance()->netItemStackToCore($this->oldItem);
$new = TypeConverter::getInstance()->netItemStackToCore($this->newItem);
if($old->equalsExact($new)){
//filter out useless noise in 1.13
return null;
}
@ -180,7 +182,7 @@ class NetworkInventoryAction{
$slot = $this->inventorySlot;
}
if($window !== null){
return new SlotChangeAction($window, $slot, $this->oldItem, $this->newItem);
return new SlotChangeAction($window, $slot, $old, $new);
}
throw new \UnexpectedValueException("No open container with window ID $this->windowId");
@ -189,13 +191,13 @@ class NetworkInventoryAction{
throw new \UnexpectedValueException("Only expecting drop-item world actions from the client!");
}
return new DropItemAction($this->newItem);
return new DropItemAction($new);
case self::SOURCE_CREATIVE:
switch($this->inventorySlot){
case self::ACTION_MAGIC_SLOT_CREATIVE_DELETE_ITEM:
return new DestroyItemAction($this->newItem);
return new DestroyItemAction($new);
case self::ACTION_MAGIC_SLOT_CREATIVE_CREATE_ITEM:
return new CreateItemAction($this->oldItem);
return new CreateItemAction($new);
default:
throw new \UnexpectedValueException("Unexpected creative action type $this->inventorySlot");

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
@ -36,7 +35,7 @@ class ReleaseItemTransactionData extends TransactionData{
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var Item */
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $headPos;
@ -49,7 +48,7 @@ class ReleaseItemTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : Item{
public function getItemInHand() : ItemStack{
return $this->itemInHand;
}
@ -78,13 +77,14 @@ class ReleaseItemTransactionData extends TransactionData{
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $actionType, int $hotbarSlot, Item $itemInHand, Vector3 $headPos) : self{
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

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
@ -38,7 +37,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var Item */
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
@ -57,7 +56,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : Item{
public function getItemInHand() : ItemStack{
return $this->itemInHand;
}
@ -94,7 +93,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, Item $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{
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;

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types\inventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
@ -41,7 +40,7 @@ class UseItemTransactionData extends TransactionData{
private $face;
/** @var int */
private $hotbarSlot;
/** @var Item */
/** @var ItemStack */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
@ -66,7 +65,7 @@ class UseItemTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : Item{
public function getItemInHand() : ItemStack{
return $this->itemInHand;
}
@ -112,7 +111,7 @@ class UseItemTransactionData extends TransactionData{
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, Item $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{
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;

View File

@ -0,0 +1,85 @@
<?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\recipe;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
final class FurnaceRecipe extends RecipeWithTypeId{
/** @var int */
private $inputId;
/** @var int|null */
private $inputMeta;
/** @var ItemStack */
private $result;
/** @var string */
private $blockName;
public function __construct(int $typeId, int $inputId, ?int $inputMeta, ItemStack $result, string $blockName){
parent::__construct($typeId);
$this->inputId = $inputId;
$this->inputMeta = $inputMeta;
$this->result = $result;
$this->blockName = $blockName;
}
public function getInputId() : int{
return $this->inputId;
}
public function getInputMeta() : ?int{
return $this->inputMeta;
}
public function getResult() : ItemStack{
return $this->result;
}
public function getBlockName() : string{
return $this->blockName;
}
public static function decode(int $typeId, NetworkBinaryStream $in) : self{
$inputId = $in->getVarInt();
$inputData = null;
if($typeId === CraftingDataPacket::ENTRY_FURNACE_DATA){
$inputData = $in->getVarInt();
}
$output = $in->getSlot();
$block = $in->getString();
return new self($typeId, $inputId, $inputData, $output, $block);
}
public function encode(NetworkBinaryStream $out) : void{
$out->putVarInt($this->inputId);
if($this->getTypeId() === CraftingDataPacket::ENTRY_FURNACE_DATA){
$out->putVarInt($this->inputMeta);
}
$out->putSlot($this->result);
$out->putString($this->blockName);
}
}

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\recipe;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\utils\UUID;
final class MultiRecipe extends RecipeWithTypeId{
/** @var UUID */
private $recipeId;
public function __construct(int $typeId, UUID $recipeId){
parent::__construct($typeId);
$this->recipeId = $recipeId;
}
public function getRecipeId() : UUID{
return $this->recipeId;
}
public static function decode(int $typeId, NetworkBinaryStream $in) : self{
return new self($typeId, $in->getUUID());
}
public function encode(NetworkBinaryStream $out) : void{
$out->putUUID($this->recipeId);
}
}

View File

@ -0,0 +1,52 @@
<?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\recipe;
final class RecipeIngredient{
/** @var int */
private $id;
/** @var int */
private $meta;
/** @var int */
private $count;
public function __construct(int $id, int $meta, int $count){
$this->id = $id;
$this->meta = $meta;
$this->count = $count;
}
public function getId() : int{
return $this->id;
}
public function getMeta() : int{
return $this->meta;
}
public function getCount() : int{
return $this->count;
}
}

View File

@ -0,0 +1,41 @@
<?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\recipe;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
abstract class RecipeWithTypeId{
/** @var int */
private $typeId;
protected function __construct(int $typeId){
$this->typeId = $typeId;
}
final public function getTypeId() : int{
return $this->typeId;
}
abstract public function encode(NetworkBinaryStream $out) : void;
}

View File

@ -0,0 +1,151 @@
<?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\recipe;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\utils\UUID;
use function count;
final class ShapedRecipe extends RecipeWithTypeId{
/** @var string */
private $recipeId;
/** @var RecipeIngredient[][] */
private $input;
/** @var ItemStack[] */
private $output;
/** @var UUID */
private $uuid;
/** @var string */
private $blockName;
/** @var int */
private $priority;
/**
* @param RecipeIngredient[][] $input
* @param ItemStack[] $output
*/
public function __construct(int $typeId, string $recipeId, array $input, array $output, UUID $uuid, string $blockType, int $priority){
parent::__construct($typeId);
$rows = count($input);
if($rows < 1 or $rows > 3){
throw new \InvalidArgumentException("Expected 1, 2 or 3 input rows");
}
$columns = null;
foreach($input as $rowNumber => $row){
if($columns === null){
$columns = count($row);
}elseif(count($row) !== $columns){
throw new \InvalidArgumentException("Expected each row to be $columns columns, but have " . count($row) . " in row $rowNumber");
}
}
$this->recipeId = $recipeId;
$this->input = $input;
$this->output = $output;
$this->blockName = $blockType;
$this->priority = $priority;
$this->uuid = $uuid;
}
public function getRecipeId() : string{
return $this->recipeId;
}
public function getWidth() : int{
return count($this->input[0]);
}
public function getHeight() : int{
return count($this->input);
}
/**
* @return RecipeIngredient[][]
*/
public function getInput() : array{
return $this->input;
}
/**
* @return ItemStack[]
*/
public function getOutput() : array{
return $this->output;
}
public function getUuid() : UUID{
return $this->uuid;
}
public function getBlockName() : string{
return $this->blockName;
}
public function getPriority() : int{
return $this->priority;
}
public static function decode(int $recipeType, NetworkBinaryStream $in) : self{
$recipeId = $in->getString();
$width = $in->getVarInt();
$height = $in->getVarInt();
$input = [];
for($row = 0; $row < $height; ++$row){
for($column = 0; $column < $width; ++$column){
$input[$row][$column] = $in->getRecipeIngredient();
}
}
$output = [];
for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){
$output[] = $in->getSlot();
}
$uuid = $in->getUUID();
$block = $in->getString();
$priority = $in->getVarInt();
return new self($recipeType, $recipeId, $input, $output, $uuid, $block, $priority);
}
public function encode(NetworkBinaryStream $out) : void{
$out->putString($this->recipeId);
$out->putVarInt($this->getWidth());
$out->putVarInt($this->getHeight());
foreach($this->input as $row){
foreach($row as $ingredient){
$out->putRecipeIngredient($ingredient);
}
}
$out->putUnsignedVarInt(count($this->output));
foreach($this->output as $item){
$out->putSlot($item);
}
$out->putUUID($this->uuid);
$out->putString($this->blockName);
$out->putVarInt($this->priority);
}
}

View File

@ -0,0 +1,123 @@
<?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\recipe;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\serializer\NetworkBinaryStream;
use pocketmine\utils\UUID;
use function count;
final class ShapelessRecipe extends RecipeWithTypeId{
/** @var string */
private $recipeId;
/** @var RecipeIngredient[] */
private $inputs;
/** @var ItemStack[] */
private $outputs;
/** @var UUID */
private $uuid;
/** @var string */
private $blockName;
/** @var int */
private $priority;
/**
* @param RecipeIngredient[] $inputs
* @param ItemStack[] $outputs
*/
public function __construct(int $typeId, string $recipeId, array $inputs, array $outputs, UUID $uuid, string $blockName, int $priority){
parent::__construct($typeId);
$this->recipeId = $recipeId;
$this->inputs = $inputs;
$this->outputs = $outputs;
$this->uuid = $uuid;
$this->blockName = $blockName;
$this->priority = $priority;
}
public function getRecipeId() : string{
return $this->recipeId;
}
/**
* @return RecipeIngredient[]
*/
public function getInputs() : array{
return $this->inputs;
}
/**
* @return ItemStack[]
*/
public function getOutputs() : array{
return $this->outputs;
}
public function getUuid() : UUID{
return $this->uuid;
}
public function getBlockName() : string{
return $this->blockName;
}
public function getPriority() : int{
return $this->priority;
}
public static function decode(int $recipeType, NetworkBinaryStream $in) : self{
$recipeId = $in->getString();
$input = [];
for($j = 0, $ingredientCount = $in->getUnsignedVarInt(); $j < $ingredientCount; ++$j){
$input[] = $in->getRecipeIngredient();
}
$output = [];
for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){
$output[] = $in->getSlot();
}
$uuid = $in->getUUID();
$block = $in->getString();
$priority = $in->getVarInt();
return new self($recipeType, $recipeId, $input, $output, $uuid, $block, $priority);
}
public function encode(NetworkBinaryStream $out) : void{
$out->putString($this->recipeId);
$out->putUnsignedVarInt(count($this->inputs));
foreach($this->inputs as $item){
$out->putRecipeIngredient($item);
}
$out->putUnsignedVarInt(count($this->outputs));
foreach($this->outputs as $item){
$out->putSlot($item);
}
$out->putUUID($this->uuid);
$out->putString($this->blockName);
$out->putVarInt($this->priority);
}
}

View File

@ -25,14 +25,10 @@ namespace pocketmine\network\mcpe\serializer;
#include <rules/DataPacket.h>
use pocketmine\item\Durable;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\ItemIds;
use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\protocol\types\command\CommandOriginData;
@ -48,8 +44,10 @@ use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\ShortMetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\Vec3MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor;
use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
use pocketmine\network\mcpe\protocol\types\SkinData;
use pocketmine\network\mcpe\protocol\types\SkinImage;
@ -63,9 +61,6 @@ use function strlen;
class NetworkBinaryStream extends BinaryStream{
private const DAMAGE_TAG = "Damage"; //TAG_Int
private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___";
/**
* @throws BinaryDataException
*/
@ -209,15 +204,15 @@ class NetworkBinaryStream extends BinaryStream{
* @throws BadPacketException
* @throws BinaryDataException
*/
public function getSlot() : Item{
public function getSlot() : ItemStack{
$id = $this->getVarInt();
if($id === 0){
return ItemFactory::get(0, 0, 0);
return ItemStack::null();
}
$auxValue = $this->getVarInt();
$data = $auxValue >> 8;
$cnt = $auxValue & 0xff;
$meta = $auxValue >> 8;
$count = $auxValue & 0xff;
$nbtLen = $this->getLShort();
@ -237,43 +232,25 @@ class NetworkBinaryStream extends BinaryStream{
throw new BadPacketException("Unexpected fake NBT length $nbtLen");
}
//TODO
for($i = 0, $canPlaceOn = $this->getVarInt(); $i < $canPlaceOn; ++$i){
$this->getString();
$canPlaceOn = [];
for($i = 0, $canPlaceOnCount = $this->getVarInt(); $i < $canPlaceOnCount; ++$i){
$canPlaceOn[] = $this->getString();
}
//TODO
for($i = 0, $canDestroy = $this->getVarInt(); $i < $canDestroy; ++$i){
$this->getString();
$canDestroy = [];
for($i = 0, $canDestroyCount = $this->getVarInt(); $i < $canDestroyCount; ++$i){
$canDestroy[] = $this->getString();
}
$shieldBlockingTick = null;
if($id === ItemIds::SHIELD){
$this->getVarLong(); //"blocking tick" (ffs mojang)
$shieldBlockingTick = $this->getVarLong();
}
if($compound !== null){
if($compound->hasTag(self::DAMAGE_TAG, IntTag::class)){
$data = $compound->getInt(self::DAMAGE_TAG);
$compound->removeTag(self::DAMAGE_TAG);
if($compound->count() === 0){
$compound = null;
goto end;
}
}
if(($conflicted = $compound->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){
$compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
$compound->setTag(self::DAMAGE_TAG, $conflicted);
}
}
end:
try{
return ItemFactory::get($id, $data, $cnt, $compound);
}catch(\InvalidArgumentException $e){
throw new BadPacketException($e->getMessage(), 0, $e);
}
return new ItemStack($id, $meta, $count, $compound, $canPlaceOn, $canDestroy, $shieldBlockingTick);
}
public function putSlot(Item $item) : void{
public function putSlot(ItemStack $item) : void{
if($item->getId() === 0){
$this->putVarInt(0);
@ -284,22 +261,7 @@ class NetworkBinaryStream extends BinaryStream{
$auxValue = (($item->getMeta() & 0x7fff) << 8) | $item->getCount();
$this->putVarInt($auxValue);
$nbt = null;
if($item->hasNamedTag()){
$nbt = clone $item->getNamedTag();
}
if($item instanceof Durable and $item->getDamage() > 0){
if($nbt !== null){
if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){
$nbt->removeTag(self::DAMAGE_TAG);
$nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing);
}
}else{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $item->getDamage());
}
$nbt = $item->getNbt();
if($nbt !== null){
$this->putLShort(0xffff);
$this->putByte(1); //TODO: some kind of count field? always 1 as of 1.9.0
@ -308,34 +270,39 @@ class NetworkBinaryStream extends BinaryStream{
$this->putLShort(0);
}
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
$this->putVarInt(0); //CanDestroy entry count (TODO)
$this->putVarInt(count($item->getCanPlaceOn()));
foreach($item->getCanPlaceOn() as $entry){
$this->putString($entry);
}
$this->putVarInt(count($item->getCanDestroy()));
foreach($item->getCanDestroy() as $entry){
$this->putString($entry);
}
if($item->getId() === ItemIds::SHIELD){
$this->putVarLong(0); //"blocking tick" (ffs mojang)
$blockingTick = $item->getShieldBlockingTick();
if($blockingTick !== null){
$this->putVarLong($blockingTick);
}
}
public function getRecipeIngredient() : Item{
public function getRecipeIngredient() : RecipeIngredient{
$id = $this->getVarInt();
if($id === 0){
return ItemFactory::get(ItemIds::AIR, 0, 0);
return new RecipeIngredient(0, 0, 0);
}
$meta = $this->getVarInt();
if($meta === 0x7fff){
$meta = -1;
}
$count = $this->getVarInt();
return ItemFactory::get($id, $meta, $count);
return new RecipeIngredient($id, $meta, $count);
}
public function putRecipeIngredient(Item $item) : void{
if($item->isNull()){
public function putRecipeIngredient(RecipeIngredient $ingredient) : void{
if($ingredient->getId() === 0){
$this->putVarInt(0);
}else{
$this->putVarInt($item->getId());
$this->putVarInt($item->getMeta() & 0x7fff);
$this->putVarInt($item->getCount());
$this->putVarInt($ingredient->getId());
$this->putVarInt($ingredient->getMeta());
$this->putVarInt($ingredient->getCount());
}
}

View File

@ -25,7 +25,6 @@ namespace pocketmine\world\particle;
use pocketmine\entity\EntityFactory;
use pocketmine\entity\Skin;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
@ -34,6 +33,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton;
use pocketmine\utils\UUID;
@ -100,7 +100,7 @@ class FloatingTextParticle implements Particle{
$pk->username = $name;
$pk->entityRuntimeId = $this->entityId;
$pk->position = $pos; //TODO: check offset
$pk->item = ItemFactory::air();
$pk->item = ItemStack::null();
$flags = (
1 << EntityMetadataFlags::IMMOBILE