motive = $nbt->getString("Motive"); $this->blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ")); if($nbt->hasTag("Direction", ByteTag::class)){ $this->direction = $nbt->getByte("Direction"); }elseif($nbt->hasTag("Facing", ByteTag::class)){ $this->direction = $nbt->getByte("Facing"); } parent::__construct($level, $nbt); } protected function initEntity() : void{ $this->setMaxHealth(1); $this->setHealth(1); parent::initEntity(); } public function saveNBT() : void{ parent::saveNBT(); $this->namedtag->setInt("TileX", (int) $this->blockIn->x); $this->namedtag->setInt("TileY", (int) $this->blockIn->y); $this->namedtag->setInt("TileZ", (int) $this->blockIn->z); $this->namedtag->setByte("Facing", (int) $this->direction); $this->namedtag->setByte("Direction", (int) $this->direction); //Save both for full compatibility $this->namedtag->setString("Motive", $this->motive); } public function kill() : void{ if(!$this->isAlive()){ return; } parent::kill(); $drops = true; if($this->lastDamageCause instanceof EntityDamageByEntityEvent){ $killer = $this->lastDamageCause->getDamager(); if($killer instanceof Player and $killer->isCreative()){ $drops = false; } } if($drops){ //non-living entities don't have a way to create drops generically yet $this->level->dropItem($this, ItemFactory::get(Item::PAINTING)); } $this->level->addParticle(new DestroyBlockParticle($this->add(0.5, 0.5, 0.5), BlockFactory::get(Block::PLANKS))); } protected function recalculateBoundingBox() : void{ static $directions = [ 0 => Vector3::SIDE_SOUTH, 1 => Vector3::SIDE_WEST, 2 => Vector3::SIDE_NORTH, 3 => Vector3::SIDE_EAST ]; $facing = $directions[$this->direction]; $this->boundingBox->setBB(self::getPaintingBB($this->blockIn->getSide($facing), $facing, $this->getMotive())); } public function onNearbyBlockChange() : void{ parent::onNearbyBlockChange(); static $directions = [ 0 => Vector3::SIDE_SOUTH, 1 => Vector3::SIDE_WEST, 2 => Vector3::SIDE_NORTH, 3 => Vector3::SIDE_EAST ]; $face = $directions[$this->direction]; if(!self::canFit($this->level, $this->blockIn->getSide($face), $face, false, $this->getMotive())){ $this->kill(); } } public function hasMovementUpdate() : bool{ return false; } protected function updateMovement(bool $teleport = false) : void{ } public function canBeCollidedWith() : bool{ return false; } protected function sendSpawnPacket(Player $player) : void{ $pk = new AddPaintingPacket(); $pk->entityRuntimeId = $this->getId(); $pk->position = new Vector3( ($this->boundingBox->minX + $this->boundingBox->maxX) / 2, ($this->boundingBox->minY + $this->boundingBox->maxY) / 2, ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2 ); $pk->direction = $this->direction; $pk->title = $this->motive; $player->dataPacket($pk); } /** * Returns the painting motive (which image is displayed on the painting) * @return PaintingMotive */ public function getMotive() : PaintingMotive{ return PaintingMotive::getMotiveByName($this->motive); } public function getDirection() : ?int{ return $this->direction; } /** * Returns the bounding-box a painting with the specified motive would have at the given position and direction. * * @param Vector3 $blockIn * @param int $facing * @param PaintingMotive $motive * * @return AxisAlignedBB */ private static function getPaintingBB(Vector3 $blockIn, int $facing, PaintingMotive $motive) : AxisAlignedBB{ $width = $motive->getWidth(); $height = $motive->getHeight(); $horizontalStart = (int) (ceil($width / 2) - 1); $verticalStart = (int) (ceil($height / 2) - 1); $thickness = 1 / 16; $minX = $maxX = 0; $minZ = $maxZ = 0; $minY = -$verticalStart; $maxY = $minY + $height; switch($facing){ case Vector3::SIDE_NORTH: $minZ = 1 - $thickness; $maxZ = 1; $maxX = $horizontalStart + 1; $minX = $maxX - $width; break; case Vector3::SIDE_SOUTH: $minZ = 0; $maxZ = $thickness; $minX = -$horizontalStart; $maxX = $minX + $width; break; case Vector3::SIDE_WEST: $minX = 1 - $thickness; $maxX = 1; $minZ = -$horizontalStart; $maxZ = $minZ + $width; break; case Vector3::SIDE_EAST: $minX = 0; $maxX = $thickness; $maxZ = $horizontalStart + 1; $minZ = $maxZ - $width; break; } return new AxisAlignedBB( $blockIn->x + $minX, $blockIn->y + $minY, $blockIn->z + $minZ, $blockIn->x + $maxX, $blockIn->y + $maxY, $blockIn->z + $maxZ ); } /** * Returns whether a painting with the specified motive can be placed at the given position. * * @param Level $level * @param Vector3 $blockIn * @param int $facing * @param bool $checkOverlap * @param PaintingMotive $motive * * @return bool */ public static function canFit(Level $level, Vector3 $blockIn, int $facing, bool $checkOverlap, PaintingMotive $motive) : bool{ $width = $motive->getWidth(); $height = $motive->getHeight(); $horizontalStart = (int) (ceil($width / 2) - 1); $verticalStart = (int) (ceil($height / 2) - 1); switch($facing){ case Vector3::SIDE_NORTH: $rotatedFace = Vector3::SIDE_WEST; break; case Vector3::SIDE_WEST: $rotatedFace = Vector3::SIDE_SOUTH; break; case Vector3::SIDE_SOUTH: $rotatedFace = Vector3::SIDE_EAST; break; case Vector3::SIDE_EAST: $rotatedFace = Vector3::SIDE_NORTH; break; default: return false; } $oppositeSide = Vector3::getOppositeSide($facing); $startPos = $blockIn->asVector3()->getSide(Vector3::getOppositeSide($rotatedFace), $horizontalStart)->getSide(Vector3::SIDE_DOWN, $verticalStart); for($w = 0; $w < $width; ++$w){ for($h = 0; $h < $height; ++$h){ $pos = $startPos->getSide($rotatedFace, $w)->getSide(Vector3::SIDE_UP, $h); $block = $level->getBlockAt($pos->x, $pos->y, $pos->z); if($block->isSolid() or !$block->getSide($oppositeSide)->isSolid()){ return false; } } } if($checkOverlap){ $bb = self::getPaintingBB($blockIn, $facing, $motive); foreach($level->getNearbyEntities($bb) as $entity){ if($entity instanceof self){ return false; } } } return true; } }