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; 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{ public function writeStateToWorld() : void{
parent::writeStateToWorld(); parent::writeStateToWorld();

View File

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

View File

@ -31,4 +31,5 @@ final class BlockTypeTags{
public const SAND = self::PREFIX . "sand"; public const SAND = self::PREFIX . "sand";
public const POTTABLE_PLANTS = self::PREFIX . "pottable"; public const POTTABLE_PLANTS = self::PREFIX . "pottable";
public const FIRE = self::PREFIX . "fire"; 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\SignLikeRotation;
use pocketmine\block\utils\SignLikeRotationTrait; use pocketmine\block\utils\SignLikeRotationTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
@ -33,6 +34,7 @@ use pocketmine\world\BlockTransaction;
final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotation{ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotation{
use SignLikeRotationTrait; use SignLikeRotationTrait;
use StaticSupportTrait;
protected function getSupportingFace() : int{ protected function getSupportingFace() : int{
return Facing::UP; return Facing::UP;
@ -49,4 +51,11 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio
} }
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); 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\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
@ -45,7 +46,23 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing
if($player !== null){ if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing()); $this->facing = Facing::opposite($player->getHorizontalFacing());
} }
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); 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{ private static function registerWoodenBlocks() : void{
$planksBreakInfo = new Info(BreakInfo::axe(2.0, null, 15.0)); $planksBreakInfo = new Info(BreakInfo::axe(2.0, null, 15.0));
$signBreakInfo = new Info(BreakInfo::axe(1.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)); $logBreakInfo = new Info(BreakInfo::axe(2.0));
$woodenDoorBreakInfo = new Info(BreakInfo::axe(3.0, null, 15.0)); $woodenDoorBreakInfo = new Info(BreakInfo::axe(3.0, null, 15.0));
$woodenButtonBreakInfo = new Info(BreakInfo::axe(0.5)); $woodenButtonBreakInfo = new Info(BreakInfo::axe(0.5));
@ -1444,9 +1445,9 @@ final class VanillaBlocks{
WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...), WoodType::CHERRY => VanillaItems::CHERRY_HANGING_SIGN(...),
WoodType::PALE_OAK => VanillaItems::PALE_OAK_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_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", $signBreakInfo, $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", $signBreakInfo, $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\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Axis; use pocketmine\math\Axis;
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;
@ -39,25 +41,41 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{
return Facing::rotateY($this->facing, clockwise: true); return Facing::rotateY($this->facing, clockwise: true);
} }
protected function getSupportingFaceOptions() : array{ public function onNearbyBlockChange() : void{
//wall hanging signs can be supported from either end of the post //NOOP - disable default self-destruct behaviour
return [ }
Facing::rotateY($this->facing, clockwise: true),
Facing::rotateY($this->facing, clockwise: false) 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{ 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; 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 //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); $this->facing = Facing::opposite($this->facing);
} }
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); 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 pocketmine\world\World;
use function implode; use function implode;
use function mb_scrub; use function mb_scrub;
use function rtrim;
use function sprintf; use function sprintf;
/** /**
@ -106,7 +107,7 @@ class Sign extends Spawnable{
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, 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())) ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()))
->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1) ->setByte(self::TAG_PERSIST_FORMATTING, 1)
@ -151,7 +152,7 @@ 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, 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())) ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()))
->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1) //TODO: not sure what this is used for ->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; namespace pocketmine\item;
use pocketmine\block\Block; use pocketmine\block\Block;
use pocketmine\block\utils\SupportType;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class HangingSign extends Item{ final class HangingSign extends Item{
@ -40,14 +41,15 @@ final class HangingSign extends Item{
parent::__construct($identifier, $name); parent::__construct($identifier, $name);
} }
public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{
//we don't verify valid placement conditions here, only decide which block to return if($face !== Facing::DOWN){
$result = $face === Facing::DOWN ? return $this->tryPlacementTransaction(clone $this->wallVariant, $blockReplace, $blockClicked, $face, $clickVector, $player);
$blockReplace->getSide(Facing::UP)->getSupportType(Facing::DOWN) === SupportType::CENTER ? }
$this->centerPointCeilingVariant : //ceiling edges sign has stricter placement conditions than ceiling center sign, so try that first
$this->edgePointCeilingVariant $ceilingEdgeTx = $player === null || !$player->isSneaking() ?
: $this->wallVariant; $this->tryPlacementTransaction(clone $this->edgePointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player) :
return clone $result; null;
return $ceilingEdgeTx ?? $this->tryPlacementTransaction(clone $this->centerPointCeilingVariant, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }
public function getBlock(?int $clickedFace = null) : Block{ public function getBlock(?int $clickedFace = null) : Block{

View File

@ -48,6 +48,7 @@ use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\TreeRoot; use pocketmine\nbt\TreeRoot;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\io\GlobalItemDataHandlers; use pocketmine\world\format\io\GlobalItemDataHandlers;
use function base64_decode; use function base64_decode;
use function base64_encode; use function base64_encode;
@ -488,8 +489,18 @@ class Item implements \JsonSerializable{
return $this->getBlock()->canBePlaced(); return $this->getBlock()->canBePlaced();
} }
public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{ protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player) : ?BlockTransaction{
return $this->getBlock($face); $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()){ if($item->isNull() || !$item->canBePlaced()){
return false; 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)){ //TODO: while passing Facing::UP mimics the vanilla behaviour with replaceable blocks, we should really pass
$blockReplace = $blockClicked; //some other value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know
//TODO: while this mimics the vanilla behaviour with replaceable blocks, we should really pass some other //about the vanilla behaviour.
//value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know about $tx =
//the vanilla behaviour. $item->getPlacementTransaction($blockClicked, $blockClicked, Facing::UP, $clickVector, $player) ??
$face = Facing::UP; $item->getPlacementTransaction($blockReplace, $blockClicked, $face, $clickVector, $player);
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z); if($tx === null){
}elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){ //no placement options available
return false;
}
$tx = new BlockTransaction($this);
if(!$hand->place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player)){
return false; return false;
} }
@ -2338,6 +2331,7 @@ class World implements ChunkManager{
if(!$tx->apply()){ if(!$tx->apply()){
return false; return false;
} }
$first = true;
foreach($tx->getBlocks() as [$x, $y, $z, $_]){ foreach($tx->getBlocks() as [$x, $y, $z, $_]){
$tile = $this->getTileAt($x, $y, $z); $tile = $this->getTileAt($x, $y, $z);
if($tile !== null){ if($tile !== null){
@ -2345,11 +2339,12 @@ class World implements ChunkManager{
$tile->copyDataFromItem($item); $tile->copyDataFromItem($item);
} }
$this->getBlockAt($x, $y, $z)->onPostPlace(); $placed = $this->getBlockAt($x, $y, $z);
} $placed->onPostPlace();
if($first && $playSound){
if($playSound){ $this->addSound($placed->getPosition(), new BlockPlaceSound($placed));
$this->addSound($hand->getPosition(), new BlockPlaceSound($hand)); }
$first = false;
} }
$item->pop(); $item->pop();