From 9402a20ee3de0232761433565fc984953939c754 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 16 Feb 2025 16:12:29 +0000 Subject: [PATCH 1/8] Update Utils::getOS() doc comment closes #6628 --- src/utils/Utils.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index b1f7e2842..46cd49ec4 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -264,14 +264,7 @@ final class Utils{ } /** - * Returns the current Operating System - * Windows => win - * MacOS => mac - * iOS => ios - * Android => android - * Linux => Linux - * BSD => bsd - * Other => other + * @return string one of the Utils::OS_* constants */ public static function getOS(bool $recalculate = false) : string{ if(self::$os === null || $recalculate){ From 91ac64783f0e5d2cffbff8b58c4bc98d5283832a Mon Sep 17 00:00:00 2001 From: Dries C Date: Sun, 16 Feb 2025 21:51:53 +0100 Subject: [PATCH 2/8] Bedrock 1.21.60 (#6627) Co-authored-by: Dylan K. Taylor --- composer.json | 8 +- composer.lock | 52 +-- src/data/bedrock/BedrockDataFiles.php | 3 +- src/data/bedrock/block/BlockStateData.php | 4 +- src/data/bedrock/block/BlockStateNames.php | 1 + .../bedrock/block/BlockStateStringValues.php | 4 + .../convert/BlockStateDeserializerHelper.php | 4 +- .../convert/BlockStateSerializerHelper.php | 4 +- src/entity/Human.php | 1 + src/inventory/CreativeCategory.php | 34 ++ src/inventory/CreativeGroup.php | 51 +++ src/inventory/CreativeInventory.php | 69 ++-- src/inventory/CreativeInventoryEntry.php | 48 +++ src/inventory/json/CreativeGroupData.php | 38 ++ src/lang/KnownTranslationFactory.php | 324 ++++++++++++++++++ src/lang/KnownTranslationKeys.php | 81 +++++ src/network/mcpe/InventoryManager.php | 2 +- src/network/mcpe/NetworkSession.php | 4 +- .../mcpe/cache/CreativeInventoryCache.php | 106 +++++- .../cache/CreativeInventoryCacheEntry.php | 48 +++ .../ItemTypeDictionaryFromDataHelper.php | 12 +- .../mcpe/handler/PreSpawnPacketHandler.php | 5 +- src/world/format/io/data/BedrockWorldData.php | 6 +- tools/generate-bedrock-data-from-packets.php | 89 ++++- 24 files changed, 902 insertions(+), 96 deletions(-) create mode 100644 src/inventory/CreativeCategory.php create mode 100644 src/inventory/CreativeGroup.php create mode 100644 src/inventory/CreativeInventoryEntry.php create mode 100644 src/inventory/json/CreativeGroupData.php create mode 100644 src/network/mcpe/cache/CreativeInventoryCacheEntry.php diff --git a/composer.json b/composer.json index bbcbe7314..f8d060bcd 100644 --- a/composer.json +++ b/composer.json @@ -33,15 +33,15 @@ "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", "netresearch/jsonmapper": "~v5.0.0", - "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", - "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50", + "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", + "pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50", - "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50", + "pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/color": "^0.3.0", "pocketmine/errorhandler": "^0.7.0", - "pocketmine/locale-data": "~2.22.0", + "pocketmine/locale-data": "~2.24.0", "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.0.0", diff --git a/composer.lock b/composer.lock index 52490968f..cc7f1f716 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6aa0a34a230d325528d2e11f1439057", + "content-hash": "af7547291a131bfac6d7087957601325", "packages": [ { "name": "adhocore/json-comment", @@ -178,16 +178,16 @@ }, { "name": "pocketmine/bedrock-block-upgrade-schema", - "version": "5.0.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", - "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52" + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52", - "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52", + "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", + "reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", "shasum": "" }, "type": "library", @@ -198,22 +198,22 @@ "description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves", "support": { "issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0" + "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0" }, - "time": "2024-11-03T14:13:50+00:00" + "time": "2025-02-11T17:41:44+00:00" }, { "name": "pocketmine/bedrock-data", - "version": "2.15.0+bedrock-1.21.50", + "version": "4.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad" + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad", - "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e", + "reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e", "shasum": "" }, "type": "library", @@ -224,9 +224,9 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60" }, - "time": "2024-12-04T12:59:12+00:00" + "time": "2025-02-16T15:56:56+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", @@ -256,16 +256,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "35.0.3+bedrock-1.21.50", + "version": "36.0.0+bedrock-1.21.60", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c" + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c4d62581cb62d29ec426914c6b4d7e0ff835da9c", - "reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963", + "reference": "2057de319c5c551001c2a544e08d1bc7727d9963", "shasum": "" }, "require": { @@ -296,9 +296,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.3+bedrock-1.21.50" + "source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60" }, - "time": "2025-01-07T23:06:29+00:00" + "time": "2025-02-16T15:59:08+00:00" }, { "name": "pocketmine/binaryutils", @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.22.1", + "version": "2.24.0", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49" + "reference": "6ec5e92c77a2102b2692763733e4763012facae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/fa4e377c437391cfcfdedd08eea3a848eabd1b49", - "reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49", + "url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9", + "reference": "6ec5e92c77a2102b2692763733e4763012facae9", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.22.1" + "source": "https://github.com/pmmp/Language/tree/2.24.0" }, - "time": "2024-12-06T14:44:17+00:00" + "time": "2025-02-16T20:46:34+00:00" }, { "name": "pocketmine/log", @@ -2967,5 +2967,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 3341c0503..6176eee34 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -39,12 +39,11 @@ final class BedrockDataFiles{ public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json'; public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt'; public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json'; - public const CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.json'; public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json'; public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt'; + public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; - public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json'; public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; diff --git a/src/data/bedrock/block/BlockStateData.php b/src/data/bedrock/block/BlockStateData.php index 6624c4ae0..e90410ac7 100644 --- a/src/data/bedrock/block/BlockStateData.php +++ b/src/data/bedrock/block/BlockStateData.php @@ -45,8 +45,8 @@ final class BlockStateData{ public const CURRENT_VERSION = (1 << 24) | //major (21 << 16) | //minor - (40 << 8) | //patch - (1); //revision + (60 << 8) | //patch + (33); //revision public const TAG_NAME = "name"; public const TAG_STATES = "states"; diff --git a/src/data/bedrock/block/BlockStateNames.php b/src/data/bedrock/block/BlockStateNames.php index 68a3e1b1d..9fed77e4a 100644 --- a/src/data/bedrock/block/BlockStateNames.php +++ b/src/data/bedrock/block/BlockStateNames.php @@ -59,6 +59,7 @@ final class BlockStateNames{ public const COVERED_BIT = "covered_bit"; public const CRACKED_STATE = "cracked_state"; public const CRAFTING = "crafting"; + public const CREAKING_HEART_STATE = "creaking_heart_state"; public const DEAD_BIT = "dead_bit"; public const DEPRECATED = "deprecated"; public const DIRECTION = "direction"; diff --git a/src/data/bedrock/block/BlockStateStringValues.php b/src/data/bedrock/block/BlockStateStringValues.php index e8171e4da..e6ae30e64 100644 --- a/src/data/bedrock/block/BlockStateStringValues.php +++ b/src/data/bedrock/block/BlockStateStringValues.php @@ -56,6 +56,10 @@ final class BlockStateStringValues{ public const CRACKED_STATE_MAX_CRACKED = "max_cracked"; public const CRACKED_STATE_NO_CRACKS = "no_cracks"; + public const CREAKING_HEART_STATE_AWAKE = "awake"; + public const CREAKING_HEART_STATE_DORMANT = "dormant"; + public const CREAKING_HEART_STATE_UPROOTED = "uprooted"; + public const DRIPSTONE_THICKNESS_BASE = "base"; public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum"; public const DRIPSTONE_THICKNESS_MERGE = "merge"; diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index fda0455aa..342f31490 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -131,7 +131,7 @@ final class BlockStateDeserializerHelper{ //TODO: check if these need any special treatment to get the appropriate data to both halves of the door return $block ->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT)) - ->setFacing(Facing::rotateY($in->readLegacyHorizontalFacing(), false)) + ->setFacing($in->readCardinalHorizontalFacing()) ->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } @@ -145,7 +145,7 @@ final class BlockStateDeserializerHelper{ /** @throws BlockStateDeserializeException */ public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{ return $block - ->setFacing($in->readLegacyHorizontalFacing()) + ->setFacing($in->readCardinalHorizontalFacing()) ->setInWall($in->readBool(BlockStateNames::IN_WALL_BIT)) ->setOpen($in->readBool(BlockStateNames::OPEN_BIT)); } diff --git a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php index 3e2215746..fa0591669 100644 --- a/src/data/bedrock/block/convert/BlockStateSerializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateSerializerHelper.php @@ -100,7 +100,7 @@ final class BlockStateSerializerHelper{ public static function encodeDoor(Door $block, Writer $out) : Writer{ return $out ->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop()) - ->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true)) + ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } @@ -112,7 +112,7 @@ final class BlockStateSerializerHelper{ public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{ return $out - ->writeLegacyHorizontalFacing($block->getFacing()) + ->writeCardinalHorizontalFacing($block->getFacing()) ->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall()) ->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen()); } diff --git a/src/entity/Human.php b/src/entity/Human.php index 833196651..c94b76097 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -513,6 +513,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ AbilitiesLayer::LAYER_BASE, array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, false), 0.0, + 0.0, 0.0 ) ])), diff --git a/src/inventory/CreativeCategory.php b/src/inventory/CreativeCategory.php new file mode 100644 index 000000000..bede48b28 --- /dev/null +++ b/src/inventory/CreativeCategory.php @@ -0,0 +1,34 @@ +getText()) : strlen($name); + if($nameLength === 0){ + throw new \InvalidArgumentException("Creative group name cannot be empty"); + } + } + + public function getName() : Translatable|string{ return $this->name; } + + public function getIcon() : Item{ return clone $this->icon; } +} diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 57e5cbb4e..77eda8138 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -24,21 +24,23 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\crafting\CraftingManagerFromDataHelper; -use pocketmine\crafting\json\ItemStackData; -use pocketmine\data\bedrock\BedrockDataFiles; +use pocketmine\inventory\json\CreativeGroupData; use pocketmine\item\Item; +use pocketmine\lang\Translatable; use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\ObjectSet; use pocketmine\utils\SingletonTrait; -use pocketmine\utils\Utils; +use Symfony\Component\Filesystem\Path; +use function array_filter; +use function array_map; final class CreativeInventory{ use SingletonTrait; use DestructorCallbackTrait; /** - * @var Item[] - * @phpstan-var array + * @var CreativeInventoryEntry[] + * @phpstan-var array */ private array $creative = []; @@ -47,17 +49,32 @@ final class CreativeInventory{ private function __construct(){ $this->contentChangedCallbacks = new ObjectSet(); - $creativeItems = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( - BedrockDataFiles::CREATIVEITEMS_JSON, - ItemStackData::class - ); - foreach($creativeItems as $data){ - $item = CraftingManagerFromDataHelper::deserializeItemStack($data); - if($item === null){ - //unknown item - continue; + + foreach([ + "construction" => CreativeCategory::CONSTRUCTION, + "nature" => CreativeCategory::NATURE, + "equipment" => CreativeCategory::EQUIPMENT, + "items" => CreativeCategory::ITEMS, + ] as $categoryId => $categoryEnum){ + $groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( + Path::join(\pocketmine\BEDROCK_DATA_PATH, "creative", $categoryId . ".json"), + CreativeGroupData::class + ); + + foreach($groups as $groupData){ + $icon = $groupData->group_icon === null ? null : CraftingManagerFromDataHelper::deserializeItemStack($groupData->group_icon); + + $group = $icon === null ? null : new CreativeGroup( + new Translatable($groupData->group_name), + $icon + ); + + $items = array_filter(array_map(static fn($itemStack) => CraftingManagerFromDataHelper::deserializeItemStack($itemStack), $groupData->items)); + + foreach($items as $item){ + $this->add($item, $categoryEnum, $group); + } } - $this->add($item); } } @@ -75,16 +92,28 @@ final class CreativeInventory{ * @phpstan-return array */ public function getAll() : array{ - return Utils::cloneObjectArray($this->creative); + return array_map(fn(CreativeInventoryEntry $entry) => $entry->getItem(), $this->creative); + } + + /** + * @return CreativeInventoryEntry[] + * @phpstan-return array + */ + public function getAllEntries() : array{ + return $this->creative; } public function getItem(int $index) : ?Item{ - return isset($this->creative[$index]) ? clone $this->creative[$index] : null; + return $this->getEntry($index)?->getItem(); + } + + public function getEntry(int $index) : ?CreativeInventoryEntry{ + return $this->creative[$index] ?? null; } public function getItemIndex(Item $item) : int{ foreach($this->creative as $i => $d){ - if($item->equals($d, true, false)){ + if($d->matchesItem($item)){ return $i; } } @@ -96,8 +125,8 @@ final class CreativeInventory{ * Adds an item to the creative menu. * Note: Players who are already online when this is called will not see this change. */ - public function add(Item $item) : void{ - $this->creative[] = clone $item; + public function add(Item $item, CreativeCategory $category = CreativeCategory::ITEMS, ?CreativeGroup $group = null) : void{ + $this->creative[] = new CreativeInventoryEntry($item, $category, $group); $this->onContentChange(); } diff --git a/src/inventory/CreativeInventoryEntry.php b/src/inventory/CreativeInventoryEntry.php new file mode 100644 index 000000000..a5d568ee8 --- /dev/null +++ b/src/inventory/CreativeInventoryEntry.php @@ -0,0 +1,48 @@ +item = clone $item; + } + + public function getItem() : Item{ return clone $this->item; } + + public function getCategory() : CreativeCategory{ return $this->category; } + + public function getGroup() : ?CreativeGroup{ return $this->group; } + + public function matchesItem(Item $item) : bool{ + return $item->equals($this->item, checkDamage: true, checkCompound: false); + } +} diff --git a/src/inventory/json/CreativeGroupData.php b/src/inventory/json/CreativeGroupData.php new file mode 100644 index 000000000..70b73d3de --- /dev/null +++ b/src/inventory/json/CreativeGroupData.php @@ -0,0 +1,38 @@ +session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory())); + $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->buildPacket($this->player->getCreativeInventory(), $this->session)); } /** diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index fdb63c467..8c457ed40 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1058,7 +1058,7 @@ class NetworkSession{ ]; $layers = [ - new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 0.1), + new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 1, 0.1), ]; if(!$for->hasBlockCollision()){ //TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a @@ -1068,7 +1068,7 @@ class NetworkSession{ $layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [ AbilitiesLayer::ABILITY_FLYING => true, - ], null, null); + ], null, null, null); } $this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData( diff --git a/src/network/mcpe/cache/CreativeInventoryCache.php b/src/network/mcpe/cache/CreativeInventoryCache.php index 04fc52604..e543f343e 100644 --- a/src/network/mcpe/cache/CreativeInventoryCache.php +++ b/src/network/mcpe/cache/CreativeInventoryCache.php @@ -23,23 +23,30 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\cache; +use pocketmine\inventory\CreativeCategory; use pocketmine\inventory\CreativeInventory; +use pocketmine\lang\Translatable; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\CreativeContentPacket; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\utils\SingletonTrait; +use function is_string; use function spl_object_id; +use const PHP_INT_MIN; final class CreativeInventoryCache{ use SingletonTrait; /** - * @var CreativeContentPacket[] - * @phpstan-var array + * @var CreativeInventoryCacheEntry[] + * @phpstan-var array */ private array $caches = []; - public function getCache(CreativeInventory $inventory) : CreativeContentPacket{ + private function getCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ $id = spl_object_id($inventory); if(!isset($this->caches[$id])){ $inventory->getDestructorCallbacks()->add(function() use ($id) : void{ @@ -48,7 +55,7 @@ final class CreativeInventoryCache{ $inventory->getContentChangedCallbacks()->add(function() use ($id) : void{ unset($this->caches[$id]); }); - $this->caches[$id] = $this->buildCreativeInventoryCache($inventory); + $this->caches[$id] = $this->buildCacheEntry($inventory); } return $this->caches[$id]; } @@ -56,14 +63,91 @@ final class CreativeInventoryCache{ /** * Rebuild the cache for the given inventory. */ - private function buildCreativeInventoryCache(CreativeInventory $inventory) : CreativeContentPacket{ - $entries = []; + private function buildCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{ + $categories = []; + $groups = []; + $typeConverter = TypeConverter::getInstance(); - //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent - foreach($inventory->getAll() as $k => $item){ - $entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item)); + + $nextIndex = 0; + $groupIndexes = []; + $itemGroupIndexes = []; + + foreach($inventory->getAllEntries() as $k => $entry){ + $group = $entry->getGroup(); + $category = $entry->getCategory(); + if($group === null){ + $groupId = PHP_INT_MIN; + }else{ + $groupId = spl_object_id($group); + unset($groupIndexes[$category->name][PHP_INT_MIN]); //start a new anonymous group for this category + } + + //group object may be reused by multiple categories + if(!isset($groupIndexes[$category->name][$groupId])){ + $groupIndexes[$category->name][$groupId] = $nextIndex++; + $categories[] = $category; + $groups[] = $group; + } + $itemGroupIndexes[$k] = $groupIndexes[$category->name][$groupId]; } - return CreativeContentPacket::create($entries); + //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent + $items = []; + foreach($inventory->getAllEntries() as $k => $entry){ + $items[] = new CreativeItemEntry( + $k, + $typeConverter->coreItemStackToNet($entry->getItem()), + $itemGroupIndexes[$k] + ); + } + + return new CreativeInventoryCacheEntry($categories, $groups, $items); + } + + public function buildPacket(CreativeInventory $inventory, NetworkSession $session) : CreativeContentPacket{ + $player = $session->getPlayer() ?? throw new \LogicException("Cannot prepare creative data for a session without a player"); + $language = $player->getLanguage(); + $forceLanguage = $player->getServer()->isLanguageForced(); + $typeConverter = $session->getTypeConverter(); + $cachedEntry = $this->getCacheEntry($inventory); + $translate = function(Translatable|string $translatable) use ($session, $language, $forceLanguage) : string{ + if(is_string($translatable)){ + $message = $translatable; + }elseif(!$forceLanguage){ + [$message,] = $session->prepareClientTranslatableMessage($translatable); + }else{ + $message = $language->translate($translatable); + } + return $message; + }; + + $groupEntries = []; + foreach($cachedEntry->categories as $index => $category){ + $group = $cachedEntry->groups[$index]; + $categoryId = match ($category) { + CreativeCategory::CONSTRUCTION => CreativeContentPacket::CATEGORY_CONSTRUCTION, + CreativeCategory::NATURE => CreativeContentPacket::CATEGORY_NATURE, + CreativeCategory::EQUIPMENT => CreativeContentPacket::CATEGORY_EQUIPMENT, + CreativeCategory::ITEMS => CreativeContentPacket::CATEGORY_ITEMS + }; + if($group === null){ + $groupEntries[] = new CreativeGroupEntry($categoryId, "", ItemStack::null()); + }else{ + $groupIcon = $group->getIcon(); + //TODO: HACK! In 1.21.60, Workaround glitchy behaviour when an item is used as an icon for a group it + //doesn't belong to. Without this hack, both instances of the item will show a +, but neither of them + //will actually expand the group work correctly. + $groupIcon->getNamedTag()->setInt("___GroupBugWorkaround___", $index); + $groupName = $group->getName(); + $groupEntries[] = new CreativeGroupEntry( + $categoryId, + $translate($groupName), + $typeConverter->coreItemStackToNet($groupIcon) + ); + } + } + + return CreativeContentPacket::create($groupEntries, $cachedEntry->items); } } diff --git a/src/network/mcpe/cache/CreativeInventoryCacheEntry.php b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php new file mode 100644 index 000000000..1fc0767df --- /dev/null +++ b/src/network/mcpe/cache/CreativeInventoryCacheEntry.php @@ -0,0 +1,48 @@ + $categories + * @phpstan-param list $groups + * @phpstan-param list $items + */ + public function __construct( + public readonly array $categories, + public readonly array $groups, + public readonly array $items, + ){ + //NOOP + } +} diff --git a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php index d962063d3..ed8ae2cc7 100644 --- a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php +++ b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php @@ -23,10 +23,15 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\errorhandler\ErrorToExceptionHandler; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; +use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; +use function base64_decode; use function is_array; use function is_bool; use function is_int; @@ -41,12 +46,15 @@ final class ItemTypeDictionaryFromDataHelper{ throw new AssumptionFailedError("Invalid item list format"); } + $emptyNBT = new CacheableNbt(new CompoundTag()); + $nbtSerializer = new LittleEndianNbtSerializer(); + $params = []; foreach(Utils::promoteKeys($table) as $name => $entry){ - if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){ + if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"], $entry["version"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"]) || !is_int($entry["version"]) || !(is_string($componentNbt = $entry["component_nbt"] ?? null) || $componentNbt === null)){ throw new AssumptionFailedError("Invalid item list format"); } - $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]); + $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"], $entry["version"], $componentNbt === null ? $emptyNBT : new CacheableNbt($nbtSerializer->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($componentNbt, true)))->mustGetCompoundTag())); } return new ItemTypeDictionary($params); } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index b80874938..9aa302c0c 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -28,6 +28,7 @@ use pocketmine\network\mcpe\cache\CraftingDataCache; use pocketmine\network\mcpe\cache\StaticPacketCache; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; @@ -110,9 +111,11 @@ class PreSpawnPacketHandler extends PacketHandler{ new NetworkPermissions(disableClientSounds: true), [], 0, - $typeConverter->getItemTypeDictionary()->getEntries(), )); + $this->session->getLogger()->debug("Sending items"); + $this->session->sendDataPacket(ItemRegistryPacket::create($typeConverter->getItemTypeDictionary()->getEntries())); + $this->session->getLogger()->debug("Sending actor identifiers"); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers()); diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index b2e079124..a9ca43bc3 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -51,12 +51,12 @@ use function time; class BedrockWorldData extends BaseNbtWorldData{ public const CURRENT_STORAGE_VERSION = 10; - public const CURRENT_STORAGE_NETWORK_VERSION = 748; + public const CURRENT_STORAGE_NETWORK_VERSION = 776; public const CURRENT_CLIENT_VERSION_TARGET = [ 1, //major 21, //minor - 40, //patch - 1, //revision + 60, //patch + 33, //revision 0 //is beta ]; diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 9aac5185e..c48a4f017 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -35,6 +35,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\inventory\json\CreativeGroupData; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -48,15 +49,17 @@ use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CreativeContentPacket; +use pocketmine\network\mcpe\protocol\ItemRegistryPacket; 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\StartGamePacket; use pocketmine\network\mcpe\protocol\types\CacheableNbt; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; +use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; 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\ItemTypeEntry; use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; @@ -134,6 +137,19 @@ class ParserPacketHandler extends PacketHandler{ return base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($statePropertiesTag))); } + /** + * @param ItemStackData[] $items + */ + private function creativeGroupEntryToJson(CreativeGroupEntry $entry, array $items) : CreativeGroupData{ + $data = new CreativeGroupData(); + + $data->group_name = $entry->getCategoryName(); + $data->group_icon = $entry->getIcon()->getId() === 0 ? null : $this->itemStackToJson($entry->getIcon()); + $data->items = $items; + + return $data; + } + private function itemStackToJson(ItemStack $itemStack) : ItemStackData{ if($itemStack->getId() === 0){ throw new InvalidArgumentException("Cannot serialize a null itemstack"); @@ -234,31 +250,68 @@ class ParserPacketHandler extends PacketHandler{ } public function handleStartGame(StartGamePacket $packet) : bool{ - $this->itemTypeDictionary = new ItemTypeDictionary($packet->itemTable); - - echo "updating legacy item ID mapping table\n"; - $table = []; - foreach($packet->itemTable as $entry){ - $table[$entry->getStringId()] = [ - "runtime_id" => $entry->getNumericId(), - "component_based" => $entry->isComponentBased() - ]; - } - ksort($table, SORT_STRING); - file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); - foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){ echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n"; } return true; } + public function handleItemRegistry(ItemRegistryPacket $packet) : bool{ + $this->itemTypeDictionary = new ItemTypeDictionary($packet->getEntries()); + + echo "updating legacy item ID mapping table\n"; + $emptyNBT = new CompoundTag(); + $table = []; + foreach($packet->getEntries() as $entry){ + $table[$entry->getStringId()] = [ + "runtime_id" => $entry->getNumericId(), + "component_based" => $entry->isComponentBased(), + "version" => $entry->getVersion(), + ]; + + $componentNBT = $entry->getComponentNbt()->getRoot(); + if(!$componentNBT->equals($emptyNBT)){ + $table[$entry->getStringId()]["component_nbt"] = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($componentNBT))); + } + } + ksort($table, SORT_STRING); + file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); + + echo "updating item registry\n"; + $items = array_map(function(ItemTypeEntry $entry) : array{ + return self::objectToOrderedArray($entry); + }, $packet->getEntries()); + file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + return true; + } + public function handleCreativeContent(CreativeContentPacket $packet) : bool{ echo "updating creative inventory data\n"; - $items = array_map(function(CreativeContentEntry $entry) : array{ - return self::objectToOrderedArray($this->itemStackToJson($entry->getItem())); - }, $packet->getEntries()); - file_put_contents($this->bedrockDataPath . '/creativeitems.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); + + $groupItems = []; + foreach($packet->getItems() as $itemEntry){ + $groupItems[$itemEntry->getGroupId()][] = $this->itemStackToJson($itemEntry->getItem()); + } + + static $typeMap = [ + CreativeContentPacket::CATEGORY_CONSTRUCTION => "construction", + CreativeContentPacket::CATEGORY_NATURE => "nature", + CreativeContentPacket::CATEGORY_EQUIPMENT => "equipment", + CreativeContentPacket::CATEGORY_ITEMS => "items", + ]; + + $groupCategories = []; + foreach(Utils::promoteKeys($packet->getGroups()) as $groupId => $group){ + $category = $typeMap[$group->getCategoryId()] ?? throw new PacketHandlingException("Unknown creative category ID " . $group->getCategoryId()); + //FIXME: objectToOrderedArray might mess with the order of groupItems + //this isn't a problem right now because it's a list, but could cause problems in the future + $groupCategories[$category][] = self::objectToOrderedArray($this->creativeGroupEntryToJson($group, $groupItems[$groupId])); + } + + foreach(Utils::promoteKeys($groupCategories) as $category => $categoryGroups){ + file_put_contents($this->bedrockDataPath . '/creative/' . $category . '.json', json_encode($categoryGroups, JSON_PRETTY_PRINT) . "\n"); + } + return true; } From 03e4b53ac46e020ed93723e3ee2bf2b673c93d9a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 20:57:16 +0000 Subject: [PATCH 3/8] BedrockDataFiles: added constants for folders as well as files we probably should have it recurse too, but this is an easy win. --- build/generate-bedrockdata-path-consts.php | 4 +++- src/Server.php | 3 ++- src/data/bedrock/BedrockDataFiles.php | 3 +++ src/inventory/CreativeInventory.php | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/build/generate-bedrockdata-path-consts.php b/build/generate-bedrockdata-path-consts.php index 6ad6d83fd..f74137dd2 100644 --- a/build/generate-bedrockdata-path-consts.php +++ b/build/generate-bedrockdata-path-consts.php @@ -28,6 +28,7 @@ use function dirname; use function fclose; use function fopen; use function fwrite; +use function is_dir; use function is_file; use function scandir; use function str_replace; @@ -59,7 +60,7 @@ foreach($files as $file){ continue; } $path = Path::join(BEDROCK_DATA_PATH, $file); - if(!is_file($path)){ + if(!is_file($path) && !is_dir($path)){ continue; } @@ -67,6 +68,7 @@ foreach($files as $file){ 'README.md', 'LICENSE', 'composer.json', + '.github' ] as $ignored){ if($file === $ignored){ continue 2; diff --git a/src/Server.php b/src/Server.php index 5a72ad048..252964a91 100644 --- a/src/Server.php +++ b/src/Server.php @@ -36,6 +36,7 @@ use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crash\CrashDump; use pocketmine\crash\CrashDumpRenderer; +use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\entity\EntityDataHelper; use pocketmine\entity\Location; use pocketmine\event\HandlerListManager; @@ -1005,7 +1006,7 @@ class Server{ $this->commandMap = new SimpleCommandMap($this); - $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes")); + $this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES); $this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger); diff --git a/src/data/bedrock/BedrockDataFiles.php b/src/data/bedrock/BedrockDataFiles.php index 6176eee34..1ecb707cc 100644 --- a/src/data/bedrock/BedrockDataFiles.php +++ b/src/data/bedrock/BedrockDataFiles.php @@ -39,13 +39,16 @@ final class BedrockDataFiles{ public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json'; public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt'; public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json'; + public const CREATIVE = BEDROCK_DATA_PATH . '/creative'; public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json'; public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt'; + public const ENUMS = BEDROCK_DATA_PATH . '/enums'; public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; + public const RECIPES = BEDROCK_DATA_PATH . '/recipes'; public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json'; } diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 77eda8138..ee27068c7 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\crafting\CraftingManagerFromDataHelper; +use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\inventory\json\CreativeGroupData; use pocketmine\item\Item; use pocketmine\lang\Translatable; @@ -57,7 +58,7 @@ final class CreativeInventory{ "items" => CreativeCategory::ITEMS, ] as $categoryId => $categoryEnum){ $groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( - Path::join(\pocketmine\BEDROCK_DATA_PATH, "creative", $categoryId . ".json"), + Path::join(BedrockDataFiles::CREATIVE, $categoryId . ".json"), CreativeGroupData::class ); From 246c1776dfb96bf49194f4fad487b84ed2e40bf4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 21:47:35 +0000 Subject: [PATCH 4/8] InventoryAction: avoid throwaway Item clones --- src/inventory/transaction/InventoryTransaction.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 8f7b57610..6e010c7b8 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -144,8 +144,9 @@ class InventoryTransaction{ $needItems = []; $haveItems = []; foreach($this->actions as $key => $action){ - if(!$action->getTargetItem()->isNull()){ - $needItems[] = $action->getTargetItem(); + $targetItem = $action->getTargetItem(); + if(!$targetItem->isNull()){ + $needItems[] = $targetItem; } try{ @@ -154,8 +155,9 @@ class InventoryTransaction{ throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e); } - if(!$action->getSourceItem()->isNull()){ - $haveItems[] = $action->getSourceItem(); + $sourceItem = $action->getSourceItem(); + if(!$sourceItem->isNull()){ + $haveItems[] = $sourceItem; } } From d96ef21c4d30ea60e76de7b9188fa0829e14137d Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 16 Feb 2025 22:16:47 +0000 Subject: [PATCH 5/8] Prepare 5.25.0 release (#6631) --- changelogs/5.25.md | 38 ++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.25.md diff --git a/changelogs/5.25.md b/changelogs/5.25.md new file mode 100644 index 000000000..cd3fb9365 --- /dev/null +++ b/changelogs/5.25.md @@ -0,0 +1,38 @@ +# 5.25.0 +Released 16th February 2025. + +This is a support release for Minecraft: Bedrock Edition 1.21.60. It also includes some minor API additions supporting new features in this version. + +**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 support for Minecraft: Bedrock Edition 1.21.60. +- Removed support for earlier versions. + +## Documentation +- Fixed the documentation of `Utils::getOS()`. It now refers to the `Utils::OS_*` constants instead of a list of hardcoded strings. + +## API +### `pocketmine\inventory` +This release allows plugins to decide which creative tab they want to add their new items to. +It also allows creating new collapsible groups of items, and modifying or removing existing ones. + +- The following new methods have been added: + - `public CreativeInventory->getAllEntries() : list` - returns an array of objects, each containing a creative item and information about its category and collapsible group (if applicable). + - `public CreativeInventory->getEntry(int $index) : ?CreativeInventoryEntry` - returns the creative item with the specified identifier, or `null` if not found +- The following methods have signature changes: + - `CreativeInventory->add()` now accepts two additional optional parameters: `CreativeCategory $category, ?CreativeGroup $group`. If not specified, the item will be added to the Items tab without a group. +- The following new classes have been added: + - `CreativeCategory` - enum of possible creative inventory categories (each has its own tab on the GUI) + - `CreativeGroup` - contains information about a collapsible group of creative items, including tooltip text and icon + - `CreativeInventoryEntry` - contains information about a creative inventory item, including its category and collapsible group (if applicable) + +## Internals +- `CreativeContentPacket` is no longer fully cached due to the requirement for translation context during construction. The individual items are still cached (which is the most expensive part), but the packet itself is now constructed on demand, and group entries are constructed on the fly. This may affect performance, but this has not been investigated. +- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files. +- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category. +- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index aaa357e0f..f5aa59c4f 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.24.1"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.25.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 34f801ee3c6d8c99dfd928b19fc58597b5b997ea Mon Sep 17 00:00:00 2001 From: "pmmp-admin-bot[bot]" <188621379+pmmp-admin-bot[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 22:18:20 +0000 Subject: [PATCH 6/8] 5.25.1 is next Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13359320328 --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index f5aa59c4f..f8b5d7c74 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.25.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.25.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 788ee9a008e7561a112764efdea63dc9cf711fea Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 22:49:40 +0000 Subject: [PATCH 7/8] Allow overriding deserializers for block and item IDs there's no technical reason not to support this, since it doesn't violate any assumptions and the type returned is a base anyway. this enables implementing stuff like snow cauldrons in a plugin, which previously would require reflection due to the minecraft:cauldron deserializer being registered already. it also enables overriding IDs to map to custom blocks, which might be useful for overriding some functionality (although this is inadvisable - and won't alter the usage of stuff like VanillaBlocks::WHATEVER()). we do *not* allow overriding serializers, since type IDs are expected to be paired to block implementations, and allowing them to be reassigned could lead to crashes if the new class was incorrect. So the correct approach for overriding nether portals would be to create a custom type ID as if you were adding a fully custom item. This will also allow other plugins to distinguish between your implementation and the built-in one. --- .../convert/BlockStateToObjectDeserializer.php | 14 +++++++++++--- src/data/bedrock/item/ItemDeserializer.php | 13 ++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index cb9a6e7ae..42d8ee0cd 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -103,10 +103,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ /** @phpstan-param \Closure(Reader) : Block $c */ public function map(string $id, \Closure $c) : void{ - if(array_key_exists($id, $this->deserializeFuncs)){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializeFuncs[$id] = $c; + $this->simpleCache = []; + } + + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return ?\Closure(Reader) : Block + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializeFuncs[$id] ?? null; } /** @phpstan-param \Closure() : Block $getBlock */ diff --git a/src/data/bedrock/item/ItemDeserializer.php b/src/data/bedrock/item/ItemDeserializer.php index f7854313f..da96f4ffe 100644 --- a/src/data/bedrock/item/ItemDeserializer.php +++ b/src/data/bedrock/item/ItemDeserializer.php @@ -51,12 +51,19 @@ final class ItemDeserializer{ * @phpstan-param \Closure(Data) : Item $deserializer */ public function map(string $id, \Closure $deserializer) : void{ - if(isset($this->deserializers[$id])){ - throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\""); - } $this->deserializers[$id] = $deserializer; } + /** + * Returns the existing data deserializer for the given ID, or null if none exists. + * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. + * + * @phpstan-return \Closure(Data) : Item + */ + public function getDeserializerForId(string $id) : ?\Closure{ + return $this->deserializers[$id] ?? null; + } + /** * @phpstan-param \Closure(Data) : Block $deserializer */ From d2d6a59c727967738a8b3a372d7dcc23bba69910 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 16 Feb 2025 22:52:11 +0000 Subject: [PATCH 8/8] ItemDeserializer: fix doc comment typo --- src/data/bedrock/item/ItemDeserializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/bedrock/item/ItemDeserializer.php b/src/data/bedrock/item/ItemDeserializer.php index da96f4ffe..ef3bee2a0 100644 --- a/src/data/bedrock/item/ItemDeserializer.php +++ b/src/data/bedrock/item/ItemDeserializer.php @@ -58,7 +58,7 @@ final class ItemDeserializer{ * Returns the existing data deserializer for the given ID, or null if none exists. * This may be useful if you need to override a deserializer, but still want to be able to fall back to the original. * - * @phpstan-return \Closure(Data) : Item + * @phpstan-return ?\Closure(Data) : Item */ public function getDeserializerForId(string $id) : ?\Closure{ return $this->deserializers[$id] ?? null;