read($tag); return self::$cachedParser->getData(); } private static function writeCompoundTag(CompoundTag $tag) : string{ if(self::$cachedParser === null){ self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN); } self::$cachedParser->setData($tag); return self::$cachedParser->write(); } /** @var \SplFixedArray */ public static $list = null; protected $block; protected $id; protected $meta; private $tags = ""; private $cachedNBT = null; public $count; protected $durability = 0; protected $name; public function canBeActivated(){ return false; } public static function init(){ if(self::$list === null){ self::$list = new \SplFixedArray(65536); self::$list[self::IRON_SHOVEL] = IronShovel::class; self::$list[self::IRON_PICKAXE] = IronPickaxe::class; self::$list[self::IRON_AXE] = IronAxe::class; self::$list[self::FLINT_STEEL] = FlintSteel::class; self::$list[self::APPLE] = Apple::class; self::$list[self::BOW] = Bow::class; self::$list[self::ARROW] = Arrow::class; self::$list[self::COAL] = Coal::class; self::$list[self::DIAMOND] = Diamond::class; self::$list[self::IRON_INGOT] = IronIngot::class; self::$list[self::GOLD_INGOT] = GoldIngot::class; self::$list[self::IRON_SWORD] = IronSword::class; self::$list[self::WOODEN_SWORD] = WoodenSword::class; self::$list[self::WOODEN_SHOVEL] = WoodenShovel::class; self::$list[self::WOODEN_PICKAXE] = WoodenPickaxe::class; self::$list[self::WOODEN_AXE] = WoodenAxe::class; self::$list[self::STONE_SWORD] = StoneSword::class; self::$list[self::STONE_SHOVEL] = StoneShovel::class; self::$list[self::STONE_PICKAXE] = StonePickaxe::class; self::$list[self::STONE_AXE] = StoneAxe::class; self::$list[self::DIAMOND_SWORD] = DiamondSword::class; self::$list[self::DIAMOND_SHOVEL] = DiamondShovel::class; self::$list[self::DIAMOND_PICKAXE] = DiamondPickaxe::class; self::$list[self::DIAMOND_AXE] = DiamondAxe::class; self::$list[self::STICK] = Stick::class; self::$list[self::BOWL] = Bowl::class; self::$list[self::MUSHROOM_STEW] = MushroomStew::class; self::$list[self::GOLD_SWORD] = GoldSword::class; self::$list[self::GOLD_SHOVEL] = GoldShovel::class; self::$list[self::GOLD_PICKAXE] = GoldPickaxe::class; self::$list[self::GOLD_AXE] = GoldAxe::class; self::$list[self::STRING] = StringItem::class; self::$list[self::FEATHER] = Feather::class; self::$list[self::GUNPOWDER] = Gunpowder::class; self::$list[self::WOODEN_HOE] = WoodenHoe::class; self::$list[self::STONE_HOE] = StoneHoe::class; self::$list[self::IRON_HOE] = IronHoe::class; self::$list[self::DIAMOND_HOE] = DiamondHoe::class; self::$list[self::GOLD_HOE] = GoldHoe::class; self::$list[self::WHEAT_SEEDS] = WheatSeeds::class; self::$list[self::WHEAT] = Wheat::class; self::$list[self::BREAD] = Bread::class; self::$list[self::LEATHER_CAP] = LeatherCap::class; self::$list[self::LEATHER_TUNIC] = LeatherTunic::class; self::$list[self::LEATHER_PANTS] = LeatherPants::class; self::$list[self::LEATHER_BOOTS] = LeatherBoots::class; self::$list[self::CHAIN_HELMET] = ChainHelmet::class; self::$list[self::CHAIN_CHESTPLATE] = ChainChestplate::class; self::$list[self::CHAIN_LEGGINGS] = ChainLeggings::class; self::$list[self::CHAIN_BOOTS] = ChainBoots::class; self::$list[self::IRON_HELMET] = IronHelmet::class; self::$list[self::IRON_CHESTPLATE] = IronChestplate::class; self::$list[self::IRON_LEGGINGS] = IronLeggings::class; self::$list[self::IRON_BOOTS] = IronBoots::class; self::$list[self::DIAMOND_HELMET] = DiamondHelmet::class; self::$list[self::DIAMOND_CHESTPLATE] = DiamondChestplate::class; self::$list[self::DIAMOND_LEGGINGS] = DiamondLeggings::class; self::$list[self::DIAMOND_BOOTS] = DiamondBoots::class; self::$list[self::GOLD_HELMET] = GoldHelmet::class; self::$list[self::GOLD_CHESTPLATE] = GoldChestplate::class; self::$list[self::GOLD_LEGGINGS] = GoldLeggings::class; self::$list[self::GOLD_BOOTS] = GoldBoots::class; self::$list[self::FLINT] = Flint::class; self::$list[self::RAW_PORKCHOP] = RawPorkchop::class; self::$list[self::COOKED_PORKCHOP] = CookedPorkchop::class; self::$list[self::PAINTING] = Painting::class; self::$list[self::GOLDEN_APPLE] = GoldenApple::class; self::$list[self::SIGN] = Sign::class; self::$list[self::WOODEN_DOOR] = WoodenDoor::class; self::$list[self::BUCKET] = Bucket::class; self::$list[self::MINECART] = Minecart::class; self::$list[self::IRON_DOOR] = IronDoor::class; self::$list[self::REDSTONE] = Redstone::class; self::$list[self::SNOWBALL] = Snowball::class; self::$list[self::BOAT] = Boat::class; self::$list[self::LEATHER] = Leather::class; self::$list[self::BRICK] = Brick::class; self::$list[self::CLAY] = Clay::class; self::$list[self::SUGARCANE] = Sugarcane::class; self::$list[self::PAPER] = Paper::class; self::$list[self::BOOK] = Book::class; self::$list[self::SLIMEBALL] = Slimeball::class; self::$list[self::EGG] = Egg::class; self::$list[self::COMPASS] = Compass::class; self::$list[self::FISHING_ROD] = FishingRod::class; self::$list[self::CLOCK] = Clock::class; self::$list[self::GLOWSTONE_DUST] = GlowstoneDust::class; self::$list[self::RAW_FISH] = Fish::class; self::$list[self::COOKED_FISH] = CookedFish::class; self::$list[self::DYE] = Dye::class; self::$list[self::BONE] = Bone::class; self::$list[self::SUGAR] = Sugar::class; self::$list[self::CAKE] = Cake::class; self::$list[self::BED] = Bed::class; self::$list[self::COOKIE] = Cookie::class; self::$list[self::SHEARS] = Shears::class; self::$list[self::MELON] = Melon::class; self::$list[self::PUMPKIN_SEEDS] = PumpkinSeeds::class; self::$list[self::MELON_SEEDS] = MelonSeeds::class; self::$list[self::RAW_BEEF] = RawBeef::class; self::$list[self::STEAK] = Steak::class; self::$list[self::RAW_CHICKEN] = RawChicken::class; self::$list[self::COOKED_CHICKEN] = CookedChicken::class; self::$list[self::GOLD_NUGGET] = GoldNugget::class; self::$list[self::NETHER_WART] = NetherWart::class; self::$list[self::POTION] = Potion::class; self::$list[self::GLASS_BOTTLE] = GlassBottle::class; self::$list[self::SPIDER_EYE] = SpiderEye::class; self::$list[self::FERMENTED_SPIDER_EYE] = FermentedSpiderEye::class; self::$list[self::BLAZE_POWDER] = BlazePowder::class; self::$list[self::MAGMA_CREAM] = MagmaCream::class; self::$list[self::BREWING_STAND] = BrewingStand::class; self::$list[self::GLISTERING_MELON] = GlisteringMelon::class; self::$list[self::SPAWN_EGG] = SpawnEgg::class; self::$list[self::EMERALD] = Emerald::class; self::$list[self::ITEM_FRAME] = ItemFrame::class; self::$list[self::FLOWER_POT] = FlowerPot::class; self::$list[self::CARROT] = Carrot::class; self::$list[self::POTATO] = Potato::class; self::$list[self::BAKED_POTATO] = BakedPotato::class; self::$list[self::GOLDEN_CARROT] = GoldenCarrot::class; self::$list[self::MOB_HEAD] = MobHead::class; self::$list[self::PUMPKIN_PIE] = PumpkinPie::class; self::$list[self::NETHER_BRICK] = NetherBrick::class; self::$list[self::QUARTZ] = Quartz::class; self::$list[self::QUARTZ] = NetherQuartz::class; self::$list[self::COOKED_RABBIT] = CookedRabbit::class; // self::$list[self::CAMERA] = Camera::class; self::$list[self::BEETROOT] = Beetroot::class; self::$list[self::BEETROOT_SEEDS] = BeetrootSeeds::class; self::$list[self::BEETROOT_SOUP] = BeetrootSoup::class; self::$list[self::PRISMARINE_CRYSTALS] = PrismarineCrystals::class; self::$list[self::PRISMARINE_SHARD] = PrismarineShard::class; self::$list[self::NETHER_STAR] = NetherStar::class; for($i = 0; $i < 256; ++$i){ if(Block::$list[$i] !== null){ self::$list[$i] = Block::$list[$i]; } } } self::initCreativeItems(); } private static $creative = []; private static function initCreativeItems(){ self::clearCreativeItems(); $creativeItems = new Config(Server::getInstance()->getFilePath() . "src/pocketmine/resources/creativeitems.json", Config::JSON, []); foreach($creativeItems->getAll() as $data){ $item = Item::get($data["id"], $data["damage"], $data["count"], $data["nbt"]); if($item->getName() === "Unknown"){ continue; } self::addCreativeItem($item); } } public static function clearCreativeItems(){ Item::$creative = []; } public static function getCreativeItems() : array{ return Item::$creative; } public static function addCreativeItem(Item $item){ Item::$creative[] = clone $item; } public static function removeCreativeItem(Item $item){ $index = self::getCreativeItemIndex($item); if($index !== -1){ unset(Item::$creative[$index]); } } public static function isCreativeItem(Item $item) : bool{ foreach(Item::$creative as $i => $d){ if($item->equals($d, !$item->isTool())){ return true; } } return false; } /** * @param $index * * @return Item */ public static function getCreativeItem(int $index){ return Item::$creative[$index] ?? null; } public static function getCreativeItemIndex(Item $item) : int{ foreach(Item::$creative as $i => $d){ if($item->equals($d, !$item->isTool())){ return $i; } } return -1; } public static function get(int $id, $meta = 0, int $count = 1, $tags = "") : Item{ try{ $class = self::$list[$id]; if($class === null){ return (new Item($id, $meta, $count))->setCompoundTag($tags); }elseif($id < 256){ return (new ItemBlock(new $class($meta), $meta, $count))->setCompoundTag($tags); }else{ return (new $class($meta, $count))->setCompoundTag($tags); } }catch(\RuntimeException $e){ return (new Item($id, $meta, $count))->setCompoundTag($tags); } } /** * @param string $str * @param bool $multiple * * @return Item[]|Item */ public static function fromString(string $str, bool $multiple = false){ if($multiple === true){ $blocks = []; foreach(explode(",", $str) as $b){ $blocks[] = self::fromString($b, false); } return $blocks; }else{ $b = explode(":", str_replace([" ", "minecraft:"], ["_", ""], trim($str))); if(!isset($b[1])){ $meta = 0; }else{ $meta = $b[1] & 0xFFFF; } if(defined(Item::class . "::" . strtoupper($b[0]))){ $item = self::get(constant(Item::class . "::" . strtoupper($b[0])), $meta); if($item->getId() === self::AIR and strtoupper($b[0]) !== "AIR"){ $item = self::get($b[0] & 0xFFFF, $meta); } }else{ $item = self::get($b[0] & 0xFFFF, $meta); } return $item; } } public function __construct(int $id, int $meta = 0, int $count = 1, string $name = "Unknown"){ $this->id = $id & 0xffff; $this->meta = $meta !== -1 ? $meta & 0xffff : -1; $this->count = $count; $this->name = $name; if(!isset($this->block) and $this->id <= 0xff and isset(Block::$list[$this->id])){ $this->block = Block::get($this->id, $this->meta); $this->name = $this->block->getName(); } } public function setCompoundTag($tags){ if($tags instanceof CompoundTag){ $this->setNamedTag($tags); }else{ $this->tags = (string) $tags; $this->cachedNBT = null; } return $this; } /** * @return string */ public function getCompoundTag() : string{ return $this->tags; } public function hasCompoundTag() : bool{ return $this->tags !== ""; } public function hasCustomBlockData() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ return true; } return false; } public function clearCustomBlockData(){ if(!$this->hasCompoundTag()){ return $this; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ unset($tag->display->BlockEntityTag); $this->setNamedTag($tag); } return $this; } public function setCustomBlockData(CompoundTag $compound){ $tags = clone $compound; $tags->setName("BlockEntityTag"); if(!$this->hasCompoundTag()){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } $tag->BlockEntityTag = $tags; $this->setNamedTag($tag); return $this; } public function getCustomBlockData(){ if(!$this->hasCompoundTag()){ return null; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ return $tag->BlockEntityTag; } return null; } public function hasEnchantments() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->ench)){ $tag = $tag->ench; if($tag instanceof ListTag){ return true; } } return false; } /** * @param $id * * @return Enchantment|null */ public function getEnchantment(int $id){ if(!$this->hasEnchantments()){ return null; } foreach($this->getNamedTag()->ench as $entry){ if($entry["id"] === $id){ $e = Enchantment::getEnchantment($entry["id"]); $e->setLevel($entry["lvl"]); return $e; } } return null; } /** * @param Enchantment $ench */ public function addEnchantment(Enchantment $ench){ if(!$this->hasCompoundTag()){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } if(!isset($tag->ench)){ $tag->ench = new ListTag("ench", []); $tag->ench->setTagType(NBT::TAG_Compound); } $found = false; foreach($tag->ench as $k => $entry){ if($entry["id"] === $ench->getId()){ $tag->ench->{$k} = new CompoundTag("", [ "id" => new ShortTag("id", $ench->getId()), "lvl" => new ShortTag("lvl", $ench->getLevel()) ]); $found = true; break; } } if(!$found){ $tag->ench->{count($tag->ench) + 1} = new CompoundTag("", [ "id" => new ShortTag("id", $ench->getId()), "lvl" => new ShortTag("lvl", $ench->getLevel()) ]); } $this->setNamedTag($tag); } /** * @return Enchantment[] */ public function getEnchantments() : array{ if(!$this->hasEnchantments()){ return []; } $enchantments = []; foreach($this->getNamedTag()->ench as $entry){ $e = Enchantment::getEnchantment($entry["id"]); $e->setLevel($entry["lvl"]); $enchantments[] = $e; } return $enchantments; } public function hasCustomName() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->display)){ $tag = $tag->display; if($tag instanceof CompoundTag and isset($tag->Name) and $tag->Name instanceof StringTag){ return true; } } return false; } public function getCustomName() : string{ if(!$this->hasCompoundTag()){ return ""; } $tag = $this->getNamedTag(); if(isset($tag->display)){ $tag = $tag->display; if($tag instanceof CompoundTag and isset($tag->Name) and $tag->Name instanceof StringTag){ return $tag->Name->getValue(); } } return ""; } public function setCustomName(string $name){ if($name === ""){ $this->clearCustomName(); } if(!($hadCompoundTag = $this->hasCompoundTag())){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } if(isset($tag->display) and $tag->display instanceof CompoundTag){ $tag->display->Name = new StringTag("Name", $name); }else{ $tag->display = new CompoundTag("display", [ "Name" => new StringTag("Name", $name) ]); } if(!$hadCompoundTag){ $this->setCompoundTag($tag); } return $this; } public function clearCustomName(){ if(!$this->hasCompoundTag()){ return $this; } $tag = $this->getNamedTag(); if(isset($tag->display) and $tag->display instanceof CompoundTag){ unset($tag->display->Name); if($tag->display->getCount() === 0){ unset($tag->display); } $this->setNamedTag($tag); } return $this; } /** * @param $name * @return Tag|null */ public function getNamedTagEntry($name){ $tag = $this->getNamedTag(); if($tag !== null){ return $tag->{$name} ?? null; } return null; } public function getNamedTag(){ if(!$this->hasCompoundTag()){ return null; }elseif($this->cachedNBT !== null){ return $this->cachedNBT; } return $this->cachedNBT = self::parseCompoundTag($this->tags); } public function setNamedTag(CompoundTag $tag){ if($tag->getCount() === 0){ return $this->clearNamedTag(); } $this->cachedNBT = $tag; $this->tags = self::writeCompoundTag($tag); return $this; } public function clearNamedTag(){ return $this->setCompoundTag(""); } public function getCount() : int{ return $this->count; } public function setCount(int $count){ $this->count = $count; } final public function getName() : string{ return $this->hasCustomName() ? $this->getCustomName() : $this->name; } final public function canBePlaced() : bool{ return $this->block !== null and $this->block->canBePlaced(); } public function canBeConsumed() : bool{ return false; } public function canBeConsumedBy(Entity $entity) : bool{ return $this->canBeConsumed(); } public function onConsume(Entity $entity){ } public function getBlock() : Block{ if($this->block instanceof Block){ return clone $this->block; }else{ return Block::get(self::AIR); } } final public function getId() : int{ return $this->id; } final public function getDamage() : int{ return $this->meta; } public function setDamage(int $meta){ $this->meta = $meta !== -1 ? $meta & 0xFFFF : -1; } public function hasAnyDamageValue() : bool{ return $this->meta === -1; } public function getMaxStackSize(){ return 64; } final public function getFuelTime(){ if(!isset(Fuel::$duration[$this->id])){ return null; } if($this->id !== self::BUCKET or $this->meta === 10){ return Fuel::$duration[$this->id]; } return null; } /** * @param Entity|Block $object * * @return bool */ public function useOn($object){ return false; } /** * @return bool */ public function isTool(){ return false; } /** * @return int|bool */ public function getMaxDurability(){ return false; } public function isPickaxe(){ return false; } public function isAxe(){ return false; } public function isSword(){ return false; } public function isShovel(){ return false; } public function isHoe(){ return false; } public function isShears(){ return false; } public function getDestroySpeed(Block $block, Player $player){ return 1; } public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ return false; } public final function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{ 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, bool $checkDamage = true, bool $checkCompound = true) : bool{ if($this->equals($item, $checkDamage, $checkCompound)){ return true; }elseif($item->hasCompoundTag() and $this->hasCompoundTag()){ return NBT::matchTree($this->getNamedTag(), $item->getNamedTag()); } return false; } final public function __toString() : string{ return "Item " . $this->name . " (" . $this->id . ":" . ($this->meta === null ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : ""); } final public function jsonSerialize(){ return [ "id" => $this->id, "damage" => $this->meta, "count" => $this->count, //TODO: separate items and stacks "nbt" => $this->tags ]; } /** * Serializes the item to an NBT CompoundTag * * @param int $slot optional, the inventory slot of the item * @param string $tagName the name to assign to the CompoundTag object * * @return CompoundTag */ public function nbtSerialize(int $slot = -1, string $tagName = "") : CompoundTag{ $tag = new CompoundTag($tagName, [ "id" => new ShortTag("id", $this->id), "Count" => new ByteTag("Count", $this->count ?? -1), "Damage" => new ShortTag("Damage", $this->meta), ]); if($this->hasCompoundTag()){ $tag->tag = clone $this->getNamedTag(); $tag->tag->setName("tag"); } if($slot !== -1){ $tag->Slot = new ByteTag("Slot", $slot); } return $tag; } /** * Deserializes an Item from an NBT CompoundTag * * @param CompoundTag $tag * * @return Item */ public static function nbtDeserialize(CompoundTag $tag) : Item{ if(!isset($tag->id) or !isset($tag->Count)){ return Item::get(0); } if($tag->id instanceof ShortTag){ $item = Item::get($tag->id->getValue(), !isset($tag->Damage) ? 0 : $tag->Damage->getValue(), $tag->Count->getValue()); }elseif($tag->id instanceof StringTag){ //PC item save format $item = Item::fromString($tag->id->getValue()); $item->setDamage(!isset($tag->Damage) ? 0 : $tag->Damage->getValue()); $item->setCount($tag->Count->getValue()); }else{ throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($tag->id) . " given"); } if(isset($tag->tag) and $tag->tag instanceof CompoundTag){ $item->setNamedTag($tag->tag); } return $item; } }