diff --git a/build/generate-biome-ids.php b/build/generate-biome-ids.php new file mode 100644 index 00000000000..f36591fe4de --- /dev/null +++ b/build/generate-biome-ids.php @@ -0,0 +1,133 @@ + $map + */ +function generate(array $map, string $outputFile) : void{ + $file = safe_fopen($outputFile, 'wb'); + fwrite($file, HEADER); + fwrite($file, <<<'CLASSHEADER' +namespace pocketmine\data\bedrock; + +final class BiomeIds{ + + private function __construct(){ + //NOOP + } + + +CLASSHEADER +); + $list = $map; + asort($list, SORT_NUMERIC); + $lastId = -1; + foreach(Utils::stringifyKeys($list) as $name => $id){ + if($name === ""){ + continue; + } + if($id !== $lastId + 1){ + fwrite($file, "\n"); + } + $lastId = $id; + fwrite($file, "\tpublic const " . make_const_name($name) . ' = ' . $id . ';' . "\n"); + } + fwrite($file, "}\n"); + fclose($file); +} + +$ids = json_decode(Filesystem::fileGetContents(BedrockDataFiles::BIOME_ID_MAP_JSON), true); +if(!is_array($ids)){ + throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object"); +} +$cleanedIds = []; +foreach($ids as $name => $id){ + if(!is_string($name) || !is_int($id)){ + throw new \RuntimeException("Invalid biome ID map, expected string => int map"); + } + $cleanedIds[$name] = $id; +} +generate($cleanedIds, dirname(__DIR__) . '/src/data/bedrock/BiomeIds.php'); + +echo "Done. Don't forget to run CS fixup after generating code.\n"; diff --git a/changelogs/5.12.md b/changelogs/5.12.md new file mode 100644 index 00000000000..f1857efa4e9 --- /dev/null +++ b/changelogs/5.12.md @@ -0,0 +1,54 @@ +# 5.12.0 +Released 28th February 2024 + +**For Minecraft: Bedrock Edition 1.20.60** + +This is a minor feature release, with a few new features and improvements. + +**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 +- Added a `--version` command-line option to display the server version and exit. + +## Tools +- Added `tools/generate-biome-ids.php` to generate `pocketmine\data\bedrock\BiomeIds`. +- Fixed ordering of property values generated by `tools/generate-block-palette-spec.php`. + +## API +### `pocketmine\block` +- The following new classes have been added: + - `utils\LightableTrait` - used by blocks with `getLit()` and `setLit()` methods +- The following methods have been deprecated: + - `Block->isSolid()` - this method returns confusing results which don't match expectations and no one really knows what it actually means +- `CocoaBlock` now extends `Flowable` to match vanilla Minecraft behaviour. + +### `pocketmine\plugin` +- `PluginManager->registerEvent()` now throws an exception when given a generator function for the event handler. +- `PluginManager->registerEvents()` now throws an exception if any of the detected event handlers are generator functions. Use `@notHandler` to have the function ignored if intended. + +### `pocketmine\promise` +- The following methods have been added: + - `public static Promise::all(list $promises) : Promise` - returns a promise that is resolved once all given promises are resolved, or is rejected if any of the promises are rejected. + +### `pocketmine\scheduler` +- The following methods have been deprecated: + - `AsyncWorker->getFromThreadStore()` - use class static properties for thread-local storage + - `AsyncWorker->removeFromThreadStore()` + - `AsyncWorker->saveToThreadStore()` + +## Documentation +- Improved documentation of various methods in `Block`. + +## Gameplay +- The following new items have been added: + - Name Tag + +## Internals +- Removed specialization of shutdown logic for `Thread` vs `Worker` (no specialization is required). +- Authentication system no longer accepts logins signed with the old Mojang root public key. +- ID to enum mappings in `pocketmine\data` now use a new `match` convention to allow static analysis to ensure that all enum cases are handled. +- Updated version of `pocketmine/bedrock-protocol` allows avoiding decoding of some itemstack data from the client in most cases, improving performance. diff --git a/composer.lock b/composer.lock index d08ab6bcf42..674fef03d30 100644 --- a/composer.lock +++ b/composer.lock @@ -151,12 +151,12 @@ "source": { "type": "git", "url": "https://github.com/NetherGamesMC/BedrockProtocol.git", - "reference": "5df8ad061673a8d3457dc6092d4b77e26134f246" + "reference": "e9544c381721044b651fb9dd9b16a03b71a8ed2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/NetherGamesMC/BedrockProtocol/zipball/5df8ad061673a8d3457dc6092d4b77e26134f246", - "reference": "5df8ad061673a8d3457dc6092d4b77e26134f246", + "url": "https://api.github.com/repos/NetherGamesMC/BedrockProtocol/zipball/e9544c381721044b651fb9dd9b16a03b71a8ed2c", + "reference": "e9544c381721044b651fb9dd9b16a03b71a8ed2c", "shasum": "" }, "require": { @@ -169,7 +169,7 @@ "ramsey/uuid": "^4.1" }, "require-dev": { - "phpstan/phpstan": "1.10.39", + "phpstan/phpstan": "1.10.59", "phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-strict-rules": "^1.0.0", "phpunit/phpunit": "^9.5 || ^10.0" @@ -198,7 +198,7 @@ "support": { "source": "https://github.com/NetherGamesMC/BedrockProtocol/tree/master" }, - "time": "2024-02-23T19:54:37+00:00" + "time": "2024-03-01T22:49:59+00:00" }, { "name": "pocketmine/bedrock-block-upgrade-schema", diff --git a/src/BootstrapOptions.php b/src/BootstrapOptions.php index 879c502a69d..c34dda94b99 100644 --- a/src/BootstrapOptions.php +++ b/src/BootstrapOptions.php @@ -45,4 +45,6 @@ private function __construct(){ public const PLUGINS = "plugins"; /** Path to store and load server data */ public const DATA = "data"; + /** Shows basic server version information and exits */ + public const VERSION = "version"; } diff --git a/src/PocketMine.php b/src/PocketMine.php index 8878bc0b276..7dc1acdf2c9 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -25,6 +25,7 @@ use Composer\InstalledVersions; use pocketmine\errorhandler\ErrorToExceptionHandler; + use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\thread\ThreadManager; use pocketmine\thread\ThreadSafeClassLoader; use pocketmine\utils\Filesystem; @@ -40,14 +41,17 @@ use function extension_loaded; use function function_exists; use function getcwd; + use function getopt; use function is_dir; use function mkdir; use function phpversion; use function preg_match; use function preg_quote; + use function printf; use function realpath; use function version_compare; use const DIRECTORY_SEPARATOR; + use const PHP_EOL; require_once __DIR__ . '/VersionInfo.php'; @@ -166,7 +170,7 @@ function check_platform_dependencies(){ * @return void */ function emit_performance_warnings(\Logger $logger){ - if(PHP_DEBUG !== 0){ + if(ZEND_DEBUG_BUILD){ $logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance."); } if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){ @@ -273,6 +277,11 @@ function server(){ ErrorToExceptionHandler::set(); + if(count(getopt("", [BootstrapOptions::VERSION])) > 0){ + printf("%s %s (git hash %s) for Minecraft: Bedrock Edition %s\n", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true), VersionInfo::GIT_HASH(), ProtocolInfo::MINECRAFT_VERSION); + exit(0); + } + $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd()))); $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd; $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins"; diff --git a/src/Server.php b/src/Server.php index 77009f9a30f..8728ce87bc8 100644 --- a/src/Server.php +++ b/src/Server.php @@ -59,7 +59,6 @@ use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\ProtocolInfo; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm; use pocketmine\network\mcpe\raklib\RakLibInterface; use pocketmine\network\mcpe\StandardEntityEventBroadcaster; @@ -1225,12 +1224,11 @@ private function startupPrepareConnectableNetworkInterfaces( bool $useQuery, PacketBroadcaster $packetBroadcaster, EntityEventBroadcaster $entityEventBroadcaster, - PacketSerializerContext $packetSerializerContext, TypeConverter $typeConverter ) : bool{ $prettyIp = $ipV6 ? "[$ip]" : $ip; try{ - $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter)); + $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $typeConverter)); }catch(NetworkInterfaceStartException $e){ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( $ip, @@ -1257,15 +1255,14 @@ private function startupPrepareNetworkInterfaces() : bool{ $useQuery = $this->configGroup->getConfigBool(ServerProperties::ENABLE_QUERY, true); $typeConverter = TypeConverter::getInstance(); - $packetSerializerContext = $this->getPacketSerializerContext($typeConverter); - $packetBroadcaster = $this->getPacketBroadcaster($packetSerializerContext); + $packetBroadcaster = $this->getPacketBroadcaster($typeConverter); $entityEventBroadcaster = $this->getEntityEventBroadcaster($packetBroadcaster, $typeConverter); if( - !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter) || + !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter) || ( $this->configGroup->getConfigBool(ServerProperties::ENABLE_IPV6, true) && - !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter) + !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter) ) ){ return false; @@ -1408,13 +1405,12 @@ public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn * * @param bool|null $sync Compression on the main thread (true) or workers (false). Default is automatic (null). */ - public function prepareBatch(string $buffer, PacketSerializerContext $packetSerializerContext, Compressor $compressor, ?bool $sync = null, ?TimingsHandler $timings = null) : CompressBatchPromise|string{ + public function prepareBatch(string $buffer, int $protocolId, Compressor $compressor, ?bool $sync = null, ?TimingsHandler $timings = null) : CompressBatchPromise|string{ $timings ??= Timings::$playerNetworkSendCompress; try{ $timings->startTiming(); $threshold = $compressor->getCompressionThreshold(); - $protocolId = $packetSerializerContext->getProtocolId(); if(($threshold === null || strlen($buffer) < $compressor->getCompressionThreshold()) && $protocolId >= ProtocolInfo::PROTOCOL_1_20_60){ $compressionType = CompressionAlgorithm::NONE; $compressed = $buffer; diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 946e53a2328..794ed17d71a 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.11.3"; + public const BASE_VERSION = "5.12.1"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/block/BaseBanner.php b/src/block/BaseBanner.php index c2bbaf737a6..6b9e493d19e 100644 --- a/src/block/BaseBanner.php +++ b/src/block/BaseBanner.php @@ -34,7 +34,6 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; -use function array_filter; use function assert; use function count; @@ -89,11 +88,12 @@ public function getPatterns() : array{ * @return $this */ public function setPatterns(array $patterns) : self{ - $checked = array_filter($patterns, fn($v) => $v instanceof BannerPatternLayer); - if(count($checked) !== count($patterns)){ - throw new \TypeError("Deque must only contain " . BannerPatternLayer::class . " objects"); + foreach($patterns as $pattern){ + if(!$pattern instanceof BannerPatternLayer){ + throw new \TypeError("Array must only contain " . BannerPatternLayer::class . " objects"); + } } - $this->patterns = $checked; + $this->patterns = $patterns; return $this; } diff --git a/src/block/Block.php b/src/block/Block.php index b2847bb3583..dbc269c6301 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -402,7 +402,7 @@ public function writeStateToWorld() : void{ } /** - * AKA: Block->isPlaceable + * Returns whether this block can be placed when obtained as an item. */ public function canBePlaced() : bool{ return true; @@ -572,16 +572,28 @@ public function blocksDirectSkyLight() : bool{ return $this->getLightFilter() > 0; } + /** + * Returns whether this block allows any light to pass through it. + */ public function isTransparent() : bool{ return false; } + /** + * @deprecated TL;DR: Don't use this function. Its results are confusing and inconsistent. + * + * No one is sure what the meaning of this property actually is. It's borrowed from Minecraft Java Edition, and is + * used by various blocks for support checks. + * + * Things like signs and banners are considered "solid" despite having no collision box, and things like skulls and + * flower pots are considered non-solid despite obviously being "solid" in the conventional, real-world sense. + */ public function isSolid() : bool{ return true; } /** - * AKA: Block->isFlowable + * Returns whether this block can be destroyed by liquid flowing into its cell. */ public function canBeFlowedInto() : bool{ return false; diff --git a/src/block/CocoaBlock.php b/src/block/CocoaBlock.php index 5cec4b933a2..6d8ce1adc9c 100644 --- a/src/block/CocoaBlock.php +++ b/src/block/CocoaBlock.php @@ -26,7 +26,6 @@ use pocketmine\block\utils\AgeableTrait; use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\HorizontalFacingTrait; -use pocketmine\block\utils\SupportType; use pocketmine\block\utils\WoodType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Fertilizer; @@ -40,7 +39,7 @@ use pocketmine\world\BlockTransaction; use function mt_rand; -class CocoaBlock extends Transparent{ +class CocoaBlock extends Flowable{ use HorizontalFacingTrait; use AgeableTrait; @@ -65,10 +64,6 @@ protected function recalculateCollisionBoxes() : array{ ]; } - public function getSupportType(int $facing) : SupportType{ - return SupportType::NONE; - } - private function canAttachTo(Block $block) : bool{ return $block instanceof Wood && $block->getWoodType() === WoodType::JUNGLE; } diff --git a/src/block/Furnace.php b/src/block/Furnace.php index fbff73c9398..7a64e3cd3e5 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -25,6 +25,7 @@ use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; +use pocketmine\block\utils\LightableTrait; use pocketmine\crafting\FurnaceType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -34,11 +35,10 @@ class Furnace extends Opaque{ use FacesOppositePlacingPlayerTrait; + use LightableTrait; protected FurnaceType $furnaceType; - protected bool $lit = false; - public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, FurnaceType $furnaceType){ $this->furnaceType = $furnaceType; parent::__construct($idInfo, $name, $typeInfo); @@ -57,18 +57,6 @@ public function getLightLevel() : int{ return $this->lit ? 13 : 0; } - public function isLit() : bool{ - return $this->lit; - } - - /** - * @return $this - */ - public function setLit(bool $lit = true) : self{ - $this->lit = $lit; - return $this; - } - public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ $furnace = $this->position->getWorld()->getTile($this->position); diff --git a/src/block/RedstoneOre.php b/src/block/RedstoneOre.php index 9e537bd279a..10e701a6f4c 100644 --- a/src/block/RedstoneOre.php +++ b/src/block/RedstoneOre.php @@ -24,7 +24,7 @@ namespace pocketmine\block; use pocketmine\block\utils\FortuneDropHelper; -use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\block\utils\LightableTrait; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\math\Vector3; @@ -32,23 +32,7 @@ use function mt_rand; class RedstoneOre extends Opaque{ - protected bool $lit = false; - - protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ - $w->bool($this->lit); - } - - public function isLit() : bool{ - return $this->lit; - } - - /** - * @return $this - */ - public function setLit(bool $lit = true) : self{ - $this->lit = $lit; - return $this; - } + use LightableTrait; public function getLightLevel() : int{ return $this->lit ? 9 : 0; diff --git a/src/block/RedstoneTorch.php b/src/block/RedstoneTorch.php index b30c011d4d8..26c86038b95 100644 --- a/src/block/RedstoneTorch.php +++ b/src/block/RedstoneTorch.php @@ -23,28 +23,22 @@ namespace pocketmine\block; +use pocketmine\block\utils\LightableTrait; use pocketmine\data\runtime\RuntimeDataDescriber; class RedstoneTorch extends Torch{ - protected bool $lit = true; + use LightableTrait; + + public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ + $this->lit = true; + parent::__construct($idInfo, $name, $typeInfo); + } protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ parent::describeBlockOnlyState($w); $w->bool($this->lit); } - public function isLit() : bool{ - return $this->lit; - } - - /** - * @return $this - */ - public function setLit(bool $lit = true) : self{ - $this->lit = $lit; - return $this; - } - public function getLightLevel() : int{ return $this->lit ? 7 : 0; } diff --git a/src/block/utils/CandleTrait.php b/src/block/utils/CandleTrait.php index 58a7443a3ce..c9da97ee0cc 100644 --- a/src/block/utils/CandleTrait.php +++ b/src/block/utils/CandleTrait.php @@ -24,7 +24,6 @@ namespace pocketmine\block\utils; use pocketmine\block\Block; -use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\entity\projectile\Projectile; use pocketmine\item\Durable; use pocketmine\item\enchantment\VanillaEnchantments; @@ -38,24 +37,12 @@ use pocketmine\world\sound\FlintSteelSound; trait CandleTrait{ - private bool $lit = false; - - protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ - $w->bool($this->lit); - } + use LightableTrait; public function getLightLevel() : int{ return $this->lit ? 3 : 0; } - public function isLit() : bool{ return $this->lit; } - - /** @return $this */ - public function setLit(bool $lit) : self{ - $this->lit = $lit; - return $this; - } - /** @see Block::onInteract() */ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE || $item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){ diff --git a/src/block/utils/LightableTrait.php b/src/block/utils/LightableTrait.php new file mode 100644 index 00000000000..51ce54f42c1 --- /dev/null +++ b/src/block/utils/LightableTrait.php @@ -0,0 +1,46 @@ +bool($this->lit); + } + + public function isLit() : bool{ + return $this->lit; + } + + /** + * @return $this + */ + public function setLit(bool $lit = true) : self{ + $this->lit = $lit; + return $this; + } +} diff --git a/src/data/bedrock/BannerPatternTypeIdMap.php b/src/data/bedrock/BannerPatternTypeIdMap.php index 064844e199f..87f9b8f5713 100644 --- a/src/data/bedrock/BannerPatternTypeIdMap.php +++ b/src/data/bedrock/BannerPatternTypeIdMap.php @@ -43,44 +43,48 @@ final class BannerPatternTypeIdMap{ private array $enumToId = []; public function __construct(){ - $this->register("bo", BannerPatternType::BORDER); - $this->register("bri", BannerPatternType::BRICKS); - $this->register("mc", BannerPatternType::CIRCLE); - $this->register("cre", BannerPatternType::CREEPER); - $this->register("cr", BannerPatternType::CROSS); - $this->register("cbo", BannerPatternType::CURLY_BORDER); - $this->register("lud", BannerPatternType::DIAGONAL_LEFT); - $this->register("rd", BannerPatternType::DIAGONAL_RIGHT); - $this->register("ld", BannerPatternType::DIAGONAL_UP_LEFT); - $this->register("rud", BannerPatternType::DIAGONAL_UP_RIGHT); - $this->register("flo", BannerPatternType::FLOWER); - $this->register("gra", BannerPatternType::GRADIENT); - $this->register("gru", BannerPatternType::GRADIENT_UP); - $this->register("hh", BannerPatternType::HALF_HORIZONTAL); - $this->register("hhb", BannerPatternType::HALF_HORIZONTAL_BOTTOM); - $this->register("vh", BannerPatternType::HALF_VERTICAL); - $this->register("vhr", BannerPatternType::HALF_VERTICAL_RIGHT); - $this->register("moj", BannerPatternType::MOJANG); - $this->register("mr", BannerPatternType::RHOMBUS); - $this->register("sku", BannerPatternType::SKULL); - $this->register("ss", BannerPatternType::SMALL_STRIPES); - $this->register("bl", BannerPatternType::SQUARE_BOTTOM_LEFT); - $this->register("br", BannerPatternType::SQUARE_BOTTOM_RIGHT); - $this->register("tl", BannerPatternType::SQUARE_TOP_LEFT); - $this->register("tr", BannerPatternType::SQUARE_TOP_RIGHT); - $this->register("sc", BannerPatternType::STRAIGHT_CROSS); - $this->register("bs", BannerPatternType::STRIPE_BOTTOM); - $this->register("cs", BannerPatternType::STRIPE_CENTER); - $this->register("dls", BannerPatternType::STRIPE_DOWNLEFT); - $this->register("drs", BannerPatternType::STRIPE_DOWNRIGHT); - $this->register("ls", BannerPatternType::STRIPE_LEFT); - $this->register("ms", BannerPatternType::STRIPE_MIDDLE); - $this->register("rs", BannerPatternType::STRIPE_RIGHT); - $this->register("ts", BannerPatternType::STRIPE_TOP); - $this->register("bt", BannerPatternType::TRIANGLE_BOTTOM); - $this->register("tt", BannerPatternType::TRIANGLE_TOP); - $this->register("bts", BannerPatternType::TRIANGLES_BOTTOM); - $this->register("tts", BannerPatternType::TRIANGLES_TOP); + foreach(BannerPatternType::cases() as $case){ + $this->register(match($case){ + BannerPatternType::BORDER => "bo", + BannerPatternType::BRICKS => "bri", + BannerPatternType::CIRCLE => "mc", + BannerPatternType::CREEPER => "cre", + BannerPatternType::CROSS => "cr", + BannerPatternType::CURLY_BORDER => "cbo", + BannerPatternType::DIAGONAL_LEFT => "lud", + BannerPatternType::DIAGONAL_RIGHT => "rd", + BannerPatternType::DIAGONAL_UP_LEFT => "ld", + BannerPatternType::DIAGONAL_UP_RIGHT => "rud", + BannerPatternType::FLOWER => "flo", + BannerPatternType::GRADIENT => "gra", + BannerPatternType::GRADIENT_UP => "gru", + BannerPatternType::HALF_HORIZONTAL => "hh", + BannerPatternType::HALF_HORIZONTAL_BOTTOM => "hhb", + BannerPatternType::HALF_VERTICAL => "vh", + BannerPatternType::HALF_VERTICAL_RIGHT => "vhr", + BannerPatternType::MOJANG => "moj", + BannerPatternType::RHOMBUS => "mr", + BannerPatternType::SKULL => "sku", + BannerPatternType::SMALL_STRIPES => "ss", + BannerPatternType::SQUARE_BOTTOM_LEFT => "bl", + BannerPatternType::SQUARE_BOTTOM_RIGHT => "br", + BannerPatternType::SQUARE_TOP_LEFT => "tl", + BannerPatternType::SQUARE_TOP_RIGHT => "tr", + BannerPatternType::STRAIGHT_CROSS => "sc", + BannerPatternType::STRIPE_BOTTOM => "bs", + BannerPatternType::STRIPE_CENTER => "cs", + BannerPatternType::STRIPE_DOWNLEFT => "dls", + BannerPatternType::STRIPE_DOWNRIGHT => "drs", + BannerPatternType::STRIPE_LEFT => "ls", + BannerPatternType::STRIPE_MIDDLE => "ms", + BannerPatternType::STRIPE_RIGHT => "rs", + BannerPatternType::STRIPE_TOP => "ts", + BannerPatternType::TRIANGLE_BOTTOM => "bt", + BannerPatternType::TRIANGLE_TOP => "tt", + BannerPatternType::TRIANGLES_BOTTOM => "bts", + BannerPatternType::TRIANGLES_TOP => "tts", + }, $case); + } } public function register(string $stringId, BannerPatternType $type) : void{ diff --git a/src/data/bedrock/BiomeIds.php b/src/data/bedrock/BiomeIds.php index ac955527805..1169a51eaee 100644 --- a/src/data/bedrock/BiomeIds.php +++ b/src/data/bedrock/BiomeIds.php @@ -111,4 +111,15 @@ private function __construct(){ public const CRIMSON_FOREST = 179; public const WARPED_FOREST = 180; public const BASALT_DELTAS = 181; + public const JAGGED_PEAKS = 182; + public const FROZEN_PEAKS = 183; + public const SNOWY_SLOPES = 184; + public const GROVE = 185; + public const MEADOW = 186; + public const LUSH_CAVES = 187; + public const DRIPSTONE_CAVES = 188; + public const STONY_PEAKS = 189; + public const DEEP_DARK = 190; + public const MANGROVE_SWAMP = 191; + public const CHERRY_GROVE = 192; } diff --git a/src/data/bedrock/DyeColorIdMap.php b/src/data/bedrock/DyeColorIdMap.php index a360e4f910a..60c50970595 100644 --- a/src/data/bedrock/DyeColorIdMap.php +++ b/src/data/bedrock/DyeColorIdMap.php @@ -48,22 +48,28 @@ final class DyeColorIdMap{ private array $enumToItemId = []; private function __construct(){ - $this->register(0, ItemTypeNames::WHITE_DYE, DyeColor::WHITE); - $this->register(1, ItemTypeNames::ORANGE_DYE, DyeColor::ORANGE); - $this->register(2, ItemTypeNames::MAGENTA_DYE, DyeColor::MAGENTA); - $this->register(3, ItemTypeNames::LIGHT_BLUE_DYE, DyeColor::LIGHT_BLUE); - $this->register(4, ItemTypeNames::YELLOW_DYE, DyeColor::YELLOW); - $this->register(5, ItemTypeNames::LIME_DYE, DyeColor::LIME); - $this->register(6, ItemTypeNames::PINK_DYE, DyeColor::PINK); - $this->register(7, ItemTypeNames::GRAY_DYE, DyeColor::GRAY); - $this->register(8, ItemTypeNames::LIGHT_GRAY_DYE, DyeColor::LIGHT_GRAY); - $this->register(9, ItemTypeNames::CYAN_DYE, DyeColor::CYAN); - $this->register(10, ItemTypeNames::PURPLE_DYE, DyeColor::PURPLE); - $this->register(11, ItemTypeNames::BLUE_DYE, DyeColor::BLUE); - $this->register(12, ItemTypeNames::BROWN_DYE, DyeColor::BROWN); - $this->register(13, ItemTypeNames::GREEN_DYE, DyeColor::GREEN); - $this->register(14, ItemTypeNames::RED_DYE, DyeColor::RED); - $this->register(15, ItemTypeNames::BLACK_DYE, DyeColor::BLACK); + foreach(DyeColor::cases() as $case){ + [$colorId, $dyeItemId] = match($case){ + DyeColor::WHITE => [0, ItemTypeNames::WHITE_DYE], + DyeColor::ORANGE => [1, ItemTypeNames::ORANGE_DYE], + DyeColor::MAGENTA => [2, ItemTypeNames::MAGENTA_DYE], + DyeColor::LIGHT_BLUE => [3, ItemTypeNames::LIGHT_BLUE_DYE], + DyeColor::YELLOW => [4, ItemTypeNames::YELLOW_DYE], + DyeColor::LIME => [5, ItemTypeNames::LIME_DYE], + DyeColor::PINK => [6, ItemTypeNames::PINK_DYE], + DyeColor::GRAY => [7, ItemTypeNames::GRAY_DYE], + DyeColor::LIGHT_GRAY => [8, ItemTypeNames::LIGHT_GRAY_DYE], + DyeColor::CYAN => [9, ItemTypeNames::CYAN_DYE], + DyeColor::PURPLE => [10, ItemTypeNames::PURPLE_DYE], + DyeColor::BLUE => [11, ItemTypeNames::BLUE_DYE], + DyeColor::BROWN => [12, ItemTypeNames::BROWN_DYE], + DyeColor::GREEN => [13, ItemTypeNames::GREEN_DYE], + DyeColor::RED => [14, ItemTypeNames::RED_DYE], + DyeColor::BLACK => [15, ItemTypeNames::BLACK_DYE], + }; + + $this->register($colorId, $dyeItemId, $case); + } } private function register(int $id, string $itemId, DyeColor $color) : void{ diff --git a/src/data/bedrock/MedicineTypeIdMap.php b/src/data/bedrock/MedicineTypeIdMap.php index 00d1f27a866..90fd835509d 100644 --- a/src/data/bedrock/MedicineTypeIdMap.php +++ b/src/data/bedrock/MedicineTypeIdMap.php @@ -32,9 +32,13 @@ final class MedicineTypeIdMap{ use IntSaveIdMapTrait; private function __construct(){ - $this->register(MedicineTypeIds::ANTIDOTE, MedicineType::ANTIDOTE); - $this->register(MedicineTypeIds::ELIXIR, MedicineType::ELIXIR); - $this->register(MedicineTypeIds::EYE_DROPS, MedicineType::EYE_DROPS); - $this->register(MedicineTypeIds::TONIC, MedicineType::TONIC); + foreach(MedicineType::cases() as $case){ + $this->register(match($case){ + MedicineType::ANTIDOTE => MedicineTypeIds::ANTIDOTE, + MedicineType::ELIXIR => MedicineTypeIds::ELIXIR, + MedicineType::EYE_DROPS => MedicineTypeIds::EYE_DROPS, + MedicineType::TONIC => MedicineTypeIds::TONIC, + }, $case); + } } } diff --git a/src/data/bedrock/MobHeadTypeIdMap.php b/src/data/bedrock/MobHeadTypeIdMap.php index ec678b19241..bf16e6eba0a 100644 --- a/src/data/bedrock/MobHeadTypeIdMap.php +++ b/src/data/bedrock/MobHeadTypeIdMap.php @@ -32,12 +32,16 @@ final class MobHeadTypeIdMap{ use IntSaveIdMapTrait; private function __construct(){ - $this->register(0, MobHeadType::SKELETON); - $this->register(1, MobHeadType::WITHER_SKELETON); - $this->register(2, MobHeadType::ZOMBIE); - $this->register(3, MobHeadType::PLAYER); - $this->register(4, MobHeadType::CREEPER); - $this->register(5, MobHeadType::DRAGON); - $this->register(6, MobHeadType::PIGLIN); + foreach(MobHeadType::cases() as $case){ + $this->register(match($case){ + MobHeadType::SKELETON => 0, + MobHeadType::WITHER_SKELETON => 1, + MobHeadType::ZOMBIE => 2, + MobHeadType::PLAYER => 3, + MobHeadType::CREEPER => 4, + MobHeadType::DRAGON => 5, + MobHeadType::PIGLIN => 6, + }, $case); + } } } diff --git a/src/data/bedrock/MushroomBlockTypeIdMap.php b/src/data/bedrock/MushroomBlockTypeIdMap.php index 92edef4b23f..a25336d897d 100644 --- a/src/data/bedrock/MushroomBlockTypeIdMap.php +++ b/src/data/bedrock/MushroomBlockTypeIdMap.php @@ -33,16 +33,20 @@ final class MushroomBlockTypeIdMap{ use IntSaveIdMapTrait; public function __construct(){ - $this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, MushroomBlockType::PORES); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, MushroomBlockType::CAP_NORTHWEST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, MushroomBlockType::CAP_NORTH); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, MushroomBlockType::CAP_NORTHEAST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, MushroomBlockType::CAP_WEST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, MushroomBlockType::CAP_MIDDLE); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, MushroomBlockType::CAP_EAST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, MushroomBlockType::CAP_SOUTHWEST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, MushroomBlockType::CAP_SOUTH); - $this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, MushroomBlockType::CAP_SOUTHEAST); - $this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, MushroomBlockType::ALL_CAP); + foreach(MushroomBlockType::cases() as $case){ + $this->register(match($case){ + MushroomBlockType::PORES => LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, + MushroomBlockType::CAP_NORTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, + MushroomBlockType::CAP_NORTH => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, + MushroomBlockType::CAP_NORTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, + MushroomBlockType::CAP_WEST => LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, + MushroomBlockType::CAP_MIDDLE => LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, + MushroomBlockType::CAP_EAST => LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, + MushroomBlockType::CAP_SOUTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, + MushroomBlockType::CAP_SOUTH => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, + MushroomBlockType::CAP_SOUTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, + MushroomBlockType::ALL_CAP => LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, + }, $case); + } } } diff --git a/src/data/bedrock/NoteInstrumentIdMap.php b/src/data/bedrock/NoteInstrumentIdMap.php index e721b881d2e..c847ecd98c0 100644 --- a/src/data/bedrock/NoteInstrumentIdMap.php +++ b/src/data/bedrock/NoteInstrumentIdMap.php @@ -32,21 +32,25 @@ final class NoteInstrumentIdMap{ use IntSaveIdMapTrait; private function __construct(){ - $this->register(0, NoteInstrument::PIANO); - $this->register(1, NoteInstrument::BASS_DRUM); - $this->register(2, NoteInstrument::SNARE); - $this->register(3, NoteInstrument::CLICKS_AND_STICKS); - $this->register(4, NoteInstrument::DOUBLE_BASS); - $this->register(5, NoteInstrument::BELL); - $this->register(6, NoteInstrument::FLUTE); - $this->register(7, NoteInstrument::CHIME); - $this->register(8, NoteInstrument::GUITAR); - $this->register(9, NoteInstrument::XYLOPHONE); - $this->register(10, NoteInstrument::IRON_XYLOPHONE); - $this->register(11, NoteInstrument::COW_BELL); - $this->register(12, NoteInstrument::DIDGERIDOO); - $this->register(13, NoteInstrument::BIT); - $this->register(14, NoteInstrument::BANJO); - $this->register(15, NoteInstrument::PLING); + foreach(NoteInstrument::cases() as $case){ + $this->register(match($case){ + NoteInstrument::PIANO => 0, + NoteInstrument::BASS_DRUM => 1, + NoteInstrument::SNARE => 2, + NoteInstrument::CLICKS_AND_STICKS => 3, + NoteInstrument::DOUBLE_BASS => 4, + NoteInstrument::BELL => 5, + NoteInstrument::FLUTE => 6, + NoteInstrument::CHIME => 7, + NoteInstrument::GUITAR => 8, + NoteInstrument::XYLOPHONE => 9, + NoteInstrument::IRON_XYLOPHONE => 10, + NoteInstrument::COW_BELL => 11, + NoteInstrument::DIDGERIDOO => 12, + NoteInstrument::BIT => 13, + NoteInstrument::BANJO => 14, + NoteInstrument::PLING => 15, + }, $case); + } } } diff --git a/src/data/bedrock/PotionTypeIdMap.php b/src/data/bedrock/PotionTypeIdMap.php index 3fef20f68ce..4e7d8d4c79f 100644 --- a/src/data/bedrock/PotionTypeIdMap.php +++ b/src/data/bedrock/PotionTypeIdMap.php @@ -32,48 +32,52 @@ final class PotionTypeIdMap{ use IntSaveIdMapTrait; private function __construct(){ - $this->register(PotionTypeIds::WATER, PotionType::WATER); - $this->register(PotionTypeIds::MUNDANE, PotionType::MUNDANE); - $this->register(PotionTypeIds::LONG_MUNDANE, PotionType::LONG_MUNDANE); - $this->register(PotionTypeIds::THICK, PotionType::THICK); - $this->register(PotionTypeIds::AWKWARD, PotionType::AWKWARD); - $this->register(PotionTypeIds::NIGHT_VISION, PotionType::NIGHT_VISION); - $this->register(PotionTypeIds::LONG_NIGHT_VISION, PotionType::LONG_NIGHT_VISION); - $this->register(PotionTypeIds::INVISIBILITY, PotionType::INVISIBILITY); - $this->register(PotionTypeIds::LONG_INVISIBILITY, PotionType::LONG_INVISIBILITY); - $this->register(PotionTypeIds::LEAPING, PotionType::LEAPING); - $this->register(PotionTypeIds::LONG_LEAPING, PotionType::LONG_LEAPING); - $this->register(PotionTypeIds::STRONG_LEAPING, PotionType::STRONG_LEAPING); - $this->register(PotionTypeIds::FIRE_RESISTANCE, PotionType::FIRE_RESISTANCE); - $this->register(PotionTypeIds::LONG_FIRE_RESISTANCE, PotionType::LONG_FIRE_RESISTANCE); - $this->register(PotionTypeIds::SWIFTNESS, PotionType::SWIFTNESS); - $this->register(PotionTypeIds::LONG_SWIFTNESS, PotionType::LONG_SWIFTNESS); - $this->register(PotionTypeIds::STRONG_SWIFTNESS, PotionType::STRONG_SWIFTNESS); - $this->register(PotionTypeIds::SLOWNESS, PotionType::SLOWNESS); - $this->register(PotionTypeIds::LONG_SLOWNESS, PotionType::LONG_SLOWNESS); - $this->register(PotionTypeIds::WATER_BREATHING, PotionType::WATER_BREATHING); - $this->register(PotionTypeIds::LONG_WATER_BREATHING, PotionType::LONG_WATER_BREATHING); - $this->register(PotionTypeIds::HEALING, PotionType::HEALING); - $this->register(PotionTypeIds::STRONG_HEALING, PotionType::STRONG_HEALING); - $this->register(PotionTypeIds::HARMING, PotionType::HARMING); - $this->register(PotionTypeIds::STRONG_HARMING, PotionType::STRONG_HARMING); - $this->register(PotionTypeIds::POISON, PotionType::POISON); - $this->register(PotionTypeIds::LONG_POISON, PotionType::LONG_POISON); - $this->register(PotionTypeIds::STRONG_POISON, PotionType::STRONG_POISON); - $this->register(PotionTypeIds::REGENERATION, PotionType::REGENERATION); - $this->register(PotionTypeIds::LONG_REGENERATION, PotionType::LONG_REGENERATION); - $this->register(PotionTypeIds::STRONG_REGENERATION, PotionType::STRONG_REGENERATION); - $this->register(PotionTypeIds::STRENGTH, PotionType::STRENGTH); - $this->register(PotionTypeIds::LONG_STRENGTH, PotionType::LONG_STRENGTH); - $this->register(PotionTypeIds::STRONG_STRENGTH, PotionType::STRONG_STRENGTH); - $this->register(PotionTypeIds::WEAKNESS, PotionType::WEAKNESS); - $this->register(PotionTypeIds::LONG_WEAKNESS, PotionType::LONG_WEAKNESS); - $this->register(PotionTypeIds::WITHER, PotionType::WITHER); - $this->register(PotionTypeIds::TURTLE_MASTER, PotionType::TURTLE_MASTER); - $this->register(PotionTypeIds::LONG_TURTLE_MASTER, PotionType::LONG_TURTLE_MASTER); - $this->register(PotionTypeIds::STRONG_TURTLE_MASTER, PotionType::STRONG_TURTLE_MASTER); - $this->register(PotionTypeIds::SLOW_FALLING, PotionType::SLOW_FALLING); - $this->register(PotionTypeIds::LONG_SLOW_FALLING, PotionType::LONG_SLOW_FALLING); - $this->register(PotionTypeIds::STRONG_SLOWNESS, PotionType::STRONG_SLOWNESS); + foreach(PotionType::cases() as $case){ + $this->register(match($case){ + PotionType::WATER => PotionTypeIds::WATER, + PotionType::MUNDANE => PotionTypeIds::MUNDANE, + PotionType::LONG_MUNDANE => PotionTypeIds::LONG_MUNDANE, + PotionType::THICK => PotionTypeIds::THICK, + PotionType::AWKWARD => PotionTypeIds::AWKWARD, + PotionType::NIGHT_VISION => PotionTypeIds::NIGHT_VISION, + PotionType::LONG_NIGHT_VISION => PotionTypeIds::LONG_NIGHT_VISION, + PotionType::INVISIBILITY => PotionTypeIds::INVISIBILITY, + PotionType::LONG_INVISIBILITY => PotionTypeIds::LONG_INVISIBILITY, + PotionType::LEAPING => PotionTypeIds::LEAPING, + PotionType::LONG_LEAPING => PotionTypeIds::LONG_LEAPING, + PotionType::STRONG_LEAPING => PotionTypeIds::STRONG_LEAPING, + PotionType::FIRE_RESISTANCE => PotionTypeIds::FIRE_RESISTANCE, + PotionType::LONG_FIRE_RESISTANCE => PotionTypeIds::LONG_FIRE_RESISTANCE, + PotionType::SWIFTNESS => PotionTypeIds::SWIFTNESS, + PotionType::LONG_SWIFTNESS => PotionTypeIds::LONG_SWIFTNESS, + PotionType::STRONG_SWIFTNESS => PotionTypeIds::STRONG_SWIFTNESS, + PotionType::SLOWNESS => PotionTypeIds::SLOWNESS, + PotionType::LONG_SLOWNESS => PotionTypeIds::LONG_SLOWNESS, + PotionType::WATER_BREATHING => PotionTypeIds::WATER_BREATHING, + PotionType::LONG_WATER_BREATHING => PotionTypeIds::LONG_WATER_BREATHING, + PotionType::HEALING => PotionTypeIds::HEALING, + PotionType::STRONG_HEALING => PotionTypeIds::STRONG_HEALING, + PotionType::HARMING => PotionTypeIds::HARMING, + PotionType::STRONG_HARMING => PotionTypeIds::STRONG_HARMING, + PotionType::POISON => PotionTypeIds::POISON, + PotionType::LONG_POISON => PotionTypeIds::LONG_POISON, + PotionType::STRONG_POISON => PotionTypeIds::STRONG_POISON, + PotionType::REGENERATION => PotionTypeIds::REGENERATION, + PotionType::LONG_REGENERATION => PotionTypeIds::LONG_REGENERATION, + PotionType::STRONG_REGENERATION => PotionTypeIds::STRONG_REGENERATION, + PotionType::STRENGTH => PotionTypeIds::STRENGTH, + PotionType::LONG_STRENGTH => PotionTypeIds::LONG_STRENGTH, + PotionType::STRONG_STRENGTH => PotionTypeIds::STRONG_STRENGTH, + PotionType::WEAKNESS => PotionTypeIds::WEAKNESS, + PotionType::LONG_WEAKNESS => PotionTypeIds::LONG_WEAKNESS, + PotionType::WITHER => PotionTypeIds::WITHER, + PotionType::TURTLE_MASTER => PotionTypeIds::TURTLE_MASTER, + PotionType::LONG_TURTLE_MASTER => PotionTypeIds::LONG_TURTLE_MASTER, + PotionType::STRONG_TURTLE_MASTER => PotionTypeIds::STRONG_TURTLE_MASTER, + PotionType::SLOW_FALLING => PotionTypeIds::SLOW_FALLING, + PotionType::LONG_SLOW_FALLING => PotionTypeIds::LONG_SLOW_FALLING, + PotionType::STRONG_SLOWNESS => PotionTypeIds::STRONG_SLOWNESS, + }, $case); + } } } diff --git a/src/data/bedrock/SuspiciousStewTypeIdMap.php b/src/data/bedrock/SuspiciousStewTypeIdMap.php index c4de4b742d5..d6a31f0ea79 100644 --- a/src/data/bedrock/SuspiciousStewTypeIdMap.php +++ b/src/data/bedrock/SuspiciousStewTypeIdMap.php @@ -32,15 +32,20 @@ final class SuspiciousStewTypeIdMap{ use IntSaveIdMapTrait; private function __construct(){ - $this->register(SuspiciousStewTypeIds::POPPY, SuspiciousStewType::POPPY); - $this->register(SuspiciousStewTypeIds::CORNFLOWER, SuspiciousStewType::CORNFLOWER); - $this->register(SuspiciousStewTypeIds::TULIP, SuspiciousStewType::TULIP); - $this->register(SuspiciousStewTypeIds::AZURE_BLUET, SuspiciousStewType::AZURE_BLUET); - $this->register(SuspiciousStewTypeIds::LILY_OF_THE_VALLEY, SuspiciousStewType::LILY_OF_THE_VALLEY); - $this->register(SuspiciousStewTypeIds::DANDELION, SuspiciousStewType::DANDELION); - $this->register(SuspiciousStewTypeIds::BLUE_ORCHID, SuspiciousStewType::BLUE_ORCHID); - $this->register(SuspiciousStewTypeIds::ALLIUM, SuspiciousStewType::ALLIUM); - $this->register(SuspiciousStewTypeIds::OXEYE_DAISY, SuspiciousStewType::OXEYE_DAISY); - $this->register(SuspiciousStewTypeIds::WITHER_ROSE, SuspiciousStewType::WITHER_ROSE); + foreach(SuspiciousStewType::cases() as $case){ + $this->register(match($case){ + SuspiciousStewType::POPPY => SuspiciousStewTypeIds::POPPY, + SuspiciousStewType::CORNFLOWER => SuspiciousStewTypeIds::CORNFLOWER, + SuspiciousStewType::TULIP => SuspiciousStewTypeIds::TULIP, + SuspiciousStewType::AZURE_BLUET => SuspiciousStewTypeIds::AZURE_BLUET, + SuspiciousStewType::LILY_OF_THE_VALLEY => SuspiciousStewTypeIds::LILY_OF_THE_VALLEY, + SuspiciousStewType::DANDELION => SuspiciousStewTypeIds::DANDELION, + SuspiciousStewType::BLUE_ORCHID => SuspiciousStewTypeIds::BLUE_ORCHID, + SuspiciousStewType::ALLIUM => SuspiciousStewTypeIds::ALLIUM, + SuspiciousStewType::OXEYE_DAISY => SuspiciousStewTypeIds::OXEYE_DAISY, + SuspiciousStewType::WITHER_ROSE => SuspiciousStewTypeIds::WITHER_ROSE, + }, $case); + } + } } diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 44fce4be3d6..9f8b6a7984d 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -307,6 +307,7 @@ private function register1to1ItemMappings() : void{ $this->map1to1Item(Ids::MUSIC_DISC_WAIT, Items::RECORD_WAIT()); $this->map1to1Item(Ids::MUSIC_DISC_WARD, Items::RECORD_WARD()); $this->map1to1Item(Ids::MUTTON, Items::RAW_MUTTON()); + $this->map1to1Item(Ids::NAME_TAG, Items::NAME_TAG()); $this->map1to1Item(Ids::NAUTILUS_SHELL, Items::NAUTILUS_SHELL()); $this->map1to1Item(Ids::NETHER_STAR, Items::NETHER_STAR()); $this->map1to1Item(Ids::NETHERBRICK, Items::NETHER_BRICK()); diff --git a/src/data/java/GameModeIdMap.php b/src/data/java/GameModeIdMap.php index 41258dd66e0..9262723ed87 100644 --- a/src/data/java/GameModeIdMap.php +++ b/src/data/java/GameModeIdMap.php @@ -44,10 +44,14 @@ final class GameModeIdMap{ private array $enumToId = []; public function __construct(){ - $this->register(0, GameMode::SURVIVAL); - $this->register(1, GameMode::CREATIVE); - $this->register(2, GameMode::ADVENTURE); - $this->register(3, GameMode::SPECTATOR); + foreach(GameMode::cases() as $case){ + $this->register(match($case){ + GameMode::SURVIVAL => 0, + GameMode::CREATIVE => 1, + GameMode::ADVENTURE => 2, + GameMode::SPECTATOR => 3, + }, $case); + } } private function register(int $id, GameMode $type) : void{ diff --git a/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php b/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php index dd35fabfbf1..3c540f7fa75 100644 --- a/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php +++ b/src/data/runtime/LegacyRuntimeEnumDescriberTrait.php @@ -29,11 +29,6 @@ * @deprecated */ trait LegacyRuntimeEnumDescriberTrait{ - - /** - * @phpstan-template T of \UnitEnum - * @phpstan-param T $case - */ abstract protected function enum(\UnitEnum &$case) : void; public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{ diff --git a/src/data/runtime/RuntimeDataDescriber.php b/src/data/runtime/RuntimeDataDescriber.php index 04217f5df56..6f1d35b9838 100644 --- a/src/data/runtime/RuntimeDataDescriber.php +++ b/src/data/runtime/RuntimeDataDescriber.php @@ -89,10 +89,6 @@ public function railShape(int &$railShape) : void; public function straightOnlyRailShape(int &$railShape) : void; - /** - * @phpstan-template T of \UnitEnum - * @phpstan-param T $case - */ public function enum(\UnitEnum &$case) : void; /** diff --git a/src/entity/Entity.php b/src/entity/Entity.php index d701e1823b5..aa814d7886e 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -254,6 +254,14 @@ public function isNameTagAlwaysVisible() : bool{ return $this->alwaysShowNameTag; } + /** + * Returns whether players can rename this entity using a name tag. + * Note that plugins can still name entities using setNameTag(). + */ + public function canBeRenamed() : bool{ + return false; + } + public function setNameTag(string $name) : void{ $this->nameTag = $name; $this->networkPropertiesDirty = true; diff --git a/src/entity/Living.php b/src/entity/Living.php index 61aaab08222..7d55bdc8ba0 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -132,6 +132,10 @@ protected function getInitialGravity() : float{ return 0.08; } abstract public function getName() : string; + public function canBeRenamed() : bool{ + return true; + } + protected function initEntity(CompoundTag $nbt) : void{ parent::initEntity($nbt); diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 74eedf3a4a2..2072cd5226f 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -37,8 +37,7 @@ class HandlerList{ private array $affectedHandlerCaches = []; /** - * @phpstan-template TEvent of Event - * @phpstan-param class-string $class + * @phpstan-param class-string $class */ public function __construct( private string $class, diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index 047632f5483..605a3874789 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -86,8 +86,7 @@ private static function resolveNearestHandleableParent(\ReflectionClass $class) * * Calling this method also lazily initializes the $classMap inheritance tree of handler lists. * - * @phpstan-template TEvent of Event - * @phpstan-param class-string $event + * @phpstan-param class-string $event * * @throws \ReflectionException * @throws \InvalidArgumentException @@ -113,8 +112,7 @@ public function getListFor(string $event) : HandlerList{ } /** - * @phpstan-template TEvent of Event - * @phpstan-param class-string $event + * @phpstan-param class-string $event * * @return RegisteredListener[] */ diff --git a/src/item/ItemTypeIds.php b/src/item/ItemTypeIds.php index 91110b18f72..4ea3da01529 100644 --- a/src/item/ItemTypeIds.php +++ b/src/item/ItemTypeIds.php @@ -323,14 +323,15 @@ private function __construct(){ public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = 20284; public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285; public const PITCHER_POD = 20286; - public const FIREWORK_ROCKET = 20287; - public const FIREWORK_STAR = 20288; - public const CROSSBOW = 20289; - public const ELYTRA = 20290; - public const TRIDENT = 20291; - public const SHIELD = 20292; + public const NAME_TAG = 20287; + public const FIREWORK_ROCKET = 20288; + public const FIREWORK_STAR = 20289; + public const CROSSBOW = 20290; + public const ELYTRA = 20291; + public const TRIDENT = 20292; + public const SHIELD = 20293; - public const FIRST_UNUSED_ITEM_ID = 20293; + public const FIRST_UNUSED_ITEM_ID = 20294; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; diff --git a/tests/phpunit/data/bedrock/DyeColorIdMapTest.php b/src/item/NameTag.php similarity index 65% rename from tests/phpunit/data/bedrock/DyeColorIdMapTest.php rename to src/item/NameTag.php index a3ca6db25ce..8c7113e1dee 100644 --- a/tests/phpunit/data/bedrock/DyeColorIdMapTest.php +++ b/src/item/NameTag.php @@ -21,18 +21,20 @@ declare(strict_types=1); -namespace pocketmine\data\bedrock; +namespace pocketmine\item; -use PHPUnit\Framework\TestCase; -use pocketmine\block\utils\DyeColor; +use pocketmine\entity\Entity; +use pocketmine\math\Vector3; +use pocketmine\player\Player; -class DyeColorIdMapTest extends TestCase{ +class NameTag extends Item{ - public function testAllColorsMapped() : void{ - foreach(DyeColor::cases() as $color){ - $id = DyeColorIdMap::getInstance()->toId($color); - $color2 = DyeColorIdMap::getInstance()->fromId($id); - self::assertTrue($color === $color2); + public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{ + if($entity->canBeRenamed() && $this->hasCustomName()){ + $entity->setNameTag($this->getCustomName()); + $this->pop(); + return true; } + return false; } } diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 8d05af178c1..63eb3a1e99a 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -1387,6 +1387,7 @@ private static function registerItems(self $result) : void{ $result->register("mutton_raw", fn() => Items::RAW_MUTTON()); $result->register("muttoncooked", fn() => Items::COOKED_MUTTON()); $result->register("muttonraw", fn() => Items::RAW_MUTTON()); + $result->register("name_tag", fn() => Items::NAME_TAG()); $result->register("nautilus_shell", fn() => Items::NAUTILUS_SHELL()); $result->register("nether_brick", fn() => Items::NETHER_BRICK()); $result->register("nether_quartz", fn() => Items::NETHER_QUARTZ()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index d334d09c47e..2520270cde2 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -221,6 +221,7 @@ * @method static MilkBucket MILK_BUCKET() * @method static Minecart MINECART() * @method static MushroomStew MUSHROOM_STEW() + * @method static NameTag NAME_TAG() * @method static Item NAUTILUS_SHELL() * @method static Axe NETHERITE_AXE() * @method static Armor NETHERITE_BOOTS() @@ -494,6 +495,7 @@ protected static function setup() : void{ self::register("milk_bucket", new MilkBucket(new IID(Ids::MILK_BUCKET), "Milk Bucket")); self::register("minecart", new Minecart(new IID(Ids::MINECART), "Minecart")); self::register("mushroom_stew", new MushroomStew(new IID(Ids::MUSHROOM_STEW), "Mushroom Stew")); + self::register("name_tag", new NameTag(new IID(Ids::NAME_TAG), "Name Tag")); self::register("nautilus_shell", new Item(new IID(Ids::NAUTILUS_SHELL), "Nautilus Shell")); self::register("nether_brick", new Item(new IID(Ids::NETHER_BRICK), "Nether Brick")); self::register("nether_quartz", new Item(new IID(Ids::NETHER_QUARTZ), "Nether Quartz")); diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index b87783a8dd1..87b98f698a6 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -27,6 +27,9 @@ use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; +use pocketmine\network\mcpe\protocol\LevelChunkPacket; +use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\types\ChunkPosition; use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\network\mcpe\serializer\ChunkSerializer; use pocketmine\scheduler\AsyncTask; @@ -72,14 +75,12 @@ public function onRun() : void{ $cache = new CachedChunk(); $converter = TypeConverter::getInstance($this->mappingProtocol); - $encoderContext = new PacketSerializerContext($converter->getItemTypeDictionary(), $this->mappingProtocol); - - foreach(ChunkSerializer::serializeSubChunks($chunk, $this->dimensionId, $converter->getBlockTranslator(), $encoderContext) as $subChunk){ + foreach(ChunkSerializer::serializeSubChunks($chunk, $this->dimensionId, $converter->getBlockTranslator(), $this->mappingProtocol) as $subChunk){ /** @phpstan-ignore-next-line */ $cache->addSubChunk(Binary::readLong(xxhash64($subChunk)), $subChunk); } - $encoder = PacketSerializer::encoder($encoderContext); + $encoder = PacketSerializer::encoder($this->mappingProtocol); $biomeEncoder = clone $encoder; ChunkSerializer::serializeBiomes($chunk, $this->dimensionId, $biomeEncoder); /** @phpstan-ignore-next-line */ @@ -94,7 +95,6 @@ public function onRun() : void{ $this->dimensionId, $chunkDataEncoder->getBuffer(), $this->compressor->deserialize(), - $encoderContext, $this->mappingProtocol ); diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 32c4cbb5176..6135167d284 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -423,6 +423,41 @@ public function onClientRemoveWindow(int $id) : void{ } } + /** + * Compares itemstack extra data for equality. This is used to verify legacy InventoryTransaction slot predictions. + * + * TODO: It would be preferable if we didn't have to deserialize this, to improve performance and reduce attack + * surface. However, the raw data may not match due to differences in ordering. Investigate whether the + * client-provided NBT is consistently sorted. + */ + private function itemStackExtraDataEqual(ItemStack $left, ItemStack $right) : bool{ + if($left->getRawExtraData() === $right->getRawExtraData()){ + return true; + } + + $typeConverter = $this->session->getTypeConverter(); + $leftExtraData = $typeConverter->deserializeItemStackExtraData($left->getRawExtraData(), $left->getId()); + $rightExtraData = $typeConverter->deserializeItemStackExtraData($right->getRawExtraData(), $right->getId()); + + $leftNbt = $leftExtraData->getNbt(); + $rightNbt = $rightExtraData->getNbt(); + return + $leftExtraData->getCanPlaceOn() === $rightExtraData->getCanPlaceOn() && + $leftExtraData->getCanDestroy() === $rightExtraData->getCanDestroy() && ( + $leftNbt === $rightNbt || //this covers null === null and fast object identity + ($leftNbt !== null && $rightNbt !== null && $leftNbt->equals($rightNbt)) + ); + } + + private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{ + return + $left->getId() === $right->getId() && + $left->getMeta() === $right->getMeta() && + $left->getBlockRuntimeId() === $right->getBlockRuntimeId() && + $left->getCount() === $right->getCount() && + $this->itemStackExtraDataEqual($left, $right); + } + public function onSlotChange(Inventory $inventory, int $slot) : void{ $inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null; if($inventoryEntry === null){ @@ -432,7 +467,7 @@ public function onSlotChange(Inventory $inventory, int $slot) : void{ } $currentItem = $this->session->getTypeConverter()->coreItemStackToNet($inventory->getItem($slot)); $clientSideItem = $inventoryEntry->predictions[$slot] ?? null; - if($clientSideItem === null || !$clientSideItem->equals($currentItem)){ + if($clientSideItem === null || !$this->itemStacksEqual($currentItem, $clientSideItem)){ //no prediction or incorrect - do not associate this with the currently active itemstack request $this->trackItemStack($inventoryEntry, $slot, $currentItem, null); $inventoryEntry->pendingSyncs[$slot] = $currentItem; diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 332d5086118..c8097289eab 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -69,7 +69,6 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\mcpe\protocol\SetDifficultyPacket; @@ -189,7 +188,6 @@ public function __construct( private Server $server, private NetworkSessionManager $manager, private PacketPool $packetPool, - private PacketSerializerContext $packetSerializerContext, protected PacketSender $sender, private PacketBroadcaster $broadcaster, private EntityEventBroadcaster $entityEventBroadcaster, @@ -412,7 +410,6 @@ public function handleEncoded(string $payload) : void{ } if($this->enableCompression){ - Timings::$playerNetworkReceiveDecompress->startTiming(); if($this->protocolId >= ProtocolInfo::PROTOCOL_1_20_60){ $compressionType = ord($payload[0]); $compressed = substr($payload, 1); @@ -420,6 +417,7 @@ public function handleEncoded(string $payload) : void{ $decompressed = $compressed; }elseif($compressionType === $this->compressor->getNetworkId()){ try{ + Timings::$playerNetworkReceiveDecompress->startTiming(); $decompressed = $this->compressor->decompress($compressed); }catch(DecompressionException $e){ $this->logger->debug("Failed to decompress packet: " . base64_encode($compressed)); @@ -432,6 +430,7 @@ public function handleEncoded(string $payload) : void{ } }else{ try{ + Timings::$playerNetworkReceiveDecompress->startTiming(); $decompressed = $this->compressor->decompress($payload); }catch(DecompressionException $e){ $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); @@ -496,7 +495,7 @@ public function handleDataPacket(Packet $packet, string $buffer) : void{ $decodeTimings = Timings::getDecodeDataPacketTimings($packet); $decodeTimings->startTiming(); try{ - $stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext); + $stream = PacketSerializer::decoder($this->getProtocolId(), $buffer, 0); try{ $packet->decode($stream); }catch(PacketDecodeException $e){ @@ -555,7 +554,7 @@ public function sendDataPacket(ClientboundPacket $packet, bool $immediate = fals } foreach($packets as $evPacket){ - $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket)); + $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->getProtocolId()), $evPacket)); } if($immediate){ $this->flushSendBuffer(true); @@ -603,7 +602,7 @@ private function flushSendBuffer(bool $immediate = false) : void{ PacketBatch::encodeRaw($stream, $this->sendBuffer); if($this->enableCompression){ - $batch = $this->server->prepareBatch($stream->getBuffer(), $this->packetSerializerContext, $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer); + $batch = $this->server->prepareBatch($stream->getBuffer(), $this->getProtocolId(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer); }else{ $batch = $stream->getBuffer(); } @@ -615,8 +614,6 @@ private function flushSendBuffer(bool $immediate = false) : void{ } } - public function getPacketSerializerContext() : PacketSerializerContext{ return $this->packetSerializerContext; } - public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; } public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; } diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index c42d2d292d2..4a0ca6cd06a 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -26,7 +26,6 @@ use pocketmine\event\server\DataPacketSendEvent; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\BinaryStream; @@ -37,8 +36,7 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ public function __construct( - private Server $server, - private PacketSerializerContext $protocolContext + private Server $server ){} public function broadcastPackets(array $recipients, array $packets) : void{ @@ -58,10 +56,6 @@ public function broadcastPackets(array $recipients, array $packets) : void{ /** @var NetworkSession[][] $targetsByCompressor */ $targetsByCompressor = []; foreach($recipients as $recipient){ - if($recipient->getPacketSerializerContext() !== $this->protocolContext){ - throw new \InvalidArgumentException("Only recipients with the same protocol context as the broadcaster can be broadcast to by this broadcaster"); - } - //TODO: different compressors might be compatible, it might not be necessary to split them up by object $compressor = $recipient->getCompressor(); $compressors[spl_object_id($compressor)] = $compressor; @@ -72,7 +66,7 @@ public function broadcastPackets(array $recipients, array $packets) : void{ $totalLength = 0; $packetBuffers = []; foreach($packets as $packet){ - $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder($this->protocolContext), $packet); + $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet); //varint length prefix + packet buffer $totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer); $packetBuffers[] = $buffer; diff --git a/src/network/mcpe/auth/ProcessLoginTask.php b/src/network/mcpe/auth/ProcessLoginTask.php index 9078fca7515..b4c9e6d9caa 100644 --- a/src/network/mcpe/auth/ProcessLoginTask.php +++ b/src/network/mcpe/auth/ProcessLoginTask.php @@ -39,16 +39,6 @@ class ProcessLoginTask extends AsyncTask{ private const TLS_KEY_ON_COMPLETION = "completion"; - /** - * Old Mojang root auth key. This was used since the introduction of Xbox Live authentication in 0.15.0. - * This key is expected to be replaced by the key below in the future, but this has not yet happened as of - * 2023-07-01. - * Ideally we would place a time expiry on this key, but since Mojang have not given a hard date for the key change, - * and one bad guess has already caused a major outage, we can't do this. - * TODO: This needs to be removed as soon as the new key is deployed by Mojang's authentication servers. - */ - public const MOJANG_OLD_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V"; - /** * New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0. * Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01, @@ -128,7 +118,6 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi try{ [$headersArray, $claimsArray, ] = JwtUtils::parse($jwt); }catch(JwtException $e){ - //TODO: we shouldn't be showing internal information like this to the client throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e); } @@ -142,13 +131,11 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi /** @var JwtHeader $headers */ $headers = $mapper->map($headersArray, new JwtHeader()); }catch(\JsonMapper_Exception $e){ - //TODO: we shouldn't be showing internal information like this to the client throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e); } $headerDerKey = base64_decode($headers->x5u, true); if($headerDerKey === false){ - //TODO: we shouldn't be showing internal information like this to the client throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u"); } @@ -164,7 +151,6 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi try{ $signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey); }catch(JwtException $e){ - //TODO: we shouldn't be showing this internal information to the client throw new VerifyLoginException("Invalid JWT public key: " . $e->getMessage(), null, 0, $e); } try{ @@ -175,7 +161,7 @@ private function validateToken(string $jwt, ?string &$currentPublicKey, bool $fi throw new VerifyLoginException($e->getMessage(), null, 0, $e); } - if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY){ + if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){ $this->authenticated = true; //we're signed into xbox live } diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 4fe6a5d034b..aaa5c4542ae 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -35,6 +35,7 @@ use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\downgrade\ItemIdMetaDowngrader; use pocketmine\event\server\TypeConverterConstructEvent; +use pocketmine\data\bedrock\item\ItemTypeNames; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\nbt\NbtException; @@ -42,8 +43,11 @@ use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient; use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor; @@ -86,7 +90,7 @@ public function __construct(int $protocolId){ $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromProtocolId($protocolId); $this->itemDataDowngrader = new ItemIdMetaDowngrader($this->itemTypeDictionary, ItemTranslator::getItemSchemaId($protocolId)); - $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId("minecraft:shield"); + $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD); $this->itemTranslator = new ItemTranslator( $this->itemTypeDictionary, @@ -234,26 +238,36 @@ public function coreItemStackToNet(Item $itemStack) : ItemStack{ [$id, $meta, $blockRuntimeId] = $idMeta; } + $extraData = $id === $this->shieldRuntimeId ? + new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) : + new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []); + $extraDataSerializer = PacketSerializer::encoder($this->protocolId); + $extraData->write($extraDataSerializer); + return new ItemStack( $id, $meta, $itemStack->getCount(), $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID, - $nbt, - [], - [], - $id === $this->shieldRuntimeId ? 0 : null + $extraDataSerializer->getBuffer(), ); } /** + * WARNING: Avoid this in server-side code. If you need to compare ItemStacks provided by the client to the + * server, prefer encoding the server's itemstack and comparing the serialized ItemStack, instead of converting + * the client's ItemStack to a core Item. + * This method will fully decode the item's extra data, which can be very costly if the item has a lot of NBT data. + * * @throws TypeConversionException */ public function netItemStackToCore(ItemStack $itemStack) : Item{ if($itemStack->getId() === 0){ return VanillaItems::AIR(); } - $compound = $itemStack->getNbt(); + $extraData = $this->deserializeItemStackExtraData($this->getProtocolId(), $itemStack->getRawExtraData(), $itemStack->getId()); + + $compound = $extraData->getNbt(); $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId()); @@ -314,4 +328,11 @@ public static function broadcastByTypeConverter(array $players, \Closure $closur } } } + + public function deserializeItemStackExtraData(int $protocolId, string $extraData, int $id) : ItemStackExtraData{ + $extraDataDeserializer = PacketSerializer::decoder($protocolId, $extraData, 0); + return $id === $this->shieldRuntimeId ? + ItemStackExtraDataShield::read($extraDataDeserializer) : + ItemStackExtraData::read($extraDataDeserializer); + } } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index fd8902a7996..753e6395d17 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -449,9 +449,18 @@ private function handleNormalTransaction(NormalTransactionData $data, int $itemS return false; } $serverItemStack = $this->session->getTypeConverter()->coreItemStackToNet($sourceSlotItem); - //because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known - //itemstack info with the one the client sent. This is costly, but we don't have any other option :( - if(!$serverItemStack->equals($clientItemStack)){ + //Sadly we don't have itemstack IDs here, so we have to compare the basic item properties to ensure that we're + //dropping the item the client expects (inventory might be out of sync with the client). + if( + $serverItemStack->getId() !== $clientItemStack->getId() || + $serverItemStack->getMeta() !== $clientItemStack->getMeta() || + $serverItemStack->getCount() !== $clientItemStack->getCount() || + $serverItemStack->getBlockRuntimeId() !== $clientItemStack->getBlockRuntimeId() + //Raw extraData may not match because of TAG_Compound key ordering differences, and decoding it to compare + //is costly. Assume that we're in sync if id+meta+count+runtimeId match. + //NB: Make sure $clientItemStack isn't used to create the dropped item, as that would allow the client + //to change the item NBT since we're not validating it. + ){ return false; } diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 17aa87e8390..b2325f5698f 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -33,7 +33,6 @@ use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\ProtocolInfo; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\Network; use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\PacketHandlingException; @@ -84,7 +83,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ private PacketBroadcaster $packetBroadcaster; private EntityEventBroadcaster $entityEventBroadcaster; - private PacketSerializerContext $packetSerializerContext; private TypeConverter $typeConverter; public function __construct( @@ -94,12 +92,10 @@ public function __construct( bool $ipV6, PacketBroadcaster $packetBroadcaster, EntityEventBroadcaster $entityEventBroadcaster, - PacketSerializerContext $packetSerializerContext, TypeConverter $typeConverter ){ $this->server = $server; $this->packetBroadcaster = $packetBroadcaster; - $this->packetSerializerContext = $packetSerializerContext; $this->entityEventBroadcaster = $entityEventBroadcaster; $this->typeConverter = $typeConverter; @@ -192,7 +188,6 @@ public function onClientConnect(int $sessionId, string $address, int $port, int $this->server, $this->network->getSessionManager(), PacketPool::getInstance(), - $this->packetSerializerContext, new RakLibPacketSender($sessionId, $this), $this->packetBroadcaster, $this->entityEventBroadcaster, diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index 905cb1a8fb0..55d327dde2c 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -31,7 +31,6 @@ use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\types\DimensionIds; use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; @@ -86,9 +85,9 @@ public static function getSubChunkCount(Chunk $chunk, int $dimensionId) : int{ * @phpstan-param DimensionIds::* $dimensionId * @return string[] */ - public static function serializeSubChunks(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, PacketSerializerContext $encoderContext) : array + public static function serializeSubChunks(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, int $protocolId) : array { - $stream = PacketSerializer::encoder($encoderContext); + $stream = PacketSerializer::encoder($protocolId); $subChunks = []; $subChunkCount = self::getSubChunkCount($chunk, $dimensionId); @@ -107,10 +106,10 @@ public static function serializeSubChunks(Chunk $chunk, int $dimensionId, BlockT /** * @phpstan-param DimensionIds::* $dimensionId */ - public static function serializeFullChunk(Chunk $chunk, int $dimensionId, TypeConverter $converter, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ - $stream = PacketSerializer::encoder($encoderContext); + public static function serializeFullChunk(Chunk $chunk, int $dimensionId, TypeConverter $converter, int $protocolId, ?string $tiles = null) : string{ + $stream = PacketSerializer::encoder($protocolId); - foreach(self::serializeSubChunks($chunk, $dimensionId, $converter->getBlockTranslator(), $encoderContext) as $subChunk){ + foreach(self::serializeSubChunks($chunk, $dimensionId, $converter->getBlockTranslator(), $protocolId) as $subChunk){ $stream->put($subChunk); } diff --git a/src/player/Player.php b/src/player/Player.php index 7f026d84e81..e6da1d6d224 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -630,6 +630,10 @@ public function setDisplayName(string $name) : void{ $this->displayName = $ev->getNewName(); } + public function canBeRenamed() : bool{ + return false; + } + /** * Returns the player's locale, e.g. en_US. */ diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 301c668547a..198e4e893bf 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -650,6 +650,11 @@ public function registerEvent(string $event, \Closure $handler, int $priority, P $handlerName = Utils::getNiceClosureName($handler); + $reflect = new \ReflectionFunction($handler); + if($reflect->isGenerator()){ + throw new PluginException("Generator function $handlerName cannot be used as an event handler"); + } + if(!$plugin->isEnabled()){ throw new PluginException("Plugin attempted to register event handler " . $handlerName . "() to event " . $event . " while not enabled"); } diff --git a/src/promise/Promise.php b/src/promise/Promise.php index be2f6a4ec94..040057d83f5 100644 --- a/src/promise/Promise.php +++ b/src/promise/Promise.php @@ -65,25 +65,30 @@ public function isResolved() : bool{ * will be an array containing the resolution values of each Promises in * `$promises` indexed by the respective Promises' array keys. * - * @phpstan-param Promise[] $promises + * @param Promise[] $promises * - * @phpstan-return Promise> + * @phpstan-template TPromiseValue + * @phpstan-template TKey of array-key + * @phpstan-param non-empty-array> $promises + * + * @phpstan-return Promise> */ - public static function all(array $promises) : Promise { - /** @phpstan-var PromiseResolver> $resolver */ + public static function all(array $promises) : Promise{ + if(count($promises) === 0){ + throw new \InvalidArgumentException("At least one promise must be provided"); + } + /** @phpstan-var PromiseResolver> $resolver */ $resolver = new PromiseResolver(); $values = []; $toResolve = count($promises); $continue = true; foreach($promises as $key => $promise){ - $values[$key] = null; - $promise->onCompletion( - function(mixed $value) use ($resolver, $key, &$toResolve, &$continue, &$values) : void{ + function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{ $values[$key] = $value; - if(--$toResolve === 0 && $continue){ + if(count($values) === $toResolve){ $resolver->resolve($values); } }, @@ -100,11 +105,6 @@ function() use ($resolver, &$continue) : void{ } } - if($toResolve === 0){ - $continue = false; - $resolver->resolve($values); - } - return $resolver->getPromise(); } } diff --git a/src/scheduler/AsyncTask.php b/src/scheduler/AsyncTask.php index b4c3ce20db6..b0b64347adc 100644 --- a/src/scheduler/AsyncTask.php +++ b/src/scheduler/AsyncTask.php @@ -24,12 +24,10 @@ namespace pocketmine\scheduler; use pmmp\thread\Runnable; -use pmmp\thread\Thread as NativeThread; use pmmp\thread\ThreadSafe; use pmmp\thread\ThreadSafeArray; use pocketmine\thread\NonThreadSafeValue; use function array_key_exists; -use function assert; use function igbinary_serialize; use function igbinary_unserialize; use function is_null; @@ -83,9 +81,7 @@ public function run() : void{ $this->onRun(); $this->finished = true; - $worker = NativeThread::getCurrentThread(); - assert($worker instanceof AsyncWorker); - $worker->getNotifier()->wakeupSleeper(); + AsyncWorker::getNotifier()->wakeupSleeper(); } /** diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 919e3eedcf9..5fdfb1ebb73 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -36,7 +36,7 @@ class AsyncWorker extends Worker{ /** @var mixed[] */ private static array $store = []; - private const TLS_KEY_NOTIFIER = self::class . "::notifier"; + private static ?SleeperNotifier $notifier = null; public function __construct( private ThreadSafeLogger $logger, @@ -45,12 +45,11 @@ public function __construct( private SleeperHandlerEntry $sleeperEntry ){} - public function getNotifier() : SleeperNotifier{ - $notifier = $this->getFromThreadStore(self::TLS_KEY_NOTIFIER); - if(!$notifier instanceof SleeperNotifier){ - throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage"); + public static function getNotifier() : SleeperNotifier{ + if(self::$notifier !== null){ + return self::$notifier; } - return $notifier; + throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage"); } protected function onRun() : void{ @@ -66,7 +65,7 @@ protected function onRun() : void{ $this->logger->debug("No memory limit set"); } - $this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier()); + self::$notifier = $this->sleeperEntry->createNotifier(); } public function getLogger() : ThreadSafeLogger{ @@ -84,6 +83,8 @@ public function getAsyncWorkerId() : int{ /** * Saves mixed data into the worker's thread-local object store. This can be used to store objects which you * want to use on this worker thread from multiple AsyncTasks. + * + * @deprecated Use static class properties instead. */ public function saveToThreadStore(string $identifier, mixed $value) : void{ if(NativeThread::getCurrentThread() !== $this){ @@ -99,6 +100,8 @@ public function saveToThreadStore(string $identifier, mixed $value) : void{ * account for the possibility that what you're trying to retrieve might not exist. * * Objects stored in this storage may ONLY be retrieved while the task is running. + * + * @deprecated Use static class properties instead. */ public function getFromThreadStore(string $identifier) : mixed{ if(NativeThread::getCurrentThread() !== $this){ @@ -109,6 +112,8 @@ public function getFromThreadStore(string $identifier) : mixed{ /** * Removes previously-stored mixed data from the worker's thread-local object store. + * + * @deprecated Use static class properties instead. */ public function removeFromThreadStore(string $identifier) : void{ if(NativeThread::getCurrentThread() !== $this){ diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index de2ecbde81d..9a14b23454b 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -23,6 +23,7 @@ namespace pocketmine\thread; +use pmmp\thread\Thread as NativeThread; use pmmp\thread\ThreadSafeArray; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\Server; @@ -77,9 +78,7 @@ public function setClassLoaders(?array $autoloaders = null) : void{ /** * Registers the class loaders for this thread. * - * WARNING: This method MUST be called from any descendent threads' run() method to make autoloading usable. - * If you do not do this, you will not be able to use new classes that were not loaded when the thread was started - * (unless you are using a custom autoloader). + * @internal */ public function registerClassLoaders() : void{ if($this->composerAutoloaderPath !== null){ @@ -96,6 +95,15 @@ public function registerClassLoaders() : void{ public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; } + public function start(int $options = NativeThread::INHERIT_NONE) : bool{ + ThreadManager::getInstance()->add($this); + + if($this->getClassLoaders() === null){ + $this->setClassLoaders(); + } + return parent::start($options); + } + final public function run() : void{ error_reporting(-1); $this->registerClassLoaders(); @@ -110,6 +118,20 @@ final public function run() : void{ $this->isKilled = true; } + /** + * Stops the thread using the best way possible. Try to stop it yourself before calling this. + */ + public function quit() : void{ + $this->isKilled = true; + + if(!$this->isJoined()){ + $this->notify(); + $this->join(); + } + + ThreadManager::getInstance()->remove($this); + } + /** * Called by set_exception_handler() when an uncaught exception is thrown. */ diff --git a/src/thread/Thread.php b/src/thread/Thread.php index 706f964298f..a6c36e14c76 100644 --- a/src/thread/Thread.php +++ b/src/thread/Thread.php @@ -37,28 +37,4 @@ */ abstract class Thread extends NativeThread{ use CommonThreadPartsTrait; - - public function start(int $options = NativeThread::INHERIT_NONE) : bool{ - //this is intentionally not traitified - ThreadManager::getInstance()->add($this); - - if($this->getClassLoaders() === null){ - $this->setClassLoaders(); - } - return parent::start($options); - } - - /** - * Stops the thread using the best way possible. Try to stop it yourself before calling this. - */ - public function quit() : void{ - $this->isKilled = true; - - if(!$this->isJoined()){ - $this->notify(); - $this->join(); - } - - ThreadManager::getInstance()->remove($this); - } } diff --git a/src/thread/ThreadSafeClassLoader.php b/src/thread/ThreadSafeClassLoader.php index 95b983dc1b8..fd9e6afed6e 100644 --- a/src/thread/ThreadSafeClassLoader.php +++ b/src/thread/ThreadSafeClassLoader.php @@ -51,12 +51,12 @@ class ThreadSafeClassLoader extends ThreadSafe{ * @var ThreadSafeArray|string[] * @phpstan-var ThreadSafeArray */ - private $fallbackLookup; + private ThreadSafeArray $fallbackLookup; /** * @var ThreadSafeArray|string[][] * @phpstan-var ThreadSafeArray> */ - private $psr4Lookup; + private ThreadSafeArray $psr4Lookup; public function __construct(){ $this->fallbackLookup = new ThreadSafeArray(); diff --git a/src/thread/Worker.php b/src/thread/Worker.php index 3bc5cda97aa..cc0b56046a4 100644 --- a/src/thread/Worker.php +++ b/src/thread/Worker.php @@ -23,7 +23,6 @@ namespace pocketmine\thread; -use pmmp\thread\Thread as NativeThread; use pmmp\thread\Worker as NativeWorker; use pocketmine\scheduler\AsyncTask; @@ -39,31 +38,4 @@ */ abstract class Worker extends NativeWorker{ use CommonThreadPartsTrait; - - public function start(int $options = NativeThread::INHERIT_NONE) : bool{ - //this is intentionally not traitified - ThreadManager::getInstance()->add($this); - - if($this->getClassLoaders() === null){ - $this->setClassLoaders(); - } - return parent::start($options); - } - - /** - * Stops the thread using the best way possible. Try to stop it yourself before calling this. - */ - public function quit() : void{ - $this->isKilled = true; - - if(!$this->isShutdown()){ - $this->synchronized(function() : void{ - while($this->unstack() !== null); - }); - $this->notify(); - $this->shutdown(); - } - - ThreadManager::getInstance()->remove($this); - } } diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 61a8bbc927d..563af69bff6 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -210,8 +210,7 @@ public static function getScheduledTaskTimings(TaskHandler $task, int $period) : } /** - * @phpstan-template T of object - * @phpstan-param class-string $class + * @phpstan-param class-string $class */ private static function shortenCoreClassName(string $class, string $prefix) : string{ if(str_starts_with($class, $prefix)){ @@ -302,8 +301,7 @@ public static function getEventTimings(Event $event) : TimingsHandler{ } /** - * @phpstan-template TEvent of Event - * @phpstan-param class-string $event + * @phpstan-param class-string $event */ public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{ if(!isset(self::$eventHandlers[$event][$handlerName])){ diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php index bd968f9042c..ff603a2dfcb 100644 --- a/src/world/WorldManager.php +++ b/src/world/WorldManager.php @@ -129,10 +129,6 @@ public function unloadWorld(World $world, bool $forceUnload = false) : bool{ } $ev = new WorldUnloadEvent($world); - if($world === $this->defaultWorld && !$forceUnload){ - $ev->cancel(); - } - $ev->call(); if(!$forceUnload && $ev->isCancelled()){ diff --git a/src/world/generator/noise/Noise.php b/src/world/generator/noise/Noise.php index af9cefe14e0..d91a58350ad 100644 --- a/src/world/generator/noise/Noise.php +++ b/src/world/generator/noise/Noise.php @@ -208,6 +208,7 @@ public function getFastNoise1D(int $xSize, int $samplingRate, int $x, int $y, in throw new \InvalidArgumentException("xSize % samplingRate must return 0"); } + /** @phpstan-var \SplFixedArray $noiseArray */ $noiseArray = new \SplFixedArray($xSize + 1); for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ @@ -217,7 +218,13 @@ public function getFastNoise1D(int $xSize, int $samplingRate, int $x, int $y, in for($xx = 0; $xx < $xSize; ++$xx){ if($xx % $samplingRate !== 0){ $nx = (int) ($xx / $samplingRate) * $samplingRate; - $noiseArray[$xx] = self::linearLerp($xx, $nx, $nx + $samplingRate, $noiseArray[$nx], $noiseArray[$nx + $samplingRate]); + $noiseArray[$xx] = self::linearLerp( + x: $xx, + x1: $nx, + x2: $nx + $samplingRate, + q0: $noiseArray[$nx], + q1: $noiseArray[$nx + $samplingRate] + ); } } @@ -234,6 +241,7 @@ public function getFastNoise2D(int $xSize, int $zSize, int $samplingRate, int $x assert($xSize % $samplingRate === 0, new \InvalidArgumentException("xSize % samplingRate must return 0")); assert($zSize % $samplingRate === 0, new \InvalidArgumentException("zSize % samplingRate must return 0")); + /** @phpstan-var \SplFixedArray<\SplFixedArray> $noiseArray */ $noiseArray = new \SplFixedArray($xSize + 1); for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ @@ -253,9 +261,16 @@ public function getFastNoise2D(int $xSize, int $zSize, int $samplingRate, int $x $nx = (int) ($xx / $samplingRate) * $samplingRate; $nz = (int) ($zz / $samplingRate) * $samplingRate; $noiseArray[$xx][$zz] = Noise::bilinearLerp( - $xx, $zz, $noiseArray[$nx][$nz], $noiseArray[$nx][$nz + $samplingRate], - $noiseArray[$nx + $samplingRate][$nz], $noiseArray[$nx + $samplingRate][$nz + $samplingRate], - $nx, $nx + $samplingRate, $nz, $nz + $samplingRate + x: $xx, + y: $zz, + q00: $noiseArray[$nx][$nz], + q01: $noiseArray[$nx][$nz + $samplingRate], + q10: $noiseArray[$nx + $samplingRate][$nz], + q11: $noiseArray[$nx + $samplingRate][$nz + $samplingRate], + x1: $nx, + x2: $nx + $samplingRate, + y1: $nz, + y2: $nz + $samplingRate ); } } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index e265d8e577d..d7f2764c90a 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1070,6 +1070,21 @@ parameters: count: 1 path: ../../../src/world/generator/hell/Nether.php + - + message: "#^Offset int does not exist on SplFixedArray\\\\|null\\.$#" + count: 4 + path: ../../../src/world/generator/noise/Noise.php + + - + message: "#^Parameter \\$q0 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: ../../../src/world/generator/noise/Noise.php + + - + message: "#^Parameter \\$q1 of static method pocketmine\\\\world\\\\generator\\\\noise\\\\Noise\\:\\:linearLerp\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: ../../../src/world/generator/noise/Noise.php + - message: "#^Cannot call method getBiomeId\\(\\) on pocketmine\\\\world\\\\format\\\\Chunk\\|null\\.$#" count: 1 diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index b3bf3dadd86..de38903bd39 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Instanceof between pocketmine\\\\block\\\\utils\\\\BannerPatternLayer and pocketmine\\\\block\\\\utils\\\\BannerPatternLayer will always evaluate to true\\.$#" - count: 1 - path: ../../../src/block/BaseBanner.php - - message: "#^Method pocketmine\\\\block\\\\CakeWithCandle\\:\\:onInteractCandle\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" count: 1 @@ -40,3 +35,18 @@ parameters: count: 1 path: ../../../src/world/generator/normal/Normal.php + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with false will always evaluate to true\\.$#" + count: 1 + path: ../../phpunit/promise/PromiseTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false and 'All promise should…' will always evaluate to false\\.$#" + count: 1 + path: ../../phpunit/promise/PromiseTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#" + count: 2 + path: ../../phpunit/promise/PromiseTest.php + diff --git a/tests/phpunit/promise/PromiseTest.php b/tests/phpunit/promise/PromiseTest.php index 7198f4f619e..682ee007035 100644 --- a/tests/phpunit/promise/PromiseTest.php +++ b/tests/phpunit/promise/PromiseTest.php @@ -39,4 +39,110 @@ function() : void{ } ); } + + public function testAllPreResolved() : void{ + $resolver = new PromiseResolver(); + $resolver->resolve(1); + + $allPromise = Promise::all([$resolver->getPromise()]); + $done = false; + $allPromise->onCompletion( + function($value) use (&$done) : void{ + $done = true; + self::assertEquals([1], $value); + }, + function() use (&$done) : void{ + $done = true; + self::fail("Promise was rejected"); + } + ); + self::assertTrue($done); + } + + public function testAllPostResolved() : void{ + $resolver = new PromiseResolver(); + + $allPromise = Promise::all([$resolver->getPromise()]); + $done = false; + $allPromise->onCompletion( + function($value) use (&$done) : void{ + $done = true; + self::assertEquals([1], $value); + }, + function() use (&$done) : void{ + $done = true; + self::fail("Promise was rejected"); + } + ); + self::assertFalse($done); + $resolver->resolve(1); + self::assertTrue($done); + } + + public function testAllResolve() : void{ + $resolver1 = new PromiseResolver(); + $resolver2 = new PromiseResolver(); + + $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]); + $done = false; + $allPromise->onCompletion( + function($value) use (&$done) : void{ + $done = true; + self::assertEquals([1, 2], $value); + }, + function() use (&$done) : void{ + $done = true; + self::fail("Promise was rejected"); + } + ); + self::assertFalse($done); + $resolver1->resolve(1); + self::assertFalse($done); + $resolver2->resolve(2); + self::assertTrue($done); + } + + public function testAllPartialReject() : void{ + $resolver1 = new PromiseResolver(); + $resolver2 = new PromiseResolver(); + + $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]); + $done = false; + $allPromise->onCompletion( + function($value) use (&$done) : void{ + $done = true; + self::fail("Promise was unexpectedly resolved"); + }, + function() use (&$done) : void{ + $done = true; + } + ); + self::assertFalse($done); + $resolver2->reject(); + self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection"); + $resolver1->resolve(1); + } + + /** + * Promise::all() should return a rejected promise if any of the input promises were rejected at the call time + */ + public function testAllPartialPreReject() : void{ + $resolver1 = new PromiseResolver(); + $resolver2 = new PromiseResolver(); + $resolver2->reject(); + + $allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]); + $done = false; + $allPromise->onCompletion( + function($value) use (&$done) : void{ + $done = true; + self::fail("Promise was unexpectedly resolved"); + }, + function() use (&$done) : void{ + $done = true; + } + ); + self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection"); + $resolver1->resolve(1); + } } diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 77d2b9d93f9..ca15bd8bec0 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -34,6 +34,7 @@ use pocketmine\crafting\json\SmithingTrimRecipeData; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\item\BlockItemIdMap; +use pocketmine\data\bedrock\item\ItemTypeNames; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -50,12 +51,12 @@ use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; -use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; -use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield; use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; @@ -170,16 +171,21 @@ private function itemStackToJson(ItemStack $itemStack) : ItemStackData{ $data->meta = $meta; } - $nbt = $itemStack->getNbt(); - if($nbt !== null && count($nbt) > 0){ - $data->nbt = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($nbt))); - } + $rawExtraData = $itemStack->getRawExtraData(); + if($rawExtraData !== ""){ + $decoder = PacketSerializer::decoder($rawExtraData, 0); + $extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder); + $nbt = $extraData->getNbt(); + if($nbt !== null && count($nbt) > 0){ + $data->nbt = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($nbt))); + } - if(count($itemStack->getCanPlaceOn()) > 0){ - $data->can_place_on = $itemStack->getCanPlaceOn(); - } - if(count($itemStack->getCanDestroy()) > 0){ - $data->can_destroy = $itemStack->getCanDestroy(); + if(count($extraData->getCanPlaceOn()) > 0){ + $data->can_place_on = $extraData->getCanPlaceOn(); + } + if(count($extraData->getCanDestroy()) > 0){ + $data->can_destroy = $extraData->getCanDestroy(); + } } return $data; @@ -421,7 +427,7 @@ public function handleCraftingData(CraftingDataPacket $packet) : bool{ $recipes["potion_type"][] = new PotionTypeRecipeData( $this->recipeIngredientToJson(new RecipeIngredient(new IntIdMetaItemDescriptor($recipe->getInputItemId(), $recipe->getInputItemMeta()), 1)), $this->recipeIngredientToJson(new RecipeIngredient(new IntIdMetaItemDescriptor($recipe->getIngredientItemId(), $recipe->getIngredientItemMeta()), 1)), - $this->itemStackToJson(new ItemStack($recipe->getOutputItemId(), $recipe->getOutputItemMeta(), 1, 0, null, [], [], null)), + $this->itemStackToJson(new ItemStack($recipe->getOutputItemId(), $recipe->getOutputItemMeta(), 1, 0, "")), ); } @@ -571,10 +577,7 @@ function main(array $argv) : int{ fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]); continue; } - $serializer = PacketSerializer::decoder($raw, 0, new PacketSerializerContext( - $handler->itemTypeDictionary ?? - new ItemTypeDictionary([new ItemTypeEntry("minecraft:shield", 0, false)])) - ); + $serializer = PacketSerializer::decoder($raw, 0); $pk->decode($serializer); $pk->handle($handler); diff --git a/tools/generate-block-palette-spec.php b/tools/generate-block-palette-spec.php index 6217d543783..879ffd6b486 100644 --- a/tools/generate-block-palette-spec.php +++ b/tools/generate-block-palette-spec.php @@ -40,6 +40,7 @@ use function json_encode; use function ksort; use const JSON_PRETTY_PRINT; +use const SORT_NATURAL; use const SORT_STRING; use const STDERR; @@ -82,7 +83,7 @@ foreach(Utils::stringifyKeys($reportMap) as $blockName => $propertyList){ foreach(Utils::stringifyKeys($propertyList) as $propertyName => $propertyValues){ - ksort($reportMap[$blockName][$propertyName]); + ksort($propertyValues, SORT_NATURAL); $reportMap[$blockName][$propertyName] = array_values($propertyValues); } }