Revamp Sign API, flatten API into blocks

This commit is contained in:
Dylan K. Taylor 2019-02-28 17:10:37 +00:00
parent f680a239f7
commit 5cca4b5e31
6 changed files with 170 additions and 191 deletions

View File

@ -92,21 +92,17 @@ use pocketmine\level\Level;
use pocketmine\level\Position; use pocketmine\level\Position;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\metadata\MetadataValue; use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\ListTag;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\CompressBatchPromise; use pocketmine\network\mcpe\CompressBatchPromise;
use pocketmine\network\mcpe\NetworkCipher; use pocketmine\network\mcpe\NetworkCipher;
use pocketmine\network\mcpe\NetworkNbtSerializer;
use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\ProcessLoginTask; use pocketmine\network\mcpe\ProcessLoginTask;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\BookEditPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket;
@ -134,7 +130,6 @@ use pocketmine\permission\PermissionAttachment;
use pocketmine\permission\PermissionAttachmentInfo; use pocketmine\permission\PermissionAttachmentInfo;
use pocketmine\permission\PermissionManager; use pocketmine\permission\PermissionManager;
use pocketmine\plugin\Plugin; use pocketmine\plugin\Plugin;
use pocketmine\tile\Spawnable;
use pocketmine\tile\Tile; use pocketmine\tile\Tile;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
@ -2457,36 +2452,6 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
return $handled; 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{ public function handleBookEdit(BookEditPacket $packet) : bool{
/** @var WritableBook $oldBook */ /** @var WritableBook $oldBook */
$oldBook = $this->inventory->getItem($packet->inventorySlot); $oldBook = $this->inventory->getItem($packet->inventorySlot);

View File

@ -23,11 +23,17 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\SignText;
use pocketmine\event\block\SignChangeEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\Player; use pocketmine\Player;
use pocketmine\tile\Sign as TileSign;
use pocketmine\utils\TextFormat;
use function array_map;
use function assert;
use function floor; use function floor;
class SignPost extends Transparent{ class SignPost extends Transparent{
@ -35,6 +41,14 @@ class SignPost extends Transparent{
/** @var int */ /** @var int */
protected $rotation = 0; 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{ protected function writeStateToMeta() : int{
return $this->rotation; return $this->rotation;
} }
@ -43,6 +57,21 @@ class SignPost extends Transparent{
$this->rotation = $stateMeta; $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{ public function getStateBitmask() : int{
return 0b1111; return 0b1111;
} }
@ -82,4 +111,36 @@ class SignPost extends Transparent{
public function getToolType() : int{ public function getToolType() : int{
return BlockToolType::TYPE_AXE; 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;
}
} }

View File

@ -23,32 +23,44 @@ declare(strict_types=1);
namespace pocketmine\event\block; namespace pocketmine\event\block;
use pocketmine\block\Block; use pocketmine\block\SignPost;
use pocketmine\block\utils\SignText;
use pocketmine\event\Cancellable; use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait; use pocketmine\event\CancellableTrait;
use pocketmine\Player; 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{ class SignChangeEvent extends BlockEvent implements Cancellable{
use CancellableTrait; use CancellableTrait;
/** @var SignPost */
private $sign;
/** @var Player */ /** @var Player */
private $player; private $player;
/** @var string[] */
private $lines = []; /** @var SignText */
private $text;
/** /**
* @param Block $theBlock * @param SignPost $sign
* @param Player $thePlayer * @param Player $player
* @param string[] $theLines * @param SignText $text
*/ */
public function __construct(Block $theBlock, Player $thePlayer, array $theLines){ public function __construct(SignPost $sign, Player $player, SignText $text){
parent::__construct($theBlock); parent::__construct($sign);
$this->player = $thePlayer; $this->sign = $sign;
$this->setLines($theLines); $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{ public function getOldText() : SignText{
return $this->lines; return $this->sign->getText();
} }
/** /**
* @param int $index 0-3 * Returns the text which will be on the sign after the event.
* *
* @return string * @return SignText
*
* @throws \InvalidArgumentException if the index is out of bounds
*/ */
public function getLine(int $index) : string{ public function getNewText() : SignText{
if($index < 0 or $index > 3){ return $this->text;
throw new \InvalidArgumentException("Index must be in the range 0-3!");
}
return $this->lines[$index];
} }
/** /**
* @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{ public function setNewText(SignText $text) : void{
if(count($lines) !== 4){ $this->text = $text;
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;
} }
} }

View File

@ -24,12 +24,17 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler; namespace pocketmine\network\mcpe\handler;
use pocketmine\block\ItemFrame; use pocketmine\block\ItemFrame;
use pocketmine\block\SignPost;
use pocketmine\block\utils\SignText;
use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\BadPacketException; use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; 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\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\UseItemTransactionData; use pocketmine\network\mcpe\protocol\types\UseItemTransactionData;
use pocketmine\Player; use pocketmine\Player;
use function base64_encode;
use function implode; use function implode;
use function json_decode; use function json_decode;
use function json_encode; use function json_encode;
@ -385,7 +391,37 @@ class SimpleSessionHandler extends SessionHandler{
} }
public function handleBlockEntityData(BlockEntityDataPacket $packet) : bool{ 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{ public function handlePlayerInput(PlayerInputPacket $packet) : bool{

View File

@ -23,139 +23,78 @@ declare(strict_types=1);
namespace pocketmine\tile; 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\CompoundTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\Player;
use pocketmine\utils\TextFormat;
use function array_map;
use function array_pad; use function array_pad;
use function array_slice; use function array_slice;
use function explode; use function explode;
use function implode; use function implode;
use function mb_check_encoding;
use function mb_scrub; use function mb_scrub;
use function sprintf; use function sprintf;
/**
* @deprecated
* @see SignPost
*/
class Sign extends Spawnable{ class Sign extends Spawnable{
public const TAG_TEXT_BLOB = "Text"; public const TAG_TEXT_BLOB = "Text";
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able 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); return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
} }
/** @var string[] */ /** @var SignText */
protected $text = ["", "", "", ""]; protected $text;
public function __construct(Level $level, Vector3 $pos){
$this->text = new SignText();
parent::__construct($level, $pos);
}
public function readSaveData(CompoundTag $nbt) : void{ public function readSaveData(CompoundTag $nbt) : void{
if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){ //MCPE 1.2 save format 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{ }else{
for($i = 1; $i <= 4; ++$i){ $this->text = new SignText();
$textKey = sprintf(self::TAG_TEXT_LINE, $i); for($i = 0; $i < SignText::LINE_COUNT; ++$i){
$textKey = sprintf(self::TAG_TEXT_LINE, $i + 1);
if($nbt->hasTag($textKey, StringTag::class)){ 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{ 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 for($i = 0; $i < SignText::LINE_COUNT; ++$i){ //Backwards-compatibility
$textKey = sprintf(self::TAG_TEXT_LINE, $i); $textKey = sprintf(self::TAG_TEXT_LINE, $i + 1);
$nbt->setString($textKey, $this->getLine($i - 1)); $nbt->setString($textKey, $this->text->getLine($i));
} }
} }
/** /**
* Changes contents of the specific lines to the string provided. * @return SignText
* 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
*/ */
public function setText(?string $line1 = "", ?string $line2 = "", ?string $line3 = "", ?string $line4 = "") : void{ public function getText() : SignText{
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{
return $this->text; 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{ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){ $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
$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;
}
} }
} }

View File

@ -27,7 +27,6 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\NetworkNbtSerializer; use pocketmine\network\mcpe\NetworkNbtSerializer;
use pocketmine\Player;
use function get_class; use function get_class;
abstract class Spawnable extends Tile{ abstract class Spawnable extends Tile{
@ -104,17 +103,4 @@ abstract class Spawnable extends Tile{
* @param CompoundTag $nbt * @param CompoundTag $nbt
*/ */
abstract protected function addAdditionalSpawnData(CompoundTag $nbt) : void; 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;
}
} }