mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-06 01:46:04 +00:00
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
This commit is contained in:
@ -41,14 +41,19 @@ use pocketmine\utils\TextFormat;
|
|||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
use pocketmine\world\sound\DyeUseSound;
|
use pocketmine\world\sound\DyeUseSound;
|
||||||
use pocketmine\world\sound\InkSacUseSound;
|
use pocketmine\world\sound\InkSacUseSound;
|
||||||
|
use function abs;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function assert;
|
use function assert;
|
||||||
|
use function atan2;
|
||||||
|
use function fmod;
|
||||||
|
use function rad2deg;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
|
|
||||||
abstract class BaseSign extends Transparent implements WoodMaterial{
|
abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||||
use WoodTypeTrait;
|
use WoodTypeTrait;
|
||||||
|
|
||||||
protected SignText $text;
|
protected SignText $text; //TODO: rename this (BC break)
|
||||||
|
protected SignText $backText;
|
||||||
private bool $waxed = false;
|
private bool $waxed = false;
|
||||||
|
|
||||||
protected ?int $editorEntityRuntimeId = null;
|
protected ?int $editorEntityRuntimeId = null;
|
||||||
@ -63,6 +68,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
$this->woodType = $woodType;
|
$this->woodType = $woodType;
|
||||||
parent::__construct($idInfo, $name, $typeInfo);
|
parent::__construct($idInfo, $name, $typeInfo);
|
||||||
$this->text = new SignText();
|
$this->text = new SignText();
|
||||||
|
$this->backText = new SignText();
|
||||||
$this->asItemCallback = $asItemCallback;
|
$this->asItemCallback = $asItemCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +77,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
$tile = $this->position->getWorld()->getTile($this->position);
|
$tile = $this->position->getWorld()->getTile($this->position);
|
||||||
if($tile instanceof TileSign){
|
if($tile instanceof TileSign){
|
||||||
$this->text = $tile->getText();
|
$this->text = $tile->getText();
|
||||||
|
$this->backText = $tile->getBackText();
|
||||||
$this->waxed = $tile->isWaxed();
|
$this->waxed = $tile->isWaxed();
|
||||||
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
|
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
|
||||||
}
|
}
|
||||||
@ -83,6 +90,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
$tile = $this->position->getWorld()->getTile($this->position);
|
$tile = $this->position->getWorld()->getTile($this->position);
|
||||||
assert($tile instanceof TileSign);
|
assert($tile instanceof TileSign);
|
||||||
$tile->setText($this->text);
|
$tile->setText($this->text);
|
||||||
|
$tile->setBackText($this->backText);
|
||||||
$tile->setWaxed($this->waxed);
|
$tile->setWaxed($this->waxed);
|
||||||
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
|
$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{
|
private function doSignChange(SignText $newText, Player $player, Item $item, bool $frontFace) : bool{
|
||||||
$ev = new SignChangeEvent($this, $player, $newText);
|
$ev = new SignChangeEvent($this, $player, $newText, $frontFace);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->text = $ev->getNewText();
|
$this->setFaceText($frontFace, $ev->getNewText());
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$this->position->getWorld()->setBlock($this->position, $this);
|
||||||
$item->pop();
|
$item->pop();
|
||||||
return true;
|
return true;
|
||||||
@ -140,8 +148,9 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function changeSignGlowingState(bool $glowing, Player $player, Item $item) : bool{
|
private function changeSignGlowingState(bool $glowing, Player $player, Item $item, bool $frontFace) : bool{
|
||||||
if($this->text->isGlowing() !== $glowing && $this->doSignChange(new SignText($this->text->getLines(), $this->text->getBaseColor(), $glowing), $player, $item)){
|
$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());
|
$this->position->getWorld()->addSound($this->position, new InkSacUseSound());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -168,6 +177,8 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$frontFace = $this->interactsFront($this->getHitboxCenter(), $player->getPosition(), $this->getFacingDegrees());
|
||||||
|
|
||||||
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
|
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
|
||||||
ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
|
ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
|
||||||
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
|
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
|
||||||
@ -176,40 +187,82 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
};
|
};
|
||||||
if($dyeColor !== null){
|
if($dyeColor !== null){
|
||||||
$color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
|
$color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
|
||||||
|
$text = $this->getFaceText($frontFace);
|
||||||
if(
|
if(
|
||||||
$color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
|
$color->toARGB() !== $text->getBaseColor()->toARGB() &&
|
||||||
$this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)
|
$this->doSignChange(new SignText($text->getLines(), $color, $text->isGlowing()), $player, $item, $frontFace)
|
||||||
){
|
){
|
||||||
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
|
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}elseif(match($item->getTypeId()){
|
}elseif(match($item->getTypeId()){
|
||||||
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item),
|
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item, $frontFace),
|
||||||
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item),
|
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item, $frontFace),
|
||||||
ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
|
ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
|
||||||
default => false
|
default => false
|
||||||
}){
|
}){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$player->openSignEditor($this->position);
|
$player->openSignEditor($this->position, $frontFace);
|
||||||
|
|
||||||
return true;
|
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.
|
* Returns an object containing information about the sign text.
|
||||||
|
* @deprecated
|
||||||
|
* @see self::getFaceText()
|
||||||
*/
|
*/
|
||||||
public function getText() : SignText{
|
public function getText() : SignText{
|
||||||
return $this->text;
|
return $this->text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return $this */
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @see self::setFaceText()
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
public function setText(SignText $text) : self{
|
public function setText(SignText $text) : self{
|
||||||
$this->text = $text;
|
$this->text = $text;
|
||||||
return $this;
|
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.
|
* 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;
|
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.
|
* Called by the player controller (network session) to update the sign text, firing events as appropriate.
|
||||||
*
|
*
|
||||||
* @return bool if the sign update was successful.
|
* @return bool if the sign update was successful.
|
||||||
* @throws \UnexpectedValueException if the text payload is too large
|
* @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;
|
$size = 0;
|
||||||
foreach($text->getLines() as $line){
|
foreach($text->getLines() as $line){
|
||||||
$size += strlen($line);
|
$size += strlen($line);
|
||||||
@ -248,15 +309,16 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
|||||||
if($size > 1000){
|
if($size > 1000){
|
||||||
throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 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{
|
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
|
||||||
return TextFormat::clean($line, false);
|
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()){
|
if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){
|
||||||
$ev->cancel();
|
$ev->cancel();
|
||||||
}
|
}
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->setText($ev->getNewText());
|
$this->setFaceText($frontFace, $ev->getNewText());
|
||||||
$this->setEditorEntityRuntimeId(null);
|
$this->setEditorEntityRuntimeId(null);
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$this->position->getWorld()->setBlock($this->position, $this);
|
||||||
return true;
|
return true;
|
||||||
|
@ -58,4 +58,8 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio
|
|||||||
$supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() ||
|
$supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() ||
|
||||||
$supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN);
|
$supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getFacingDegrees() : float{
|
||||||
|
return $this->rotation * 22.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ use pocketmine\item\Item;
|
|||||||
use pocketmine\math\Facing;
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
|
|
||||||
final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{
|
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->getSupportType(Facing::DOWN) === SupportType::FULL ||
|
||||||
(($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing));
|
(($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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,8 @@ final class FloorSign extends BaseSign implements SignLikeRotation{
|
|||||||
}
|
}
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getFacingDegrees() : float{
|
||||||
|
return $this->rotation * 22.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ use pocketmine\math\AxisAlignedBB;
|
|||||||
use pocketmine\math\Facing;
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
|
|
||||||
final class WallHangingSign extends BaseSign implements HorizontalFacing{
|
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 instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) ||
|
||||||
$block->getSupportType(Facing::opposite($face)) === SupportType::FULL;
|
$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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ use pocketmine\math\Axis;
|
|||||||
use pocketmine\math\Facing;
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
|
|
||||||
final class WallSign extends BaseSign implements HorizontalFacing{
|
final class WallSign extends BaseSign implements HorizontalFacing{
|
||||||
@ -46,4 +47,25 @@ final class WallSign extends BaseSign implements HorizontalFacing{
|
|||||||
$this->facing = $face;
|
$this->facing = $face;
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,16 +70,18 @@ class Sign extends Spawnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected SignText $text;
|
protected SignText $text;
|
||||||
|
protected SignText $backText;
|
||||||
private bool $waxed = false;
|
private bool $waxed = false;
|
||||||
|
|
||||||
protected ?int $editorEntityRuntimeId = null;
|
protected ?int $editorEntityRuntimeId = null;
|
||||||
|
|
||||||
public function __construct(World $world, Vector3 $pos){
|
public function __construct(World $world, Vector3 $pos){
|
||||||
$this->text = new SignText();
|
$this->text = new SignText();
|
||||||
|
$this->backText = new SignText();
|
||||||
parent::__construct($world, $pos);
|
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);
|
$baseColor = new Color(0, 0, 0);
|
||||||
$glowingText = false;
|
$glowingText = false;
|
||||||
if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){
|
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
|
//see https://bugs.mojang.com/browse/MCPE-117835
|
||||||
$glowingText = $glowingTextTag->getValue() !== 0;
|
$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{
|
public function readSaveData(CompoundTag $nbt) : void{
|
||||||
$frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT);
|
$frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT);
|
||||||
if($frontTextTag instanceof CompoundTag){
|
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
|
}elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format
|
||||||
$lightingBugResolved = false;
|
$lightingBugResolved = false;
|
||||||
if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){
|
if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){
|
||||||
$lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0;
|
$lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0;
|
||||||
}
|
}
|
||||||
$this->readTextTag($nbt, $lightingBugResolved);
|
$this->text = $this->readTextTag($nbt, $lightingBugResolved);
|
||||||
}else{
|
}else{
|
||||||
$text = [];
|
$text = [];
|
||||||
for($i = 0; $i < SignText::LINE_COUNT; ++$i){
|
for($i = 0; $i < SignText::LINE_COUNT; ++$i){
|
||||||
@ -113,22 +123,14 @@ class Sign extends Spawnable{
|
|||||||
}
|
}
|
||||||
$this->text = new SignText($text);
|
$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;
|
$this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function writeSaveData(CompoundTag $nbt) : void{
|
protected function writeSaveData(CompoundTag $nbt) : void{
|
||||||
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
|
$nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
|
||||||
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n"))
|
$nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
|
||||||
->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->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
|
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
|
||||||
}
|
}
|
||||||
@ -141,6 +143,10 @@ class Sign extends Spawnable{
|
|||||||
$this->text = $text;
|
$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 isWaxed() : bool{ return $this->waxed; }
|
||||||
|
|
||||||
public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
|
public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
|
||||||
@ -162,19 +168,8 @@ class Sign extends Spawnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
|
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
|
||||||
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
|
$nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
|
||||||
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n"))
|
$nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
|
||||||
->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->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
|
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
|
||||||
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
|
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,15 @@ use pocketmine\player\Player;
|
|||||||
class SignChangeEvent extends BlockEvent implements Cancellable{
|
class SignChangeEvent extends BlockEvent implements Cancellable{
|
||||||
use CancellableTrait;
|
use CancellableTrait;
|
||||||
|
|
||||||
|
private SignText $oldText;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private BaseSign $sign,
|
private BaseSign $sign,
|
||||||
private Player $player,
|
private Player $player,
|
||||||
private SignText $text
|
private SignText $text,
|
||||||
|
private bool $frontFace = true
|
||||||
){
|
){
|
||||||
|
$this->oldText = $this->sign->getFaceText($this->frontFace);
|
||||||
parent::__construct($sign);
|
parent::__construct($sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
|
|||||||
* Returns the text currently on the sign.
|
* Returns the text currently on the sign.
|
||||||
*/
|
*/
|
||||||
public function getOldText() : SignText{
|
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{
|
public function setNewText(SignText $text) : void{
|
||||||
$this->text = $text;
|
$this->text = $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isFrontFace() : bool{ return $this->frontFace; }
|
||||||
}
|
}
|
||||||
|
@ -756,6 +756,43 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
return true; //this packet is useless
|
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{
|
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
|
||||||
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
|
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
|
||||||
if($pos->distanceSquared($this->player->getLocation()) > 10000){
|
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(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
|
||||||
|
|
||||||
if($block instanceof BaseSign){
|
if($block instanceof BaseSign){
|
||||||
$frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT);
|
if(!$this->updateSignText($nbt, Sign::TAG_FRONT_TEXT, true, $block, $pos)){
|
||||||
if(!$frontTextTag instanceof CompoundTag){
|
//only one side can be updated at a time
|
||||||
throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data");
|
$this->updateSignText($nbt, Sign::TAG_BACK_TEXT, false, $block, $pos);
|
||||||
}
|
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -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.
|
* 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);
|
$block = $this->getWorld()->getBlock($position);
|
||||||
if($block instanceof BaseSign){
|
if($block instanceof BaseSign){
|
||||||
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
|
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
|
||||||
$this->getNetworkSession()->onOpenSignEditor($position, true);
|
$this->getNetworkSession()->onOpenSignEditor($position, $frontFace);
|
||||||
}else{
|
}else{
|
||||||
throw new \InvalidArgumentException("Block at this position is not a sign");
|
throw new \InvalidArgumentException("Block at this position is not a sign");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user