diff --git a/src/pocketmine/block/BlockFactory.php b/src/pocketmine/block/BlockFactory.php index 3144e2732..eed630862 100644 --- a/src/pocketmine/block/BlockFactory.php +++ b/src/pocketmine/block/BlockFactory.php @@ -245,8 +245,8 @@ class BlockFactory{ self::registerBlock(new Coal()); self::registerBlock(new PackedIce()); self::registerBlock(new DoublePlant()); - //TODO: STANDING_BANNER - //TODO: WALL_BANNER + self::registerBlock(new StandingBanner()); + self::registerBlock(new WallBanner()); //TODO: DAYLIGHT_DETECTOR_INVERTED self::registerBlock(new RedSandstone()); self::registerBlock(new RedSandstoneStairs()); diff --git a/src/pocketmine/block/StandingBanner.php b/src/pocketmine/block/StandingBanner.php new file mode 100644 index 000000000..e6af88995 --- /dev/null +++ b/src/pocketmine/block/StandingBanner.php @@ -0,0 +1,109 @@ +meta = $meta; + } + + public function getHardness() : float{ + return 1; + } + + public function isSolid() : bool{ + return false; + } + + public function getName() : string{ + return "Standing Banner"; + } + + protected function recalculateBoundingBox() : ?AxisAlignedBB{ + return null; + } + + public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $facePos, Player $player = null) : bool{ + if($face !== Vector3::SIDE_DOWN){ + if($face === Vector3::SIDE_UP and $player !== null){ + $this->meta = floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f; + $this->getLevel()->setBlock($blockReplace, $this, true); + }else{ + $this->meta = $face; + $this->getLevel()->setBlock($blockReplace, BlockFactory::get(Block::WALL_BANNER, $this->meta), true); + } + + Tile::createTile(Tile::BANNER, $this->getLevel(), TileBanner::createNBT($this, $face, $item, $player)); + return true; + } + + return false; + } + + public function onUpdate(int $type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){ + $this->getLevel()->useBreakOn($this); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function getToolType() : int{ + return Tool::TYPE_AXE; + } + + public function getVariantBitmask() : int{ + return 0; + } + + public function getDrops(Item $item) : array{ + $tile = $this->level->getTile($this); + + $drop = ItemFactory::get(Item::BANNER, ($tile instanceof TileBanner ? $tile->getBaseColor() : 0)); + if($tile instanceof TileBanner and ($patterns = $tile->namedtag->getListTag(TileBanner::TAG_PATTERNS)) !== null and $patterns->getCount() > 0){ + $drop->setNamedTagEntry($patterns); + } + + return [$drop]; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/WallBanner.php b/src/pocketmine/block/WallBanner.php new file mode 100644 index 000000000..90c2d69d1 --- /dev/null +++ b/src/pocketmine/block/WallBanner.php @@ -0,0 +1,45 @@ +getSide($this->meta ^ 0x01)->getId() === self::AIR){ + $this->getLevel()->useBreakOn($this); + } + return Level::BLOCK_UPDATE_NORMAL; + } + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Banner.php b/src/pocketmine/item/Banner.php new file mode 100644 index 000000000..342615f03 --- /dev/null +++ b/src/pocketmine/item/Banner.php @@ -0,0 +1,251 @@ +block = BlockFactory::get(Block::STANDING_BANNER); + parent::__construct(self::BANNER, $meta, "Banner"); + } + + public function getMaxStackSize() : int{ + return 16; + } + + /** + * Returns the color of the banner base. + * + * @return int + */ + public function getBaseColor() : int{ + return $this->getNamedTag()->getInt(self::TAG_BASE, 0); + } + + /** + * Sets the color of the banner base. + * Banner items have to be resent to see the changes in the inventory. + * + * @param int $color + */ + public function setBaseColor(int $color) : void{ + $namedTag = $this->getNamedTag(); + $namedTag->setInt(self::TAG_BASE, $color & 0x0f); + $this->setNamedTag($namedTag); + } + + /** + * Applies a new pattern on the banner with the given color. + * Banner items have to be resent to see the changes in the inventory. + * + * @param string $pattern + * @param int $color + * + * @return int ID of pattern. + */ + public function addPattern(string $pattern, int $color) : int{ + $patternId = 0; + if($this->getPatternCount() !== 0){ + $patternId = max($this->getPatternIds()) + 1; + } + + $patternsTag = $this->getNamedTag()->getListTag(self::TAG_PATTERNS); + assert($patternsTag !== null); + + $patternsTag[$patternId] = new CompoundTag("", [ + new IntTag(self::TAG_PATTERN_COLOR, $color & 0x0f), + new StringTag(self::TAG_PATTERN_NAME, $pattern) + ]); + + $this->setNamedTagEntry($patternsTag); + + return $patternId; + } + + /** + * Returns whether a pattern with the given ID exists on the banner or not. + * + * @param int $patternId + * + * @return bool + */ + public function patternExists(int $patternId) : bool{ + $this->correctNBT(); + return isset($this->getNamedTag()->getListTag(self::TAG_PATTERNS)[$patternId]); + } + + /** + * Returns the data of a pattern with the given ID. + * + * @param int $patternId + * + * @return array + */ + public function getPatternData(int $patternId) : array{ + if(!$this->patternExists($patternId)){ + return []; + } + + $patternsTag = $this->getNamedTag()->getListTag(self::TAG_PATTERNS); + assert($patternsTag !== null); + $pattern = $patternsTag[$patternId]; + assert($pattern instanceof CompoundTag); + + return [ + self::TAG_PATTERN_COLOR => $pattern->getInt(self::TAG_PATTERN_COLOR), + self::TAG_PATTERN_NAME => $pattern->getString(self::TAG_PATTERN_NAME) + ]; + } + + /** + * Changes the pattern of a previously existing pattern. + * Banner items have to be resent to see the changes in the inventory. + * + * @param int $patternId + * @param string $pattern + * @param int $color + * + * @return bool indicating success. + */ + public function changePattern(int $patternId, string $pattern, int $color) : bool{ + if(!$this->patternExists($patternId)){ + return false; + } + + $patternsTag = $this->getNamedTag()->getListTag(self::TAG_PATTERNS); + assert($patternsTag !== null); + + $patternsTag[$patternId] = new CompoundTag("", [ + new IntTag(self::TAG_PATTERN_COLOR, $color & 0x0f), + new StringTag(self::TAG_PATTERN_NAME, $pattern) + ]); + + $this->setNamedTagEntry($patternsTag); + return true; + } + + /** + * Deletes a pattern from the banner with the given ID. + * Banner items have to be resent to see the changes in the inventory. + * + * @param int $patternId + * + * @return bool indicating whether the pattern existed or not. + */ + public function deletePattern(int $patternId) : bool{ + if(!$this->patternExists($patternId)){ + return false; + } + + $patternsTag = $this->getNamedTag()->getListTag(self::TAG_PATTERNS); + if($patternsTag instanceof ListTag){ + unset($patternsTag[$patternId]); + $this->setNamedTagEntry($patternsTag); + } + + return true; + } + + /** + * Deletes the top most pattern of the banner. + * Banner items have to be resent to see the changes in the inventory. + * + * @return bool indicating whether the banner was empty or not. + */ + public function deleteTopPattern() : bool{ + $keys = $this->getPatternIds(); + if(empty($keys)){ + return false; + } + + return $this->deletePattern(max($keys)); + } + + /** + * Deletes the bottom pattern of the banner. + * Banner items have to be resent to see the changes in the inventory. + * + * @return bool indicating whether the banner was empty or not. + */ + public function deleteBottomPattern() : bool{ + $keys = $this->getPatternIds(); + if(empty($keys)){ + return false; + } + + return $this->deletePattern(min($keys)); + } + + /** + * Returns an array containing all pattern IDs + * + * @return array + */ + public function getPatternIds() : array{ + $this->correctNBT(); + + $keys = array_keys((array) ($this->getNamedTag()->getListTag(self::TAG_PATTERNS) ?? [])); + + return array_filter($keys, function($key){ + return is_numeric($key); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Returns the total count of patterns on this banner. + * + * @return int + */ + public function getPatternCount() : int{ + return count($this->getPatternIds()); + } + + public function correctNBT() : void{ + $tag = $this->getNamedTag(); + if(!$tag->hasTag(self::TAG_BASE, IntTag::class)){ + $tag->setInt(self::TAG_BASE, 0); + } + + if(!$tag->hasTag(self::TAG_PATTERNS, ListTag::class)){ + $tag->setTag(new ListTag(self::TAG_PATTERNS)); + } + $this->setNamedTag($tag); + } + + public function getFuelTime() : int{ + return 300; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/ItemFactory.php b/src/pocketmine/item/ItemFactory.php index 19433e68e..a48d988c6 100644 --- a/src/pocketmine/item/ItemFactory.php +++ b/src/pocketmine/item/ItemFactory.php @@ -225,7 +225,7 @@ class ItemFactory{ //TODO: COMMAND_BLOCK_MINECART //TODO: ELYTRA self::registerItem(new Item(Item::SHULKER_SHELL, 0, "Shulker Shell")); - //TODO: BANNER + self::registerItem(new Banner()); //TODO: TOTEM diff --git a/src/pocketmine/nbt/tag/ListTag.php b/src/pocketmine/nbt/tag/ListTag.php index ebff8ab11..6b3db2636 100644 --- a/src/pocketmine/nbt/tag/ListTag.php +++ b/src/pocketmine/nbt/tag/ListTag.php @@ -109,6 +109,11 @@ class ListTag extends NamedTag implements \ArrayAccess, \Countable{ return isset($this->{$offset}); } + /** + * @param int $offset + * + * @return CompoundTag|ListTag|mixed + */ public function offsetGet($offset){ if(isset($this->{$offset}) and $this->{$offset} instanceof Tag){ if($this->{$offset} instanceof \ArrayAccess){ diff --git a/src/pocketmine/tile/Banner.php b/src/pocketmine/tile/Banner.php new file mode 100644 index 000000000..6f066177a --- /dev/null +++ b/src/pocketmine/tile/Banner.php @@ -0,0 +1,304 @@ +hasTag(self::TAG_BASE, IntTag::class)){ + $nbt->setInt(self::TAG_BASE, 0); + } + if(!$nbt->hasTag(self::TAG_PATTERNS, ListTag::class)){ + $nbt->setTag(new ListTag(self::TAG_PATTERNS)); + } + parent::__construct($level, $nbt); + } + + public function addAdditionalSpawnData(CompoundTag $nbt) : void{ + $nbt->setTag($this->namedtag->getTag(self::TAG_PATTERNS)); + $nbt->setTag($this->namedtag->getTag(self::TAG_BASE)); + } + + /** + * Returns the color of the banner base. + * + * @return int + */ + public function getBaseColor() : int{ + return $this->namedtag->getInt(self::TAG_BASE, 0); + } + + /** + * Sets the color of the banner base. + * + * @param int $color + */ + public function setBaseColor(int $color) : void{ + $this->namedtag->setInt(self::TAG_BASE, $color & 0x0f); + $this->onChanged(); + } + + /** + * Returns an array containing all pattern IDs + * + * @return array + */ + public function getPatternIds() : array{ + $keys = array_keys((array) $this->namedtag->getTag(self::TAG_PATTERNS)); + return array_filter($keys, function(string $key){ + return is_numeric($key); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Applies a new pattern on the banner with the given color. + * + * @param string $pattern + * @param int $color + * + * @return int ID of pattern. + */ + public function addPattern(string $pattern, int $color) : int{ + $patternId = 0; + if($this->getPatternCount() !== 0){ + $patternId = max($this->getPatternIds()) + 1; + } + + $list = $this->namedtag->getListTag(self::TAG_PATTERNS); + assert($list !== null); + $list[$patternId] = new CompoundTag("", [ + new IntTag(self::TAG_PATTERN_COLOR, $color & 0x0f), + new StringTag(self::TAG_PATTERN_NAME, $pattern) + ]); + + $this->onChanged(); + return $patternId; + } + + /** + * Returns whether a pattern with the given ID exists on the banner or not. + * + * @param int $patternId + * + * @return bool + */ + public function patternExists(int $patternId) : bool{ + return isset($this->namedtag->getListTag(self::TAG_PATTERNS)[$patternId]); + } + + /** + * Returns the data of a pattern with the given ID. + * + * @param int $patternId + * + * @return array + */ + public function getPatternData(int $patternId) : array{ + if(!$this->patternExists($patternId)){ + return []; + } + + $list = $this->namedtag->getListTag(self::TAG_PATTERNS); + assert($list instanceof ListTag); + $patternTag = $list[$patternId]; + assert($patternTag instanceof CompoundTag); + + return [ + self::TAG_PATTERN_COLOR => $patternTag->getInt(self::TAG_PATTERN_COLOR), + self::TAG_PATTERN_NAME => $patternTag->getString(self::TAG_PATTERN_NAME) + ]; + } + + /** + * Changes the pattern of a previously existing pattern. + * + * @param int $patternId + * @param string $pattern + * @param int $color + * + * @return bool indicating success. + */ + public function changePattern(int $patternId, string $pattern, int $color) : bool{ + if(!$this->patternExists($patternId)){ + return false; + } + + $list = $this->namedtag->getListTag(self::TAG_PATTERNS); + assert($list instanceof ListTag); + + $list[$patternId] = new CompoundTag("", [ + new IntTag(self::TAG_PATTERN_COLOR, $color & 0x0f), + new StringTag(self::TAG_PATTERN_NAME, $pattern) + ]); + + $this->onChanged(); + return true; + } + + /** + * Deletes a pattern from the banner with the given ID. + * + * @param int $patternId + * + * @return bool indicating whether the pattern existed or not. + */ + public function deletePattern(int $patternId) : bool{ + if(!$this->patternExists($patternId)){ + return false; + } + + $list = $this->namedtag->getListTag(self::TAG_PATTERNS); + if($list !== null){ + unset($list[$patternId]); + } + + $this->onChanged(); + return true; + } + + /** + * Deletes the top most pattern of the banner. + * + * @return bool indicating whether the banner was empty or not. + */ + public function deleteTopPattern() : bool{ + $keys = $this->getPatternIds(); + if(empty($keys)){ + return false; + } + + return $this->deletePattern(max($keys)); + } + + /** + * Deletes the bottom pattern of the banner. + * + * @return bool indicating whether the banner was empty or not. + */ + public function deleteBottomPattern() : bool{ + $keys = $this->getPatternIds(); + if(empty($keys)){ + return false; + } + + return $this->deletePattern(min($keys)); + } + + /** + * Returns the total count of patterns on this banner. + * + * @return int + */ + public function getPatternCount() : int{ + return count($this->getPatternIds()); + } + + protected static function createAdditionalNBT(CompoundTag $nbt, Vector3 $pos, ?int $face = null, ?Item $item = null, ?Player $player = null) : void{ + $nbt->setInt(self::TAG_BASE, $item !== null ? $item->getDamage() & 0x0f : 0); + + if($item !== null){ + if($item->getNamedTag()->hasTag(self::TAG_PATTERNS, ListTag::class)){ + $nbt->setTag($item->getNamedTag()->getListTag(self::TAG_PATTERNS)); + } + if($item->hasCustomName()){ + $nbt->setString("CustomName", $item->getCustomName()); + } + } + } + + public function getDefaultName() : string{ + return "Banner"; + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Tile.php b/src/pocketmine/tile/Tile.php index a03134d5a..7c9cfe434 100644 --- a/src/pocketmine/tile/Tile.php +++ b/src/pocketmine/tile/Tile.php @@ -59,6 +59,7 @@ abstract class Tile extends Position{ const SIGN = "Sign"; const SKULL = "Skull"; const BED = "Bed"; + const BANNER = "Banner"; /** @var int */ public static $tileCount = 1; @@ -84,6 +85,7 @@ abstract class Tile extends Position{ protected $timings; public static function init(){ + self::registerTile(Banner::class); self::registerTile(Bed::class); self::registerTile(Chest::class); self::registerTile(EnchantTable::class);