From b2d0be5b75050082988d5b106af799256673c784 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 1 Sep 2025 17:15:29 +0100 Subject: [PATCH] Support editing the back side of signs (#6774) * Deprecate BaseSign get/set/updateText(), add get/set/updateFaceText() which accepts true/false for front/back * add isFrontFace() to SignChangeEvent * add optional frontFace to Player::openSignEditor() * add BaseSign::getFacingDegrees() and getHitboxCenter() which need to be implemented by subclasses --- src/block/BaseSign.php | 92 ++++++++++++++++--- src/block/CeilingCenterHangingSign.php | 4 + src/block/CeilingEdgesHangingSign.php | 11 +++ src/block/FloorSign.php | 4 + src/block/WallHangingSign.php | 11 +++ src/block/WallSign.php | 22 +++++ src/block/tile/Sign.php | 53 +++++------ src/event/block/SignChangeEvent.php | 10 +- .../mcpe/handler/InGamePacketHandler.php | 63 ++++++++----- src/player/Player.php | 5 +- 10 files changed, 203 insertions(+), 72 deletions(-) diff --git a/src/block/BaseSign.php b/src/block/BaseSign.php index 0efaa603c..8c324918b 100644 --- a/src/block/BaseSign.php +++ b/src/block/BaseSign.php @@ -41,14 +41,19 @@ use pocketmine\utils\TextFormat; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\DyeUseSound; use pocketmine\world\sound\InkSacUseSound; +use function abs; use function array_map; use function assert; +use function atan2; +use function fmod; +use function rad2deg; use function strlen; abstract class BaseSign extends Transparent implements WoodMaterial{ use WoodTypeTrait; - protected SignText $text; + protected SignText $text; //TODO: rename this (BC break) + protected SignText $backText; private bool $waxed = false; protected ?int $editorEntityRuntimeId = null; @@ -63,6 +68,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $this->woodType = $woodType; parent::__construct($idInfo, $name, $typeInfo); $this->text = new SignText(); + $this->backText = new SignText(); $this->asItemCallback = $asItemCallback; } @@ -71,6 +77,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileSign){ $this->text = $tile->getText(); + $this->backText = $tile->getBackText(); $this->waxed = $tile->isWaxed(); $this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId(); } @@ -83,6 +90,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ $tile = $this->position->getWorld()->getTile($this->position); assert($tile instanceof TileSign); $tile->setText($this->text); + $tile->setBackText($this->backText); $tile->setWaxed($this->waxed); $tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId); } @@ -127,11 +135,11 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ } } - private function doSignChange(SignText $newText, Player $player, Item $item) : bool{ - $ev = new SignChangeEvent($this, $player, $newText); + private function doSignChange(SignText $newText, Player $player, Item $item, bool $frontFace) : bool{ + $ev = new SignChangeEvent($this, $player, $newText, $frontFace); $ev->call(); if(!$ev->isCancelled()){ - $this->text = $ev->getNewText(); + $this->setFaceText($frontFace, $ev->getNewText()); $this->position->getWorld()->setBlock($this->position, $this); $item->pop(); return true; @@ -140,8 +148,9 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return false; } - private function changeSignGlowingState(bool $glowing, Player $player, Item $item) : bool{ - if($this->text->isGlowing() !== $glowing && $this->doSignChange(new SignText($this->text->getLines(), $this->text->getBaseColor(), $glowing), $player, $item)){ + private function changeSignGlowingState(bool $glowing, Player $player, Item $item, bool $frontFace) : bool{ + $text = $this->getFaceText($frontFace); + if($text->isGlowing() !== $glowing && $this->doSignChange(new SignText($text->getLines(), $text->getBaseColor(), $glowing), $player, $item, $frontFace)){ $this->position->getWorld()->addSound($this->position, new InkSacUseSound()); return true; } @@ -168,6 +177,8 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return true; } + $frontFace = $this->interactsFront($this->getHitboxCenter(), $player->getPosition(), $this->getFacingDegrees()); + $dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){ ItemTypeIds::BONE_MEAL => DyeColor::WHITE, ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE, @@ -176,40 +187,82 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ }; if($dyeColor !== null){ $color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue(); + $text = $this->getFaceText($frontFace); if( - $color->toARGB() !== $this->text->getBaseColor()->toARGB() && - $this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item) + $color->toARGB() !== $text->getBaseColor()->toARGB() && + $this->doSignChange(new SignText($text->getLines(), $color, $text->isGlowing()), $player, $item, $frontFace) ){ $this->position->getWorld()->addSound($this->position, new DyeUseSound()); return true; } }elseif(match($item->getTypeId()){ - ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item), - ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item), + ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item, $frontFace), + ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item, $frontFace), ItemTypeIds::HONEYCOMB => $this->wax($player, $item), default => false }){ return true; } - $player->openSignEditor($this->position); + $player->openSignEditor($this->position, $frontFace); return true; } + private function interactsFront(Vector3 $hitboxCenter, Vector3 $playerPosition, float $signFacingDegrees) : bool{ + $playerCenterDiffX = $playerPosition->x - $hitboxCenter->x; + $playerCenterDiffZ = $playerPosition->z - $hitboxCenter->z; + + $f1 = rad2deg(atan2($playerCenterDiffZ, $playerCenterDiffX)) - 90.0; + + $rotationDiff = $signFacingDegrees - $f1; + $rotation = fmod($rotationDiff + 180.0, 360.0) - 180.0; // Normalize to [-180, 180] + return abs($rotation) <= 90.0; + } + + /** + * Returns the center of the sign's hitbox. Used to decide which face of the sign to open when a player interacts. + */ + protected function getHitboxCenter() : Vector3{ + return $this->position->add(0.5, 0.5, 0.5); + } + + /** + * TODO: make this abstract (BC break) + */ + protected function getFacingDegrees() : float{ + return 0; + } + /** * Returns an object containing information about the sign text. + * @deprecated + * @see self::getFaceText() */ public function getText() : SignText{ return $this->text; } - /** @return $this */ + /** + * @deprecated + * @see self::setFaceText() + * @return $this + */ public function setText(SignText $text) : self{ $this->text = $text; return $this; } + public function getFaceText(bool $frontFace) : SignText{ + return $frontFace ? $this->text : $this->backText; + } + + /** @return $this */ + public function setFaceText(bool $frontFace, SignText $text) : self{ + $frontFace ? $this->text = $text : $this->backText = $text; + return $this; + } + /** * Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player. */ @@ -234,13 +287,21 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ return $this; } + /** + * @deprecated + * @see self::updateFaceText() + */ + public function updateText(Player $author, SignText $text) : bool{ + return $this->updateFaceText($author, true, $text); + } + /** * Called by the player controller (network session) to update the sign text, firing events as appropriate. * * @return bool if the sign update was successful. * @throws \UnexpectedValueException if the text payload is too large */ - public function updateText(Player $author, SignText $text) : bool{ + public function updateFaceText(Player $author, bool $frontFace, SignText $text) : bool{ $size = 0; foreach($text->getLines() as $line){ $size += strlen($line); @@ -248,15 +309,16 @@ abstract class BaseSign extends Transparent implements WoodMaterial{ if($size > 1000){ throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 1000)"); } + $oldText = $this->getFaceText($frontFace); $ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{ return TextFormat::clean($line, false); - }, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing())); + }, $text->getLines()), $oldText->getBaseColor(), $oldText->isGlowing()), $frontFace); if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){ $ev->cancel(); } $ev->call(); if(!$ev->isCancelled()){ - $this->setText($ev->getNewText()); + $this->setFaceText($frontFace, $ev->getNewText()); $this->setEditorEntityRuntimeId(null); $this->position->getWorld()->setBlock($this->position, $this); return true; diff --git a/src/block/CeilingCenterHangingSign.php b/src/block/CeilingCenterHangingSign.php index 1125de553..df6b4e229 100644 --- a/src/block/CeilingCenterHangingSign.php +++ b/src/block/CeilingCenterHangingSign.php @@ -58,4 +58,8 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio $supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() || $supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN); } + + protected function getFacingDegrees() : float{ + return $this->rotation * 22.5; + } } diff --git a/src/block/CeilingEdgesHangingSign.php b/src/block/CeilingEdgesHangingSign.php index 3f7b6489b..503915fa0 100644 --- a/src/block/CeilingEdgesHangingSign.php +++ b/src/block/CeilingEdgesHangingSign.php @@ -30,6 +30,7 @@ use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{ @@ -65,4 +66,14 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || (($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing)); } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/FloorSign.php b/src/block/FloorSign.php index 94e51ffe8..08bc480d8 100644 --- a/src/block/FloorSign.php +++ b/src/block/FloorSign.php @@ -48,4 +48,8 @@ final class FloorSign extends BaseSign implements SignLikeRotation{ } return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + protected function getFacingDegrees() : float{ + return $this->rotation * 22.5; + } } diff --git a/src/block/WallHangingSign.php b/src/block/WallHangingSign.php index df959c720..6d4cfb95e 100644 --- a/src/block/WallHangingSign.php +++ b/src/block/WallHangingSign.php @@ -32,6 +32,7 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class WallHangingSign extends BaseSign implements HorizontalFacing{ @@ -78,4 +79,14 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{ ($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) || $block->getSupportType(Facing::opposite($face)) === SupportType::FULL; } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/WallSign.php b/src/block/WallSign.php index c6b42608d..40e1ba458 100644 --- a/src/block/WallSign.php +++ b/src/block/WallSign.php @@ -30,6 +30,7 @@ use pocketmine\math\Axis; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use pocketmine\world\BlockTransaction; final class WallSign extends BaseSign implements HorizontalFacing{ @@ -46,4 +47,25 @@ final class WallSign extends BaseSign implements HorizontalFacing{ $this->facing = $face; return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } + + protected function getHitboxCenter() : Vector3{ + [$xOffset, $zOffset] = match($this->facing){ + Facing::NORTH => [0, 15 / 16], + Facing::SOUTH => [0, 1 / 16], + Facing::WEST => [15 / 16, 0], + Facing::EAST => [1 / 16, 0], + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + return $this->position->add($xOffset, 0.5, $zOffset); + } + + protected function getFacingDegrees() : float{ + return match($this->facing){ + Facing::SOUTH => 0, + Facing::WEST => 90, + Facing::NORTH => 180, + Facing::EAST => 270, + default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing), + }; + } } diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index aef83e3cc..3040e74c0 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -70,16 +70,18 @@ class Sign extends Spawnable{ } protected SignText $text; + protected SignText $backText; private bool $waxed = false; protected ?int $editorEntityRuntimeId = null; public function __construct(World $world, Vector3 $pos){ $this->text = new SignText(); + $this->backText = new SignText(); parent::__construct($world, $pos); } - private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{ + private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : SignText{ $baseColor = new Color(0, 0, 0); $glowingText = false; if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){ @@ -90,19 +92,27 @@ class Sign extends Spawnable{ //see https://bugs.mojang.com/browse/MCPE-117835 $glowingText = $glowingTextTag->getValue() !== 0; } - $this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); + return SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); + } + + private function writeTextTag(SignText $text) : CompoundTag{ + return CompoundTag::create() + ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $text->getLines()), "\n")) + ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($text->getBaseColor()->toARGB())) + ->setByte(self::TAG_GLOWING_TEXT, $text->isGlowing() ? 1 : 0) + ->setByte(self::TAG_PERSIST_FORMATTING, 1); } public function readSaveData(CompoundTag $nbt) : void{ $frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT); if($frontTextTag instanceof CompoundTag){ - $this->readTextTag($frontTextTag, true); + $this->text = $this->readTextTag($frontTextTag, true); }elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format $lightingBugResolved = false; if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){ $lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0; } - $this->readTextTag($nbt, $lightingBugResolved); + $this->text = $this->readTextTag($nbt, $lightingBugResolved); }else{ $text = []; for($i = 0; $i < SignText::LINE_COUNT; ++$i){ @@ -113,22 +123,14 @@ class Sign extends Spawnable{ } $this->text = new SignText($text); } + $backTextTag = $nbt->getTag(self::TAG_BACK_TEXT); + $this->backText = $backTextTag instanceof CompoundTag ? $this->readTextTag($backTextTag, true) : new SignText(); $this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0; } protected function writeSaveData(CompoundTag $nbt) : void{ - $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) - ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); - $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, "") - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)) - ->setByte(self::TAG_GLOWING_TEXT, 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); + $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text)); + $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText)); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0); } @@ -141,6 +143,10 @@ class Sign extends Spawnable{ $this->text = $text; } + public function getBackText() : SignText{ return $this->backText; } + + public function setBackText(SignText $backText) : void{ $this->backText = $backText; } + public function isWaxed() : bool{ return $this->waxed; } public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; } @@ -162,19 +168,8 @@ class Sign extends Spawnable{ } protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ - $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) - ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) //TODO: not sure what this is used for - ); - //TODO: this is not yet used by the server, but needed to rollback any client-side changes to the back text - $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create() - ->setString(self::TAG_TEXT_BLOB, "") - ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)) - ->setByte(self::TAG_GLOWING_TEXT, 0) - ->setByte(self::TAG_PERSIST_FORMATTING, 1) - ); + $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text)); + $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText)); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0); $nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1); } diff --git a/src/event/block/SignChangeEvent.php b/src/event/block/SignChangeEvent.php index aed59a462..e337ebc36 100644 --- a/src/event/block/SignChangeEvent.php +++ b/src/event/block/SignChangeEvent.php @@ -35,11 +35,15 @@ use pocketmine\player\Player; class SignChangeEvent extends BlockEvent implements Cancellable{ use CancellableTrait; + private SignText $oldText; + public function __construct( private BaseSign $sign, private Player $player, - private SignText $text + private SignText $text, + private bool $frontFace = true ){ + $this->oldText = $this->sign->getFaceText($this->frontFace); parent::__construct($sign); } @@ -55,7 +59,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{ * Returns the text currently on the sign. */ public function getOldText() : SignText{ - return $this->sign->getText(); + return $this->oldText; } /** @@ -71,4 +75,6 @@ class SignChangeEvent extends BlockEvent implements Cancellable{ public function setNewText(SignText $text) : void{ $this->text = $text; } + + public function isFrontFace() : bool{ return $this->frontFace; } } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6aae60745..dc8c3a0ba 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -756,6 +756,43 @@ class InGamePacketHandler extends PacketHandler{ return true; //this packet is useless } + /** + * @throws PacketHandlingException + */ + private function updateSignText(CompoundTag $nbt, string $tagName, bool $frontFace, BaseSign $block, Vector3 $pos) : bool{ + $textTag = $nbt->getTag($tagName); + if(!$textTag instanceof CompoundTag){ + throw new PacketHandlingException("Invalid tag type " . get_debug_type($textTag) . " for tag \"$tagName\" in sign update data"); + } + $textBlobTag = $textTag->getTag(Sign::TAG_TEXT_BLOB); + if(!$textBlobTag instanceof StringTag){ + throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data"); + } + + try{ + $text = SignText::fromBlob($textBlobTag->getValue()); + }catch(\InvalidArgumentException $e){ + throw PacketHandlingException::wrap($e, "Invalid sign text update"); + } + + $oldText = $block->getFaceText($frontFace); + if($text->getLines() === $oldText->getLines()){ + return false; + } + + try{ + if(!$block->updateFaceText($this->player, $frontFace, $text)){ + foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){ + $this->session->sendDataPacket($updatePacket); + } + return false; + } + return true; + }catch(\UnexpectedValueException $e){ + throw PacketHandlingException::wrap($e); + } + } + public function handleBlockActorData(BlockActorDataPacket $packet) : bool{ $pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()); if($pos->distanceSquared($this->player->getLocation()) > 10000){ @@ -767,29 +804,9 @@ class InGamePacketHandler extends PacketHandler{ if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit if($block instanceof BaseSign){ - $frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT); - if(!$frontTextTag instanceof CompoundTag){ - throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data"); - } - $textBlobTag = $frontTextTag->getTag(Sign::TAG_TEXT_BLOB); - if(!$textBlobTag instanceof StringTag){ - throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data"); - } - - try{ - $text = SignText::fromBlob($textBlobTag->getValue()); - }catch(\InvalidArgumentException $e){ - throw PacketHandlingException::wrap($e, "Invalid sign text update"); - } - - try{ - if(!$block->updateText($this->player, $text)){ - foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){ - $this->session->sendDataPacket($updatePacket); - } - } - }catch(\UnexpectedValueException $e){ - throw PacketHandlingException::wrap($e); + if(!$this->updateSignText($nbt, Sign::TAG_FRONT_TEXT, true, $block, $pos)){ + //only one side can be updated at a time + $this->updateSignText($nbt, Sign::TAG_BACK_TEXT, false, $block, $pos); } return true; diff --git a/src/player/Player.php b/src/player/Player.php index aa2d2af88..4468d929e 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2838,13 +2838,12 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ /** * Opens the player's sign editor GUI for the sign at the given position. - * TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations) */ - public function openSignEditor(Vector3 $position) : void{ + public function openSignEditor(Vector3 $position, bool $frontFace = true) : void{ $block = $this->getWorld()->getBlock($position); if($block instanceof BaseSign){ $this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId())); - $this->getNetworkSession()->onOpenSignEditor($position, true); + $this->getNetworkSession()->onOpenSignEditor($position, $frontFace); }else{ throw new \InvalidArgumentException("Block at this position is not a sign"); }