From 76e6ccebd576c350fc7470d5c85d505068437600 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Fri, 15 May 2015 13:59:19 +0200 Subject: [PATCH] Proper recipe matching from network, bumped protocol, build 11, fixed entities not being killed on void (closes #3021), fixes achievement acquireIron not being possible (fixes #2600) --- src/pocketmine/Player.php | 210 ++++++++++++------ src/pocketmine/PocketMine.php | 4 +- src/pocketmine/entity/Entity.php | 2 +- src/pocketmine/inventory/CraftingManager.php | 31 ++- .../protocol/ContainerSetContentPacket.php | 1 + src/pocketmine/network/protocol/Info.php | 2 +- src/pocketmine/tile/Furnace.php | 2 +- 7 files changed, 179 insertions(+), 73 deletions(-) diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 95e780ce1..c288563c9 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -70,6 +70,7 @@ use pocketmine\inventory\FurnaceInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\PlayerInventory; +use pocketmine\inventory\ShapelessRecipe; use pocketmine\inventory\SimpleTransactionGroup; use pocketmine\inventory\StonecutterShapelessRecipe; use pocketmine\item\Item; @@ -2383,6 +2384,139 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ unset($this->windowIndex[$packet->windowid]); } break; + + case ProtocolInfo::CONTAINER_SET_CONTENT_PACKET: + if($packet->windowid === ContainerSetContentPacket::SPECIAL_CRAFTING){ + if(count($packet->slots) < 9){ + $this->inventory->sendContents($this); + break; + } + + foreach($packet->slots as $i => $item){ + /** @var Item $item */ + if($item->getDamage() === -1 or $item->getDamage() === 0xffff){ + $item->setDamage(null); + } + + if($i < 9 and $item->getId() > 0){ + $item->setCount(1); + } + } + + $result = $packet->slots[9]; + + if($this->craftingType === 1 or $this->craftingType === 2){ + $recipe = new BigShapelessRecipe($result); + }else{ + $recipe = new ShapelessRecipe($result); + } + + /** @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; + } + } + } + + if(!Server::getInstance()->getCraftingManager()->matchRecipe($recipe)){ + $this->server->getLogger()->debug("Unmatched recipe from player ". $this->getName() .": " . $recipe->getResult().", using: " . implode(", ", $recipe->getIngredientList())); + $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->equals($i, $checkDamage) and ($i->getCount() - $used[$index]) >= 1){ + $slot = $index; + $used[$index]++; + break; + } + } + + if($slot === -1){ + $canCraft = false; + break; + } + } + + if(!$canCraft){ + $this->inventory->sendContents($this); + break; + } + + foreach($used as $slot => $count){ + if($count === 0){ + continue; + } + + $item = $this->inventory->getItem($slot); + + if($item->getCount() > $count){ + $newItem = clone $item; + $newItem->setCount($item->getCount() - $count); + }else{ + $newItem = Item::get(Item::AIR, 0, 0); + } + + $this->inventory->setItem($slot, $newItem); + } + + $extraItem = $this->inventory->addItem($recipe->getResult()); + if(count($extraItem) > 0){ + foreach($extraItem as $item){ + $this->level->dropItem($this, $item); + } + } + + switch($recipe->getResult()->getId()){ + case Item::WORKBENCH: + $this->awardAchievement("buildWorkBench"); + break; + case Item::WOODEN_PICKAXE: + $this->awardAchievement("buildPickaxe"); + break; + case Item::FURNACE: + $this->awardAchievement("buildFurnace"); + break; + case Item::WOODEN_HOE: + $this->awardAchievement("buildHoe"); + break; + case Item::BREAD: + $this->awardAchievement("makeBread"); + break; + case Item::CAKE: + //TODO: detect complex recipes like cake that leave remains + $this->awardAchievement("bakeCake"); + $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3)); + break; + case Item::STONE_PICKAXE: + case Item::GOLD_PICKAXE: + case Item::IRON_PICKAXE: + case Item::DIAMOND_PICKAXE: + $this->awardAchievement("buildBetterPickaxe"); + break; + case Item::WOODEN_SWORD: + $this->awardAchievement("buildSword"); + break; + case Item::DIAMOND: + $this->awardAchievement("diamond"); + break; + } + } + break; + case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: if($this->spawned === false or $this->blocked === true or !$this->isAlive()){ break; @@ -2424,7 +2558,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ if($this->currentTransaction === null or $this->currentTransaction->getCreationTime() < (microtime(true) - 8)){ - if($this->currentTransaction instanceof SimpleTransactionGroup){ + if($this->currentTransaction !== null){ foreach($this->currentTransaction->getInventories() as $inventory){ if($inventory instanceof PlayerInventory){ $inventory->sendArmorContents($this); @@ -2438,77 +2572,29 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->currentTransaction->addTransaction($transaction); if($this->currentTransaction->canExecute()){ - if($this->currentTransaction->execute()){ - foreach($this->currentTransaction->getTransactions() as $ts){ - $inv = $ts->getInventory(); - if($inv instanceof FurnaceInventory){ - if($ts->getSlot() === 2){ - switch($inv->getResult()->getId()){ - case Item::IRON_INGOT: - $this->awardAchievement("acquireIron"); - break; - } + $achievements = []; + foreach($this->currentTransaction->getTransactions() as $ts){ + $inv = $ts->getInventory(); + if($inv instanceof FurnaceInventory){ + if($ts->getSlot() === 2){ + switch($inv->getResult()->getId()){ + case Item::IRON_INGOT: + $achievements[] = "acquireIron"; + break; } } } } - $this->currentTransaction = null; - }elseif($packet->windowid == 0){ //Try crafting - $craftingGroup = new CraftingTransactionGroup($this->currentTransaction); - if($craftingGroup->canExecute()){ //We can craft! - $recipe = $craftingGroup->getMatchingRecipe(); - if($recipe instanceof BigShapelessRecipe and $this->craftingType !== 1){ - break; - }elseif($recipe instanceof StonecutterShapelessRecipe and $this->craftingType !== 2){ - break; + if($this->currentTransaction->execute()){ + foreach($achievements as $a){ + $this->awardAchievement($a); } - - if($craftingGroup->execute()){ - switch($craftingGroup->getResult()->getId()){ - case Item::WORKBENCH: - $this->awardAchievement("buildWorkBench"); - break; - case Item::WOODEN_PICKAXE: - $this->awardAchievement("buildPickaxe"); - break; - case Item::FURNACE: - $this->awardAchievement("buildFurnace"); - break; - case Item::WOODEN_HOE: - $this->awardAchievement("buildHoe"); - break; - case Item::BREAD: - $this->awardAchievement("makeBread"); - break; - case Item::CAKE: - //TODO: detect complex recipes like cake that leave remains - $this->awardAchievement("bakeCake"); - $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3)); - break; - case Item::STONE_PICKAXE: - case Item::GOLD_PICKAXE: - case Item::IRON_PICKAXE: - case Item::DIAMOND_PICKAXE: - $this->awardAchievement("buildBetterPickaxe"); - break; - case Item::WOODEN_SWORD: - $this->awardAchievement("buildSword"); - break; - case Item::DIAMOND: - $this->awardAchievement("diamond"); - break; - } - } - - - $this->currentTransaction = null; } - + $this->currentTransaction = null; } - break; case ProtocolInfo::TILE_ENTITY_DATA_PACKET: if($this->spawned === false or $this->blocked === true or !$this->isAlive()){ diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 1c018371f..77141448f 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -74,8 +74,8 @@ namespace pocketmine { const VERSION = "1.5dev"; const API_VERSION = "1.12.0"; const CODENAME = "活発(Kappatsu)フグ(Fugu)"; - const MINECRAFT_VERSION = "v0.11.0 alpha build 10"; - const MINECRAFT_VERSION_NETWORK = "0.11.0.10"; + const MINECRAFT_VERSION = "v0.11.0 alpha build 11"; + const MINECRAFT_VERSION_NETWORK = "0.11.0.11"; /* * Startup code. Do not look at it, it may harm you. diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index 27a17f69a..13457bcc3 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -744,7 +744,7 @@ abstract class Entity extends Location implements Metadatable{ $this->checkBlockCollision(); - if($this->y < 0 and !$this->isAlive()){ + if($this->y < 0 and $this->isAlive()){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); $this->attack($ev->getFinalDamage(), $ev); $hasUpdate = true; diff --git a/src/pocketmine/inventory/CraftingManager.php b/src/pocketmine/inventory/CraftingManager.php index 4abae9c04..fc010f9d6 100644 --- a/src/pocketmine/inventory/CraftingManager.php +++ b/src/pocketmine/inventory/CraftingManager.php @@ -78,12 +78,12 @@ class CraftingManager{ $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE, Planks::JUNGLE, 3)))->addIngredient(Item::get(Item::STICK, 0, 2))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::JUNGLE, 4))); $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE, Planks::ACACIA, 3)))->addIngredient(Item::get(Item::STICK, 0, 2))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::ACACIA, 4))); $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE, Planks::DARK_OAK, 3)))->addIngredient(Item::get(Item::STICK, 0, 2))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::DARK_OAK, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::OAK, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_SPRUCE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::SPRUCE, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_BIRCH, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::BIRCH, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_JUNGLE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::JUNGLE, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_DARK_OAK, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::DARK_OAK, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); - $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_ACACIA, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::ACACIA, 2))->addIngredient(Item::get(Item::STICK, 0, 4))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::OAK, 2))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_SPRUCE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::SPRUCE, 2))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_BIRCH, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::BIRCH, 2))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_JUNGLE, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::JUNGLE, 2))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_DARK_OAK, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::DARK_OAK, 2))); + $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FENCE_GATE_ACACIA, 0, 1)))->addIngredient(Item::get(Item::STICK, 0, 4))->addIngredient(Item::get(Item::WOODEN_PLANK, Planks::ACACIA, 2))); $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::FURNACE, 0, 1)))->addIngredient(Item::get(Item::COBBLESTONE, 0, 8))); $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::GLASS_PANE, 0, 16)))->addIngredient(Item::get(Item::GLASS, 0, 6))); $this->registerRecipe((new BigShapelessRecipe(Item::get(Item::LADDER, 0, 2)))->addIngredient(Item::get(Item::STICK, 0, 7))); @@ -370,6 +370,25 @@ class CraftingManager{ $this->furnaceRecipes[$input->getId() . ":" . ($input->getDamage() === null ? "?" : $input->getDamage())] = $recipe; } + /** + * @param ShapelessRecipe $recipe + * @return bool + */ + public function matchRecipe(ShapelessRecipe $recipe){ + if(!isset($this->recipeLookup[$idx = $recipe->getResult()->getId() . ":" . $recipe->getResult()->getDamage()])){ + return false; + } + + $hash = ""; + $ingredients = $recipe->getIngredientList(); + usort($ingredients, [$this, "sort"]); + foreach($ingredients as $item){ + $hash .= $item->getId() . ":" . ($item->getDamage() === null ? "?" : $item->getDamage()) . "x" . $item->getCount() . ","; + } + + return isset($this->recipeLookup[$idx][$hash]); + } + /** * @param CraftingTransactionGroup $ts * diff --git a/src/pocketmine/network/protocol/ContainerSetContentPacket.php b/src/pocketmine/network/protocol/ContainerSetContentPacket.php index b0b996fce..626192c11 100644 --- a/src/pocketmine/network/protocol/ContainerSetContentPacket.php +++ b/src/pocketmine/network/protocol/ContainerSetContentPacket.php @@ -31,6 +31,7 @@ class ContainerSetContentPacket extends DataPacket{ const SPECIAL_INVENTORY = 0; const SPECIAL_ARMOR = 0x78; const SPECIAL_CREATIVE = 0x79; + const SPECIAL_CRAFTING = 0x7a; public $windowid; public $slots = []; diff --git a/src/pocketmine/network/protocol/Info.php b/src/pocketmine/network/protocol/Info.php index 594cb6701..1cf3d41a3 100644 --- a/src/pocketmine/network/protocol/Info.php +++ b/src/pocketmine/network/protocol/Info.php @@ -30,7 +30,7 @@ interface Info{ /** * Actual Minecraft: PE protocol version */ - const CURRENT_PROTOCOL = 25; + const CURRENT_PROTOCOL = 26; const LOGIN_PACKET = 0x82; const PLAY_STATUS_PACKET = 0x83; diff --git a/src/pocketmine/tile/Furnace.php b/src/pocketmine/tile/Furnace.php index 01cea0d9a..cc7d3c347 100644 --- a/src/pocketmine/tile/Furnace.php +++ b/src/pocketmine/tile/Furnace.php @@ -79,7 +79,7 @@ class Furnace extends Tile implements InventoryHolder, Container{ } public function saveNBT(){ - $this->namedtag->Items = new Enum("Inventory", []); + $this->namedtag->Items = new Enum("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index));