diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index 7bf12c723..b1820ca52 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -329,6 +329,15 @@ abstract class Entity extends Location implements Metadatable, EntityIds{ self::$saveNames[$className] = $saveNames; } + /** + * Returns an array of all registered entity classpaths. + * + * @return string[] + */ + public static function getKnownEntityTypes() : array{ + return array_unique(self::$knownEntities); + } + /** * Helper function which creates minimal NBT needed to spawn an entity. * diff --git a/src/pocketmine/item/Bucket.php b/src/pocketmine/item/Bucket.php index ca3551d95..254c4af60 100644 --- a/src/pocketmine/item/Bucket.php +++ b/src/pocketmine/item/Bucket.php @@ -34,16 +34,20 @@ use pocketmine\math\Vector3; use pocketmine\Player; class Bucket extends Item implements Consumable{ - public function __construct(int $meta = 0){ - parent::__construct(self::BUCKET, $meta, "Bucket"); + /** @var int|null */ + protected $blockId; + + public function __construct(int $id, int $meta, string $name, ?int $blockId){ + parent::__construct($id, $meta, $name); + $this->blockId = $blockId; } public function getMaxStackSize() : int{ - return $this->meta === Block::AIR ? 16 : 1; //empty buckets stack to 16 + return $this->blockId === Block::AIR ? 16 : 1; //empty buckets stack to 16 } public function getFuelTime() : int{ - if($this->meta === Block::LAVA or $this->meta === Block::FLOWING_LAVA){ + if($this->blockId === Block::LAVA or $this->blockId === Block::FLOWING_LAVA){ return 20000; } @@ -51,7 +55,10 @@ class Bucket extends Item implements Consumable{ } public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{ - $resultBlock = BlockFactory::get($this->meta); + if($this->blockId === null){ + return false; + } + $resultBlock = BlockFactory::get($this->blockId); if($resultBlock instanceof Air){ if($blockClicked instanceof Liquid and $blockClicked->isSource()){ @@ -108,7 +115,7 @@ class Bucket extends Item implements Consumable{ } public function canBeConsumed() : bool{ - return $this->meta === 1; //Milk + return $this->blockId === null; //Milk } public function onConsume(Living $consumer){ diff --git a/src/pocketmine/item/Coal.php b/src/pocketmine/item/Coal.php index 89c214b3b..ebdb65ee3 100644 --- a/src/pocketmine/item/Coal.php +++ b/src/pocketmine/item/Coal.php @@ -25,12 +25,6 @@ namespace pocketmine\item; class Coal extends Item{ - public function __construct(int $meta = 0){ - parent::__construct(self::COAL, $meta, "Coal"); - if($this->meta === 1){ - $this->name = "Charcoal"; - } - } public function getFuelTime() : int{ return 1600; diff --git a/src/pocketmine/item/Durable.php b/src/pocketmine/item/Durable.php index 76e6927b3..e0e1d00e6 100644 --- a/src/pocketmine/item/Durable.php +++ b/src/pocketmine/item/Durable.php @@ -28,6 +28,9 @@ use pocketmine\nbt\tag\ByteTag; abstract class Durable extends Item{ + /** @var int */ + protected $damage = 0; + /** * Returns whether this item will take damage when used. * @return bool @@ -57,7 +60,7 @@ abstract class Durable extends Item{ $amount -= $this->getUnbreakingDamageReduction($amount); - $this->meta = min($this->meta + $amount, $this->getMaxDurability()); + $this->damage = min($this->damage + $amount, $this->getMaxDurability()); if($this->isBroken()){ $this->onBroken(); } @@ -65,6 +68,18 @@ abstract class Durable extends Item{ return true; } + public function getDamage() : int{ + return $this->damage; + } + + public function setDamage(int $meta) : Item{ + if($meta < 0 or $meta > $this->getMaxDurability()){ + throw new \InvalidArgumentException("Damage must be in range 0 - " , $this->getMaxDurability()); + } + $this->damage = $meta; + return $this; + } + protected function getUnbreakingDamageReduction(int $amount) : int{ if(($unbreakingLevel = $this->getEnchantmentLevel(Enchantment::UNBREAKING)) > 0){ $negated = 0; @@ -101,6 +116,6 @@ abstract class Durable extends Item{ * @return bool */ public function isBroken() : bool{ - return $this->meta >= $this->getMaxDurability(); + return $this->damage >= $this->getMaxDurability(); } } diff --git a/src/pocketmine/item/Item.php b/src/pocketmine/item/Item.php index 7e7182914..2440bf97e 100644 --- a/src/pocketmine/item/Item.php +++ b/src/pocketmine/item/Item.php @@ -200,7 +200,7 @@ class Item implements ItemIds, \JsonSerializable{ throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff); } $this->id = $id; - $this->setDamage($meta); + $this->meta = $meta !== -1 ? $meta & 0x7FFF : -1; $this->name = $name; } @@ -663,20 +663,10 @@ class Item implements ItemIds, \JsonSerializable{ /** * @return int */ - final public function getDamage() : int{ + public function getDamage() : int{ return $this->meta; } - /** - * @param int $meta - * @return Item - */ - public function setDamage(int $meta) : Item{ - $this->meta = $meta !== -1 ? $meta & 0x7FFF : -1; - - return $this; - } - /** * Returns whether this item can match any item with an equivalent ID with any meta value. * Used in crafting recipes which accept multiple variants of the same item, for example crafting tables recipes. @@ -856,7 +846,7 @@ class Item implements ItemIds, \JsonSerializable{ * @return string */ final public function __toString() : string{ - return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : ""); + return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->getDamage()) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : ""); } /** @@ -921,7 +911,7 @@ class Item implements ItemIds, \JsonSerializable{ $result = new CompoundTag($tagName, [ new ShortTag("id", $this->id), new ByteTag("Count", Binary::signByte($this->count)), - new ShortTag("Damage", $this->meta) + new ShortTag("Damage", $this->getDamage()) ]); if($this->hasCompoundTag()){ @@ -957,12 +947,11 @@ class Item implements ItemIds, \JsonSerializable{ $item = ItemFactory::get($idTag->getValue(), $meta, $count); }elseif($idTag instanceof StringTag){ //PC item save format try{ - $item = ItemFactory::fromString($idTag->getValue()); + $item = ItemFactory::fromString($idTag->getValue() . ":$meta"); }catch(\InvalidArgumentException $e){ //TODO: improve error handling return ItemFactory::get(Item::AIR, 0, 0); } - $item->setDamage($meta); $item->setCount($count); }else{ throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given"); diff --git a/src/pocketmine/item/ItemBlock.php b/src/pocketmine/item/ItemBlock.php index da455e517..a0c4375f5 100644 --- a/src/pocketmine/item/ItemBlock.php +++ b/src/pocketmine/item/ItemBlock.php @@ -46,7 +46,7 @@ class ItemBlock extends Item{ $blockId = 255 - $blockId; } $this->blockId = $blockId; - $this->setDamage($meta); + $this->meta = $meta; parent::__construct($itemId ?? $blockId, $meta, $this->getBlock()->getName()); } diff --git a/src/pocketmine/item/ItemFactory.php b/src/pocketmine/item/ItemFactory.php index 8a0c24e46..89730fa96 100644 --- a/src/pocketmine/item/ItemFactory.php +++ b/src/pocketmine/item/ItemFactory.php @@ -25,6 +25,8 @@ namespace pocketmine\item; use pocketmine\block\Block; use pocketmine\block\BlockFactory; +use pocketmine\entity\Entity; +use pocketmine\entity\Living; use pocketmine\nbt\tag\CompoundTag; /** @@ -45,7 +47,8 @@ class ItemFactory{ self::registerItem(new Apple()); self::registerItem(new Bow()); self::registerItem(new Arrow()); - self::registerItem(new Coal()); + self::registerItem(new Coal(Item::COAL, 0, "Coal")); + self::registerItem(new Coal(Item::COAL, 1, "Charcoal")); self::registerItem(new Item(Item::DIAMOND, 0, "Diamond")); self::registerItem(new Item(Item::IRON_INGOT, 0, "Iron Ingot")); self::registerItem(new Item(Item::GOLD_INGOT, 0, "Gold Ingot")); @@ -107,13 +110,19 @@ class ItemFactory{ self::registerItem(new GoldenApple()); self::registerItem(new Sign()); self::registerItem(new ItemBlock(Block::OAK_DOOR_BLOCK, 0, Item::OAK_DOOR)); - self::registerItem(new Bucket()); + self::registerItem(new Bucket(Item::BUCKET, 0, "Bucket", Block::AIR)); + self::registerItem(new Bucket(Item::BUCKET, 1, "Milk Bucket", null)); //TODO: this ought to get its own class, it has completely different behaviour + //TODO: fix metadata for buckets with still liquid in them + //the meta values are intentionally hardcoded because block IDs will change in the future + self::registerItem(new Bucket(Item::BUCKET, 8, "Water Bucket", Block::FLOWING_WATER)); + self::registerItem(new Bucket(Item::BUCKET, 10, "Lava Bucket", Block::FLOWING_LAVA)); self::registerItem(new Minecart()); //TODO: SADDLE self::registerItem(new ItemBlock(Block::IRON_DOOR_BLOCK, 0, Item::IRON_DOOR)); self::registerItem(new Redstone()); self::registerItem(new Snowball()); + self::registerItem(new Boat()); self::registerItem(new Item(Item::LEATHER, 0, "Leather")); //TODO: KELP @@ -132,7 +141,10 @@ class ItemFactory{ self::registerItem(new Item(Item::GLOWSTONE_DUST, 0, "Glowstone Dust")); self::registerItem(new RawFish()); self::registerItem(new CookedFish()); - self::registerItem(new Dye()); + for($i = 0; $i < 16; ++$i){ + //TODO: add colour constants (this is messy) + self::registerItem(new Dye($i)); + } self::registerItem(new Item(Item::BONE, 0, "Bone")); self::registerItem(new Item(Item::SUGAR, 0, "Sugar")); self::registerItem(new ItemBlock(Block::CAKE_BLOCK, 0, Item::CAKE)); @@ -154,7 +166,12 @@ class ItemFactory{ self::registerItem(new Item(Item::GHAST_TEAR, 0, "Ghast Tear")); self::registerItem(new Item(Item::GOLD_NUGGET, 0, "Gold Nugget")); self::registerItem(new ItemBlock(Block::NETHER_WART_PLANT, 0, Item::NETHER_WART)); - self::registerItem(new Potion()); + + foreach(Potion::ALL as $type){ + self::registerItem(new Potion($type)); + self::registerItem(new SplashPotion($type)); + //TODO: LINGERING_POTION + } self::registerItem(new GlassBottle()); self::registerItem(new SpiderEye()); self::registerItem(new Item(Item::FERMENTED_SPIDER_EYE, 0, "Fermented Spider Eye")); @@ -164,7 +181,14 @@ class ItemFactory{ self::registerItem(new ItemBlock(Block::CAULDRON_BLOCK, 0, Item::CAULDRON)); //TODO: ENDER_EYE self::registerItem(new Item(Item::GLISTERING_MELON, 0, "Glistering Melon")); - self::registerItem(new SpawnEgg()); + + foreach(Entity::getKnownEntityTypes() as $className){ + /** @var Living $className */ + if(is_a($className, Living::class, true) and $className::NETWORK_ID !== -1){ + self::registerItem(new SpawnEgg(Item::SPAWN_EGG, $className::NETWORK_ID, "Spawn Egg")); + } + } + self::registerItem(new ExperienceBottle()); //TODO: FIREBALL self::registerItem(new WritableBook()); @@ -217,9 +241,7 @@ class ItemFactory{ self::registerItem(new Item(Item::CHORUS_FRUIT_POPPED, 0, "Popped Chorus Fruit")); self::registerItem(new Item(Item::DRAGON_BREATH, 0, "Dragon's Breath")); - self::registerItem(new SplashPotion()); - //TODO: LINGERING_POTION //TODO: SPARKLER //TODO: COMMAND_BLOCK_MINECART //TODO: ELYTRA @@ -279,11 +301,19 @@ class ItemFactory{ */ public static function registerItem(Item $item, bool $override = false){ $id = $item->getId(); - if(!$override and self::isRegistered($id)){ + $variant = $item->getDamage(); + + if(!$override and self::isRegistered($id, $variant)){ throw new \RuntimeException("Trying to overwrite an already registered item"); } - self::$list[self::getListOffset($id)] = clone $item; + $offset = self::getListOffset($id); + $sublist = self::$list[$offset] ?? new \SplFixedArray(); + if($sublist->getSize() < $variant + 1){ + $sublist->setSize($variant + 1); + } + $sublist[$variant] = clone $item; + self::$list[$offset] = $sublist; } /** @@ -303,24 +333,28 @@ class ItemFactory{ } try{ + $sublist = self::$list[self::getListOffset($id)]; + /** @var Item|null $listed */ - $listed = self::$list[self::getListOffset($id)]; - if($listed !== null){ - $item = clone $listed; + if($sublist !== null and isset($sublist[$meta])){ + $item = clone $sublist[$meta]; }elseif($id < 256){ //intentionally includes negatives, for extended block IDs /* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are * crafting ingredients with any-damage. */ $item = new ItemBlock($id, $meta); }else{ + //negative damage values will fallthru to here, to avoid crazy shit with crafting wildcard hacks $item = new Item($id, $meta); } }catch(\RuntimeException $e){ throw new \InvalidArgumentException("Item ID $id is invalid or out of bounds"); } - $item->setDamage($meta); $item->setCount($count); $item->setCompoundTag($tags); + if($item instanceof Durable){ //nasty, but necessary for BC reasons + $item->setDamage($meta); + } return $item; } @@ -376,13 +410,17 @@ class ItemFactory{ * Returns whether the specified item ID is already registered in the item factory. * * @param int $id + * @param int $variant + * * @return bool */ - public static function isRegistered(int $id) : bool{ + public static function isRegistered(int $id, int $variant = 0) : bool{ if($id < 256){ return BlockFactory::isRegistered($id); } - return self::$list[self::getListOffset($id)] !== null; + + $sublist = self::$list[self::getListOffset($id)]; + return $sublist !== null and isset($sublist[$variant]); } private static function getListOffset(int $id) : int{ diff --git a/src/pocketmine/item/Potion.php b/src/pocketmine/item/Potion.php index 6a04fe73c..7df7eafe2 100644 --- a/src/pocketmine/item/Potion.php +++ b/src/pocketmine/item/Potion.php @@ -67,6 +67,46 @@ class Potion extends Item implements Consumable{ public const LONG_WEAKNESS = 35; public const WITHER = 36; + public const ALL = [ + self::WATER, + self::MUNDANE, + self::LONG_MUNDANE, + self::THICK, + self::AWKWARD, + self::NIGHT_VISION, + self::LONG_NIGHT_VISION, + self::INVISIBILITY, + self::LONG_INVISIBILITY, + self::LEAPING, + self::LONG_LEAPING, + self::STRONG_LEAPING, + self::FIRE_RESISTANCE, + self::LONG_FIRE_RESISTANCE, + self::SWIFTNESS, + self::LONG_SWIFTNESS, + self::STRONG_SWIFTNESS, + self::SLOWNESS, + self::LONG_SLOWNESS, + self::WATER_BREATHING, + self::LONG_WATER_BREATHING, + self::HEALING, + self::STRONG_HEALING, + self::HARMING, + self::STRONG_HARMING, + self::POISON, + self::LONG_POISON, + self::STRONG_POISON, + self::REGENERATION, + self::LONG_REGENERATION, + self::STRONG_REGENERATION, + self::STRENGTH, + self::LONG_STRENGTH, + self::STRONG_STRENGTH, + self::WEAKNESS, + self::LONG_WEAKNESS, + self::WITHER + ]; + /** * Returns a list of effects applied by potions with the specified ID. * diff --git a/src/pocketmine/item/SpawnEgg.php b/src/pocketmine/item/SpawnEgg.php index 46acc47de..4eca401f9 100644 --- a/src/pocketmine/item/SpawnEgg.php +++ b/src/pocketmine/item/SpawnEgg.php @@ -29,9 +29,6 @@ use pocketmine\math\Vector3; use pocketmine\Player; class SpawnEgg extends Item{ - public function __construct(int $meta = 0){ - parent::__construct(self::SPAWN_EGG, $meta, "Spawn Egg"); - } public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{ $nbt = Entity::createBaseNBT($blockReplace->add(0.5, 0, 0.5), null, lcg_value() * 360, 0);