Flatten Item variants, remove Item->setDamage()

This commit is contained in:
Dylan K. Taylor 2018-10-06 20:09:11 +01:00
parent 14ef4558c2
commit 5fb7825485
9 changed files with 138 additions and 49 deletions

View File

@ -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.
*

View File

@ -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){

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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");

View File

@ -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());
}

View File

@ -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{

View File

@ -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.
*

View File

@ -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);