diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 521e3f83d..f48431571 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -92,21 +92,17 @@ use pocketmine\level\Level; use pocketmine\level\Position; use pocketmine\math\Vector3; use pocketmine\metadata\MetadataValue; -use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\network\BadPacketException; use pocketmine\network\mcpe\CompressBatchPromise; use pocketmine\network\mcpe\NetworkCipher; -use pocketmine\network\mcpe\NetworkNbtSerializer; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\ProcessLoginTask; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; -use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; @@ -134,7 +130,6 @@ use pocketmine\permission\PermissionAttachment; use pocketmine\permission\PermissionAttachmentInfo; use pocketmine\permission\PermissionManager; use pocketmine\plugin\Plugin; -use pocketmine\tile\Spawnable; use pocketmine\tile\Tile; use pocketmine\timings\Timings; use pocketmine\utils\TextFormat; @@ -2457,36 +2452,6 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, return $handled; } - /** - * @param BlockEntityDataPacket $packet - * - * @return bool - * @throws BadPacketException - */ - public function handleBlockEntityData(BlockEntityDataPacket $packet) : bool{ - $this->doCloseInventory(); - - $pos = new Vector3($packet->x, $packet->y, $packet->z); - if($pos->distanceSquared($this) > 10000){ - return true; - } - - $t = $this->level->getTile($pos); - if($t instanceof Spawnable){ - $nbt = new NetworkNbtSerializer(); - try{ - $compound = $nbt->read($packet->namedtag); - }catch(NbtDataException $e){ - throw new BadPacketException($e->getMessage(), 0, $e); - } - if(!$t->updateCompoundTag($compound, $this)){ - $this->level->sendBlocks([$this], [$pos]); - } - } - - return true; - } - public function handleBookEdit(BookEditPacket $packet) : bool{ /** @var WritableBook $oldBook */ $oldBook = $this->inventory->getItem($packet->inventorySlot); diff --git a/src/pocketmine/block/SignPost.php b/src/pocketmine/block/SignPost.php index 1470fea51..48748c6ed 100644 --- a/src/pocketmine/block/SignPost.php +++ b/src/pocketmine/block/SignPost.php @@ -23,11 +23,17 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\SignText; +use pocketmine\event\block\SignChangeEvent; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\Player; +use pocketmine\tile\Sign as TileSign; +use pocketmine\utils\TextFormat; +use function array_map; +use function assert; use function floor; class SignPost extends Transparent{ @@ -35,6 +41,14 @@ class SignPost extends Transparent{ /** @var int */ protected $rotation = 0; + /** @var SignText */ + protected $text; + + public function __construct(BlockIdentifier $idInfo, string $name){ + parent::__construct($idInfo, $name); + $this->text = new SignText(); + } + protected function writeStateToMeta() : int{ return $this->rotation; } @@ -43,6 +57,21 @@ class SignPost extends Transparent{ $this->rotation = $stateMeta; } + public function readStateFromWorld() : void{ + parent::readStateFromWorld(); + $tile = $this->level->getTile($this); + if($tile instanceof TileSign){ + $this->text = $tile->getText(); + } + } + + public function writeStateToWorld() : void{ + parent::writeStateToWorld(); + $tile = $this->level->getTile($this); + assert($tile instanceof TileSign); + $tile->setText($this->text); + } + public function getStateBitmask() : int{ return 0b1111; } @@ -82,4 +111,36 @@ class SignPost extends Transparent{ public function getToolType() : int{ return BlockToolType::TYPE_AXE; } + + /** + * Returns an object containing information about the sign text. + * + * @return SignText + */ + public function getText() : SignText{ + return $this->text; + } + + /** + * Called by the player controller (network session) to update the sign text, firing events as appropriate. + * + * @param Player $author + * @param SignText $text + * + * @return bool if the sign update was successful. + */ + public function updateText(Player $author, SignText $text) : bool{ + $removeFormat = $author->getRemoveFormat(); + $ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) use ($removeFormat){ + return TextFormat::clean($line, $removeFormat); + }, $text->getLines()))); + $ev->call(); + if(!$ev->isCancelled()){ + $this->text = clone $ev->getNewText(); + $this->level->setBlock($this, $this); + return true; + } + + return false; + } } diff --git a/src/pocketmine/event/block/SignChangeEvent.php b/src/pocketmine/event/block/SignChangeEvent.php index 267f00224..66af600e1 100644 --- a/src/pocketmine/event/block/SignChangeEvent.php +++ b/src/pocketmine/event/block/SignChangeEvent.php @@ -23,32 +23,44 @@ declare(strict_types=1); namespace pocketmine\event\block; -use pocketmine\block\Block; +use pocketmine\block\SignPost; +use pocketmine\block\utils\SignText; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; use pocketmine\Player; -use function count; /** - * Called when a sign is changed by a player. + * Called when a sign's text is changed by a player. */ class SignChangeEvent extends BlockEvent implements Cancellable{ use CancellableTrait; + /** @var SignPost */ + private $sign; + /** @var Player */ private $player; - /** @var string[] */ - private $lines = []; + + /** @var SignText */ + private $text; /** - * @param Block $theBlock - * @param Player $thePlayer - * @param string[] $theLines + * @param SignPost $sign + * @param Player $player + * @param SignText $text */ - public function __construct(Block $theBlock, Player $thePlayer, array $theLines){ - parent::__construct($theBlock); - $this->player = $thePlayer; - $this->setLines($theLines); + public function __construct(SignPost $sign, Player $player, SignText $text){ + parent::__construct($sign); + $this->sign = $sign; + $this->player = $player; + $this->text = $text; + } + + /** + * @return SignPost + */ + public function getSign() : SignPost{ + return $this->sign; } /** @@ -59,49 +71,29 @@ class SignChangeEvent extends BlockEvent implements Cancellable{ } /** - * @return string[] + * Returns the text currently on the sign. + * + * @return SignText */ - public function getLines() : array{ - return $this->lines; + public function getOldText() : SignText{ + return $this->sign->getText(); } /** - * @param int $index 0-3 + * Returns the text which will be on the sign after the event. * - * @return string - * - * @throws \InvalidArgumentException if the index is out of bounds + * @return SignText */ - public function getLine(int $index) : string{ - if($index < 0 or $index > 3){ - throw new \InvalidArgumentException("Index must be in the range 0-3!"); - } - - return $this->lines[$index]; + public function getNewText() : SignText{ + return $this->text; } /** - * @param string[] $lines + * Sets the text to be written on the sign after the event. * - * @throws \InvalidArgumentException if there are more or less than 4 lines in the passed array + * @param SignText $text */ - public function setLines(array $lines) : void{ - if(count($lines) !== 4){ - throw new \InvalidArgumentException("Array size must be 4!"); - } - $this->lines = $lines; - } - - /** - * @param int $index 0-3 - * @param string $line - * - * @throws \InvalidArgumentException if the index is out of bounds - */ - public function setLine(int $index, string $line) : void{ - if($index < 0 or $index > 3){ - throw new \InvalidArgumentException("Index must be in the range 0-3!"); - } - $this->lines[$index] = $line; + public function setNewText(SignText $text) : void{ + $this->text = $text; } } diff --git a/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php b/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php index 49476ed98..0b93502e1 100644 --- a/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php +++ b/src/pocketmine/network/mcpe/handler/SimpleSessionHandler.php @@ -24,12 +24,17 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\handler; use pocketmine\block\ItemFrame; +use pocketmine\block\SignPost; +use pocketmine\block\utils\SignText; use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\math\Vector3; +use pocketmine\nbt\NbtDataException; +use pocketmine\nbt\tag\StringTag; use pocketmine\network\BadPacketException; +use pocketmine\network\mcpe\NetworkNbtSerializer; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; @@ -72,6 +77,7 @@ use pocketmine\network\mcpe\protocol\types\ReleaseItemTransactionData; use pocketmine\network\mcpe\protocol\types\UseItemOnEntityTransactionData; use pocketmine\network\mcpe\protocol\types\UseItemTransactionData; use pocketmine\Player; +use function base64_encode; use function implode; use function json_decode; use function json_encode; @@ -385,7 +391,37 @@ class SimpleSessionHandler extends SessionHandler{ } public function handleBlockEntityData(BlockEntityDataPacket $packet) : bool{ - return $this->player->handleBlockEntityData($packet); + $pos = new Vector3($packet->x, $packet->y, $packet->z); + if($pos->distanceSquared($this->player) > 10000){ + return false; + } + + $block = $this->player->getLevel()->getBlock($pos); + try{ + $nbt = (new NetworkNbtSerializer())->read($packet->namedtag); + }catch(NbtDataException $e){ + throw new BadPacketException($e->getMessage(), 0, $e); + } + + if($block instanceof SignPost){ + if($nbt->hasTag("Text", StringTag::class)){ + try{ + $text = SignText::fromBlob($nbt->getString("Text")); + }catch(\InvalidArgumentException $e){ + throw new BadPacketException("Invalid sign text update: " . $e->getMessage(), 0, $e); + } + + if(!$block->updateText($this->player, $text)){ + $this->player->getLevel()->sendBlocks([$this->player], [$block]); + } + + return true; + } + + $this->player->getServer()->getLogger()->debug("Invalid sign update data from " . $this->player->getName() . ": " . base64_encode($packet->namedtag)); + } + + return false; } public function handlePlayerInput(PlayerInputPacket $packet) : bool{ diff --git a/src/pocketmine/tile/Sign.php b/src/pocketmine/tile/Sign.php index 83fa79fa7..14a034dc4 100644 --- a/src/pocketmine/tile/Sign.php +++ b/src/pocketmine/tile/Sign.php @@ -23,139 +23,78 @@ declare(strict_types=1); namespace pocketmine\tile; -use pocketmine\event\block\SignChangeEvent; +use pocketmine\block\SignPost; +use pocketmine\block\utils\SignText; +use pocketmine\level\Level; +use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\StringTag; -use pocketmine\Player; -use pocketmine\utils\TextFormat; -use function array_map; use function array_pad; use function array_slice; use function explode; use function implode; -use function mb_check_encoding; use function mb_scrub; use function sprintf; +/** + * @deprecated + * @see SignPost + */ class Sign extends Spawnable{ public const TAG_TEXT_BLOB = "Text"; public const TAG_TEXT_LINE = "Text%d"; //sprintf()able - private static function fixTextBlob(string $blob) : array{ + public static function fixTextBlob(string $blob) : array{ return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4); } - /** @var string[] */ - protected $text = ["", "", "", ""]; + /** @var SignText */ + protected $text; + + public function __construct(Level $level, Vector3 $pos){ + $this->text = new SignText(); + parent::__construct($level, $pos); + } public function readSaveData(CompoundTag $nbt) : void{ if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){ //MCPE 1.2 save format - $this->text = self::fixTextBlob($nbt->getString(self::TAG_TEXT_BLOB)); + $this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8')); }else{ - for($i = 1; $i <= 4; ++$i){ - $textKey = sprintf(self::TAG_TEXT_LINE, $i); + $this->text = new SignText(); + for($i = 0; $i < SignText::LINE_COUNT; ++$i){ + $textKey = sprintf(self::TAG_TEXT_LINE, $i + 1); if($nbt->hasTag($textKey, StringTag::class)){ - $this->text[$i - 1] = $nbt->getString($textKey); + $this->text->setLine($i, mb_scrub($nbt->getString($textKey), 'UTF-8')); } } } - $this->text = array_map(function(string $line) : string{ - return mb_scrub($line, 'UTF-8'); - }, $this->text); } protected function writeSaveData(CompoundTag $nbt) : void{ - $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text)); + $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())); - for($i = 1; $i <= 4; ++$i){ //Backwards-compatibility - $textKey = sprintf(self::TAG_TEXT_LINE, $i); - $nbt->setString($textKey, $this->getLine($i - 1)); + for($i = 0; $i < SignText::LINE_COUNT; ++$i){ //Backwards-compatibility + $textKey = sprintf(self::TAG_TEXT_LINE, $i + 1); + $nbt->setString($textKey, $this->text->getLine($i)); } } /** - * Changes contents of the specific lines to the string provided. - * Leaves contents of the specific lines as is if null is provided. - * - * @param null|string $line1 - * @param null|string $line2 - * @param null|string $line3 - * @param null|string $line4 + * @return SignText */ - public function setText(?string $line1 = "", ?string $line2 = "", ?string $line3 = "", ?string $line4 = "") : void{ - if($line1 !== null){ - $this->setLine(0, $line1); - } - if($line2 !== null){ - $this->setLine(1, $line2); - } - if($line3 !== null){ - $this->setLine(2, $line3); - } - if($line4 !== null){ - $this->setLine(3, $line4); - } - - $this->onChanged(); - } - - /** - * @param int $index 0-3 - * @param string $line - */ - public function setLine(int $index, string $line) : void{ - if($index < 0 or $index > 3){ - throw new \InvalidArgumentException("Index must be in the range 0-3!"); - } - if(!mb_check_encoding($line, 'UTF-8')){ - throw new \InvalidArgumentException("Text must be valid UTF-8"); - } - - $this->text[$index] = $line; - $this->onChanged(); - } - - /** - * @param int $index 0-3 - * - * @return string - */ - public function getLine(int $index) : string{ - if($index < 0 or $index > 3){ - throw new \InvalidArgumentException("Index must be in the range 0-3!"); - } - return $this->text[$index]; - } - - /** - * @return string[] - */ - public function getText() : array{ + public function getText() : SignText{ return $this->text; } - protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ - $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text)); + /** + * @param SignText $text + */ + public function setText(SignText $text) : void{ + $this->text = $text; + $this->onChanged(); } - public function updateCompoundTag(CompoundTag $nbt, Player $player) : bool{ - if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){ - $lines = self::fixTextBlob($nbt->getString(self::TAG_TEXT_BLOB)); - }else{ - return false; - } - - $removeFormat = $player->getRemoveFormat(); - - $ev = new SignChangeEvent($this->getBlock(), $player, array_map(function(string $line) use ($removeFormat){ return TextFormat::clean($line, $removeFormat); }, $lines)); - $ev->call(); - - if(!$ev->isCancelled()){ - $this->setText(...$ev->getLines()); - - return true; - }else{ - return false; - } + protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ + $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())); } } diff --git a/src/pocketmine/tile/Spawnable.php b/src/pocketmine/tile/Spawnable.php index 2e155acf6..0d707bce2 100644 --- a/src/pocketmine/tile/Spawnable.php +++ b/src/pocketmine/tile/Spawnable.php @@ -27,7 +27,6 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\NetworkNbtSerializer; -use pocketmine\Player; use function get_class; abstract class Spawnable extends Tile{ @@ -104,17 +103,4 @@ abstract class Spawnable extends Tile{ * @param CompoundTag $nbt */ abstract protected function addAdditionalSpawnData(CompoundTag $nbt) : void; - - /** - * Called when a player updates a block entity's NBT data - * for example when writing on a sign. - * - * @param CompoundTag $nbt - * @param Player $player - * - * @return bool indication of success, will respawn the tile to the player if false. - */ - public function updateCompoundTag(CompoundTag $nbt, Player $player) : bool{ - return false; - } }