Improved placement logic

This commit is contained in:
Dylan K. Taylor 2025-08-26 00:10:50 +01:00
parent db54c481aa
commit de234d1f38
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
3 changed files with 37 additions and 46 deletions

View File

@ -24,13 +24,10 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\Block;
use pocketmine\block\CeilingCenterHangingSign;
use pocketmine\block\CeilingEdgesHangingSign;
use pocketmine\block\utils\SupportType;
use pocketmine\block\WallHangingSign;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class HangingSign extends Item{
@ -44,27 +41,15 @@ final class HangingSign extends Item{
parent::__construct($identifier, $name);
}
public function getPlacementBlock(?Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{
//we don't verify valid placement conditions here, only decide which block to return
if($face === Facing::DOWN){
if($player !== null && $player->isSneaking()){
return clone $this->centerPointCeilingVariant;
}
//we select the center variant when support is edge/wall sign with perpendicular player facing,
//support is a center sign itself, or support provides center support.
//otherwise use the edge variant.
$support = $blockReplace->getSide(Facing::UP);
$result =
(($support instanceof CeilingEdgesHangingSign || $support instanceof WallHangingSign) && ($player === null || Facing::axis($player->getHorizontalFacing()) !== Facing::axis($support->getFacing()))) ||
$support instanceof CeilingCenterHangingSign ||
$support->getSupportType(Facing::DOWN) === SupportType::CENTER ?
$this->centerPointCeilingVariant :
$this->edgePointCeilingVariant;
}else{
$result = $this->wallVariant;
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);
}
return clone $result;
//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;
@ -485,8 +486,18 @@ class Item implements \JsonSerializable{
return $this->getBlock()->canBePlaced();
}
public function getPlacementBlock(?Player $player, 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

@ -2298,22 +2298,15 @@ class World implements ChunkManager{
if($item->isNull() || !$item->canBePlaced()){
return false;
}
$hand = $item->getPlacementBlock($player, $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;
}
@ -2357,6 +2350,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){
@ -2364,11 +2358,12 @@ class World implements ChunkManager{
$tile->copyDataFromItem($item);
}
$this->getBlockAt($x, $y, $z)->onPostPlace();
}
if($playSound){
$this->addSound($hand->getPosition(), new BlockPlaceSound($hand));
$placed = $this->getBlockAt($x, $y, $z);
$placed->onPostPlace();
if($first && $playSound){
$this->addSound($placed->getPosition(), new BlockPlaceSound($placed));
}
$first = false;
}
$item->pop();