diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 8adc0f4dd..a7a251c77 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index ceb3b1d9d..2d140eb5a 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.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd3cff752..e86db1be2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,11 +13,11 @@ jobs: strategy: matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.22, 8.1.9] steps: - name: Build and prepare PHP cache - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -31,13 +31,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.22, 8.1.9] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -69,13 +69,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.22, 8.1.9] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -107,7 +107,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.22, 8.1.9] steps: - uses: actions/checkout@v3 @@ -115,7 +115,7 @@ jobs: submodules: true - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -147,13 +147,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.22, 8.1.9] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -198,10 +198,10 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 - tools: php-cs-fixer:3.2 + tools: php-cs-fixer:3.8 - name: Run PHP-CS-Fixer run: php-cs-fixer fix --dry-run --diff --ansi diff --git a/.github/workflows/update-php-versions.php b/.github/workflows/update-php-versions.php index 0ba7382cf..7ff798566 100644 --- a/.github/workflows/update-php-versions.php +++ b/.github/workflows/update-php-versions.php @@ -22,7 +22,8 @@ declare(strict_types=1); const VERSIONS = [ - "8.0" + "8.0", + "8.1" ]; $workflowFile = file_get_contents(__DIR__ . '/main.yml'); diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 59d42fdf1..70801c065 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -70,6 +70,10 @@ BODY, 'scope' => 'namespaced', 'include' => ['@all'], ], + 'new_with_braces' => [ + 'named_class' => true, + 'anonymous_class' => false, + ], 'no_closing_tag' => true, 'no_empty_phpdoc' => true, 'no_extra_blank_lines' => true, diff --git a/SECURITY.md b/SECURITY.md index 2c4bc43d0..67b8ba69d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,10 +7,11 @@ GitHub is public and anyone can see the issues you post on the issue tracker, in **WARNING: You may put live servers at risk by reporting a vulnerability on the GitHub issue tracker.** -**Contact us** by sending an email to [**team@pmmp.io**](mailto:team@pmmp.io?subject=Security%20Vulnerability%20in%20PocketMine-MP). Include the following information: +**Contact us** by sending an email to [**security@pmmp.io**](mailto:security@pmmp.io). Include the following information: - Version of PocketMine-MP - Detailed description of the vulnerability (e.g. how to exploit it, what the effects are) +- Your GitHub username, if you wish to be credited for reporting the problem in the security advisory Please note that we can't guarantee a reply to every email. diff --git a/changelogs/4.7.md b/changelogs/4.7.md index 22fbe9ecc..950158cd3 100644 --- a/changelogs/4.7.md +++ b/changelogs/4.7.md @@ -21,3 +21,11 @@ Released 14th August 2022. - Fixed Turtle Master potions not giving any effects. - Unimplemented items are no longer craftable. - Fixed incorrect items appearing in item frames (due to an obsolete workaround for 1.19.10). + +# 4.7.2 +Released 16th August 2022. + +## Fixes +- Fixed crash when processing player skins with invalid geometry data. +- Fixed spectator players being able to pick blocks using mousewheel click. +- Improved supporting requirements for sugarcane. diff --git a/composer.lock b/composer.lock index f2aeaa9cb..fd93078e5 100644 --- a/composer.lock +++ b/composer.lock @@ -586,16 +586,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.8.3", + "version": "2.8.6", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "113c115a3b8976917eb22b74dccab464831b6483" + "reference": "87f1afaa4824998ece14d71ce37e94bc1f1d3116" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/113c115a3b8976917eb22b74dccab464831b6483", - "reference": "113c115a3b8976917eb22b74dccab464831b6483", + "url": "https://api.github.com/repos/pmmp/Language/zipball/87f1afaa4824998ece14d71ce37e94bc1f1d3116", + "reference": "87f1afaa4824998ece14d71ce37e94bc1f1d3116", "shasum": "" }, "type": "library", @@ -603,9 +603,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.8.3" + "source": "https://github.com/pmmp/Language/tree/2.8.6" }, - "time": "2022-05-11T13:51:37+00:00" + "time": "2022-08-18T15:25:44+00:00" }, { "name": "pocketmine/log", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9ebc98502..6fc15b07a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,4 +1,5 @@ includes: + - tests/phpstan/analyse-for-current-php-version.neon.php - tests/phpstan/configs/actual-problems.neon - tests/phpstan/configs/gc-hacks.neon - tests/phpstan/configs/impossible-generics.neon diff --git a/src/PocketMine.php b/src/PocketMine.php index 9cb8eedf8..f4f9a198d 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -37,6 +37,7 @@ namespace pocketmine { use Webmozart\PathUtil\Path; use function defined; use function extension_loaded; + use function function_exists; use function getcwd; use function phpversion; use function preg_match; @@ -160,7 +161,7 @@ namespace pocketmine { if(PHP_DEBUG !== 0){ $logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance."); } - if(extension_loaded("xdebug")){ + if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){ $logger->warning("Xdebug extension is enabled. This has a major impact on performance."); } if(((int) ini_get('zend.assertions')) !== -1){ diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index 3acaaa367..2b7dfd0b6 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -127,7 +127,8 @@ class Sugarcane extends Flowable{ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); }elseif($this->canBeSupportedBy($down)){ foreach(Facing::HORIZONTAL as $side){ - if($down->getSide($side) instanceof Water){ + $sideBlock = $down->getSide($side); + if($sideBlock instanceof Water || $sideBlock instanceof FrostedIce){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } } diff --git a/src/entity/Skin.php b/src/entity/Skin.php index f10d72fc3..0cdfa1160 100644 --- a/src/entity/Skin.php +++ b/src/entity/Skin.php @@ -28,7 +28,6 @@ use pocketmine\utils\Limits; use function implode; use function in_array; use function json_encode; -use function json_last_error_msg; use function strlen; use const JSON_THROW_ON_ERROR; @@ -68,9 +67,10 @@ final class Skin{ } if($geometryData !== ""){ - $decodedGeometry = (new CommentedJsonDecoder())->decode($geometryData); - if($decodedGeometry === false){ - throw new InvalidSkinException("Invalid geometry data (" . json_last_error_msg() . ")"); + try{ + $decodedGeometry = (new CommentedJsonDecoder())->decode($geometryData); + }catch(\RuntimeException $e){ + throw new InvalidSkinException("Invalid geometry data: " . $e->getMessage(), 0, $e); } /* diff --git a/src/entity/effect/StringToEffectParser.php b/src/entity/effect/StringToEffectParser.php index 5fa49027e..d336af33b 100644 --- a/src/entity/effect/StringToEffectParser.php +++ b/src/entity/effect/StringToEffectParser.php @@ -35,7 +35,7 @@ final class StringToEffectParser extends StringToTParser{ use SingletonTrait; private static function make() : self{ - $result = new self; + $result = new self(); $result->register("absorption", fn() => VanillaEffects::ABSORPTION()); $result->register("blindness", fn() => VanillaEffects::BLINDNESS()); diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index 2440eca45..ecff0b85b 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -31,7 +31,7 @@ class HandlerListManager{ private static ?self $globalInstance = null; public static function global() : self{ - return self::$globalInstance ?? (self::$globalInstance = new self); + return self::$globalInstance ?? (self::$globalInstance = new self()); } /** @var HandlerList[] classname => HandlerList */ diff --git a/src/inventory/transaction/TransactionBuilder.php b/src/inventory/transaction/TransactionBuilder.php new file mode 100644 index 000000000..f56b2aaa1 --- /dev/null +++ b/src/inventory/transaction/TransactionBuilder.php @@ -0,0 +1,61 @@ +extraActions[spl_object_id($action)] = $action; + } + + public function getInventory(Inventory $inventory) : TransactionBuilderInventory{ + $id = spl_object_id($inventory); + return $this->inventories[$id] ??= new TransactionBuilderInventory($inventory); + } + + /** + * @return InventoryAction[] + */ + public function generateActions() : array{ + $actions = $this->extraActions; + + foreach($this->inventories as $inventory){ + foreach($inventory->generateActions() as $action){ + $actions[spl_object_id($action)] = $action; + } + } + + return $actions; + } +} diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 8daf0f87e..96d542cc2 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -46,7 +46,7 @@ final class StringToItemParser extends StringToTParser{ use SingletonTrait; private static function make() : self{ - $result = new self; + $result = new self(); self::registerDynamicBlocks($result); self::registerBlocks($result); diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php index b2e7e989d..ec4b1d1c6 100644 --- a/src/item/enchantment/StringToEnchantmentParser.php +++ b/src/item/enchantment/StringToEnchantmentParser.php @@ -35,7 +35,7 @@ final class StringToEnchantmentParser extends StringToTParser{ use SingletonTrait; private static function make() : self{ - $result = new self; + $result = new self(); $result->register("blast_protection", fn() => VanillaEnchantments::BLAST_PROTECTION()); $result->register("efficiency", fn() => VanillaEnchantments::EFFICIENCY()); diff --git a/src/network/mcpe/ComplexWindowMapEntry.php b/src/network/mcpe/ComplexWindowMapEntry.php new file mode 100644 index 000000000..c2792297b --- /dev/null +++ b/src/network/mcpe/ComplexWindowMapEntry.php @@ -0,0 +1,64 @@ + + */ + private array $reverseSlotMap = []; + + /** + * @param int[] $slotMap + * @phpstan-param array $slotMap + */ + public function __construct( + private Inventory $inventory, + private array $slotMap + ){ + foreach($slotMap as $slot => $index){ + $this->reverseSlotMap[$index] = $slot; + } + } + + public function getInventory() : Inventory{ return $this->inventory; } + + /** + * @return int[] + * @phpstan-return array + */ + public function getSlotMap() : array{ return $this->slotMap; } + + public function mapNetToCore(int $slot) : ?int{ + return $this->slotMap[$slot] ?? null; + } + + public function mapCoreToNet(int $slot) : ?int{ + return $this->reverseSlotMap[$slot] ?? null; + } +} diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 723e02d13..e40975f00 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -55,6 +55,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; +use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; @@ -63,6 +64,7 @@ use pocketmine\utils\ObjectSet; use function array_map; use function array_search; use function get_class; +use function is_int; use function max; use function spl_object_id; @@ -72,6 +74,17 @@ use function spl_object_id; class InventoryManager{ /** @var Inventory[] */ private array $windowMap = []; + /** + * @var ComplexWindowMapEntry[] + * @phpstan-var array + */ + private array $complexWindows = []; + /** + * @var ComplexWindowMapEntry[] + * @phpstan-var array + */ + private array $complexSlotToWindowMap = []; + private int $lastInventoryNetworkId = ContainerIds::FIRST; /** @@ -98,7 +111,8 @@ class InventoryManager{ $this->add(ContainerIds::INVENTORY, $this->player->getInventory()); $this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory()); $this->add(ContainerIds::ARMOR, $this->player->getArmorInventory()); - $this->add(ContainerIds::UI, $this->player->getCursorInventory()); + $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); + $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); $this->player->getInventory()->getHeldItemIndexChangeListeners()->add(function() : void{ $this->syncSelectedHotbarSlot(); @@ -115,8 +129,27 @@ class InventoryManager{ return $this->lastInventoryNetworkId; } + /** + * @param int[]|int $slotMap + * @phpstan-param array|int $slotMap + */ + private function addComplex(array|int $slotMap, Inventory $inventory) : void{ + $entry = new ComplexWindowMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap); + $this->complexWindows[spl_object_id($inventory)] = $entry; + foreach($entry->getSlotMap() as $netSlot => $coreSlot){ + $this->complexSlotToWindowMap[$netSlot] = $entry; + } + } + private function remove(int $id) : void{ - unset($this->windowMap[$id], $this->initiatedSlotChanges[$id]); + $inventory = $this->windowMap[$id]; + $splObjectId = spl_object_id($inventory); + unset($this->windowMap[$id], $this->initiatedSlotChanges[$id], $this->complexWindows[$splObjectId]); + foreach($this->complexSlotToWindowMap as $netSlot => $entry){ + if($entry->getInventory() === $inventory){ + unset($this->complexSlotToWindowMap[$netSlot]); + } + } } public function getWindowId(Inventory $inventory) : ?int{ @@ -127,8 +160,22 @@ class InventoryManager{ return $this->lastInventoryNetworkId; } - public function getWindow(int $windowId) : ?Inventory{ - return $this->windowMap[$windowId] ?? null; + /** + * @phpstan-return array{Inventory, int} + */ + public function locateWindowAndSlot(int $windowId, int $netSlotId) : ?array{ + if($windowId === ContainerIds::UI){ + $entry = $this->complexSlotToWindowMap[$netSlotId] ?? null; + if($entry === null){ + return null; + } + $coreSlotId = $entry->mapNetToCore($netSlotId); + return $coreSlotId !== null ? [$entry->getInventory(), $coreSlotId] : null; + } + if(isset($this->windowMap[$windowId])){ + return [$this->windowMap[$windowId], $netSlotId]; + } + return null; } public function onTransactionStart(InventoryTransaction $tx) : void{ @@ -181,11 +228,32 @@ class InventoryManager{ } } + /** + * @return int[]|null + * @phpstan-return array|null + */ + private function createComplexSlotMapping(Inventory $inventory) : ?array{ + //TODO: make this dynamic so plugins can add mappings for stuff not implemented by PM + return match(true){ + $inventory instanceof AnvilInventory => UIInventorySlotOffset::ANVIL, + $inventory instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE, + $inventory instanceof LoomInventory => UIInventorySlotOffset::LOOM, + $inventory instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT], + $inventory instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT, + $inventory instanceof CartographyTableInventory => UIInventorySlotOffset::CARTOGRAPHY_TABLE, + $inventory instanceof SmithingTableInventory => UIInventorySlotOffset::SMITHING_TABLE, + default => null, + }; + } + public function onCurrentWindowChange(Inventory $inventory) : void{ $this->onCurrentWindowRemove(); $this->openWindowDeferred(function() use ($inventory) : void{ $windowId = $this->addDynamic($inventory); + if(($slotMap = $this->createComplexSlotMapping($inventory)) !== null){ + $this->addComplex($slotMap, $inventory); + } foreach($this->containerOpenCallbacks as $callback){ $pks = $callback($windowId, $inventory); @@ -284,10 +352,17 @@ class InventoryManager{ } public function syncSlot(Inventory $inventory, int $slot) : void{ - $windowId = $this->getWindowId($inventory); - if($windowId !== null){ + $slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null; + if($slotMap !== null){ + $windowId = ContainerIds::UI; + $netSlot = $slotMap->mapCoreToNet($slot) ?? null; + }else{ + $windowId = $this->getWindowId($inventory); + $netSlot = $slot; + } + if($windowId !== null && $netSlot !== null){ $currentItem = $inventory->getItem($slot); - $clientSideItem = $this->initiatedSlotChanges[$windowId][$slot] ?? null; + $clientSideItem = $this->initiatedSlotChanges[$windowId][$netSlot] ?? null; if($clientSideItem === null || !$clientSideItem->equalsExact($currentItem)){ $itemStackWrapper = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($currentItem)); if($windowId === ContainerIds::OFFHAND){ @@ -298,30 +373,37 @@ class InventoryManager{ //BDS (Bedrock Dedicated Server) also seems to work this way. $this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper])); }else{ - $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, $itemStackWrapper)); + $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper)); } } - unset($this->initiatedSlotChanges[$windowId][$slot]); + unset($this->initiatedSlotChanges[$windowId][$netSlot]); } } public function syncContents(Inventory $inventory) : void{ - $windowId = $this->getWindowId($inventory); + $slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null; + if($slotMap !== null){ + $windowId = ContainerIds::UI; + }else{ + $windowId = $this->getWindowId($inventory); + } + $typeConverter = TypeConverter::getInstance(); if($windowId !== null){ - unset($this->initiatedSlotChanges[$windowId]); - $typeConverter = TypeConverter::getInstance(); - if($windowId === ContainerIds::UI){ - //TODO: HACK! - //Since 1.13, cursor is now part of a larger "UI inventory", and sending contents for this larger inventory does - //not work the way it's intended to. Even if it did, it would be necessary to send all 51 slots just to update - //this one, which is just not worth it. - //This workaround isn't great, but it's at least simple. - $this->session->sendDataPacket(InventorySlotPacket::create( - $windowId, - 0, - ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($inventory->getItem(0))) - )); + if($slotMap !== null){ + foreach($inventory->getContents(true) as $slotId => $item){ + $packetSlot = $slotMap->mapCoreToNet($slotId) ?? null; + if($packetSlot === null){ + continue; + } + unset($this->initiatedSlotChanges[$windowId][$packetSlot]); + $this->session->sendDataPacket(InventorySlotPacket::create( + $windowId, + $packetSlot, + ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($inventory->getItem($slotId))) + )); + } }else{ + unset($this->initiatedSlotChanges[$windowId]); $this->session->sendDataPacket(InventoryContentPacket::create($windowId, array_map(function(Item $itemStack) use ($typeConverter) : ItemStackWrapper{ return ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($itemStack)); }, $inventory->getContents(true)))); @@ -333,16 +415,20 @@ class InventoryManager{ foreach($this->windowMap as $inventory){ $this->syncContents($inventory); } + foreach($this->complexWindows as $entry){ + $this->syncContents($entry->getInventory()); + } } public function syncMismatchedPredictedSlotChanges() : void{ foreach($this->initiatedSlotChanges as $windowId => $slots){ - if(!isset($this->windowMap[$windowId])){ - continue; - } - $inventory = $this->windowMap[$windowId]; + foreach($slots as $netSlot => $expectedItem){ + $located = $this->locateWindowAndSlot($windowId, $netSlot); + if($located === null){ + continue; + } + [$inventory, $slot] = $located; - foreach($slots as $slot => $expectedItem){ if(!$inventory->slotExists($slot)){ continue; //TODO: size desync ??? } diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 9ba48f4db..6850dcc74 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -23,13 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; -use pocketmine\block\inventory\AnvilInventory; -use pocketmine\block\inventory\CartographyTableInventory; -use pocketmine\block\inventory\CraftingTableInventory; -use pocketmine\block\inventory\EnchantInventory; -use pocketmine\block\inventory\LoomInventory; -use pocketmine\block\inventory\SmithingTableInventory; -use pocketmine\block\inventory\StonecutterInventory; use pocketmine\block\VanillaBlocks; use pocketmine\crafting\ExactRecipeIngredient; use pocketmine\crafting\MetaWildcardRecipeIngredient; @@ -242,40 +235,12 @@ class TypeConverter{ } switch($action->sourceType){ case NetworkInventoryAction::SOURCE_CONTAINER: - $window = null; - if($action->windowId === ContainerIds::UI && $action->inventorySlot > 0){ - if($action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){ - return null; //useless noise - } - $pSlot = $action->inventorySlot; - - $slot = UIInventorySlotOffset::CRAFTING2X2_INPUT[$pSlot] ?? null; - if($slot !== null){ - $window = $player->getCraftingGrid(); - }elseif(($current = $player->getCurrentWindow()) !== null){ - $slotMap = match(true){ - $current instanceof AnvilInventory => UIInventorySlotOffset::ANVIL, - $current instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE, - $current instanceof LoomInventory => UIInventorySlotOffset::LOOM, - $current instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT], - $current instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT, - $current instanceof CartographyTableInventory => UIInventorySlotOffset::CARTOGRAPHY_TABLE, - $current instanceof SmithingTableInventory => UIInventorySlotOffset::SMITHING_TABLE, - default => null - }; - if($slotMap !== null){ - $window = $current; - $slot = $slotMap[$pSlot] ?? null; - } - } - if($slot === null){ - throw new TypeConversionException("Unmatched UI inventory slot offset $pSlot"); - } - }else{ - $window = $inventoryManager->getWindow($action->windowId); - $slot = $action->inventorySlot; + if($action->windowId === ContainerIds::UI && $action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){ + return null; //useless noise } - if($window !== null){ + $located = $inventoryManager->locateWindowAndSlot($action->windowId, $action->inventorySlot); + if($located !== null){ + [$window, $slot] = $located; return new SlotChangeAction($window, $slot, $old, $new); } diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index eca94cbd1..11e5b4af4 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -165,13 +165,13 @@ class LoginPacketHandler extends PacketHandler{ if(!is_array($claims["extraData"])){ throw new PacketHandlingException("'extraData' key should be an array"); } - $mapper = new \JsonMapper; + $mapper = new \JsonMapper(); $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models $mapper->bExceptionOnMissingData = true; $mapper->bExceptionOnUndefinedProperty = true; try{ /** @var AuthenticationData $extraData */ - $extraData = $mapper->map($claims["extraData"], new AuthenticationData); + $extraData = $mapper->map($claims["extraData"], new AuthenticationData()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e); } @@ -193,12 +193,12 @@ class LoginPacketHandler extends PacketHandler{ throw PacketHandlingException::wrap($e); } - $mapper = new \JsonMapper; + $mapper = new \JsonMapper(); $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models $mapper->bExceptionOnMissingData = true; $mapper->bExceptionOnUndefinedProperty = true; try{ - $clientData = $mapper->map($clientDataClaims, new ClientData); + $clientData = $mapper->map($clientDataClaims, new ClientData()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e); } diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index c2ff31b3a..ddb221692 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -84,8 +84,8 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ $this->sleeper = new SleeperNotifier(); - $mainToThreadBuffer = new \Threaded; - $threadToMainBuffer = new \Threaded; + $mainToThreadBuffer = new \Threaded(); + $threadToMainBuffer = new \Threaded(); $this->rakLib = new RakLibServer( $this->server->getLogger(), diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index ae383fe8a..cbb917781 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -31,7 +31,7 @@ class PermissionManager{ public static function getInstance() : PermissionManager{ if(self::$instance === null){ - self::$instance = new self; + self::$instance = new self(); } return self::$instance; diff --git a/src/player/Player.php b/src/player/Player.php index 72b4884ac..75c87ff80 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -86,7 +86,7 @@ use pocketmine\inventory\PlayerCursorInventory; use pocketmine\inventory\TemporaryInventory; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\InventoryTransaction; -use pocketmine\inventory\transaction\TransactionBuilderInventory; +use pocketmine\inventory\transaction\TransactionBuilder; use pocketmine\inventory\transaction\TransactionCancelledException; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\ConsumableItem; @@ -1583,7 +1583,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $ev = new PlayerBlockPickEvent($this, $block, $item); $existingSlot = $this->inventory->first($item); - if($existingSlot === -1 && $this->hasFiniteResources()){ + if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){ $ev->cancel(); } $ev->call(); @@ -2444,29 +2444,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $inventories[] = $this->currentWindow; } - $transaction = new InventoryTransaction($this); - $mainInventoryTransactionBuilder = new TransactionBuilderInventory($this->inventory); + $builder = new TransactionBuilder(); foreach($inventories as $inventory){ $contents = $inventory->getContents(); if(count($contents) > 0){ - $drops = $mainInventoryTransactionBuilder->addItem(...$contents); + $drops = $builder->getInventory($this->inventory)->addItem(...$contents); foreach($drops as $drop){ - $transaction->addAction(new DropItemAction($drop)); + $builder->addAction(new DropItemAction($drop)); } - $clearedInventoryTransactionBuilder = new TransactionBuilderInventory($inventory); - $clearedInventoryTransactionBuilder->clearAll(); - foreach($clearedInventoryTransactionBuilder->generateActions() as $action){ - $transaction->addAction($action); - } + $builder->getInventory($inventory)->clearAll(); } } - foreach($mainInventoryTransactionBuilder->generateActions() as $action){ - $transaction->addAction($action); - } - if(count($transaction->getActions()) !== 0){ + $actions = $builder->generateActions(); + if(count($actions) !== 0){ + $transaction = new InventoryTransaction($this, $actions); try{ $transaction->execute(); $this->logger->debug("Successfully evacuated items from temporary inventories"); diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php index b42d78d74..76fe6c052 100644 --- a/src/scheduler/AsyncPool.php +++ b/src/scheduler/AsyncPool.php @@ -158,7 +158,7 @@ class AsyncPool{ throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once"); } - $task->progressUpdates = new \Threaded; + $task->progressUpdates = new \Threaded(); $task->setSubmitted(); $this->getWorker($worker)->stack($task); diff --git a/src/utils/SingletonTrait.php b/src/utils/SingletonTrait.php index 3873e0dff..3a95b8872 100644 --- a/src/utils/SingletonTrait.php +++ b/src/utils/SingletonTrait.php @@ -28,7 +28,7 @@ trait SingletonTrait{ private static $instance = null; private static function make() : self{ - return new self; + return new self(); } public static function getInstance() : self{ diff --git a/src/world/WorldCreationOptions.php b/src/world/WorldCreationOptions.php index bf97206fe..47dfcec40 100644 --- a/src/world/WorldCreationOptions.php +++ b/src/world/WorldCreationOptions.php @@ -48,7 +48,7 @@ final class WorldCreationOptions{ } public static function create() : self{ - return new self; + return new self(); } /** @phpstan-return class-string */ diff --git a/src/world/generator/normal/Normal.php b/src/world/generator/normal/Normal.php index f48f8f4fa..c03a06feb 100644 --- a/src/world/generator/normal/Normal.php +++ b/src/world/generator/normal/Normal.php @@ -123,6 +123,7 @@ class Normal extends Generator{ private function pickBiome(int $x, int $z) : Biome{ $hash = $x * 2345803 ^ $z * 9236449 ^ $this->seed; $hash *= $hash + 223; + $hash = (int) $hash; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; if($xNoise == 3){ diff --git a/tests/phpstan/analyse-for-current-php-version.neon.php b/tests/phpstan/analyse-for-current-php-version.neon.php new file mode 100644 index 000000000..6a7d74029 --- /dev/null +++ b/tests/phpstan/analyse-for-current-php-version.neon.php @@ -0,0 +1,32 @@ + [ + 'phpVersion' => PHP_VERSION_ID + ] +]; diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index a1c53d2a4..bfb3b1734 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -80,3 +80,8 @@ parameters: count: 2 path: ../../../src/world/format/io/region/RegionLoader.php + - + message: "#^Casting to int something that's already int\\.$#" + count: 1 + path: ../../../src/world/generator/normal/Normal.php +