Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17223908234
This commit is contained in:
pmmp-admin-bot[bot] 2025-08-26 00:03:10 +00:00
commit 24795eef0e
11 changed files with 107 additions and 64 deletions

View File

@ -61,7 +61,12 @@ abstract class BaseBanner extends Transparent implements Colored{
return $this;
}
abstract protected function getOminousVersion() : Block;
/**
* TODO: make this abstract in PM6 (BC break)
*/
protected function getOminousVersion() : Block{
return VanillaBlocks::AIR();
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();

View File

@ -103,27 +103,10 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
return SupportType::NONE;
}
/**
* @deprecated
*/
abstract protected function getSupportingFace() : int;
/**
* @return int[]
*/
protected function getSupportingFaceOptions() : array{
return [$this->getSupportingFace()];
}
public function onNearbyBlockChange() : void{
$foundSupport = false;
foreach($this->getSupportingFaceOptions() as $face){
if($this->getSide($face)->getTypeId() !== BlockTypeIds::AIR){
$foundSupport = true;
break;
}
}
if(!$foundSupport){
if($this->getSide($this->getSupportingFace())->getTypeId() === BlockTypeIds::AIR){
$this->position->getWorld()->useBreakOn($this->position);
}
}

View File

@ -31,4 +31,5 @@ final class BlockTypeTags{
public const SAND = self::PREFIX . "sand";
public const POTTABLE_PLANTS = self::PREFIX . "pottable";
public const FIRE = self::PREFIX . "fire";
public const HANGING_SIGN = self::PREFIX . "hanging_sign";
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\SignLikeRotation;
use pocketmine\block\utils\SignLikeRotationTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
@ -33,6 +34,7 @@ use pocketmine\world\BlockTransaction;
final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotation{
use SignLikeRotationTrait;
use StaticSupportTrait;
protected function getSupportingFace() : int{
return Facing::UP;
@ -49,4 +51,11 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::UP);
return
$supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() ||
$supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN);
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
@ -45,7 +46,23 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing
if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::UP);
return
$supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL ||
(($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing));
}
}

View File

