diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 5e855a3ad2..d836b41529 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -37,6 +37,7 @@ use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\event\entity\EntityShootBowEvent; use pocketmine\event\entity\ProjectileLaunchEvent; +use pocketmine\event\inventory\CraftItemEvent; use pocketmine\event\inventory\InventoryCloseEvent; use pocketmine\event\inventory\InventoryPickupArrowEvent; use pocketmine\event\inventory\InventoryPickupItemEvent; @@ -64,12 +65,14 @@ use pocketmine\event\TextContainer; use pocketmine\event\Timings; use pocketmine\event\TranslationContainer; use pocketmine\inventory\BaseTransaction; +use pocketmine\inventory\BigShapedRecipe; use pocketmine\inventory\BigShapelessRecipe; use pocketmine\inventory\CraftingTransactionGroup; use pocketmine\inventory\FurnaceInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\PlayerInventory; +use pocketmine\inventory\ShapedRecipe; use pocketmine\inventory\ShapelessRecipe; use pocketmine\inventory\SimpleTransactionGroup; use pocketmine\inventory\StonecutterShapelessRecipe; @@ -99,6 +102,7 @@ use pocketmine\network\Network; use pocketmine\network\protocol\AdventureSettingsPacket; use pocketmine\network\protocol\AnimatePacket; use pocketmine\network\protocol\BatchPacket; +use pocketmine\network\protocol\ContainerClosePacket; use pocketmine\network\protocol\ContainerSetContentPacket; use pocketmine\network\protocol\DataPacket; use pocketmine\network\protocol\DisconnectPacket; @@ -2556,16 +2560,24 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade break; case ProtocolInfo::CRAFTING_EVENT_PACKET: - //TODO HACK - $this->server->getLogger()->warning("CRAFTING NOT YET IMPLEMENTED!"); - break; - if(count($packet->slots) < 9){ + if($this->spawned === false or !$this->isAlive()){ + break; + }elseif(!isset($this->windowIndex[$packet->windowId])){ + $this->inventory->sendContents($this); + $pk = new ContainerClosePacket(); + $pk->windowid = $packet->windowId; + $this->dataPacket($pk); + break; + } + + $recipe = $this->server->getCraftingManager()->getRecipe($packet->id); + + if($recipe === null or (($recipe instanceof BigShapelessRecipe or $recipe instanceof BigShapedRecipe) and $this->craftingType === 0)){ $this->inventory->sendContents($this); break; } - foreach($packet->slots as $i => $item){ - /** @var Item $item */ + /*foreach($packet->input as $i => $item){ if($item->getDamage() === -1 or $item->getDamage() === 0xffff){ $item->setDamage(null); } @@ -2573,57 +2585,98 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade if($i < 9 and $item->getId() > 0){ $item->setCount(1); } - } + }*/ - $result = $packet->slots[9]; + $canCraft = true; - if($this->craftingType === 1 or $this->craftingType === 2){ - $recipe = new BigShapelessRecipe($result); + + if($recipe instanceof ShapedRecipe){ + for($x = 0; $x < 3 and $canCraft; ++$x){ + for($y = 0; $y < 3; ++$y){ + $item = $packet->input[$x * 3 + $y]; + $ingredient = $recipe->getIngredient($x, $y); + if($item->getCount() > 0 and $item->getId() > 0){ + if($ingredient === null or !$ingredient->deepEquals($item, $ingredient->getDamage() === null, $ingredient->getCompoundTag() === null)){ + $canCraft = false; + break; + } + + }elseif($ingredient !== null and $ingredient->getId() !== 0){ + $canCraft = false; + break; + } + } + } + }elseif($recipe instanceof ShapelessRecipe){ + $needed = $recipe->getIngredientList(); + + for($x = 0; $x < 3 and $canCraft; ++$x){ + for($y = 0; $y < 3; ++$y){ + $item = clone $packet->input[$x * 3 + $y]; + + foreach($needed as $k => $n){ + if($n->deepEquals($item, $n->getDamage() === null, $n->getCompoundTag() === null)){ + $remove = min($n->getCount(), $item->getCount()); + $n->setCount($n->getCount() - $remove); + $item->setCount($item->getCount() - $remove); + + if($n->getCount() === 0){ + unset($needed[$k]); + } + } + } + + if($item->getCount() > 0){ + $canCraft = false; + break; + } + } + } + + if(count($needed) > 0){ + $canCraft = false; + } }else{ - $recipe = new ShapelessRecipe($result); + $canCraft = false; } /** @var Item[] $ingredients */ - $ingredients = []; - for($x = 0; $x < 3; ++$x){ - for($y = 0; $y < 3; ++$y){ - $item = $packet->slots[$x * 3 + $y]; - if($item->getCount() > 0 and $item->getId() > 0){ - //TODO shaped - $recipe->addIngredient($item); - $ingredients[$x * 3 + $y] = $item; - } - } - } + $ingredients = $packet->input; + $result = $packet->output[0]; - if(!Server::getInstance()->getCraftingManager()->matchRecipe($recipe)){ - $this->server->getLogger()->debug("Unmatched recipe from player ". $this->getName() .": " . $recipe->getResult().", using: " . implode(", ", $recipe->getIngredientList())); + if(!$canCraft or !$recipe->getResult()->deepEquals($result)){ + $this->server->getLogger()->debug("Unmatched recipe ". $recipe->getId() ." from player ". $this->getName() .": expected " . $recipe->getResult() . ", got ". $result .", using: " . implode(", ", $ingredients)); $this->inventory->sendContents($this); break; } - $canCraft = true; - $used = array_fill(0, $this->inventory->getSize(), 0); foreach($ingredients as $ingredient){ $slot = -1; - $checkDamage = $ingredient->getDamage() === null ? false : true; foreach($this->inventory->getContents() as $index => $i){ - if($ingredient->deepEquals($i, $checkDamage) and ($i->getCount() - $used[$index]) >= 1){ + if($ingredient->getId() !== 0 and $ingredient->deepEquals($i) and ($i->getCount() - $used[$index]) >= 1){ $slot = $index; $used[$index]++; break; } } - if($slot === -1){ + if($ingredient->getId() !== 0 and $slot === -1){ $canCraft = false; break; } } if(!$canCraft){ + $this->server->getLogger()->debug("Unmatched recipe ". $recipe->getId() ." from player ". $this->getName() .": client does not have enough items, using: " . implode(", ", $ingredients)); + $this->inventory->sendContents($this); + break; + } + + $this->server->getPluginManager()->callEvent($ev = new CraftItemEvent($ingredients, $recipe)); + + if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index 9870203254..905da3661a 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -1456,7 +1456,7 @@ abstract class Entity extends Location implements Metadatable{ } public function spawnToAll(){ - if($this->chunk === null){ + if($this->chunk === null or $this->closed){ return; } foreach($this->level->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){ diff --git a/src/pocketmine/event/inventory/CraftItemEvent.php b/src/pocketmine/event/inventory/CraftItemEvent.php index 96eac6d430..41d8640a4e 100644 --- a/src/pocketmine/event/inventory/CraftItemEvent.php +++ b/src/pocketmine/event/inventory/CraftItemEvent.php @@ -23,31 +23,36 @@ namespace pocketmine\event\inventory; use pocketmine\event\Cancellable; use pocketmine\event\Event; -use pocketmine\inventory\CraftingTransactionGroup; use pocketmine\inventory\Recipe; +use pocketmine\item\Item; class CraftItemEvent extends Event implements Cancellable{ public static $handlerList = null; - /** @var CraftingTransactionGroup */ - private $ts; + /** @var Item[] */ + private $input = []; /** @var Recipe */ private $recipe; /** - * @param CraftingTransactionGroup $ts - * @param Recipe $recipe + * @param Item[] $input + * @param Recipe $recipe */ - public function __construct(CraftingTransactionGroup $ts, Recipe $recipe){ - $this->ts = $ts; + public function __construct(array $input, Recipe $recipe){ + $this->input = $input; $this->recipe = $recipe; } /** - * @return CraftingTransactionGroup + * @return Item[] */ - public function getTransaction(){ - return $this->ts; + public function getInput(){ + $items = []; + foreach($items as $i => $item){ + $items[$i] = clone $item; + } + + return $items; } /** diff --git a/src/pocketmine/inventory/CraftingManager.php b/src/pocketmine/inventory/CraftingManager.php index 459fa89718..ae3a430ae7 100644 --- a/src/pocketmine/inventory/CraftingManager.php +++ b/src/pocketmine/inventory/CraftingManager.php @@ -340,6 +340,14 @@ class CraftingManager{ } } + /** + * @param UUID $id + * @return Recipe + */ + public function getRecipe(UUID $id){ + $index = $id->toBinary(); + return isset($this->recipes[$index]) ? $this->recipes[$index] : null; + } /** * @return Recipe[] @@ -375,7 +383,7 @@ class CraftingManager{ */ public function registerShapedRecipe(ShapedRecipe $recipe){ $result = $recipe->getResult(); - $this->recipes[spl_object_hash($recipe)] = $recipe; + $this->recipes[$recipe->getId()->toBinary()] = $recipe; $ingredients = $recipe->getIngredientMap(); $hash = ""; foreach($ingredients as $v){ @@ -395,7 +403,7 @@ class CraftingManager{ */ public function registerShapelessRecipe(ShapelessRecipe $recipe){ $result = $recipe->getResult(); - $this->recipes[spl_object_hash($recipe)] = $recipe; + $this->recipes[$recipe->getId()->toBinary()] = $recipe; $hash = ""; $ingredients = $recipe->getIngredientList(); usort($ingredients, [$this, "sort"]); @@ -471,86 +479,12 @@ class CraftingManager{ } - /** - * @param CraftingTransactionGroup $ts - * - * @return Recipe - */ - public function matchTransaction(CraftingTransactionGroup $ts){ - $result = $ts->getResult(); - - if(!($result instanceof Item)){ - return false; - } - $k = $result->getId() . ":" . $result->getDamage(); - - if(!isset($this->recipeLookup[$k])){ - return false; - } - $hash = ""; - $input = $ts->getRecipe(); - usort($input, [$this, "sort"]); - $inputCount = 0; - foreach($input as $item){ - $inputCount += $item->getCount(); - $hash .= $item->getId() . ":" . ($item->getDamage() === null ? "?" : $item->getDamage()) . "x" . $item->getCount() . ","; - } - if(!isset($this->recipeLookup[$k][$hash])){ - $hasRecipe = null; - foreach($this->recipeLookup[$k] as $recipe){ - if($recipe instanceof ShapelessRecipe){ - if($recipe->getIngredientCount() !== $inputCount){ - continue; - } - $checkInput = $recipe->getIngredientList(); - foreach($input as $item){ - $amount = $item->getCount(); - foreach($checkInput as $k => $checkItem){ - if($checkItem->equals($item, $checkItem->getDamage() === null ? false : true, $checkItem->getCompoundTag() === null ? false : true)){ - $remove = min($checkItem->getCount(), $amount); - $checkItem->setCount($checkItem->getCount() - $remove); - if($checkItem->getCount() === 0){ - unset($checkInput[$k]); - } - $amount -= $remove; - if($amount === 0){ - break; - } - } - } - } - - if(count($checkInput) === 0){ - $hasRecipe = $recipe; - break; - } - } - if($hasRecipe instanceof Recipe){ - break; - } - } - - if($hasRecipe === null){ - return false; - } - - $recipe = $hasRecipe; - }else{ - $recipe = $this->recipeLookup[$k][$hash]; - } - - $checkResult = $recipe->getResult(); - if($checkResult->equals($result) and $checkResult->getCount() === $result->getCount()){ - return $recipe; - } - - return null; - } - /** * @param Recipe $recipe */ public function registerRecipe(Recipe $recipe){ + $recipe->setId(UUID::fromData(++self::$RECIPE_COUNT, $recipe->getResult()->getId(), $recipe->getResult()->getDamage(), $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag())); + if($recipe instanceof ShapedRecipe){ $this->registerShapedRecipe($recipe); }elseif($recipe instanceof ShapelessRecipe){ @@ -558,8 +492,6 @@ class CraftingManager{ }elseif($recipe instanceof FurnaceRecipe){ $this->registerFurnaceRecipe($recipe); } - - $recipe->setId(UUID::fromData(++self::$RECIPE_COUNT, $recipe->getResult()->getId(), $recipe->getResult()->getDamage(), $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag())); } } diff --git a/src/pocketmine/inventory/CraftingTransactionGroup.php b/src/pocketmine/inventory/CraftingTransactionGroup.php deleted file mode 100644 index 74d23da209..0000000000 --- a/src/pocketmine/inventory/CraftingTransactionGroup.php +++ /dev/null @@ -1,111 +0,0 @@ -transactions = $group->getTransactions(); - $this->inventories = $group->getInventories(); - $this->source = $group->getSource(); - - $this->matchItems($this->output, $this->input); - } - - public function addTransaction(Transaction $transaction){ - parent::addTransaction($transaction); - $this->input = []; - $this->output = []; - $this->matchItems($this->output, $this->input); - } - - /** - * Gets the Items that have been used - * - * @return Item[] - */ - public function getRecipe(){ - return $this->input; - } - - /** - * @return Item - */ - public function getResult(){ - reset($this->output); - - return current($this->output); - } - - public function canExecute(){ - if(count($this->output) !== 1 or count($this->input) === 0){ - return false; - } - - return $this->getMatchingRecipe() instanceof Recipe; - } - - /** - * @return Recipe - */ - public function getMatchingRecipe(){ - if($this->recipe === null){ - $this->recipe = Server::getInstance()->getCraftingManager()->matchTransaction($this); - } - - return $this->recipe; - } - - public function execute(){ - if($this->hasExecuted() or !$this->canExecute()){ - return false; - } - - Server::getInstance()->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $this->getMatchingRecipe())); - if($ev->isCancelled()){ - foreach($this->inventories as $inventory){ - $inventory->sendContents($inventory->getViewers()); - } - - return false; - } - - foreach($this->transactions as $transaction){ - $transaction->getInventory()->setItem($transaction->getSlot(), $transaction->getTargetItem(), $this->getSource()); - } - $this->hasExecuted = true; - - return true; - } -} \ No newline at end of file diff --git a/src/pocketmine/item/Item.php b/src/pocketmine/item/Item.php index 5db473e767..648f3357cb 100644 --- a/src/pocketmine/item/Item.php +++ b/src/pocketmine/item/Item.php @@ -1247,8 +1247,8 @@ class Item{ return $this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage()) and ($checkCompound === false or $this->getCompoundTag() === $item->getCompoundTag()); } - public final function deepEquals(Item $item){ - if($item->equals($item)){ + public final function deepEquals(Item $item, $checkDamage = true, $checkCompound = true){ + if($item->equals($item, $checkDamage, $checkCompound)){ return true; }elseif($item->hasCompoundTag() or $this->hasCompoundTag()){ return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());