diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 0d1553bcb3..a9e6a2e316 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -46,7 +46,7 @@ jobs: run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}") - name: Build image for tag - uses: docker/build-push-action@v3.0.0 + uses: docker/build-push-action@v3.1.0 with: push: true context: ./pocketmine-mp @@ -59,7 +59,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.0.0 + uses: docker/build-push-action@v3.1.0 with: push: true context: ./pocketmine-mp @@ -72,7 +72,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.0.0 + uses: docker/build-push-action@v3.1.0 with: push: true context: ./pocketmine-mp @@ -85,7 +85,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.0.0 + uses: docker/build-push-action@v3.1.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 711c9db646..9cb10390e1 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -18,7 +18,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.20.0 + uses: shivammathur/setup-php@2.21.0 with: php-version: 8.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4ad624f85..08b4e253c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -195,7 +195,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.20.0 + uses: shivammathur/setup-php@2.21.0 with: php-version: 8.0 tools: php-cs-fixer:3.2 diff --git a/build/php b/build/php index 11103498ca..f292501a70 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 11103498ca761be83598f9759cc7196c167fcb7e +Subproject commit f292501a703352ab793b07b7861f3e1b3860ed86 diff --git a/changelogs/4.6.md b/changelogs/4.6.md index 473edca1f1..e32098da69 100644 --- a/changelogs/4.6.md +++ b/changelogs/4.6.md @@ -12,3 +12,20 @@ Released 13th July 2022. ## General - Added support for Minecraft: Bedrock Edition 1.19.10. - Removed support for older versions. + +# 4.6.1 +Released 22nd July 2022. + +## Tools +- `build/generate-registry-annotations.php` now supports processing single files (useful for PhpStorm file watchers). + +## API +- Updated documentation for `AsyncTask`. + +## Fixes +- Fixed incorrect items being displayed in item frames. +- Fixed books not showing in lecterns. +- Fixed incorrect damage interval of Wither status effect. +- Fixed incorrect fire ticks when being set on fire by lava (8 seconds in Bedrock instead of 15). +- `Entity->attack()` now cancels damage from `FIRE` and `FIRE_TICK` damage causes if the entity is fireproof. +- Fixed inventory windows getting force-closed when the client attempts to use an enchanting table or anvil. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 465db22e1c..20020cb251 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.6.1"; + public const BASE_VERSION = "4.6.2"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "beta"; diff --git a/src/entity/Human.php b/src/entity/Human.php index 683e0c7d65..ae323f0f89 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -50,6 +50,7 @@ use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; +use pocketmine\network\mcpe\protocol\types\command\CommandPermissions; use pocketmine\network\mcpe\protocol\types\DeviceOS; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; @@ -57,12 +58,15 @@ use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty; use pocketmine\network\mcpe\protocol\types\GameMode; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; +use pocketmine\network\mcpe\protocol\types\PlayerPermissions; +use pocketmine\network\mcpe\protocol\types\UpdateAbilitiesPacketLayer; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\player\Player; use pocketmine\utils\Limits; use pocketmine\world\sound\TotemUseSound; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; +use function array_fill; use function array_filter; use function array_key_exists; use function array_merge; @@ -481,7 +485,14 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand())), GameMode::SURVIVAL, $this->getAllNetworkData(), - UpdateAbilitiesPacket::create(0, 0, $this->getId() /* TODO: this should be unique ID */, []), + UpdateAbilitiesPacket::create(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() /* TODO: this should be unique ID */, [ + new UpdateAbilitiesPacketLayer( + UpdateAbilitiesPacketLayer::LAYER_BASE, + array_fill(0, UpdateAbilitiesPacketLayer::NUMBER_OF_ABILITIES, false), + 0.0, + 0.0 + ) + ]), [], //TODO: entity links "", //device ID (we intentionally don't send this - secvuln) DeviceOS::UNKNOWN //we intentionally don't send this (secvuln) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 3ab7b380a3..ebc8d3cc13 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -145,6 +145,11 @@ class InGamePacketHandler extends PacketHandler{ /** @var UseItemTransactionData|null */ protected $lastRightClickData = null; + protected ?Vector3 $lastPlayerAuthInputPosition = null; + protected ?float $lastPlayerAuthInputYaw = null; + protected ?float $lastPlayerAuthInputPitch = null; + protected ?int $lastPlayerAuthInputFlags = null; + /** @var bool */ public $forceMoveSync = false; @@ -168,9 +173,10 @@ class InGamePacketHandler extends PacketHandler{ return true; } - private function resolveOnOffInputFlags(PlayerAuthInputPacket $packet, int $startFlag, int $stopFlag) : ?bool{ - $enabled = $packet->hasFlag($startFlag); - if($enabled !== $packet->hasFlag($stopFlag)){ + private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{ + $enabled = ($inputFlags & (1 << $startFlag)) !== 0; + $disabled = ($inputFlags & (1 << $stopFlag)) !== 0; + if($enabled !== $disabled){ return $enabled; } //neither flag was set, or both were set @@ -179,51 +185,68 @@ class InGamePacketHandler extends PacketHandler{ public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{ $rawPos = $packet->getPosition(); - foreach([$rawPos->x, $rawPos->y, $rawPos->z, $packet->getYaw(), $packet->getHeadYaw(), $packet->getPitch()] as $float){ + $rawYaw = $packet->getYaw(); + $rawPitch = $packet->getPitch(); + foreach([$rawPos->x, $rawPos->y, $rawPos->z, $rawYaw, $packet->getHeadYaw(), $rawPitch] as $float){ if(is_infinite($float) || is_nan($float)){ $this->session->getLogger()->debug("Invalid movement received, contains NAN/INF components"); return false; } } - $yaw = fmod($packet->getYaw(), 360); - $pitch = fmod($packet->getPitch(), 360); - if($yaw < 0){ - $yaw += 360; + if($rawYaw !== $this->lastPlayerAuthInputYaw || $rawPitch !== $this->lastPlayerAuthInputPitch){ + $this->lastPlayerAuthInputYaw = $rawYaw; + $this->lastPlayerAuthInputPitch = $rawPitch; + + $yaw = fmod($rawYaw, 360); + $pitch = fmod($rawPitch, 360); + if($yaw < 0){ + $yaw += 360; + } + + $this->player->setRotation($yaw, $pitch); } - $this->player->setRotation($yaw, $pitch); - - $curPos = $this->player->getLocation(); + $hasMoved = $this->lastPlayerAuthInputPosition === null || !$this->lastPlayerAuthInputPosition->equals($rawPos); $newPos = $rawPos->round(4)->subtract(0, 1.62, 0); - if($this->forceMoveSync && $newPos->distanceSquared($curPos) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks - $this->session->getLogger()->debug("Got outdated pre-teleport movement, received " . $newPos . ", expected " . $curPos); - //Still getting movements from before teleport, ignore them - return false; + if($this->forceMoveSync && $hasMoved){ + $curPos = $this->player->getLocation(); + + if($newPos->distanceSquared($curPos) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks + $this->session->getLogger()->debug("Got outdated pre-teleport movement, received " . $newPos . ", expected " . $curPos); + //Still getting movements from before teleport, ignore them + return false; + } + + // Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock + $this->forceMoveSync = false; } - // Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock - $this->forceMoveSync = false; + $inputFlags = $packet->getInputFlags(); + if($inputFlags !== $this->lastPlayerAuthInputFlags){ + $this->lastPlayerAuthInputFlags = $inputFlags; - $sneaking = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); - $sprinting = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING); - $swimming = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING); - $gliding = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING); - $mismatch = - ($sneaking !== null && !$this->player->toggleSneak($sneaking)) | - ($sprinting !== null && !$this->player->toggleSprint($sprinting)) | - ($swimming !== null && !$this->player->toggleSwim($swimming)) | - ($gliding !== null && !$this->player->toggleGlide($gliding)); - if((bool) $mismatch){ - $this->player->sendData([$this->player]); + $sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); + $sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING); + $swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING); + $gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING); + $mismatch = + ($sneaking !== null && !$this->player->toggleSneak($sneaking)) | + ($sprinting !== null && !$this->player->toggleSprint($sprinting)) | + ($swimming !== null && !$this->player->toggleSwim($swimming)) | + ($gliding !== null && !$this->player->toggleGlide($gliding)); + if((bool) $mismatch){ + $this->player->sendData([$this->player]); + } + + if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ + $this->player->jump(); + } } - if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ - $this->player->jump(); - } - - if(!$this->forceMoveSync){ + if(!$this->forceMoveSync && $hasMoved){ + $this->lastPlayerAuthInputPosition = $rawPos; //TODO: this packet has WAYYYYY more useful information that we're not using $this->player->handleMovement($newPos); } diff --git a/src/player/Player.php b/src/player/Player.php index 33be6c4e8e..86e18a6c66 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1180,7 +1180,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return; } - $oldPos = $this->getLocation(); + $oldPos = $this->location; $distanceSquared = $newPos->distanceSquared($oldPos); $revert = false; @@ -1198,7 +1198,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * asking for help if you suffer the consequences of messing with this. */ $this->logger->debug("Moved too fast, reverting movement"); - $this->logger->debug("Old position: " . $this->location->asVector3() . ", new position: " . $newPos); + $this->logger->debug("Old position: " . $oldPos->asVector3() . ", new position: " . $newPos); $revert = true; }elseif(!$this->getWorld()->isInLoadedTerrain($newPos)){ $revert = true; @@ -1206,9 +1206,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } if(!$revert && $distanceSquared != 0){ - $dx = $newPos->x - $this->location->x; - $dy = $newPos->y - $this->location->y; - $dz = $newPos->z - $this->location->z; + $dx = $newPos->x - $oldPos->x; + $dy = $newPos->y - $oldPos->y; + $dz = $newPos->z - $oldPos->z; $this->move($dx, $dy, $dz); } diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index 28c7a29573..c65552bf35 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -33,19 +33,31 @@ use function spl_object_id; /** * Class used to run async tasks in other threads. * - * An AsyncTask does not have its own thread. It is queued into an AsyncPool and executed if there is an async worker - * with no AsyncTask running. Therefore, an AsyncTask SHOULD NOT execute for more than a few seconds. For tasks that - * run for a long time or infinitely, start another thread instead. + * An AsyncTask is run by a thread pool of reusable threads, and doesn't have its own dedicated thread. A thread is + * usually chosen from the pool at random to run the task (though a specific thread in the pool may be selected + * manually, if needed). + * Reusing threads this way has a much lower performance cost than starting an entirely new thread for every operation. + * AsyncTasks are therefore suitable for brief CPU-bound tasks, such as world generation, compression/decompression of + * data, etc. + * + * AsyncTask SHOULD NOT be used for I/O-bound tasks, such as network I/O, file I/O, database I/O, etc. The server's + * central AsyncPool is used for things like compressing network packets for sending, so using AsyncTask for I/O will + * slow the whole server down, stall chunk loading, etc. + * + * An AsyncTask SHOULD NOT run for more than a few seconds. For tasks that run for a long time or indefinitely, create + * a dedicated thread instead. + * + * The Server instance is not accessible inside {@link AsyncTask::onRun()}. It can only be accessed in the main server + * thread, e.g. during {@link AsyncTask::onCompletion()} or {@link AsyncTask::onProgressUpdate()}. This means that + * whatever you do in onRun() must be able to work without the Server instance. * * WARNING: Any non-Threaded objects WILL BE SERIALIZED when assigned to members of AsyncTasks or other Threaded object. * If later accessed from said Threaded object, you will be operating on a COPY OF THE OBJECT, NOT THE ORIGINAL OBJECT. * If you want to store non-serializable objects to access when the task completes, store them using * {@link AsyncTask::storeLocal}. * - * WARNING: As of pthreads v3.1.6, arrays are converted to Volatile objects when assigned as members of Threaded objects. + * WARNING: Arrays are converted to Volatile objects when assigned as members of Threaded objects. * Keep this in mind when using arrays stored as members of your AsyncTask. - * - * WARNING: Do not call PocketMine-MP API methods from other Threads!! */ abstract class AsyncTask extends \Threaded{ /** diff --git a/src/world/particle/FloatingTextParticle.php b/src/world/particle/FloatingTextParticle.php index be1fdd5dc2..313e40d48b 100644 --- a/src/world/particle/FloatingTextParticle.php +++ b/src/world/particle/FloatingTextParticle.php @@ -23,25 +23,20 @@ declare(strict_types=1); namespace pocketmine\world\particle; +use pocketmine\block\VanillaBlocks; use pocketmine\entity\Entity; -use pocketmine\entity\Skin; use pocketmine\math\Vector3; -use pocketmine\network\mcpe\convert\SkinAdapterSingleton; -use pocketmine\network\mcpe\protocol\AddPlayerPacket; -use pocketmine\network\mcpe\protocol\PlayerListPacket; +use pocketmine\network\mcpe\convert\RuntimeBlockMapping; +use pocketmine\network\mcpe\protocol\AddActorPacket; use pocketmine\network\mcpe\protocol\RemoveActorPacket; -use pocketmine\network\mcpe\protocol\types\DeviceOS; +use pocketmine\network\mcpe\protocol\types\entity\ByteMetadataProperty; +use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty; +use pocketmine\network\mcpe\protocol\types\entity\IntMetadataProperty; use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty; -use pocketmine\network\mcpe\protocol\types\GameMode; -use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; -use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; -use pocketmine\network\mcpe\protocol\types\PlayerListEntry; -use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; -use Ramsey\Uuid\Uuid; -use function str_repeat; +use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty; class FloatingTextParticle implements Particle{ //TODO: HACK! @@ -94,38 +89,34 @@ class FloatingTextParticle implements Particle{ } if(!$this->invisible){ - $uuid = Uuid::uuid4(); $name = $this->title . ($this->text !== "" ? "\n" . $this->text : ""); - $p[] = PlayerListPacket::add([PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, SkinAdapterSingleton::get()->toSkinData(new Skin("Standard_Custom", str_repeat("\x00", 8192))))]); - $actorFlags = ( 1 << EntityMetadataFlags::IMMOBILE ); $actorMetadata = [ EntityMetadataProperties::FLAGS => new LongMetadataProperty($actorFlags), - EntityMetadataProperties::SCALE => new FloatMetadataProperty(0.01) //zero causes problems on debug builds + EntityMetadataProperties::SCALE => new FloatMetadataProperty(0.01), //zero causes problems on debug builds + EntityMetadataProperties::BOUNDING_BOX_WIDTH => new FloatMetadataProperty(0.0), + EntityMetadataProperties::BOUNDING_BOX_HEIGHT => new FloatMetadataProperty(0.0), + EntityMetadataProperties::NAMETAG => new StringMetadataProperty($name), + EntityMetadataProperties::VARIANT => new IntMetadataProperty(RuntimeBlockMapping::getInstance()->toRuntimeId(VanillaBlocks::AIR()->getFullId())), + EntityMetadataProperties::ALWAYS_SHOW_NAMETAG => new ByteMetadataProperty(1), ]; - $p[] = AddPlayerPacket::create( - $uuid, - $name, + $p[] = AddActorPacket::create( $this->entityId, //TODO: actor unique ID - "", - $pos, //TODO: check offset + $this->entityId, + EntityIds::FALLING_BLOCK, + $pos, //TODO: check offset (0.49?) null, 0, 0, 0, - ItemStackWrapper::legacy(ItemStack::null()), - GameMode::SURVIVAL, - $actorMetadata, - UpdateAbilitiesPacket::create(0, 0, $this->entityId, []), + 0, [], - "", - DeviceOS::UNKNOWN + $actorMetadata, + [] ); - - $p[] = PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($uuid)]); } return $p;