@ -1391,6 +1391,7 @@ final class VanillaBlocks{
private static function registerWoodenBlocks() : void{
$planksBreakInfo = new Info(BreakInfo::axe(2.0, null, 15.0));
$signBreakInfo = new Info(BreakInfo::axe(1.0));
$hangingSignBreakInfo = new Info(BreakInfo::axe(1.0), [Tags::HANGING_SIGN]);
$logBreakInfo = new Info(BreakInfo::axe(2.0));
$woodenDoorBreakInfo = new Info(BreakInfo::axe(3.0, null, 15.0));
$woodenButtonBreakInfo = new Info(BreakInfo::axe(0.5));
@ -1444,9 +1445,9 @@ final class VanillaBlocks{
WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...),
WoodType::PALE_OAK => VanillaItems::PALE_OAK_HANGING_SIGN(...),
};
self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . "Center Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . "Edges Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $signBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
self::register($idName("ceiling_center_hanging_sign"), fn(BID $id) => new CeilingCenterHangingSign($id, $name . " Center Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
self::register($idName("ceiling_edges_hanging_sign"), fn(BID $id) => new CeilingEdgesHangingSign($id, $name . " Edges Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
self::register($idName("wall_hanging_sign"), fn(BID $id) => new WallHangingSign($id, $name . " Wall Hanging Sign", $hangingSignBreakInfo, $woodType, $hangingSignAsItem), TileHangingSign::class);
}
}

View File

@ -25,8 +25,10 @@ namespace pocketmine\block;
use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
@ -39,25 +41,41 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{
return Facing::rotateY($this->facing, clockwise: true);
}
protected function getSupportingFaceOptions() : array{
//wall hanging signs can be supported from either end of the post
return [
Facing::rotateY($this->facing, clockwise: true),
Facing::rotateY($this->facing, clockwise: false)
];
public function onNearbyBlockChange() : void{
//NOOP - disable default self-destruct behaviour
}
protected function recalculateCollisionBoxes() : array{
//only the cross bar is collidable
return [AxisAlignedBB::one()->trim(Facing::DOWN, 7 / 8)->squash(Facing::axis($this->facing), 3 / 4)];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(Facing::axis($face) === Axis::Y){
if($player === null){
return false;
}
$attachFace = Facing::axis($face) === Axis::Y ? Facing::rotateY($player->getHorizontalFacing(), clockwise: true) : $face;
if($this->canBeSupportedAt($blockReplace->getSide($attachFace), $attachFace)){
$direction = $attachFace;
}elseif($this->canBeSupportedAt($blockReplace->getSide($opposite = Facing::opposite($attachFace)), $opposite)){
$direction = $opposite;
}else{
return false;
}
$this->facing = Facing::rotateY($face, clockwise: true);
$this->facing = Facing::rotateY(Facing::opposite($direction), clockwise: true);
//the front should always face the player if possible
if($player !== null && $this->facing === $player->getHorizontalFacing()){
if($this->facing === $player->getHorizontalFacing()){
$this->facing = Facing::opposite($this->facing);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
private function canBeSupportedAt(Block $block, int $face) : bool{
return
($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) ||
$block->getSupportType(Facing::opposite($face)) === SupportType::FULL;
}
}

View File

@ -34,6 +34,7 @@ use pocketmine\utils\Binary;
use pocketmine\world\World;
use function implode;
use function mb_scrub;
use function rtrim;
use function sprintf;
/**
@ -106,7 +107,7 @@ class Sign extends Spawnable{
protected function writeSaveData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()))
->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)
@ -151,7 +152,7 @@ class Sign extends Spawnable{
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()))
->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

View File

@ -24,9 +24,10 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\Block;
use pocketmine\block\utils\SupportType;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class HangingSign extends Item{
@ -40,14 +41,15 @@ final class HangingSign extends Item{
parent::__construct($identifier, $name);
}
public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{
//we don't verify valid placement conditions here, only decide which block to return
$result = $face === Facing::DOWN ?
$blockReplace->getSide(Facing::UP)->getSupportType(Facing::DOWN) === SupportType::CENTER ?
$this->centerPointCeilingVariant :
$this->edgePointCeilingVariant
: $this->wallVariant;
return clone $result;
public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{
if($face !== Facing::DOWN){
return $this->tryPlacementTransaction(clone $this->wallVariant, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
//ceiling edges sign has stricter placement conditions than ceiling center sign, so try that first
$ceilingEdgeTx = $player === null || !$player->isSneaking() ?
$this->tryPlacementTransaction(clone $this->edgePointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player) :
null;
return $ceilingEdgeTx ?? $this->tryPlacementTransaction(clone $this->centerPointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function getBlock(?int $clickedFace = null) : Block{

View File

@ -48,6 +48,7 @@ use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\io\GlobalItemDataHandlers;
use function base64_decode;
use function base64_encode;
@ -488,8 +489,18 @@ class Item implements \JsonSerializable{
return $this->getBlock()->canBePlaced();
}
public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{
return $this->getBlock($face);
protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player) : ?BlockTransaction{
$position = $blockReplace->getPosition();
$blockPlace->position($position->getWorld(), $position->getFloorX(), $position->getFloorY(), $position->getFloorZ());
if(!$blockPlace->canBePlacedAt($blockReplace, $clickVector, $face, $blockReplace->getPosition()->equals($blockClicked->getPosition()))){
return null;
}
$transaction = new BlockTransaction($position->getWorld());
return $blockPlace->place($transaction, $this, $blockReplace, $blockClicked, $face, $clickVector, $player) ? $transaction : null;
}
public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{
return $this->tryPlacementTransaction($this->getBlock($face), $blockReplace, $blockClicked, $face, $clickVector, $player);
}
/**

View File

@ -2279,22 +2279,15 @@ class World implements ChunkManager{
if($item->isNull() || !$item->canBePlaced()){
return false;
}
$hand = $item->getPlacementBlock($blockReplace, $blockClicked, $face, $clickVector);
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z);
if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){
$blockReplace = $blockClicked;
//TODO: while this mimics the vanilla behaviour with replaceable blocks, we should really pass some other
//value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know about
//the vanilla behaviour.
$face = Facing::UP;
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z);
}elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){
return false;
}
$tx = new BlockTransaction($this);
if(!$hand->place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player)){
//TODO: while passing Facing::UP mimics the vanilla behaviour with replaceable blocks, we should really pass
//some other value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know
//about the vanilla behaviour.
$tx =
$item->getPlacementTransaction($blockClicked, $blockClicked, Facing::UP, $clickVector, $player) ??
$item->getPlacementTransaction($blockReplace, $blockClicked, $face, $clickVector, $player);
if($tx === null){
//no placement options available
return false;
}
@ -2338,6 +2331,7 @@ class World implements ChunkManager{
if(!$tx->apply()){
return false;
}
$first = true;
foreach($tx->getBlocks() as [$x, $y, $z, $_]){
$tile = $this->getTileAt($x, $y, $z);
if($tile !== null){
@ -2345,11 +2339,12 @@ class World implements ChunkManager{
$tile->copyDataFromItem($item);
}
$this->getBlockAt($x, $y, $z)->onPostPlace();
$placed = $this->getBlockAt($x, $y, $z);
$placed->onPostPlace();
if($first && $playSound){
$this->addSound($placed->getPosition(), new BlockPlaceSound($placed));
}
if($playSound){
$this->addSound($hand->getPosition(), new BlockPlaceSound($hand));
$first = false;
}
$item->pop();