From 81dee2f9fca6bbcb42dfee85d29449be92b90bca Mon Sep 17 00:00:00 2001 From: Sandertv Date: Sun, 8 Oct 2017 12:49:57 +0200 Subject: [PATCH] Adding writable and written books. (#1397) * Adding writable and written books. * Added a PlayerEditBookEvent. * Changed BookEditPacket field names. --- src/pocketmine/Player.php | 52 ++++ .../event/player/PlayerEditBookEvent.php | 101 ++++++++ src/pocketmine/item/ItemFactory.php | 4 +- src/pocketmine/item/WritableBook.php | 228 ++++++++++++++++++ src/pocketmine/item/WrittenBook.php | 131 ++++++++++ .../mcpe/PlayerNetworkSessionAdapter.php | 5 + .../network/mcpe/protocol/BookEditPacket.php | 12 +- 7 files changed, 525 insertions(+), 8 deletions(-) create mode 100644 src/pocketmine/event/player/PlayerEditBookEvent.php create mode 100644 src/pocketmine/item/WritableBook.php create mode 100644 src/pocketmine/item/WrittenBook.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index fcc10da0e..6501ec2ca 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -51,6 +51,7 @@ use pocketmine\event\player\PlayerChangeSkinEvent; use pocketmine\event\player\PlayerChatEvent; use pocketmine\event\player\PlayerCommandPreprocessEvent; use pocketmine\event\player\PlayerDeathEvent; +use pocketmine\event\player\PlayerEditBookEvent; use pocketmine\event\player\PlayerExhaustEvent; use pocketmine\event\player\PlayerGameModeChangeEvent; use pocketmine\event\player\PlayerInteractEvent; @@ -81,6 +82,8 @@ use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\item\Item; use pocketmine\item\ItemFactory; +use pocketmine\item\WritableBook; +use pocketmine\item\WrittenBook; use pocketmine\level\ChunkLoader; use pocketmine\level\format\Chunk; use pocketmine\level\Level; @@ -103,6 +106,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\BatchPacket; use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; +use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\BossEventPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket; @@ -3045,6 +3049,54 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ return true; } + public function handleBookEdit(BookEditPacket $packet) : bool{ + /** @var WritableBook $oldBook */ + $oldBook = $this->inventory->getItem($packet->inventorySlot - 9); + if($oldBook->getId() !== Item::WRITABLE_BOOK){ + return false; + } + + $newBook = clone $oldBook; + $modifiedPages = []; + + switch($packet->type){ + case BookEditPacket::TYPE_REPLACE_PAGE: + $newBook->setPageText($packet->pageNumber, $packet->text); + $modifiedPages[] = $packet->pageNumber; + break; + case BookEditPacket::TYPE_ADD_PAGE: + $newBook->insertPage($packet->pageNumber, $packet->text); + $modifiedPages[] = $packet->pageNumber; + break; + case BookEditPacket::TYPE_DELETE_PAGE: + $newBook->deletePage($packet->pageNumber); + $modifiedPages[] = $packet->pageNumber; + break; + case BookEditPacket::TYPE_SWAP_PAGES: + $newBook->swapPage($packet->pageNumber, $packet->secondaryPageNumber); + $modifiedPages = [$packet->pageNumber, $packet->secondaryPageNumber]; + break; + case BookEditPacket::TYPE_SIGN_BOOK: + /** @var WrittenBook $newBook */ + $newBook = Item::get(Item::WRITTEN_BOOK, 0, 1, $newBook->getNamedTag()); + $newBook->setAuthor($packet->author); + $newBook->setTitle($packet->title); + $newBook->setGeneration(WrittenBook::GENERATION_ORIGINAL); + break; + default: + return false; + } + + $this->getServer()->getPluginManager()->callEvent($event = new PlayerEditBookEvent($this, $oldBook, $newBook, $packet->type, $modifiedPages)); + if($event->isCancelled()){ + return true; + } + + $this->getInventory()->setItem($packet->inventorySlot - 9, $event->getNewBook()); + + return true; + } + /** * Called when a packet is received from the client. This method will call DataPacketReceiveEvent. * diff --git a/src/pocketmine/event/player/PlayerEditBookEvent.php b/src/pocketmine/event/player/PlayerEditBookEvent.php new file mode 100644 index 000000000..fa335c222 --- /dev/null +++ b/src/pocketmine/event/player/PlayerEditBookEvent.php @@ -0,0 +1,101 @@ +player = $player; + $this->oldBook = $oldBook; + $this->newBook = $newBook; + $this->action = $action; + $this->modifiedPages = $modifiedPages; + } + + /** + * Returns the action of the event. + * + * @return int + */ + public function getAction() : int{ + return $this->action; + } + + /** + * Returns the book before it was modified. + * + * @return WritableBook + */ + public function getOldBook() : WritableBook{ + return $this->oldBook; + } + + /** + * Returns the book after it was modified. + * The new book may be a written book, if the book was signed. + * + * @return WritableBook + */ + public function getNewBook() : WritableBook{ + return $this->newBook; + } + + /** + * Sets the new book as the given instance. + * + * @param WritableBook $book + */ + public function setNewBook(WritableBook $book) : void{ + $this->newBook = $book; + } + + /** + * Returns an array containing the page IDs of modified pages. + * + * @return int[] + */ + public function getModifiedPages() : array{ + return $this->modifiedPages; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/ItemFactory.php b/src/pocketmine/item/ItemFactory.php index e3d5c4485..c53e6210c 100644 --- a/src/pocketmine/item/ItemFactory.php +++ b/src/pocketmine/item/ItemFactory.php @@ -167,8 +167,8 @@ class ItemFactory{ self::registerItem(new SpawnEgg()); //TODO: BOTTLE_O_ENCHANTING //TODO: FIREBALL - //TODO: WRITABLE_BOOK - //TODO: WRITTEN_BOOK + self::registerItem(new WritableBook()); + self::registerItem(new WrittenBook()); self::registerItem(new Item(Item::EMERALD, 0, "Emerald")); self::registerItem(new ItemFrame()); self::registerItem(new FlowerPot()); diff --git a/src/pocketmine/item/WritableBook.php b/src/pocketmine/item/WritableBook.php new file mode 100644 index 000000000..552d3d12f --- /dev/null +++ b/src/pocketmine/item/WritableBook.php @@ -0,0 +1,228 @@ +getNamedTag()->pages->{$pageId}); + } + + /** + * Returns a string containing the content of a page (which could be empty), or null if the page doesn't exist. + * + * @param int $pageId + * + * @return string|null + */ + public function getPageText(int $pageId) : ?string{ + if(!$this->pageExists($pageId)){ + return null; + } + return $this->getNamedTag()->pages->{$pageId}->text->getValue(); + } + + /** + * Sets the text of a page in the book. Adds the page if the page does not yet exist. + * + * @param int $pageId + * @param string $pageText + * + * @return bool indicating whether the page was created or not. + */ + public function setPageText(int $pageId, string $pageText) : bool{ + $created = false; + if(!$this->pageExists($pageId)){ + $this->addPage($pageId); + $created = true; + } + + $namedTag = $this->getNamedTag(); + $namedTag->pages->{$pageId}->text->setValue($pageText); + $this->setNamedTag($namedTag); + + return $created; + } + + /** + * Adds a new page with the given page ID. + * Creates a new page for every page between the given ID and existing pages that doesn't yet exist. + * + * @param int $pageId + */ + public function addPage(int $pageId) : void{ + if($pageId < 0){ + throw new \InvalidArgumentException("Page number \"$pageId\" is out of range"); + } + $namedTag = $this->getCorrectedNamedTag(); + + if(!isset($namedTag->pages) or !($namedTag->pages instanceof ListTag)){ + $namedTag->pages = new ListTag("pages", []); + } + + for($id = 0; $id <= $pageId; $id++){ + if(!$this->pageExists($id)){ + $namedTag->pages->{$id} = new CompoundTag("", [ + new StringTag("text", ""), + new StringTag("photoname", "") + ]); + } + } + + $this->setNamedTag($namedTag); + } + + /** + * Deletes an existing page with the given page ID. + * + * @param int $pageId + * + * @return bool indicating success + */ + public function deletePage(int $pageId) : bool{ + if(!$this->pageExists($pageId)){ + return false; + } + + $namedTag = $this->getNamedTag(); + unset($namedTag->pages->{$pageId}); + $this->pushPages($pageId, $namedTag); + $this->setNamedTag($namedTag); + + return true; + } + + /** + * Inserts a new page with the given text and moves other pages upwards. + * + * @param int $pageId + * @param string $pageText + * + * @return bool indicating success + */ + public function insertPage(int $pageId, string $pageText = "") : bool{ + $namedTag = $this->getCorrectedNamedTag(); + if(!isset($namedTag->pages) or !($namedTag->pages instanceof ListTag)){ + $namedTag->pages = new ListTag("pages", []); + } + $this->pushPages($pageId, $namedTag, false); + + $namedTag->pages->{$pageId}->text->setValue($pageText); + $this->setNamedTag($namedTag); + return true; + } + + /** + * Switches the text of two pages with each other. + * + * @param int $pageId1 + * @param int $pageId2 + * + * @return bool indicating success + */ + public function swapPage(int $pageId1, int $pageId2) : bool{ + if(!$this->pageExists($pageId1) or !$this->pageExists($pageId2)){ + return false; + } + + $pageContents1 = $this->getPageText($pageId1); + $pageContents2 = $this->getPageText($pageId2); + $this->setPageText($pageId1, $pageContents2); + $this->setPageText($pageId2, $pageContents1); + + return true; + } + + /** + * @return CompoundTag + */ + protected function getCorrectedNamedTag() : CompoundTag{ + return $this->getNamedTag() ?? new CompoundTag(); + } + + public function getMaxStackSize() : int{ + return 1; + } + + /** + * @param int $pageId + * @param CompoundTag $namedTag + * @param bool $downwards + * + * @return bool + */ + private function pushPages(int $pageId, CompoundTag $namedTag, bool $downwards = true) : bool{ + if(empty($this->getPages())){ + return false; + } + + $pages = $this->getPages(); + $type = $downwards ? -1 : 1; + foreach($pages as $key => $page){ + if(($key <= $pageId and $downwards) or ($key < $pageId and !$downwards)){ + continue; + } + + if($downwards){ + unset($namedTag->pages->{$key}); + } + $namedTag->pages->{$key + $type} = new CompoundTag("", [ + new StringTag("text", $page->text->getValue()), + new StringTag("photoname", "") + ]); + } + return true; + } + + /** + * Returns an array containing all pages of this book. + * + * @return CompoundTag[] + */ + public function getPages() : array{ + $namedTag = $this->getCorrectedNamedTag(); + if(!isset($namedTag->pages)){ + return []; + } + + return array_filter((array) $namedTag->pages, function(string $key){ + return is_numeric($key); + }, ARRAY_FILTER_USE_KEY); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/WrittenBook.php b/src/pocketmine/item/WrittenBook.php new file mode 100644 index 000000000..c17003ad3 --- /dev/null +++ b/src/pocketmine/item/WrittenBook.php @@ -0,0 +1,131 @@ +getNamedTag()->generation)) { + return -1; + } + return $this->getNamedTag()->generation->getValue(); + } + + /** + * Sets the generation of a book. + * + * @param int $generation + */ + public function setGeneration(int $generation) : void{ + if($generation < 0 or $generation > 3){ + throw new \InvalidArgumentException("Generation \"$generation\" is out of range"); + } + $namedTag = $this->getCorrectedNamedTag(); + + if(isset($namedTag->generation)){ + $namedTag->generation->setValue($generation); + }else{ + $namedTag->generation = new IntTag("generation", $generation); + } + $this->setNamedTag($namedTag); + } + + /** + * Returns the author of this book. + * This is not a reliable way to get the name of the player who signed this book. + * The author can be set to anything when signing a book. + * + * @return string + */ + public function getAuthor() : string{ + if(!isset($this->getNamedTag()->author)){ + return ""; + } + return $this->getNamedTag()->author->getValue(); + } + + /** + * Sets the author of this book. + * + * @param string $authorName + */ + public function setAuthor(string $authorName) : void{ + $namedTag = $this->getCorrectedNamedTag(); + if(isset($namedTag->author)){ + $namedTag->author->setValue($authorName); + }else{ + $namedTag->author = new StringTag("author", $authorName); + } + $this->setNamedTag($namedTag); + } + + /** + * Returns the title of this book. + * + * @return string + */ + public function getTitle() : string{ + if(!isset($this->getNamedTag()->title)){ + return ""; + } + return $this->getNamedTag()->title->getValue(); + } + + /** + * Sets the author of this book. + * + * @param string $title + */ + public function setTitle(string $title) : void{ + $namedTag = $this->getCorrectedNamedTag(); + if(isset($namedTag->title)){ + $namedTag->title->setValue($title); + }else{ + $namedTag->title = new StringTag("title", $title); + } + $this->setNamedTag($namedTag); + } +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php index 45c35740a..bcfb73dca 100644 --- a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php +++ b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php @@ -30,6 +30,7 @@ use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; +use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\BossEventPacket; use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket; use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket; @@ -231,6 +232,10 @@ class PlayerNetworkSessionAdapter extends NetworkSession{ return $this->player->changeSkin($packet->skin, $packet->newSkinName, $packet->oldSkinName); } + public function handleBookEdit(BookEditPacket $packet) : bool{ + return $this->player->handleBookEdit($packet); + } + public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{ return false; //TODO: GUI stuff } diff --git a/src/pocketmine/network/mcpe/protocol/BookEditPacket.php b/src/pocketmine/network/mcpe/protocol/BookEditPacket.php index 042035798..cb9d312db 100644 --- a/src/pocketmine/network/mcpe/protocol/BookEditPacket.php +++ b/src/pocketmine/network/mcpe/protocol/BookEditPacket.php @@ -46,9 +46,9 @@ class BookEditPacket extends DataPacket{ public $secondaryPageNumber; /** @var string */ - public $content1; + public $text; /** @var string */ - public $content2; + public $photoName; /** @var string */ public $title; @@ -63,8 +63,8 @@ class BookEditPacket extends DataPacket{ case self::TYPE_REPLACE_PAGE: case self::TYPE_ADD_PAGE: $this->pageNumber = $this->getByte(); - $this->content1 = $this->getString(); - $this->content2 = $this->getString(); + $this->text = $this->getString(); + $this->photoName = $this->getString(); break; case self::TYPE_DELETE_PAGE: $this->pageNumber = $this->getByte(); @@ -90,8 +90,8 @@ class BookEditPacket extends DataPacket{ case self::TYPE_REPLACE_PAGE: case self::TYPE_ADD_PAGE: $this->putByte($this->pageNumber); - $this->putString($this->content1); - $this->putString($this->content2); + $this->putString($this->text); + $this->putString($this->photoName); break; case self::TYPE_DELETE_PAGE: $this->putByte($this->pageNumber);