diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 69fc684a1..0266407c9 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -115,6 +115,8 @@ abstract class Entity{ /** @var Block[]|null */ protected $blocksAround; + private bool $checkBlockIntersectionsNextTick = true; + /** @var Location */ protected $location; /** @var Location */ @@ -649,7 +651,10 @@ abstract class Entity{ $hasUpdate = false; - $this->checkBlockIntersections(); + if($this->checkBlockIntersectionsNextTick){ + $this->checkBlockIntersections(); + } + $this->checkBlockIntersectionsNextTick = true; if($this->location->y <= World::Y_MIN - 16 && $this->isAlive()){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); @@ -1308,6 +1313,7 @@ abstract class Entity{ } protected function checkBlockIntersections() : void{ + $this->checkBlockIntersectionsNextTick = false; $vectors = []; foreach($this->getBlocksAroundWithEntityInsideActions() as $block){ @@ -1319,10 +1325,12 @@ abstract class Entity{ } } - $vector = Vector3::sum(...$vectors); - if($vector->lengthSquared() > 0){ - $d = 0.014; - $this->motion = $this->motion->addVector($vector->normalize()->multiply($d)); + if(count($vectors) > 0){ + $vector = Vector3::sum(...$vectors); + if($vector->lengthSquared() > 0){ + $d = 0.014; + $this->motion = $this->motion->addVector($vector->normalize()->multiply($d)); + } } } diff --git a/src/event/RegisteredListener.php b/src/event/RegisteredListener.php index d227fc2cb..6b29dfec3 100644 --- a/src/event/RegisteredListener.php +++ b/src/event/RegisteredListener.php @@ -57,8 +57,11 @@ class RegisteredListener{ return; } $this->timings->startTiming(); - ($this->handler)($event); - $this->timings->stopTiming(); + try{ + ($this->handler)($event); + }finally{ + $this->timings->stopTiming(); + } } public function isHandlingCancelled() : bool{ diff --git a/src/item/Item.php b/src/item/Item.php index 785a38ca0..d59e15cd7 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -353,30 +353,35 @@ class Item implements \JsonSerializable{ } protected function serializeCompoundTag(CompoundTag $tag) : void{ - $display = $tag->getCompoundTag(self::TAG_DISPLAY) ?? new CompoundTag(); + $display = $tag->getCompoundTag(self::TAG_DISPLAY); - $this->hasCustomName() ? - $display->setString(self::TAG_DISPLAY_NAME, $this->getCustomName()) : - $display->removeTag(self::TAG_DISPLAY_NAME); + if($this->customName !== ""){ + $display ??= new CompoundTag(); + $display->setString(self::TAG_DISPLAY_NAME, $this->customName); + }else{ + $display?->removeTag(self::TAG_DISPLAY_NAME); + } if(count($this->lore) > 0){ $loreTag = new ListTag(); foreach($this->lore as $line){ $loreTag->push(new StringTag($line)); } + $display ??= new CompoundTag(); $display->setTag(self::TAG_DISPLAY_LORE, $loreTag); }else{ - $display->removeTag(self::TAG_DISPLAY_LORE); + $display?->removeTag(self::TAG_DISPLAY_LORE); } - $display->count() > 0 ? + $display !== null && $display->count() > 0 ? $tag->setTag(self::TAG_DISPLAY, $display) : $tag->removeTag(self::TAG_DISPLAY); - if($this->hasEnchantments()){ + if(count($this->enchantments) > 0){ $ench = new ListTag(); - foreach($this->getEnchantments() as $enchantmentInstance){ + $enchantmentIdMap = EnchantmentIdMap::getInstance(); + foreach($this->enchantments as $enchantmentInstance){ $ench->push(CompoundTag::create() - ->setShort(self::TAG_ENCH_ID, EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType())) + ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType())) ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel()) ); } @@ -385,8 +390,8 @@ class Item implements \JsonSerializable{ $tag->removeTag(self::TAG_ENCH); } - ($blockData = $this->getCustomBlockData()) !== null ? - $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $blockData) : + $this->blockEntityTag !== null ? + $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) : $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG); if(count($this->canPlaceOn) > 0){ diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index e8826e591..cfc2454d9 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\block\Block; use pocketmine\block\BlockLegacyIds; use pocketmine\item\Durable; use pocketmine\item\Item; @@ -135,14 +136,16 @@ class TypeConverter{ if($itemStack->isNull()){ return ItemStack::null(); } - $nbt = null; - if($itemStack->hasNamedTag()){ - $nbt = clone $itemStack->getNamedTag(); + $nbt = $itemStack->getNamedTag(); + if($nbt->count() === 0){ + $nbt = null; + }else{ + $nbt = clone $nbt; } - $isBlockItem = $itemStack->getId() < 256; - - $idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($itemStack->getId(), $itemStack->getMeta()); + $internalId = $itemStack->getId(); + $internalMeta = $itemStack->getMeta(); + $idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($internalId, $internalMeta); if($idMeta === null){ //Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with //other unmapped items. @@ -150,8 +153,8 @@ class TypeConverter{ if($nbt === null){ $nbt = new CompoundTag(); } - $nbt->setInt(self::PM_ID_TAG, $itemStack->getId()); - $nbt->setInt(self::PM_META_TAG, $itemStack->getMeta()); + $nbt->setInt(self::PM_ID_TAG, $internalId); + $nbt->setInt(self::PM_META_TAG, $internalMeta); }else{ [$id, $meta] = $idMeta; @@ -166,23 +169,15 @@ class TypeConverter{ } $nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage()); $meta = 0; - }elseif($isBlockItem && $itemStack->getMeta() !== 0){ - //TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the - //client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata - //client-side. Aside from being very annoying, this also breaks various server-side behaviours. - if($nbt === null){ - $nbt = new CompoundTag(); - } - $nbt->setInt(self::PM_META_TAG, $itemStack->getMeta()); - $meta = 0; } } $blockRuntimeId = 0; - if($isBlockItem){ + if($internalId < 256){ $block = $itemStack->getBlock(); if($block->getId() !== BlockLegacyIds::AIR){ $blockRuntimeId = RuntimeBlockMapping::getInstance()->toRuntimeId($block->getFullId()); + $meta = 0; } } @@ -208,6 +203,11 @@ class TypeConverter{ $compound = $itemStack->getNbt(); [$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($itemStack->getId(), $itemStack->getMeta()); + if($itemStack->getBlockRuntimeId() !== 0){ + //blockitem meta is zeroed out by the client, so we have to infer it from the block runtime ID + $blockFullId = RuntimeBlockMapping::getInstance()->fromRuntimeId($itemStack->getBlockRuntimeId()); + $meta = $blockFullId & Block::INTERNAL_METADATA_MASK; + } if($compound !== null){ $compound = clone $compound; @@ -222,12 +222,6 @@ class TypeConverter{ $compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION); $compound->setTag(self::DAMAGE_TAG, $conflicted); } - }elseif(($metaTag = $compound->getTag(self::PM_META_TAG)) instanceof IntTag){ - //TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the - //client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata - //client-side. Aside from being very annoying, this also breaks various server-side behaviours. - $meta = $metaTag->getValue(); - $compound->removeTag(self::PM_META_TAG); } if($compound->count() === 0){ $compound = null; diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 46927b4f6..f420d4352 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -53,6 +53,7 @@ use function implode; use function mt_rand; use function random_bytes; use function rtrim; +use function str_split; use function substr; use const PHP_INT_MAX; @@ -196,7 +197,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ try{ $session->handleEncoded($buf); }catch(PacketHandlingException $e){ - $errorId = bin2hex(random_bytes(6)); + $errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4)); $logger = $session->getLogger(); $logger->error("Bad packet (error ID $errorId): " . $e->getMessage()); diff --git a/tools/simulate-chunk-selector.php b/tools/simulate-chunk-selector.php index 81beb6bb3..0b279268a 100644 --- a/tools/simulate-chunk-selector.php +++ b/tools/simulate-chunk-selector.php @@ -24,12 +24,10 @@ declare(strict_types=1); namespace pocketmine\tools\simulate_chunk_selector; use pocketmine\player\ChunkSelector; -use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; use pocketmine\world\format\Chunk; use pocketmine\world\World; use Symfony\Component\Filesystem\Path; -use function assert; use function count; use function dirname; use function fwrite; @@ -128,7 +126,12 @@ if(count(getopt("", ["help"])) !== 0){ exit(0); } -foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:"])) as $name => $value){ +$opts = getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:", "output:"]); +foreach(["radius", "baseX", "baseZ", "scale", "chunksPerStep"] as $name){ + $value = $opts[$name] ?? null; + if($value === null){ + continue; + } if(!is_string($value) || (string) ((int) $value) !== $value){ fwrite(STDERR, "Value for --$name must be an integer\n"); exit(1); @@ -139,8 +142,7 @@ foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:" "baseX" => ($baseX = $value), "baseZ" => ($baseZ = $value), "scale" => ($scale = $value), - "chunksPerStep" => ($nChunksPerStep = $value), - default => throw new AssumptionFailedError("getopt() returned unknown option") + "chunksPerStep" => ($nChunksPerStep = $value) }; } if($radius === null){ @@ -149,10 +151,10 @@ if($radius === null){ } $outputDirectory = null; -foreach(Utils::stringifyKeys(getopt("", ["output:"])) as $name => $value){ - assert($name === "output"); +if(isset($opts["output"])){ + $value = $opts["output"]; if(!is_string($value)){ - fwrite(STDERR, "Value for --$name must be a string\n"); + fwrite(STDERR, "Value for --output be a string\n"); exit(1); } if(!@mkdir($value) && !is_dir($value)){