diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 94b7bb99d..ad30aa4f2 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,9 +13,9 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.26.0 + uses: shivammathur/setup-php@2.27.1 with: - php-version: 8.1 + php-version: 8.2 - name: Restore Composer package cache uses: actions/cache@v3 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index f7026c646..d4f68ee0b 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [8.1] + php-version: [8.2] steps: - uses: actions/checkout@v4 @@ -20,7 +20,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.26.0 + uses: shivammathur/setup-php@2.27.1 with: php-version: ${{ matrix.php-version }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1fd1731a2..8bf6fd7d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -173,10 +173,10 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.26.0 + uses: shivammathur/setup-php@2.27.1 with: - php-version: 8.1 - tools: php-cs-fixer:3.17 + php-version: 8.2 + tools: php-cs-fixer:3.38 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/BUILDING.md b/BUILDING.md index 95197de6b..986f098e2 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -2,7 +2,7 @@ ## Pre-requisites - A bash shell (git bash is sufficient for Windows) - [`git`](https://git-scm.com) available in your shell -- PHP 8.1 or newer available in your shell +- PHP 8.2 or newer available in your shell - [`composer`](https://getcomposer.org) available in your shell ## Custom PHP binaries diff --git a/build/php b/build/php index a34e48e7d..39885cf24 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit a34e48e7da753b633ffaa4a4f9516eae4bb97baa +Subproject commit 39885cf24826773bc3a0e8134e04a2032e97f477 diff --git a/changelogs/5.7.md b/changelogs/5.7.md index 65830c220..9134f299e 100644 --- a/changelogs/5.7.md +++ b/changelogs/5.7.md @@ -18,3 +18,10 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if ## Fixes - Fixed `cartography_table`, `smithing_table`, `stripped_cherry_log` and `stripped_cherry_wood` not working in `StringToItemParser`. - Fixed `Promise::onCompletion()` always calling the reject handler if the promise was already completed. + +# 5.7.1 +Released 1st November 2023. + +## Fixes +- Fixed non-reentrant-safe code in `PermissionManager` and various other subscriber subsystems. + - These issues caused server crashes when deleting a subscriber indirectly triggered the deletion of other subscribers (e.g. due to the GC activating in `unset()`). diff --git a/changelogs/5.8.md b/changelogs/5.8.md new file mode 100644 index 000000000..7ae64742c --- /dev/null +++ b/changelogs/5.8.md @@ -0,0 +1,138 @@ +# 5.8.0 +Released 1st November 2023. + +**Borked release, forgot to merge branches.** + +# 5.8.1 +Released 1st November 2023. + +**For Minecraft: Bedrock Edition 1.20.40** + +This is a minor feature release, including new gameplay features, various performance improvements to internal `World` and `Block` systems, and changes to the API. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Neighbour block updates now have a separate timer for timings. Previously, these were counted under `Scheduled Block Updates`, which was misleading. + +## Performance +- `LightUpdate` now avoids attempting to propagate back in the same direction the light came from. This produces a small performance improvement of around 6% in light propagation. +- Improved worst-case (non-cached) performance of `World::getCollisionBlocks()` (and its successor `World::getBlockCollisionBlocks()`). + - While 5.5.0 introduced caching at the `World` level for AABBs, the cache was rarely useful due to entity and player movement being too unpredictable. This meant that most users saw a performance degradation with lots of moving entities, except in specific situations. + - Performance for fetching non-cached AABBs for a cell is now improved by 2x. Overall performance benefit to a server depends on the number of entities and players. +- Added cache for hydrated farmland blocks to remember the last known location of nearby water. + - If nearby water sources are not changed, this cache allows hydrated farmland to completely avoid checking up to 161 nearby blocks for water after the first check. + - Tests with large wheat farms showed a 25% performance improvement in overall server performance compared to previous 5.x versions. +- Migrated various internal enums to native PHP 8.1 enums. Bypassing magic `__callStatic()` accessors improved performance in many areas, although it's hard to quantify the exact benefit. +- Made use of `Facing::OFFSET` constant in various places to avoid unnecessary `Vector3` and `Position` object allocations. Many pathways benefit from this, including neighbour block updates (due to faster `Block::getSide()` and less useless objects). +- Avoided clearing block AABB caches except when strictly necessary. Previously, the cache was wiped every time blocks were read from the world, making them mostly useless. +- Avoided random updates on blocks which have reached their final state, such as fully-grown crops. This produces a minimal performance improvement. +- Removed useless checks in some `World` hot paths. + +## API +### General +- All enums have been migrated to native PHP 8.1 enums. + - For now, the old APIs and accessors are still usable (via `LegacyEnumShimTrait`), but these will be removed in the next major release. + - `EnumTrait` has been deprecated, and will be removed in the next major release. + - Migration for most plugin developers will simply involve deleting `()` from the end of enum case usages, which is a trivial change and also improves performance. + - Plugin usages of `EnumTrait` are encouraged to move to native enums, optionally using `LegacyEnumShimTrait` to provide backwards compatibility. + - See [this code](https://github.com/pmmp/PocketMine-MP/blob/9832fe899f13a8ea47cc9d73de7088f7775a12f5/src/block/utils/DyeColor.php#L85-L107) for an example of how to associate properties with enum cases (since native enums don't support this directly). + - Thanks to improvements in `RuntimeDataDescriber`, any native enum can now be used as a custom block's state property. + +### `pocketmine\block` +- The following classes have been added: + - `utils\AgeableTrait` - used by blocks which have an age property, such as crops + - `utils\StaticSupportTrait` - used by blocks which have the same support requirements regardless of their state, such as crops + +### `pocketmine\data\runtime` +- The following API methods have been added: + - `public RuntimeDataDescriber->boundedIntAuto(int $min, int $max, int &$value) : void` - similar to `boundedInt()`, but automatically calculates the needed number of bits based on the given min/max + - `public RuntimeDataDescriber->enum(T extends \UnitEnum &$case) : void` - describes any native PHP 8.1 enum case + - `public RuntimeDataDescriber->enumSet(array &$set, array $allCases) : void` - describes a set of enum cases (similar to bitflags) +- The following API methods have been deprecated: + - `RuntimeDataDescriber->bellAttachmentType()` - use `enum()` instead + - `RuntimeDataDescriber->boundedInt()` - use `boundedIntAuto()` instead + - `RuntimeDataDescriber->brewingStandSlots()` - use `enumSet()` instead + - `RuntimeDataDescriber->copperOxidation()` - use `enum()` instead + - `RuntimeDataDescriber->coralType()` - use `enum()` instead + - `RuntimeDataDescriber->dirtType()` - use `enum()` instead + - `RuntimeDataDescriber->dripleafState()` - use `enum()` instead + - `RuntimeDataDescriber->dyeColor()` - use `enum()` instead + - `RuntimeDataDescriber->froglightType()` - use `enum()` instead + - `RuntimeDataDescriber->leverFacing()` - use `enum()` instead + - `RuntimeDataDescriber->medicineType()` - use `enum()` instead + - `RuntimeDataDescriber->mobHeadType()` - use `enum()` instead + - `RuntimeDataDescriber->mushroomBlockType()` - use `enum()` instead + - `RuntimeDataDescriber->potionType()` - use `enum()` instead + - `RuntimeDataDescriber->slabType()` - use `enum()` instead + - `RuntimeDataDescriber->suspiciousStewType()` - use `enum()` instead + +### `pocketmine\player` +- `TitleID` is now included in `PlayerInfo` metadata for plugin consumption. + +### `pocketmine\world` +- The following API methods have been added: + - `public World->getBlockCollisionBoxes(AxisAlignedBB $bb) : list` - similar to `getCollisionBoxes` but exclusively for blocks, avoiding the need for conditionally useless parameters +- The following API methods have been deprecated: + - `World->getCollisionBoxes()` - use `getBlockCollisionBoxes()` instead (alongside `getCollidingEntities()` if entity collision boxes are also required) + +### `pocketmine\utils` +- The following classes have been deprecated: + - `EnumTrait` - use native PHP 8.1 enums instead +- The following classes have been added: + - `LegacyEnumShimTrait` - can be `use`d by native PHP 8.1 enums to provide the same API as `EnumTrait` + +## Gameplay +### Blocks +- Implemented the following blocks: + - Amethyst + - Amethyst Cluster + - Chiseled Bookshelf + - Crimson Roots + - Pitcher Crop + - Pitcher Plant + - Torchflower + - Torchflower Crop + - Warped Roots + +### Items +- Implemented the following items: + - Pitcher Pod + - Torchflower Seeds + +## Internals +- `Farmland` block now has an extra property (`waterPositionIndex`) stored in its blockstate ID to track the position of nearby water. This property is not saved to disk, and is only used for caching. +- The format of internal blockstate ID has been updated. + - The lower `11` bits are now reserved for state data (previously `8` bits). This increase facilitates the new cache for `Farmland` blocks. + - The state data bits are now XOR'd with the `xxh3` of the block's type ID, instead of being directly XOR'd with the type ID. + - This XOR improves the distribution of the lower bits of the blockstate ID, which is important for hashtable indexing to minimize collisions. + - Previously, the lower bits were XOR'd with the type ID directly, but the effectiveness of this reduced as more state data bits were added. + - `xxh3` produces consistently good results for this purpose regardless of the number of state data bits allocated. + - Hash collisions with blockstate IDs are reduced by 50% with this change, which is a happy side effect. + - This is backwards-incompatible, so anyone saving internal blockstate IDs on disk or in a DB will be burned by this change (though they shouldn't have been doing that anyway). +- Removed code generation step for `RuntimeDataDescriber` enum serialization. All described enums now use PHP 8.1 native enums, which can be described without codegen using `RuntimeDataDescriber->enum()`. +- Added `DeprecatedLegacyEnumAccessRule` custom PHPStan rule to flag legacy `EnumTrait` case accessors. +- Cleaned up remaining hardcoded `Config` keys in `SetupWizard`. These usages now use auto-generated constants like the rest of the codebase. + +# 5.8.2 +Released 9th November 2023. + +## Performance +- Improved performance of small packet zero-compression (unintended use of slow zlib compressor instead of fast libdeflate one). + - This affected the majority of outbound packets, as most packets are below the 256-byte threshold for compression. + - This faster method is over 20x faster than the old method, producing noticeable performance gains for large servers. + +## Fixes +- Fixed melons and pumpkins not growing. +- Fixed melon and pumpkin stems not attaching to the grown melon/pumpkin. +- Fixed iron and gold ores not being affected by the Fortune enchantment. +- Fixed ancient debris burning in lava. +- Fixed sign (front) text loading from vanilla world saves (back text is not yet supported). + +## Internals +- Removed bogus optimization from `tools/generate-blockstate-upgrade-schema.php` that could cause incorrect `remappedStates` generation when some of the states stayed under the old ID. +- Fixed possible crash in `BlockStateUpgrader` name flattening rule handling with invalid blockstate NBT data. diff --git a/composer.json b/composer.json index 1775f4e4d..c7bfd7d27 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/filesystem": "~6.3.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39", + "phpstan/phpstan": "1.10.41", "phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.2.0", "phpunit/phpunit": "~10.3.0 || ~10.2.0 || ~10.1.0" diff --git a/composer.lock b/composer.lock index cdb41d563..2888a3291 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "feefde772166966ee8065e613fe9a56e", + "content-hash": "5c19f4766fd04be0cbd38d9f4681864e", "packages": [ { "name": "adhocore/json-comment", @@ -1378,16 +1378,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.39", + "version": "1.10.41", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" + "reference": "c6174523c2a69231df55bdc65b61655e72876d76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76", + "reference": "c6174523c2a69231df55bdc65b61655e72876d76", "shasum": "" }, "require": { @@ -1436,7 +1436,7 @@ "type": "tidelift" } ], - "time": "2023-10-17T15:46:26+00:00" + "time": "2023-11-05T12:57:57+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1492,21 +1492,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6" + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542", + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10.34" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -1535,9 +1535,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2" }, - "time": "2023-03-29T14:47:40+00:00" + "time": "2023-10-30T14:35:06+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/BootstrapOptions.php b/src/BootstrapOptions.php new file mode 100644 index 000000000..879c502a6 --- /dev/null +++ b/src/BootstrapOptions.php @@ -0,0 +1,48 @@ += 0){ + $messages[] = "php-libdeflate ^0.2.0 is required, while you have $libdeflate_version."; + } + } + if(extension_loaded("pocketmine")){ $messages[] = "The native PocketMine extension is no longer supported."; } @@ -267,8 +274,8 @@ JIT_WARNING ErrorToExceptionHandler::set(); $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd()))); - $dataPath = getopt_string("data") ?? $cwd; - $pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins"; + $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd; + $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins"; Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX); if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){ @@ -301,10 +308,10 @@ JIT_WARNING //Logger has a dependency on timezone Timezone::init(); - $opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]); - if(isset($opts["enable-ansi"])){ + $opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI]); + if(isset($opts[BootstrapOptions::ENABLE_ANSI])){ Terminal::init(true); - }elseif(isset($opts["disable-ansi"])){ + }elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){ Terminal::init(false); }else{ Terminal::init(); @@ -317,7 +324,7 @@ JIT_WARNING $exitCode = 0; do{ - if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts["no-wizard"])){ + if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){ $installer = new SetupWizard($dataPath); if(!$installer->run()){ $exitCode = -1; diff --git a/src/Server.php b/src/Server.php index 5789638e3..6b3a91543 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1259,9 +1259,10 @@ class Server{ */ public function unsubscribeFromBroadcastChannel(string $channelId, CommandSender $subscriber) : void{ if(isset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)])){ - unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]); - if(count($this->broadcastSubscribers[$channelId]) === 0){ + if(count($this->broadcastSubscribers[$channelId]) === 1){ unset($this->broadcastSubscribers[$channelId]); + }else{ + unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]); } } } diff --git a/src/VersionInfo.php b/src/VersionInfo.php index e6cae5059..e0f4ca4f3 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 = "5.7.1"; + public const BASE_VERSION = "5.8.3"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/block/Block.php b/src/block/Block.php index a1d553b9d..b2847bb35 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -270,11 +270,22 @@ class Block{ } private function encodeFullState() : int{ - $writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits); - $writer->writeInt($this->requiredBlockItemStateDataBits, $this->encodeBlockItemState()); - $writer->writeInt($this->requiredBlockOnlyStateDataBits, $this->encodeBlockOnlyState()); + $blockItemBits = $this->requiredBlockItemStateDataBits; + $blockOnlyBits = $this->requiredBlockOnlyStateDataBits; - return $writer->getValue(); + if($blockOnlyBits === 0 && $blockItemBits === 0){ + return 0; + } + + $result = 0; + if($blockItemBits > 0){ + $result |= $this->encodeBlockItemState(); + } + if($blockOnlyBits > 0){ + $result |= $this->encodeBlockOnlyState() << $blockItemBits; + } + + return $result; } /** diff --git a/src/block/Crops.php b/src/block/Crops.php index 350268863..e90ac6236 100644 --- a/src/block/Crops.php +++ b/src/block/Crops.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CropGrowthHelper; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\item\Fertilizer; use pocketmine\item\Item; @@ -66,7 +67,7 @@ abstract class Crops extends Flowable{ } public function onRandomTick() : void{ - if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){ + if($this->age < self::MAX_AGE && CropGrowthHelper::canGrow($this)){ $block = clone $this; ++$block->age; BlockEventHelper::grow($this, $block, null); diff --git a/src/block/DoublePitcherCrop.php b/src/block/DoublePitcherCrop.php index db700a625..e34dd1baf 100644 --- a/src/block/DoublePitcherCrop.php +++ b/src/block/DoublePitcherCrop.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\AgeableTrait; +use pocketmine\block\utils\CropGrowthHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\event\block\StructureGrowEvent; use pocketmine\item\Fertilizer; @@ -34,7 +35,6 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -use function mt_rand; final class DoublePitcherCrop extends DoublePlant{ use AgeableTrait { @@ -101,9 +101,8 @@ final class DoublePitcherCrop extends DoublePlant{ } public function onRandomTick() : void{ - //TODO: the growth speed is influenced by farmland and nearby crops //only the bottom half of the plant can grow randomly - if(mt_rand(0, 2) === 0 && !$this->top){ + if(CropGrowthHelper::canGrow($this) && !$this->top){ $this->grow(null); } } diff --git a/src/block/GoldOre.php b/src/block/GoldOre.php index ae26d8b4e..75374c1b0 100644 --- a/src/block/GoldOre.php +++ b/src/block/GoldOre.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; final class GoldOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ - return [VanillaItems::RAW_GOLD()]; + return [VanillaItems::RAW_GOLD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))]; } public function isAffectedBySilkTouch() : bool{ return true; } diff --git a/src/block/IronOre.php b/src/block/IronOre.php index 8d7e9bd3f..11c8cc299 100644 --- a/src/block/IronOre.php +++ b/src/block/IronOre.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\FortuneDropHelper; use pocketmine\item\Item; use pocketmine\item\VanillaItems; final class IronOre extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ - return [VanillaItems::RAW_IRON()]; + return [VanillaItems::RAW_IRON()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))]; } public function isAffectedBySilkTouch() : bool{ return true; } diff --git a/src/block/PitcherCrop.php b/src/block/PitcherCrop.php index 2d9a02162..e0b9af3d2 100644 --- a/src/block/PitcherCrop.php +++ b/src/block/PitcherCrop.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CropGrowthHelper; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\event\block\StructureGrowEvent; use pocketmine\item\Fertilizer; @@ -35,7 +36,6 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -use function mt_rand; final class PitcherCrop extends Flowable{ use AgeableTrait; @@ -97,8 +97,7 @@ final class PitcherCrop extends Flowable{ } public function onRandomTick() : void{ - //TODO: the growth speed is influenced by farmland and nearby crops - if(mt_rand(0, 2) === 0){ + if(CropGrowthHelper::canGrow($this)){ $this->grow(null); } } diff --git a/src/block/Stem.php b/src/block/Stem.php index 7223572dd..2ac95aa3f 100644 --- a/src/block/Stem.php +++ b/src/block/Stem.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CropGrowthHelper; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -58,8 +59,12 @@ abstract class Stem extends Crops{ parent::onNearbyBlockChange(); } + public function ticksRandomly() : bool{ + return $this->age < self::MAX_AGE || $this->facing === Facing::UP; + } + public function onRandomTick() : void{ - if($this->facing === Facing::UP && mt_rand(0, 2) === 1){ + if($this->facing === Facing::UP && CropGrowthHelper::canGrow($this)){ $world = $this->position->getWorld(); if($this->age < self::MAX_AGE){ $block = clone $this; @@ -76,7 +81,9 @@ abstract class Stem extends Crops{ $facing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]; $side = $this->getSide($facing); if($side->getTypeId() === BlockTypeIds::AIR && $side->getSide(Facing::DOWN)->hasTypeTag(BlockTypeTags::DIRT)){ - BlockEventHelper::grow($side, $grow, null); + if(BlockEventHelper::grow($side, $grow, null)){ + $this->position->getWorld()->setBlock($this->position, $this->setFacing($facing)); + } } } } diff --git a/src/block/TorchflowerCrop.php b/src/block/TorchflowerCrop.php index 75efe142b..033b08552 100644 --- a/src/block/TorchflowerCrop.php +++ b/src/block/TorchflowerCrop.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\BlockEventHelper; +use pocketmine\block\utils\CropGrowthHelper; use pocketmine\block\utils\StaticSupportTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Fertilizer; @@ -32,7 +33,6 @@ use pocketmine\item\VanillaItems; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -use function mt_rand; final class TorchflowerCrop extends Flowable{ use StaticSupportTrait; @@ -79,7 +79,7 @@ final class TorchflowerCrop extends Flowable{ } public function onRandomTick() : void{ - if(mt_rand(0, 2) === 1){ + if(CropGrowthHelper::canGrow($this)){ BlockEventHelper::grow($this, $this->getNextState(), null); } } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 8349f70c0..67a77c183 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -1495,7 +1495,9 @@ final class VanillaBlocks{ //for some reason, slabs have weird hardness like the legacy ones $slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0)); - self::register("ancient_debris", new Opaque(new BID(Ids::ANCIENT_DEBRIS), "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0)))); + self::register("ancient_debris", new class(new BID(Ids::ANCIENT_DEBRIS), "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{ + public function isFireProofAsItem() : bool{ return true; } + }); $netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 3600.0)); self::register("netherite", new class(new BID(Ids::NETHERITE), "Netherite Block", $netheriteBreakInfo) extends Opaque{ public function isFireProofAsItem() : bool{ return true; } diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index d5d314ee3..2ced414ff 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -77,22 +77,30 @@ class Sign extends Spawnable{ parent::__construct($world, $pos); } + private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{ + $baseColor = new Color(0, 0, 0); + $glowingText = false; + if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){ + $baseColor = Color::fromARGB(Binary::unsignInt($baseColorTag->getValue())); + } + if($lightingBugResolved && ($glowingTextTag = $nbt->getTag(self::TAG_GLOWING_TEXT)) instanceof ByteTag){ + //both of these must be 1 - if only one is set, it's a leftover from 1.16.210 experimental features + //see https://bugs.mojang.com/browse/MCPE-117835 + $glowingText = $glowingTextTag->getValue() !== 0; + } + $this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); + } + public function readSaveData(CompoundTag $nbt) : void{ - if(($textBlobTag = $nbt->getTag(self::TAG_TEXT_BLOB)) instanceof StringTag){ //MCPE 1.2 save format - $baseColor = new Color(0, 0, 0); - $glowingText = false; - if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){ - $baseColor = Color::fromARGB(Binary::unsignInt($baseColorTag->getValue())); + $frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT); + if($frontTextTag instanceof CompoundTag){ + $this->readTextTag($frontTextTag, true); + }elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format + $lightingBugResolved = false; + if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){ + $lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0; } - if( - ($glowingTextTag = $nbt->getTag(self::TAG_GLOWING_TEXT)) instanceof ByteTag && - ($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag - ){ - //both of these must be 1 - if only one is set, it's a leftover from 1.16.210 experimental features - //see https://bugs.mojang.com/browse/MCPE-117835 - $glowingText = $glowingTextTag->getValue() !== 0 && $lightingBugResolvedTag->getValue() !== 0; - } - $this->text = SignText::fromBlob(mb_scrub($textBlobTag->getValue(), 'UTF-8'), $baseColor, $glowingText); + $this->readTextTag($nbt, $lightingBugResolved); }else{ $text = []; for($i = 0; $i < SignText::LINE_COUNT; ++$i){ @@ -107,15 +115,19 @@ class Sign extends Spawnable{ } protected function writeSaveData(CompoundTag $nbt) : void{ - $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())); + $nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() + ->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())) + ->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())) + ->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0) + ->setByte(self::TAG_PERSIST_FORMATTING, 1) + ); + $nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create() + ->setString(self::TAG_TEXT_BLOB, "") + ->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)) + ->setByte(self::TAG_GLOWING_TEXT, 0) + ->setByte(self::TAG_PERSIST_FORMATTING, 1) + ); - for($i = 0; $i < SignText::LINE_COUNT; ++$i){ //Backwards-compatibility - $textKey = sprintf(self::TAG_TEXT_LINE, $i + 1); - $nbt->setString($textKey, $this->text->getLine($i)); - } - $nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB())); - $nbt->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0); - $nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0); } diff --git a/src/block/utils/CropGrowthHelper.php b/src/block/utils/CropGrowthHelper.php new file mode 100644 index 000000000..e85b0b82d --- /dev/null +++ b/src/block/utils/CropGrowthHelper.php @@ -0,0 +1,120 @@ +getPosition(); + + $world = $position->getWorld(); + $baseX = $position->getFloorX(); + $baseY = $position->getFloorY(); + $baseZ = $position->getFloorZ(); + + $farmland = $world->getBlockAt($baseX, $baseY - 1, $baseZ); + + if($farmland instanceof Farmland){ + $result += $farmland->getWetness() > 0 ? self::ON_HYDRATED_FARMLAND_BONUS : self::ON_DRY_FARMLAND_BONUS; + } + + $xRow = false; + $zRow = false; + $improperArrangement = false; + + for($x = -1; $x <= 1; $x++){ + for($z = -1; $z <= 1; $z++){ + if($x === 0 && $z === 0){ + continue; + } + $nextFarmland = $world->getBlockAt($baseX + $x, $baseY - 1, $baseZ + $z); + + if(!$nextFarmland instanceof Farmland){ + continue; + } + + $result += $nextFarmland->getWetness() > 0 ? self::ADJACENT_HYDRATED_FARMLAND_BONUS : self::ADJACENT_DRY_FARMLAND_BONUS; + + if(!$improperArrangement){ + $nextCrop = $world->getBlockAt($baseX + $x, $baseY, $baseZ + $z); + if($nextCrop->hasSameTypeId($block)){ + match(0){ + $x => $zRow ? $improperArrangement = true : $xRow = true, + $z => $xRow ? $improperArrangement = true : $zRow = true, + default => $improperArrangement = true, + }; + } + } + } + } + + //crops can be arranged in rows, but the rows must not cross and must be spaced apart by at least one block + if($improperArrangement){ + $result /= self::IMPROPER_ARRANGEMENT_DIVISOR; + } + + return $result; + } + + public static function hasEnoughLight(Block $block, int $minLevel = self::MIN_LIGHT_LEVEL) : bool{ + $position = $block->getPosition(); + $world = $position->getWorld(); + + //crop growth is not affected by time of day since 1.11 or so + return $world->getPotentialLightAt($position->x, $position->y, $position->z) >= $minLevel; + } + + public static function canGrow(Block $block) : bool{ + //while it may be tempting to use mt_rand(0, 25) < multiplier, this would make crops grow a bit faster than + //vanilla in most cases due to the remainder of 25 / multiplier not being discarded + return mt_rand(0, (int) (25 / self::calculateMultiplier($block))) === 0 && self::hasEnoughLight($block); + } +} diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index 5c84cd383..582039305 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -86,7 +86,7 @@ final class BlockStateUpgrader{ if(is_string($remap->newName)){ $newName = $remap->newName; }else{ - $flattenedValue = $oldState[$remap->newName->flattenedProperty]; + $flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null; if($flattenedValue instanceof StringTag){ $newName = sprintf("%s%s%s", $remap->newName->prefix, $flattenedValue->getValue(), $remap->newName->suffix); unset($oldState[$remap->newName->flattenedProperty]); diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 6bb3a56b3..a2bed6ee1 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -193,6 +193,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::CLAY_BALL, Items::CLAY()); $this->map1to1Item(Ids::CLOCK, Items::CLOCK()); $this->map1to1Item(Ids::COAL, Items::COAL()); + $this->map1to1Item(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::COCOA_BEANS, Items::COCOA_BEANS()); $this->map1to1Item(Ids::COD, Items::RAW_FISH()); $this->map1to1Item(Ids::COMPASS, Items::COMPASS()); @@ -221,6 +222,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::DISC_FRAGMENT_5, Items::DISC_FRAGMENT_5()); $this->map1to1Item(Ids::DRAGON_BREATH, Items::DRAGON_BREATH()); $this->map1to1Item(Ids::DRIED_KELP, Items::DRIED_KELP()); + $this->map1to1Item(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::ECHO_SHARD, Items::ECHO_SHARD()); $this->map1to1Item(Ids::EGG, Items::EGG()); $this->map1to1Item(Ids::EMERALD, Items::EMERALD()); @@ -228,6 +230,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE()); $this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL()); $this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE()); + $this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::FEATHER, Items::FEATHER()); $this->map1to1Item(Ids::FERMENTED_SPIDER_EYE, Items::FERMENTED_SPIDER_EYE()); $this->map1to1Item(Ids::FIRE_CHARGE, Items::FIRE_CHARGE()); @@ -257,6 +260,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::HEART_OF_THE_SEA, Items::HEART_OF_THE_SEA()); $this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE()); $this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB()); + $this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::INK_SAC, Items::INK_SAC()); $this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE()); $this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS()); @@ -316,6 +320,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::NETHERITE_SCRAP, Items::NETHERITE_SCRAP()); $this->map1to1Item(Ids::NETHERITE_SHOVEL, Items::NETHERITE_SHOVEL()); $this->map1to1Item(Ids::NETHERITE_SWORD, Items::NETHERITE_SWORD()); + $this->map1to1Item(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT()); $this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN()); $this->map1to1Item(Ids::PAINTING, Items::PAINTING()); @@ -335,18 +340,25 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::RABBIT_FOOT, Items::RABBIT_FOOT()); $this->map1to1Item(Ids::RABBIT_HIDE, Items::RABBIT_HIDE()); $this->map1to1Item(Ids::RABBIT_STEW, Items::RABBIT_STEW()); + $this->map1to1Item(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER()); $this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD()); $this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON()); $this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST()); + $this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); $this->map1to1Item(Ids::SALMON, Items::RAW_SALMON()); $this->map1to1Item(Ids::SCUTE, Items::SCUTE()); + $this->map1to1Item(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE()); + $this->map1to1Item(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::SHEARS, Items::SHEARS()); $this->map1to1Item(Ids::SHULKER_SHELL, Items::SHULKER_SHELL()); + $this->map1to1Item(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::SLIME_BALL, Items::SLIMEBALL()); + $this->map1to1Item(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::SNOWBALL, Items::SNOWBALL()); $this->map1to1Item(Ids::SPIDER_EYE, Items::SPIDER_EYE()); + $this->map1to1Item(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::SPRUCE_BOAT, Items::SPRUCE_BOAT()); $this->map1to1Item(Ids::SPRUCE_SIGN, Items::SPRUCE_SIGN()); $this->map1to1Item(Ids::SPYGLASS, Items::SPYGLASS()); @@ -361,14 +373,19 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Item(Ids::SUGAR, Items::SUGAR()); $this->map1to1Item(Ids::SWEET_BERRIES, Items::SWEET_BERRIES()); $this->map1to1Item(Ids::TORCHFLOWER_SEEDS, Items::TORCHFLOWER_SEEDS()); + $this->map1to1Item(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::TOTEM_OF_UNDYING, Items::TOTEM()); $this->map1to1Item(Ids::TROPICAL_FISH, Items::CLOWNFISH()); $this->map1to1Item(Ids::TURTLE_HELMET, Items::TURTLE_HELMET()); + $this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::VILLAGER_SPAWN_EGG, Items::VILLAGER_SPAWN_EGG()); + $this->map1to1Item(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::WARPED_SIGN, Items::WARPED_SIGN()); $this->map1to1Item(Ids::WATER_BUCKET, Items::WATER_BUCKET()); + $this->map1to1Item(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::WHEAT, Items::WHEAT()); $this->map1to1Item(Ids::WHEAT_SEEDS, Items::WHEAT_SEEDS()); + $this->map1to1Item(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::WOODEN_AXE, Items::WOODEN_AXE()); $this->map1to1Item(Ids::WOODEN_HOE, Items::WOODEN_HOE()); $this->map1to1Item(Ids::WOODEN_PICKAXE, Items::WOODEN_PICKAXE()); diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 4c2f0a1ec..451d25a59 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -305,8 +305,25 @@ final class ItemTypeIds{ public const CHERRY_SIGN = 20266; public const ENCHANTED_BOOK = 20267; public const TORCHFLOWER_SEEDS = 20268; + public const NETHERITE_UPGRADE_SMITHING_TEMPLATE = 20269; + public const SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE = 20270; + public const VEX_ARMOR_TRIM_SMITHING_TEMPLATE = 20271; + public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = 20272; + public const COAST_ARMOR_TRIM_SMITHING_TEMPLATE = 20273; + public const DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = 20274; + public const WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = 20275; + public const RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = 20276; + public const SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = 20277; + public const HOST_ARMOR_TRIM_SMITHING_TEMPLATE = 20278; + public const WARD_ARMOR_TRIM_SMITHING_TEMPLATE = 20279; + public const SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = 20280; + public const TIDE_ARMOR_TRIM_SMITHING_TEMPLATE = 20281; + public const SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = 20282; + public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = 20283; + public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = 20284; + public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285; - public const FIRST_UNUSED_ITEM_ID = 20269; + public const FIRST_UNUSED_ITEM_ID = 20286; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index d4f242621..8d78fcf95 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1264,6 +1264,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("clown_fish", fn() => Items::CLOWNFISH()); $result->register("clownfish", fn() => Items::CLOWNFISH()); $result->register("coal", fn() => Items::COAL()); + $result->register("coast_armor_trim_smithing_template", fn() => Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("cocoa_beans", fn() => Items::COCOA_BEANS()); $result->register("cod", fn() => Items::RAW_FISH()); $result->register("compass", fn() => Items::COMPASS()); @@ -1292,6 +1293,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("disc_fragment_5", fn() => Items::DISC_FRAGMENT_5()); $result->register("dragon_breath", fn() => Items::DRAGON_BREATH()); $result->register("dried_kelp", fn() => Items::DRIED_KELP()); + $result->register("dune_armor_trim_smithing_template", fn() => Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("dye", fn() => Items::INK_SAC()); $result->register("echo_shard", fn() => Items::ECHO_SHARD()); $result->register("egg", fn() => Items::EGG()); @@ -1302,6 +1304,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE()); $result->register("ender_pearl", fn() => Items::ENDER_PEARL()); $result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE()); + $result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("eye_drops", fn() => Items::MEDICINE()->setType(MedicineType::EYE_DROPS)); $result->register("feather", fn() => Items::FEATHER()); $result->register("fermented_spider_eye", fn() => Items::FERMENTED_SPIDER_EYE()); @@ -1343,6 +1346,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("gunpowder", fn() => Items::GUNPOWDER()); $result->register("heart_of_the_sea", fn() => Items::HEART_OF_THE_SEA()); $result->register("honey_bottle", fn() => Items::HONEY_BOTTLE()); + $result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("honeycomb", fn() => Items::HONEYCOMB()); $result->register("ink_sac", fn() => Items::INK_SAC()); $result->register("iron_axe", fn() => Items::IRON_AXE()); @@ -1396,6 +1400,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("netherite_shovel", fn() => Items::NETHERITE_SHOVEL()); $result->register("netherite_sword", fn() => Items::NETHERITE_SWORD()); $result->register("netherstar", fn() => Items::NETHER_STAR()); + $result->register("netherite_upgrade_smithing_template", fn() => Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE()); $result->register("oak_boat", fn() => Items::OAK_BOAT()); $result->register("painting", fn() => Items::PAINTING()); $result->register("paper", fn() => Items::PAPER()); @@ -1416,6 +1421,7 @@ final class StringToItemParser extends StringToTParser{ $result->register("rabbit_foot", fn() => Items::RABBIT_FOOT()); $result->register("rabbit_hide", fn() => Items::RABBIT_HIDE()); $result->register("rabbit_stew", fn() => Items::RABBIT_STEW()); + $result->register("raiser_armor_trim_smithing_template", fn() => Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("raw_beef", fn() => Items::RAW_BEEF()); $result->register("raw_cod", fn() => Items::RAW_FISH()); $result->register("raw_copper", fn() => Items::RAW_COPPER()); @@ -1444,17 +1450,23 @@ final class StringToItemParser extends StringToTParser{ $result->register("record_ward", fn() => Items::RECORD_WARD()); $result->register("redstone", fn() => Items::REDSTONE_DUST()); $result->register("redstone_dust", fn() => Items::REDSTONE_DUST()); + $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("rotten_flesh", fn() => Items::ROTTEN_FLESH()); $result->register("salmon", fn() => Items::RAW_SALMON()); $result->register("scute", fn() => Items::SCUTE()); + $result->register("sentry_armor_trim_smithing_template", fn() => Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE()); + $result->register("shaper_armor_trim_smithing_template", fn() => Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("seeds", fn() => Items::WHEAT_SEEDS()); $result->register("shears", fn() => Items::SHEARS()); $result->register("shulker_shell", fn() => Items::SHULKER_SHELL()); + $result->register("silence_armor_trim_smithing_template", fn() => Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("slime_ball", fn() => Items::SLIMEBALL()); + $result->register("snout_armor_trim_smithing_template", fn() => Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("slimeball", fn() => Items::SLIMEBALL()); $result->register("snowball", fn() => Items::SNOWBALL()); $result->register("speckled_melon", fn() => Items::GLISTERING_MELON()); $result->register("spider_eye", fn() => Items::SPIDER_EYE()); + $result->register("spire_armor_trim_smithing_template", fn() => Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("splash_potion", fn() => Items::SPLASH_POTION()); $result->register("spruce_boat", fn() => Items::SPRUCE_BOAT()); $result->register("spyglass", fn() => Items::SPYGLASS()); @@ -1473,13 +1485,18 @@ final class StringToItemParser extends StringToTParser{ $result->register("sweet_berries", fn() => Items::SWEET_BERRIES()); $result->register("tonic", fn() => Items::MEDICINE()->setType(MedicineType::TONIC)); $result->register("torchflower_seeds", fn() => Items::TORCHFLOWER_SEEDS()); + $result->register("tide_armor_trim_smithing_template", fn() => Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("totem", fn() => Items::TOTEM()); $result->register("turtle_helmet", fn() => Items::TURTLE_HELMET()); + $result->register("vex_armor_trim_smithing_template", fn() => Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("turtle_shell_piece", fn() => Items::SCUTE()); $result->register("villager_spawn_egg", fn() => Items::VILLAGER_SPAWN_EGG()); + $result->register("ward_armor_trim_smithing_template", fn() => Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("water_bucket", fn() => Items::WATER_BUCKET()); + $result->register("wayfinder_armor_trim_smithing_template", fn() => Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("wheat", fn() => Items::WHEAT()); $result->register("wheat_seeds", fn() => Items::WHEAT_SEEDS()); + $result->register("wild_armor_trim_smithing_template", fn() => Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("wooden_axe", fn() => Items::WOODEN_AXE()); $result->register("wooden_hoe", fn() => Items::WOODEN_HOE()); $result->register("wooden_pickaxe", fn() => Items::WOODEN_PICKAXE()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index e07d4161b..5f4f60f8e 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -121,6 +121,7 @@ use function strtolower; * @method static Clock CLOCK() * @method static Clownfish CLOWNFISH() * @method static Coal COAL() + * @method static Item COAST_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static CocoaBeans COCOA_BEANS() * @method static Compass COMPASS() * @method static CookedChicken COOKED_CHICKEN() @@ -148,6 +149,7 @@ use function strtolower; * @method static Item DISC_FRAGMENT_5() * @method static Item DRAGON_BREATH() * @method static DriedKelp DRIED_KELP() + * @method static Item DUNE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Dye DYE() * @method static Item ECHO_SHARD() * @method static Egg EGG() @@ -156,6 +158,7 @@ use function strtolower; * @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE() * @method static EnderPearl ENDER_PEARL() * @method static ExperienceBottle EXPERIENCE_BOTTLE() + * @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item FEATHER() * @method static Item FERMENTED_SPIDER_EYE() * @method static FireCharge FIRE_CHARGE() @@ -185,6 +188,7 @@ use function strtolower; * @method static Item HEART_OF_THE_SEA() * @method static Item HONEYCOMB() * @method static HoneyBottle HONEY_BOTTLE() + * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item INK_SAC() * @method static Axe IRON_AXE() * @method static Armor IRON_BOOTS() @@ -227,6 +231,7 @@ use function strtolower; * @method static Item NETHERITE_SCRAP() * @method static Shovel NETHERITE_SHOVEL() * @method static Sword NETHERITE_SWORD() + * @method static Item NETHERITE_UPGRADE_SMITHING_TEMPLATE() * @method static Item NETHER_BRICK() * @method static Item NETHER_QUARTZ() * @method static Item NETHER_STAR() @@ -247,6 +252,7 @@ use function strtolower; * @method static Item RABBIT_FOOT() * @method static Item RABBIT_HIDE() * @method static RabbitStew RABBIT_STEW() + * @method static Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static RawBeef RAW_BEEF() * @method static RawChicken RAW_CHICKEN() * @method static Item RAW_COPPER() @@ -273,13 +279,19 @@ use function strtolower; * @method static Record RECORD_WAIT() * @method static Record RECORD_WARD() * @method static Redstone REDSTONE_DUST() + * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static RottenFlesh ROTTEN_FLESH() * @method static Item SCUTE() + * @method static Item SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE() + * @method static Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Shears SHEARS() * @method static Item SHULKER_SHELL() + * @method static Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item SLIMEBALL() + * @method static Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Snowball SNOWBALL() * @method static SpiderEye SPIDER_EYE() + * @method static Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static SplashPotion SPLASH_POTION() * @method static Boat SPRUCE_BOAT() * @method static ItemBlockWallOrFloor SPRUCE_SIGN() @@ -296,14 +308,19 @@ use function strtolower; * @method static Item SUGAR() * @method static SuspiciousStew SUSPICIOUS_STEW() * @method static SweetBerries SWEET_BERRIES() + * @method static Item TIDE_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static TorchflowerSeeds TORCHFLOWER_SEEDS() * @method static Totem TOTEM() * @method static TurtleHelmet TURTLE_HELMET() + * @method static Item VEX_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static SpawnEgg VILLAGER_SPAWN_EGG() + * @method static Item WARD_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static ItemBlockWallOrFloor WARPED_SIGN() * @method static LiquidBucket WATER_BUCKET() + * @method static Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item WHEAT() * @method static WheatSeeds WHEAT_SEEDS() + * @method static Item WILD_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Axe WOODEN_AXE() * @method static Hoe WOODEN_HOE() * @method static Pickaxe WOODEN_PICKAXE() @@ -339,6 +356,7 @@ final class VanillaItems{ self::registerArmorItems(); self::registerSpawnEggs(); self::registerTierToolItems(); + self::registerSmithingTemplates(); self::register("air", Blocks::AIR()->asItem()->setCount(0)); @@ -644,4 +662,24 @@ final class VanillaItems{ self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS])); } + private static function registerSmithingTemplates() : void{ + self::register("netherite_upgrade_smithing_template", new Item(new IID(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE), "Netherite Upgrade Smithing Template")); + self::register("coast_armor_trim_smithing_template", new Item(new IID(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE), "Coast Armor Trim Smithing Template")); + self::register("dune_armor_trim_smithing_template", new Item(new IID(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), "Dune Armor Trim Smithing Template")); + self::register("eye_armor_trim_smithing_template", new Item(new IID(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE), "Eye Armor Trim Smithing Template")); + self::register("host_armor_trim_smithing_template", new Item(new IID(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE), "Host Armor Trim Smithing Template")); + self::register("raiser_armor_trim_smithing_template", new Item(new IID(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE), "Raiser Armor Trim Smithing Template")); + self::register("rib_armor_trim_smithing_template", new Item(new IID(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE), "Rib Armor Trim Smithing Template")); + self::register("sentry_armor_trim_smithing_template", new Item(new IID(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE), "Sentry Armor Trim Smithing Template")); + self::register("shaper_armor_trim_smithing_template", new Item(new IID(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE), "Shaper Armor Trim Smithing Template")); + self::register("silence_armor_trim_smithing_template", new Item(new IID(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE), "Silence Armor Trim Smithing Template")); + self::register("snout_armor_trim_smithing_template", new Item(new IID(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE), "Snout Armor Trim Smithing Template")); + self::register("spire_armor_trim_smithing_template", new Item(new IID(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE), "Spire Armor Trim Smithing Template")); + self::register("tide_armor_trim_smithing_template", new Item(new IID(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE), "Tide Armor Trim Smithing Template")); + self::register("vex_armor_trim_smithing_template", new Item(new IID(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE), "Vex Armor Trim Smithing Template")); + self::register("ward_armor_trim_smithing_template", new Item(new IID(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE), "Ward Armor Trim Smithing Template")); + self::register("wayfinder_armor_trim_smithing_template", new Item(new IID(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE), "Wayfinder Armor Trim Smithing Template")); + self::register("wild_armor_trim_smithing_template", new Item(new IID(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE), "Wild Armor Trim Smithing Template")); + } + } diff --git a/src/network/mcpe/compression/ZlibCompressor.php b/src/network/mcpe/compression/ZlibCompressor.php index 317a64451..066ba876a 100644 --- a/src/network/mcpe/compression/ZlibCompressor.php +++ b/src/network/mcpe/compression/ZlibCompressor.php @@ -67,17 +67,12 @@ final class ZlibCompressor implements Compressor{ return $result; } - private static function zlib_encode(string $data, int $level) : string{ - return Utils::assumeNotFalse(zlib_encode($data, ZLIB_ENCODING_RAW, $level), "ZLIB compression failed"); - } - public function compress(string $payload) : string{ $compressible = $this->minCompressionSize !== null && strlen($payload) >= $this->minCompressionSize; - if(function_exists('libdeflate_deflate_compress')){ - return $compressible ? - libdeflate_deflate_compress($payload, $this->level) : - self::zlib_encode($payload, 0); - } - return self::zlib_encode($payload, $compressible ? $this->level : 0); + $level = $compressible ? $this->level : 0; + + return function_exists('libdeflate_deflate_compress') ? + libdeflate_deflate_compress($payload, $level) : + Utils::assumeNotFalse(zlib_encode($payload, ZLIB_ENCODING_RAW, $level), "ZLIB compression failed"); } } diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index 2d8324887..1291ba86b 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -72,19 +72,21 @@ class PermissionManager{ } public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{ - if(isset($this->permSubs[$permission])){ - unset($this->permSubs[$permission][spl_object_id($permissible)]); - if(count($this->permSubs[$permission]) === 0){ + if(isset($this->permSubs[$permission][spl_object_id($permissible)])){ + if(count($this->permSubs[$permission]) === 1){ unset($this->permSubs[$permission]); + }else{ + unset($this->permSubs[$permission][spl_object_id($permissible)]); } } } public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{ foreach($this->permSubs as $permission => $subs){ - unset($this->permSubs[$permission][spl_object_id($permissible)]); - if(count($this->permSubs[$permission]) === 0){ + if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){ unset($this->permSubs[$permission]); + }else{ + unset($this->permSubs[$permission][spl_object_id($permissible)]); } } } diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 520acae64..997a5f451 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -513,9 +513,12 @@ class PluginManager{ unset($this->enabledPlugins[$plugin->getDescription()->getName()]); foreach(Utils::stringifyKeys($this->pluginDependents) as $dependency => $dependentList){ - unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]); - if(count($this->pluginDependents[$dependency]) === 0){ - unset($this->pluginDependents[$dependency]); + if(isset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()])){ + if(count($this->pluginDependents[$dependency]) === 1){ + unset($this->pluginDependents[$dependency]); + }else{ + unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]); + } } } diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 053f7970c..db28ea503 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -36,6 +36,7 @@ use function get_class; use function str_starts_with; abstract class Timings{ + public const GROUP_MINECRAFT = "Minecraft"; public const GROUP_BREAKDOWN = "Minecraft - Breakdown"; private static bool $initialized = false; @@ -137,8 +138,8 @@ abstract class Timings{ self::$initialized = true; self::$fullTick = new TimingsHandler("Full Server Tick"); - self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick, group: self::GROUP_BREAKDOWN); - self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick, group: self::GROUP_BREAKDOWN); + self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick); + self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick); self::$memoryManager = new TimingsHandler("Memory Manager"); self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager); self::$titleTick = new TimingsHandler("Console Title Tick"); @@ -146,51 +147,51 @@ abstract class Timings{ self::$connection = new TimingsHandler("Connection Handler"); self::$playerNetworkSend = new TimingsHandler("Player Network Send", self::$connection); - self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); - self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN); - self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN); - self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); - self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); - self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); + self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend); + self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress); + self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress); + self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend); + self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend); + self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend); self::$playerNetworkReceive = new TimingsHandler("Player Network Receive", self::$connection); - self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN); - self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN); + self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive); + self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive); - self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); + self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend); self::$playerMove = new TimingsHandler("Player Movement"); self::$playerChunkOrder = new TimingsHandler("Player Order Chunks"); - self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); + self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend); self::$scheduler = new TimingsHandler("Scheduler"); self::$serverCommand = new TimingsHandler("Server Command"); self::$permissibleCalculation = new TimingsHandler("Permissible Calculation"); - self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN); - self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN); + self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation); + self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation); self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load"); self::$syncPlayerDataSave = new TimingsHandler("Player Data Save"); - self::$entityMove = new TimingsHandler("Entity Movement", group: self::GROUP_BREAKDOWN); - self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove, group: self::GROUP_BREAKDOWN); + self::$entityMove = new TimingsHandler("Entity Movement"); + self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove); - self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove, group: self::GROUP_BREAKDOWN); - self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN); + self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove); + self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove); - self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN); - self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN); - self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN); - self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN); + self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities"); + self::$entityBaseTick = new TimingsHandler("Entity Base Tick"); + self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living"); + self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity"); - self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN); + self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks"); - self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN); - self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync, group: self::GROUP_BREAKDOWN); - self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN); - self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN); + self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks"); + self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync); + self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync); + self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync); - self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN); - self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN); + self::$playerCommand = new TimingsHandler("Player Command"); + self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache"); } @@ -232,7 +233,7 @@ abstract class Timings{ }else{ $displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\"); } - self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN); + self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName); } return self::$entityTypeTimingMap[$entity::class]; @@ -242,8 +243,7 @@ abstract class Timings{ self::init(); if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){ self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler( - "Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"), - group: self::GROUP_BREAKDOWN + "Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\") ); } @@ -253,7 +253,7 @@ abstract class Timings{ public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{ self::init(); if(!isset(self::$packetReceiveTimingMap[$pk::class])){ - self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN); + self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive); } return self::$packetReceiveTimingMap[$pk::class]; @@ -262,31 +262,28 @@ abstract class Timings{ public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{ return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler( "Decode - " . $pk->getName(), - self::getReceiveDataPacketTimings($pk), - group: self::GROUP_BREAKDOWN + self::getReceiveDataPacketTimings($pk) ); } public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{ return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler( "Handler - " . $pk->getName(), - self::getReceiveDataPacketTimings($pk), - group: self::GROUP_BREAKDOWN + self::getReceiveDataPacketTimings($pk) ); } public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{ return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler( "Encode - " . $pk->getName(), - self::getSendDataPacketTimings($pk), - group: self::GROUP_BREAKDOWN + self::getSendDataPacketTimings($pk) ); } public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{ self::init(); if(!isset(self::$packetSendTimingMap[$pk::class])){ - self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN); + self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend); } return self::$packetSendTimingMap[$pk::class]; @@ -295,7 +292,7 @@ abstract class Timings{ public static function getCommandDispatchTimings(string $commandName) : TimingsHandler{ self::init(); - return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName, group: self::GROUP_BREAKDOWN); + return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName); } public static function getEventTimings(Event $event) : TimingsHandler{ @@ -328,7 +325,7 @@ abstract class Timings{ return self::$eventHandlers[$event][$handlerName]; } - public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{ + public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ $taskClass = $task::class; if(!isset(self::$asyncTaskProgressUpdate[$taskClass])){ self::init(); @@ -342,7 +339,7 @@ abstract class Timings{ return self::$asyncTaskProgressUpdate[$taskClass]; } - public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{ + public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ $taskClass = $task::class; if(!isset(self::$asyncTaskCompletion[$taskClass])){ self::init(); @@ -356,7 +353,7 @@ abstract class Timings{ return self::$asyncTaskCompletion[$taskClass]; } - public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{ + public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ $taskClass = $task::class; if(!isset(self::$asyncTaskError[$taskClass])){ self::init(); diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php index ba6c3cfea..574dd6d2b 100644 --- a/src/timings/TimingsHandler.php +++ b/src/timings/TimingsHandler.php @@ -120,7 +120,7 @@ class TimingsHandler{ public function __construct( private string $name, private ?TimingsHandler $parent = null, - private string $group = "Minecraft" + private string $group = Timings::GROUP_MINECRAFT ){} public function getName() : string{ return $this->name; } diff --git a/src/world/World.php b/src/world/World.php index 1ecbe20a9..f554479c9 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -826,14 +826,15 @@ class World implements ChunkManager{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $loaderId = spl_object_id($loader); if(isset($this->chunkLoaders[$chunkHash][$loaderId])){ - unset($this->chunkLoaders[$chunkHash][$loaderId]); - if(count($this->chunkLoaders[$chunkHash]) === 0){ + if(count($this->chunkLoaders[$chunkHash]) === 1){ unset($this->chunkLoaders[$chunkHash]); $this->unloadChunkRequest($chunkX, $chunkZ, true); if(isset($this->chunkPopulationRequestMap[$chunkHash]) && !isset($this->activeChunkPopulationTasks[$chunkHash])){ $this->chunkPopulationRequestMap[$chunkHash]->reject(); unset($this->chunkPopulationRequestMap[$chunkHash]); } + }else{ + unset($this->chunkLoaders[$chunkHash][$loaderId]); } } } @@ -861,11 +862,12 @@ class World implements ChunkManager{ public function unregisterChunkListener(ChunkListener $listener, int $chunkX, int $chunkZ) : void{ $hash = World::chunkHash($chunkX, $chunkZ); if(isset($this->chunkListeners[$hash])){ - unset($this->chunkListeners[$hash][spl_object_id($listener)]); - unset($this->playerChunkListeners[$hash][spl_object_id($listener)]); - if(count($this->chunkListeners[$hash]) === 0){ + if(count($this->chunkListeners[$hash]) === 1){ unset($this->chunkListeners[$hash]); unset($this->playerChunkListeners[$hash]); + }else{ + unset($this->chunkListeners[$hash][spl_object_id($listener)]); + unset($this->playerChunkListeners[$hash][spl_object_id($listener)]); } } } @@ -1219,13 +1221,14 @@ class World implements ChunkManager{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $tickerId = spl_object_id($ticker); if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){ - unset($this->registeredTickingChunks[$chunkHash][$tickerId]); - if(count($this->registeredTickingChunks[$chunkHash]) === 0){ + if(count($this->registeredTickingChunks[$chunkHash]) === 1){ unset( $this->registeredTickingChunks[$chunkHash], $this->recheckTickingChunks[$chunkHash], $this->validTickingChunks[$chunkHash] ); + }else{ + unset($this->registeredTickingChunks[$chunkHash][$tickerId]); } } } @@ -1663,16 +1666,19 @@ class World implements ChunkManager{ } /** - * Returns the highest available level of any type of light at the given coordinates, adjusted for the current - * weather and time of day. + * Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and + * time of day. */ public function getFullLight(Vector3 $pos) : int{ - return $this->getFullLightAt($pos->x, $pos->y, $pos->z); + $floorX = $pos->getFloorX(); + $floorY = $pos->getFloorY(); + $floorZ = $pos->getFloorZ(); + return $this->getFullLightAt($floorX, $floorY, $floorZ); } /** - * Returns the highest available level of any type of light at the given coordinates, adjusted for the current - * weather and time of day. + * Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and + * time of day. */ public function getFullLightAt(int $x, int $y, int $z) : int{ $skyLight = $this->getRealBlockSkyLightAt($x, $y, $z); @@ -1684,18 +1690,43 @@ class World implements ChunkManager{ } /** - * Returns the highest available level of any type of light at, or adjacent to, the given coordinates, adjusted for - * the current weather and time of day. + * Returns the highest level of any type of light at, or adjacent to, the given coordinates, adjusted for the + * current weather and time of day. */ public function getHighestAdjacentFullLightAt(int $x, int $y, int $z) : int{ return $this->getHighestAdjacentLight($x, $y, $z, $this->getFullLightAt(...)); } + /** + * Returns the highest potential level of any type of light at the target coordinates. + * This is not affected by weather or time of day. + */ + public function getPotentialLight(Vector3 $pos) : int{ + $floorX = $pos->getFloorX(); + $floorY = $pos->getFloorY(); + $floorZ = $pos->getFloorZ(); + return $this->getPotentialLightAt($floorX, $floorY, $floorZ); + } + + /** + * Returns the highest potential level of any type of light at the target coordinates. + * This is not affected by weather or time of day. + */ + public function getPotentialLightAt(int $x, int $y, int $z) : int{ + return max($this->getPotentialBlockSkyLightAt($x, $y, $z), $this->getBlockLightAt($x, $y, $z)); + } + + /** + * Returns the highest potential level of any type of light at, or adjacent to, the given coordinates. + * This is not affected by weather or time of day. + */ + public function getHighestAdjacentPotentialLightAt(int $x, int $y, int $z) : int{ + return $this->getHighestAdjacentLight($x, $y, $z, $this->getPotentialLightAt(...)); + } + /** * Returns the highest potential level of sky light at the target coordinates, regardless of the time of day or * weather conditions. - * You usually don't want to use this for vanilla gameplay logic; prefer the real sky light instead. - * @see World::getRealBlockSkyLightAt() * * @return int 0-15 */ @@ -2292,9 +2323,6 @@ class World implements ChunkManager{ for($x = $minX; $x <= $maxX; ++$x){ for($z = $minZ; $z <= $maxZ; ++$z){ - if(!$this->isChunkLoaded($x, $z)){ - continue; - } foreach($this->getChunkEntities($x, $z) as $ent){ if($ent !== $entity && $ent->boundingBox->intersectsWith($bb)){ $nearby[] = $ent; @@ -2335,9 +2363,6 @@ class World implements ChunkManager{ for($x = $minX; $x <= $maxX; ++$x){ for($z = $minZ; $z <= $maxZ; ++$z){ - if(!$this->isChunkLoaded($x, $z)){ - continue; - } foreach($this->getChunkEntities($x, $z) as $entity){ if(!($entity instanceof $entityType) || $entity->isFlaggedForDespawn() || (!$includeDead && !$entity->isAlive())){ continue; @@ -2664,9 +2689,10 @@ class World implements ChunkManager{ $pos = $this->entityLastKnownPositions[$entity->getId()]; $chunkHash = World::chunkHash($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE); if(isset($this->entitiesByChunk[$chunkHash][$entity->getId()])){ - unset($this->entitiesByChunk[$chunkHash][$entity->getId()]); - if(count($this->entitiesByChunk[$chunkHash]) === 0){ + if(count($this->entitiesByChunk[$chunkHash]) === 1){ unset($this->entitiesByChunk[$chunkHash]); + }else{ + unset($this->entitiesByChunk[$chunkHash][$entity->getId()]); } } unset($this->entityLastKnownPositions[$entity->getId()]); @@ -2699,9 +2725,10 @@ class World implements ChunkManager{ if($oldChunkX !== $newChunkX || $oldChunkZ !== $newChunkZ){ $oldChunkHash = World::chunkHash($oldChunkX, $oldChunkZ); if(isset($this->entitiesByChunk[$oldChunkHash][$entity->getId()])){ - unset($this->entitiesByChunk[$oldChunkHash][$entity->getId()]); - if(count($this->entitiesByChunk[$oldChunkHash]) === 0){ + if(count($this->entitiesByChunk[$oldChunkHash]) === 1){ unset($this->entitiesByChunk[$oldChunkHash]); + }else{ + unset($this->entitiesByChunk[$oldChunkHash][$entity->getId()]); } } diff --git a/src/world/WorldTimings.php b/src/world/WorldTimings.php index 2d3a2090c..9a43828c2 100644 --- a/src/world/WorldTimings.php +++ b/src/world/WorldTimings.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\world; -use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; class WorldTimings{ @@ -66,7 +65,7 @@ class WorldTimings{ private static function newTimer(string $worldName, string $timerName) : TimingsHandler{ $aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table - return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN); + return new TimingsHandler("$worldName - $timerName", $aggregator); } public function __construct(World $world){ diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index f48f611ee..023f11ff3 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -425,6 +425,21 @@ parameters: count: 3 path: ../../../src/block/tile/Spawnable.php + - + message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/utils/CropGrowthHelper.php + + - + message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/utils/CropGrowthHelper.php + + - + message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/utils/CropGrowthHelper.php + - message: "#^Cannot call method addParticle\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" count: 1 @@ -910,11 +925,6 @@ parameters: count: 1 path: ../../../src/world/World.php - - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" - count: 1 - path: ../../../src/world/World.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" count: 1 @@ -940,11 +950,6 @@ parameters: count: 1 path: ../../../src/world/World.php - - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" - count: 1 - path: ../../../src/world/World.php - - message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" count: 1 @@ -975,11 +980,6 @@ parameters: count: 1 path: ../../../src/world/World.php - - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" - count: 1 - path: ../../../src/world/World.php - - message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" count: 1 diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index f247d6112..dfb8f6066 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -37,7 +37,6 @@ use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; -use function array_filter; use function array_key_first; use function array_keys; use function array_map; @@ -481,16 +480,9 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad throw new \RuntimeException("States with the same ID should be fully consistent"); } }else{ - if(isset($newNameFound[$oldName])){ - //some of the states stayed under the same ID - we can process these as normal states - $stateGroup = array_filter($blockStateMappings, fn(BlockStateMapping $m) => $m->new->getName() === $oldName); - if(processStateGroup($oldName, $stateGroup, $result)){ - foreach(Utils::stringifyKeys($stateGroup) as $k => $mapping){ - unset($blockStateMappings[$k]); - } - } - } //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap + //even if some of the states stay under the same ID, the compression techniques used by this function + //implicitly rely on knowing the full set of old states and their new transformations $result->remappedStates[$oldName] = processRemappedStates($blockStateMappings); } }