mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 11:16:57 +00:00
Compare commits
20 Commits
5.33.1
...
minor-next
Author | SHA1 | Date | |
---|---|---|---|
9fcb16b6c1 | |||
ce90835c7b | |||
2f2c53067b | |||
dc04992ba9 | |||
bddab47ee8 | |||
1bc7cf340d | |||
3411103e11 | |||
26cd5c471c | |||
1868536916 | |||
3999a1f9f4 | |||
925b34e5c6 | |||
fa5cc3301c | |||
9a0a8a55b1 | |||
b2d0be5b75 | |||
d3c36e6287 | |||
e569cc3275 | |||
c931437a30 | |||
09cc76ae2b | |||
dca9d3a010 | |||
9310c46ea1 |
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.3
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
2
.github/workflows/draft-release-pr-check.yml
vendored
2
.github/workflows/draft-release-pr-check.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.3
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
2
.github/workflows/draft-release.yml
vendored
2
.github/workflows/draft-release.yml
vendored
@ -87,7 +87,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.3
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
|
||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ["8.1", "8.2", "8.3"]
|
||||
php: ["8.1", "8.2", "8.3", "8.4"]
|
||||
|
||||
uses: ./.github/workflows/main-php-matrix.yml
|
||||
with:
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.3
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
with:
|
||||
php-version: 8.3
|
||||
tools: php-cs-fixer:3.75
|
||||
|
Submodule build/php updated: ce1b095a9c...1d9cda6688
@ -4,6 +4,8 @@ includes:
|
||||
- tests/phpstan/configs/impossible-generics.neon
|
||||
- tests/phpstan/configs/php-bugs.neon
|
||||
- tests/phpstan/configs/phpstan-bugs.neon
|
||||
- tests/phpstan/configs/property-hook-sadness.neon
|
||||
- tests/phpstan/configs/reflection-class-sadness.neon
|
||||
- tests/phpstan/configs/spl-fixed-array-sucks.neon
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
- vendor/phpstan/phpstan-phpunit/rules.neon
|
||||
|
@ -31,8 +31,8 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.33.1";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BASE_VERSION = "5.33.2";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -247,6 +247,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
return $this->enderInventory;
|
||||
}
|
||||
|
||||
public function getSneakOffset() : float{
|
||||
return 0.31;
|
||||
}
|
||||
|
||||
/**
|
||||
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
|
||||
*/
|
||||
|
@ -242,6 +242,10 @@ abstract class Living extends Entity{
|
||||
$this->absorptionAttr->setValue($absorption);
|
||||
}
|
||||
|
||||
public function getSneakOffset() : float{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public function isSneaking() : bool{
|
||||
return $this->sneaking;
|
||||
}
|
||||
@ -292,7 +296,7 @@ abstract class Living extends Entity{
|
||||
$width = $size->getWidth();
|
||||
$this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale()));
|
||||
}elseif($this->isSneaking()){
|
||||
$this->setSize((new EntitySizeInfo(3 / 4 * $size->getHeight(), $size->getWidth(), 3 / 4 * $size->getEyeHeight()))->scale($this->getScale()));
|
||||
$this->setSize((new EntitySizeInfo($size->getHeight() - $this->getSneakOffset(), $size->getWidth(), $size->getEyeHeight() - $this->getSneakOffset()))->scale($this->getScale()));
|
||||
}else{
|
||||
$this->setSize($size->scale($this->getScale()));
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -30,13 +30,11 @@ use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function min;
|
||||
use function shuffle;
|
||||
use function spl_object_hash;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -95,10 +93,13 @@ class InventoryTransaction{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an **unordered** set of actions involved in this transaction.
|
||||
* Returns a set of actions involved in this transaction.
|
||||
*
|
||||
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
|
||||
* significance and should not be relied on.
|
||||
* Note: This system is designed to care only about item balances. While you can usually assume that the actions
|
||||
* are provided in the correct order, it will still successfully complete transactions whose actions are provided in
|
||||
* the "wrong" order, as long as the transaction balances.
|
||||
* For example, you may see that an action setting a slot to a particular item may appear before the action that
|
||||
* removes that item from its original slot. While unintuitive, this is still valid.
|
||||
*
|
||||
* @return InventoryAction[]
|
||||
* @phpstan-return array<int, InventoryAction>
|
||||
@ -119,19 +120,6 @@ class InventoryTransaction{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
|
||||
*/
|
||||
private function shuffleActions() : void{
|
||||
$keys = array_keys($this->actions);
|
||||
shuffle($keys);
|
||||
$actions = [];
|
||||
foreach($keys as $key){
|
||||
$actions[$key] = $this->actions[$key];
|
||||
}
|
||||
$this->actions = $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
@ -308,8 +296,6 @@ class InventoryTransaction{
|
||||
throw new TransactionValidationException("Transaction has already been executed");
|
||||
}
|
||||
|
||||
$this->shuffleActions();
|
||||
|
||||
$this->validate();
|
||||
|
||||
if(!$this->callExecuteEvent()){
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pocketmine\block\tile\Container;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\crafting\ExactRecipeIngredient;
|
||||
use pocketmine\crafting\MetaWildcardRecipeIngredient;
|
||||
@ -31,10 +32,16 @@ use pocketmine\crafting\TagWildcardRecipeIngredient;
|
||||
use pocketmine\data\bedrock\BedrockDataFiles;
|
||||
use pocketmine\data\bedrock\item\BlockItemIdMap;
|
||||
use pocketmine\data\bedrock\item\ItemTypeNames;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\NbtException;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
|
||||
@ -52,11 +59,13 @@ use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\world\format\io\GlobalBlockStateHandlers;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
use function get_class;
|
||||
use function hash;
|
||||
|
||||
class TypeConverter{
|
||||
use SingletonTrait;
|
||||
|
||||
private const PM_ID_TAG = "___Id___";
|
||||
private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___";
|
||||
|
||||
private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
|
||||
|
||||
@ -197,6 +206,85 @@ class TypeConverter{
|
||||
return new ExactRecipeIngredient($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips unnecessary block actor NBT from items that have it.
|
||||
* This tag can potentially be extremely large, and is not read by the client anyway.
|
||||
*/
|
||||
protected function stripBlockEntityNBT(CompoundTag $tag) : bool{
|
||||
if(($tag->getTag(Item::TAG_BLOCK_ENTITY_TAG)) !== null){
|
||||
//client doesn't use this tag, so it's fine to delete completely
|
||||
$tag->removeTag(Item::TAG_BLOCK_ENTITY_TAG);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips non-viewable data from shulker boxes and similar blocks
|
||||
* The lore for shulker boxes only requires knowing the type & count of items and possibly custom name
|
||||
* We don't need to, and should not allow, sending nested inventories across the network.
|
||||
*/
|
||||
protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{
|
||||
if(
|
||||
($blockEntityInventoryTag = $tag->getTag(Container::TAG_ITEMS)) !== null &&
|
||||
$blockEntityInventoryTag instanceof ListTag &&
|
||||
$blockEntityInventoryTag->getTagType() === NBT::TAG_Compound &&
|
||||
$blockEntityInventoryTag->count() > 0
|
||||
){
|
||||
$stripped = new ListTag();
|
||||
|
||||
/** @var CompoundTag $itemTag */
|
||||
foreach($blockEntityInventoryTag as $itemTag){
|
||||
try{
|
||||
$containedItem = Item::nbtDeserialize($itemTag);
|
||||
$customName = $containedItem->getCustomName();
|
||||
$containedItem->clearNamedTag();
|
||||
$containedItem->setCustomName($customName);
|
||||
$stripped->push($containedItem->nbtSerialize());
|
||||
}catch(SavedDataLoadingException){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$tag->setTag(Container::TAG_ITEMS, $stripped);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash of an item's server-side NBT.
|
||||
* This is baked into an item's network NBT to make sure the client doesn't try to stack items with the same network
|
||||
* NBT but different server-side NBT.
|
||||
*/
|
||||
protected function hashNBT(Tag $tag) : string{
|
||||
$encoded = (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
|
||||
return hash('sha256', $encoded, binary: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: HACK!
|
||||
* Creates a copy of an item's NBT with non-viewable data stripped.
|
||||
* This is a pretty yucky hack that's mainly needed because of inventories inside blockitems containing blockentity
|
||||
* data. There isn't really a good way to deal with this due to the way tiles currently require a position,
|
||||
* otherwise we could just keep a copy of the tile context and ask it for persistent vs network NBT as needed.
|
||||
* Unfortunately, making this nice will require significant BC breaks, so this will have to do for now.
|
||||
*/
|
||||
protected function cleanupUnnecessaryItemNBT(CompoundTag $original) : CompoundTag{
|
||||
$tag = clone $original;
|
||||
$anythingStripped = false;
|
||||
foreach([
|
||||
$this->stripContainedItemNonVisualNBT($tag),
|
||||
$this->stripBlockEntityNBT($tag)
|
||||
] as $stripped){
|
||||
$anythingStripped = $anythingStripped || $stripped;
|
||||
}
|
||||
|
||||
if($anythingStripped){
|
||||
$tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original));
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function coreItemStackToNet(Item $itemStack) : ItemStack{
|
||||
if($itemStack->isNull()){
|
||||
return ItemStack::null();
|
||||
@ -205,7 +293,7 @@ class TypeConverter{
|
||||
if($nbt->count() === 0){
|
||||
$nbt = null;
|
||||
}else{
|
||||
$nbt = clone $nbt;
|
||||
$nbt = $this->cleanupUnnecessaryItemNBT($nbt);
|
||||
}
|
||||
|
||||
$idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ final class AsyncGeneratorExecutor implements GeneratorExecutor{
|
||||
\Logger $logger,
|
||||
private readonly AsyncPool $workerPool,
|
||||
private readonly GeneratorExecutorSetupParameters $setupParameters,
|
||||
int $asyncContextId = null
|
||||
?int $asyncContextId = null
|
||||
){
|
||||
$this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor");
|
||||
|
||||
|
@ -1158,12 +1158,6 @@ parameters:
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
@ -1188,12 +1182,6 @@ parameters:
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
@ -1218,12 +1206,6 @@ parameters:
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
count: 2
|
||||
path: ../../../src/world/World.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
|
||||
identifier: return.type
|
||||
|
61
tests/phpstan/configs/property-hook-sadness.neon
Normal file
61
tests/phpstan/configs/property-hook-sadness.neon
Normal file
@ -0,0 +1,61 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$enderInventory because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Human.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$hungerManager because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Human.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$inventory because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Human.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$offHandInventory because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Human.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$xpManager because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Human.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$armorInventory because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Living.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$effectManager because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/entity/Living.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$craftingGrid because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/player/Player.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$cursorInventory because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/player/Player.php
|
||||
|
||||
-
|
||||
message: '#^Cannot unset property pocketmine\\world\\format\\io\\leveldb\\LevelDB\:\:\$db because it might have hooks in a subclass\.$#'
|
||||
identifier: unset.possiblyHookedProperty
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/leveldb/LevelDB.php
|
31
tests/phpstan/configs/reflection-class-sadness.neon
Normal file
31
tests/phpstan/configs/reflection-class-sadness.neon
Normal file
@ -0,0 +1,31 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @param for parameter \$class is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
|
||||
identifier: generics.callSiteVarianceRedundant
|
||||
count: 2
|
||||
path: ../../phpunit/event/HandlerListManagerTest.php
|
||||
|
||||
-
|
||||
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @param for parameter \$expect is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
|
||||
identifier: generics.callSiteVarianceRedundant
|
||||
count: 1
|
||||
path: ../../phpunit/event/HandlerListManagerTest.php
|
||||
|
||||
-
|
||||
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @return is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
|
||||
identifier: generics.callSiteVarianceRedundant
|
||||
count: 3
|
||||
path: ../../phpunit/event/HandlerListManagerTest.php
|
||||
|
||||
-
|
||||
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$isValidFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
|
||||
identifier: generics.callSiteVarianceRedundant
|
||||
count: 1
|
||||
path: ../../phpunit/event/HandlerListManagerTest.php
|
||||
|
||||
-
|
||||
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$resolveParentFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
|
||||
identifier: generics.callSiteVarianceRedundant
|
||||
count: 2
|
||||
path: ../../phpunit/event/HandlerListManagerTest.php
|
@ -35,12 +35,12 @@ class HandlerListManagerTest extends TestCase{
|
||||
|
||||
/**
|
||||
* @var \Closure
|
||||
* @phpstan-var \Closure(\ReflectionClass<Event>) : bool
|
||||
* @phpstan-var \Closure(\ReflectionClass<covariant Event>) : bool
|
||||
*/
|
||||
private $isValidFunc;
|
||||
/**
|
||||
* @var \Closure
|
||||
* @phpstan-var \Closure(\ReflectionClass<Event>) : ?\ReflectionClass<Event>
|
||||
* @phpstan-var \Closure(\ReflectionClass<covariant Event>) : ?\ReflectionClass<covariant Event>
|
||||
*/
|
||||
private $resolveParentFunc;
|
||||
|
||||
@ -53,7 +53,7 @@ class HandlerListManagerTest extends TestCase{
|
||||
|
||||
/**
|
||||
* @return \Generator|mixed[][]
|
||||
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, bool, string}, void, void>
|
||||
* @phpstan-return \Generator<int, array{\ReflectionClass<covariant Event>, bool, string}, void, void>
|
||||
*/
|
||||
public static function isValidClassProvider() : \Generator{
|
||||
yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"];
|
||||
@ -65,7 +65,7 @@ class HandlerListManagerTest extends TestCase{
|
||||
/**
|
||||
* @dataProvider isValidClassProvider
|
||||
*
|
||||
* @phpstan-param \ReflectionClass<Event> $class
|
||||
* @phpstan-param \ReflectionClass<covariant Event> $class
|
||||
*/
|
||||
public function testIsValidClass(\ReflectionClass $class, bool $isValid, string $reason) : void{
|
||||
self::assertSame($isValid, ($this->isValidFunc)($class), $reason);
|
||||
@ -73,7 +73,7 @@ class HandlerListManagerTest extends TestCase{
|
||||
|
||||
/**
|
||||
* @return \Generator|\ReflectionClass[][]
|
||||
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, \ReflectionClass<Event>|null}, void, void>
|
||||
* @phpstan-return \Generator<int, array{\ReflectionClass<covariant Event>, \ReflectionClass<covariant Event>|null}, void, void>
|
||||
*/
|
||||
public static function resolveParentClassProvider() : \Generator{
|
||||
yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)];
|
||||
@ -85,8 +85,8 @@ class HandlerListManagerTest extends TestCase{
|
||||
/**
|
||||
* @dataProvider resolveParentClassProvider
|
||||
*
|
||||
* @phpstan-param \ReflectionClass<Event> $class
|
||||
* @phpstan-param \ReflectionClass<Event>|null $expect
|
||||
* @phpstan-param \ReflectionClass<covariant Event> $class
|
||||
* @phpstan-param \ReflectionClass<covariant Event>|null $expect
|
||||
*/
|
||||
public function testResolveParentClass(\ReflectionClass $class, ?\ReflectionClass $expect) : void{
|
||||
if($expect === null){
|
||||
|
Reference in New Issue
Block a user