Skip to content

Instantly share code, notes, and snippets.

@dktapps
Last active May 31, 2023 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dktapps/8d21a965d642e9140c82ebc1dc3faab6 to your computer and use it in GitHub Desktop.
Save dktapps/8d21a965d642e9140c82ebc1dc3faab6 to your computer and use it in GitHub Desktop.
Draft of PM 5.0.0 changelog

For Minecraft: Bedrock Edition 1.19.80

5.0.0 is a major update to PocketMine-MP, including many new features and API changes. It is not compatible with plugins written for previous versions of PocketMine-MP.

5.0.0

Released 1st June 2023.

Core

  • Worlds are now saved according to the Bedrock 1.19.80 format.
  • Worlds generated by Bedrock from 1.13.0 and up are now supported (previously, only worlds up to 1.12 were supported).
  • /particle now accepts strings for particle data instead of integers.
  • /particle no longer accepts integers for block or item IDs.
  • The usage of blockcrack, iconcrack and blockdust particle types in /particle now follows the same pattern as other particle types, with the data for each being passed in the data parameter instead of being baked into the particle name.
  • Commands are now enabled by default in worlds exported from PocketMine-MP to Bedrock.

Build system

  • build/generate-runtime-enum-serializers.php has been added to generate RuntimeEnumSerializer, RuntimeEnumSerializerTrait, and RuntimeEnumSizeCalculatorTrait.

Localization

  • Localized disconnect messages are now used in the following cases:
    • Server full
    • Player not on the server whitelist
    • Player on the server ban list
    • Invalid skin
    • Invalid username
    • Kicked using /kick
    • Banned using /ban
    • Failure to find a safe spawn position
  • Session open, session close and session player name discovery messages are now localized.
  • All permissions now have localized descriptions. These are not currently used by PocketMine-MP, but may be useful for plugins.

Performance

  • Improved performance of dropping block inventory contents when the block is destroyed.
  • Improved light propagation performance by 10-15%.
  • ext-pmmpthread version 6.0.0 is now used, featuring significant performance improvements for thread-safe objects and various threading use-cases.

Tools

  • The following tool scripts have been added:
    • generate-block-palette-spec.php - generates a JSON file with a readable overview of blocks, their state properties, and their possible values
    • generate-blockstate-upgrade-schema.php - generates JSON blockstate upgrade schemas like those found in BedrockBlockUpgradeSchema
    • generate-item-upgrade-schema.php - generates JSON item ID/meta upgrade schemas like those found in BedrockItemUpgradeSchema
    • generate-bedrock-data-from-packets.php - generates various files for BedrockData
      • This script accepts a txt file containing base64-encoded packet dumps.
      • This script has been used to generate data for for several years, but has only now been open-sourced.
      • It's used to generate data such as crafting recipes, creative inventory data, and various other blobs of data needed to support the current version of Minecraft: Bedrock Edition.

Gameplay

Blocks

  • Added the following new blocks:
    • Amethyst Block
    • Ancient Debris
    • Azalea Leaves
    • Basalt
    • Blackstone blocks, slabs, stairs, and walls
    • Cakes with Candle & Dyed Candle
    • Calcite
    • Candle & Dyed Candle
    • Cartography Table (not currently usable due to maps not being implemented)
    • Cauldron
    • Cave Vines
    • Chain
    • Chiseled Deepslate
    • Chiseled Nether Bricks
    • Chiseled Polished Blackstone
    • Chorus Flower
    • Chorus Plant
    • Cobbled Deepslate blocks, slabs, stairs, and walls
    • Copper Ore
    • Copper block (random oxidation not yet implemented)
    • Crached Deepslate Tiles
    • Cracked Deepslate Bricks
    • Cracked Nether Bricks
    • Cracked Polished Blackstone Bricks
    • Crimson buttons, doors, fences, fence gates, hyphae, planks, pressure plates, signs, slabs, stairs, stems, and trapdoors
    • Crying Obsidian
    • Cut Copper block, stairs and slabs (random oxidation not yet implemented)
    • Deepslate
    • Deepslate Bricks blocks, slabs, stairs, and walls
    • Deepslate Ores (coal, copper, diamond, emerald, gold, iron, lapis lazuli, redstone)
    • Deepslate Tiles blocks, slabs, stairs, and walls
    • Flowering Azalea Leaves
    • Froglight (pearlescent, verdant, ochre)
    • Gilded Blackstone
    • Glow Item Frame
    • Hanging Roots
    • Honeycomb Block
    • Light Block
    • Lightning Rod
    • Mangrove Leaves
    • Mangrove Roots
    • Mangrove buttons, doors, fences, fence gates, logs, planks, pressure plates, signs, slabs, stairs, trapdoors, and wood
    • Mud Bricks blocks, slabs, stairs, and walls
    • Muddy Mangrove Roots
    • Nether Gold Ore
    • Netherite Block
    • Polished Basalt
    • Polished Blackstone Bricks blocks, slabs, stairs, and walls
    • Polished Blackstone blocks, buttons, pressure plates, slabs, stairs, and walls
    • Polished Deepslate blocks, slabs, stairs, and walls
    • Quartz Bricks
    • Reinforced Deepslate
    • Rooted Dirt
    • Sculk
    • Shroomlight
    • Smithing Table
    • Smooth Basalt
    • Soul Fire
    • Soul Lantern
    • Soul Soil
    • Soul Torch
    • Spore Blossom
    • Tinted Glass
    • Tuff
    • Twisting Vines
    • Warped Wart Block
    • Warped buttons, doors, fences, fence gates, hyphae, planks, pressure plates, signs, slabs, stairs, stems, and trapdoors
    • Weeping Vines
    • Wither Rose
  • Added support for basalt generators
  • Added support for dyeing sign text and making it glow.
  • All-sided logs ("wood", for want of a better name) can now be placed in X, Y, and Z orientations.
  • Coral and coral fans now behave correctly when placed out of water (they no longer immediately die).
  • Fixed dead bush being able to be placed on some invalid blocks (e.g. stone).
  • Fixed lava setting entities on fire for an incorrect duration (Java vs Bedrock inconsistency).
  • Fixed sugarcane not being able to be placed on some blocks.
  • Iron Ore and Gold Ore now drop Raw Iron and Raw Gold respectively, instead of the ore blocks.
  • Item frames can now be placed on the top and bottom of blocks.
  • Stripping logs by right-clicking them with an axe is now supported.
  • TNT can now be ignited by fire charges.
  • Vines can now only be placed on the side of full-cube blocks.
  • Walls now connect when placed, following the pre-1.16 logic. (1.16 logic is planned to be implemented, but currently low priority.)
  • Anvils are now damaged when they hit the ground after falling.
  • Added missing sounds for anvils hitting the ground after falling.
  • Anvils now damage entities when they fall on top of them.

Items

  • Added the following new items:
    • Amethyst Shard
    • Antidote (from Education Edition)
    • Copper Ingot
    • Disc Fragment (5)
    • Echo Shard
    • Elixir (from Education Edition)
    • Eye Drops (from Education Edition)
    • Fire Charge
    • Glow Berries
    • Glow Ink Sac
    • Honey Bottle
    • Honeycomb
    • Mangrove Boat (incomplete)
    • Music Disc (5)
    • Music Disc (Otherside)
    • Music Disc (Pigstep)
    • Netherite Axe
    • Netherite Boots
    • Netherite Chestplate
    • Netherite Helmet
    • Netherite Ingot
    • Netherite Leggings
    • Netherite Pickaxe
    • Netherite Scrap
    • Netherite Shovel
    • Netherite Sword
    • Phantom Membrane
    • Raw Copper
    • Raw Gold
    • Raw Iron
    • Spyglass
    • Suspicious Stew
    • Tonic (from Education Edition)
  • Glass bottles can now be filled with water by clicking on a water source block.
  • Implemented Swift Sneak enchantment.
  • Armour durability is now only reduced when the wearer receives a type of damage that the armour can protect against.
  • Bells now ring when hit by a projectile.

Worlds

  • World height of -64 to 319 is now supported.
  • Added support for 3D biomes. This isn't used by PocketMine-MP yet, but is necessary to be able to fully load 1.18 worlds.

API

General

  • Union and mixed native parameter, return and property types are now used where appropriate.
  • Protected and public properties now use native property types wherever possible.
  • Parameter and return typehints have been applied in many places where it wasn't previously possible.

Dependencies

  • ext-pmmpthread version 6.0.0 (renamed from ext-pthreads) is now required. This version features major API changes and improvements. Please read the upgrading guide for details.
  • pocketmine/snooze version 0.5.0 is now required.
  • pocketmine/raklib version 0.15.0 is now required.
  • pocketmine/raklib-ipc version 0.2.0 is now required.
  • pocketmine/classloader and pocketmine/log-pthreads packages have been removed. The relevant classes from these packages are now included in-house in the pocketmine/thread namespace.
    • BaseClassLoader is replaced with pocketmine\thread\ThreadSafeClassLoader
    • ThreadedLogger is replaced by pocketmine\thread\ThreadSafeLogger
    • AttachableThreadedLogger is replaced by pocketmine\thread\AttachableThreadSafeLogger
    • ThreadedLoggerAttachment is replaced by pocketmine\thread\ThreadSafeLoggerAttachment
  • webmozart/path-util has been removed (replaced by symfony/filesystem).

pocketmine\block

Highlights

  • Blocks no longer use internal Minecraft IDs and metadata to identify themselves. All APIs associated with legacy IDs and meta have been removed.
  • A new set of runtime IDs generated from VanillaBlocks is used to identify block types. These IDs are defined in BlockTypeIds.
    • These new IDs are used for runtime representation of blocks on chunks, and for type comparison purposes.
    • Block type IDs are used at runtime only. Do not store them in configs or databases, as they are subject to change without warning.
    • Block type IDs are specific to PocketMine-MP and have no relation to the IDs used by Minecraft.
    • Block type IDs cannot be negative
    • Block type IDs must not be reused, even if overriding an already defined block
  • Block state properties (e.g. facing, colour, etc.) are now represented by PM-specific state data instead of legacy metadata. The state data consists of:
    • Block-item state properties - retained by items when the block is broken (colour, wet/dry, coral type, etc.) - handled by Block->describeBlockItemState()
    • Block-only state data - discarded when the block is broken (facing direction, lit/unlit, powered/unpowered, etc.) - handled by Block->describeBlockOnlyState()
  • Chunks now store dynamic state ID derived from the runtime type ID and runtime (PocketMine-MP defined) state data.
  • Introduced "type tags" concept, which allows marking certain blocks as having certain behaviours.
    • The idea for this system was borrowed from the Minecraft Java tags system.
    • It's still in very early concept stage, but is currently used for deciding which types of blocks plants can be placed on without needing to enumerate every single ID in every class, eliminating a bunch of boilerplate code and improving consistency.
  • All Block descendents now accept BlockTypeInfo in the constructor, instead of BlockBreakInfo. This allows for future additions without needing to change dozens of overridden constructors.
  • &$returnedItems reference parameter is now used in some places such as Block->onInteract() to enable actions to return items to players without caring about whether they are in creative or anything else.
    • This eliminates boilerplate code of deciding whether to set the player's held item or not, as well as automatically dropping any overflow items that don't fit into the inventory.
    • This is currently used when filling/emptying cauldrons using buckets or glass bottles.
  • Dependency between RuntimeBlockStateRegistry (previously BlockFactory) and VanillaBlocks has been inverted.
    • Now, blocks types are defined in VanillaBlocks
    • RuntimeBlockStateRegistry automatically registers states for blocks defined in VanillaBlocks.
    • Manual registration in RuntimeBlockStateRegistry is still required for custom blocks (see section below about registering new blocks).
  • RuntimeBlockStateRegistry now has only one purpose - to map internal blockstate IDs to Block objects when reading blocks from chunks. It should not be used by plugins unless registering new blocks.
    • To get a block at runtime, e.g. stone, use VanillaBlocks::STONE()
    • To load a block from old config or database data:
      1. Use GlobalBlockStateHandlers::getUpgrader()->upgradeIntIdMeta() to convert it to modern data
      2. Pass the data to GlobalBlockStateHandlers::getDeserializer() to get a blockstate ID
      3. Pass the blockstate ID to RuntimeBlockStateRegistry::fromStateId() to get a Block instance
    • Prefer using StringToItemParser wherever possible for configs and databases (see lookupAliases() and lookupBlockAliases()).

Registering new blocks

In a plugin

To add a vanilla block in a plugin which isn't yet supported by PocketMine-MP, do the following:

  1. Get a new type ID using BlockTypeIds::newId() - you'll want to keep this in a property somewhere if you want to compare using getTypeId() later
  2. Set up the block type somewhere - this can be anywhere you like, e.g. a plugin main class property, but using a RegistryTrait class is recommended - you'll need this later to create new instances of the block
  3. Register the block in RuntimeBlockStateRegistry - this informs the server of all the block's possible states so that it can be read from chunks at runtime
  4. Register a deserializer for the block's Minecraft ID in BlockStateToObjectDeserializer - needed for the block to be recognized when loaded from disk
  5. Register a serializer for the block in BlockObjectToStateSerializer - needed for the block to be saved to disk, and to be sent over the network
  6. Optionally, register a string alias for the block in StringToItemParser - so that it can be given via /give

To see a demo of how to do this in a plugin, see this example plugin.

Registering custom blocks follows a similar process, but requires additional steps to modify BlockStateDictionary which won't be covered here. Since this is not currently officially supported by PocketMine-MP, this won't be covered here.

This is admittedly rather more of a hassle than in PM4, but this system offers significantly more flexibility than the old system.

As a PocketMine-MP core contribution

To register a new vanilla block into the core, the process is slightly different:

  1. Instead of using BlockTypeIds::newId(), add a new constant for the block to BlockTypeIds
  2. Register the new block in VanillaBlocks - RuntimeBlockStateRegistry will automagically take notice of all blocks defined in VanillaBlocks
  3. Follow steps 4 onwards above

Change list

  • The following classes have been removed:
    • BlockIdentifierFlattened
    • BlockLegacyIdHelper
    • BlockLegacyIds
    • BlockLegacyMetadata
    • utils\BlockDataSerializer
    • utils\ColorInMetadataTrait - utils\ColoredTrait now implements colour type data serialization uniformly
    • utils\InvalidBlockStateException - this has been superseded by pocketmine\data\runtime\InvalidSerializedRuntimeDataException
    • utils\NormalHorizontalFacingInMetadataTrait - utils\HorizontalFacingTrait now implements facing type data serialization uniformly
    • utils\PillarRotationInMetadataTrait - utils\PillarRotationTrait now implements rotation type data serialization uniformly
  • The following classes have been renamed:
    • BlockFactory -> RuntimeBlockStateRegistry - this class is now exclusively used for mapping state IDs to block instances for runtime chunk block reading
    • Skull -> MobHead
    • utils\SkullType -> utils\MobHeadType
    • utils\TreeType -> pocketmine\world\generator\object\TreeType
  • The following classes have been added:
    • BaseCake
    • BaseFire
    • BlockTypeIds - list of type IDs, one for each entry in VanillaBlocks
    • BlockTypeInfo
    • BlockTypeTags
    • CakeWithCandle
    • CakeWithDyedCandle
    • Candle
    • CartographyTable
    • Chain
    • CopperOre
    • CopperSlab
    • CopperStairs
    • Copper
    • DyedCandle
    • GildedBlackstone
    • GoldOre
    • HangingRoots
    • IronOre
    • Light
    • LightningRod
    • NetherGoldOre
    • Sculk
    • SmithingTable
    • SoulFire
    • WitherRose
    • utils\CandleTrait
    • utils\CopperOxidation
    • utils\CopperTrait
    • utils\SaplingType - enum of all sapling types
    • utils\WallConnectionType - enum of all possible wall connection types
    • utils\WoodTypeTrait
    • utils\WoodType - enum of all possible wood types, used for wood material blocks like planks and logs
  • The following API methods have been removed:
    • Block->getId() - for type comparisons, use Block->getTypeId() instead
    • Block->getMeta() - for state comparisons, use Block->getStateId() instead
    • Block->getStateBitmask()
    • Block->readStateFromData()
    • Block->writeStateToItemMeta()
    • Block->writeStateToMeta()
    • BlockFactory->get() - see notes above about RuntimeBlockStateRegistry
    • BlockIdentifier->getAllBlockIds()
    • BlockIdentifier->getBlockId()
    • BlockIdentifier->getItemId()
    • BlockIdentifier->getVariant()
    • Door->isPowered()
    • Door->setPowered()
    • MobHead->isNoDrops() (previously Skull->isNoDrops())
    • MobHead->setNoDrops() (previously Skull->setNoDrops())
    • VanillaBlocks::*_GLAZED_TERRACOTTA() - use VanillaBlocks::GLAZED_TERRACOTTA()->setColor(DyeColor::WHATEVER()) instead
    • utils\FallableTrait->getId() is no longer required
    • utils\FallableTrait->getMeta() is no longer required
    • utils\MobHeadType->getMagicNumber() (previously utils\SkullType->getMagicNumber())
    • utils\MobHeadType::fromMagicNumber() (previously utils\SkullType::fromMagicNumber())
  • The following constants have been removed:
    • Block::INTERNAL_METADATA_BITS
    • Block::INTERNAL_METADATA_MASK
  • The following API methods have been renamed:
    • Block->getFullId() -> Block->getStateId()
    • Block->isSameType() -> Block->hasSameTypeId()
    • MobHead->getSkullType() -> MobHead->getMobHeadType() (previously Skull->getSkullType())
    • MobHead->setSkullType() -> MobHead->setMobHeadType() (previously Skull->setSkullType())
  • The following API methods have signature changes:
    • Block->onBreak() now accepts array<Item> &$returnedItems reference parameter.
    • Block->onInteract() now accepts array<Item> &$returnedItems reference parameter.
    • Block->readStateFromWorld() now returns Block - this allows blocks to replace themselves with a different block entirely based on world conditions.
    • BlockIdentifier->__construct() now accepts int $blockTypeId, and no longer accepts int $blockId, int $variant, ?int $itemId
    • ItemFrame->getFacing() may now return Facing::UP and Facing::DOWN
    • ItemFrame->setFacing() now accepts Facing::UP and Facing::DOWN
    • Leaves->__construct() now accepts LeavesType $leavesType instead of TreeType $treeType
    • RuntimeBlockStateRegistry->register() no longer accepts an $override parameter.
    • Sapling::__construct() now accepts SaplingType $saplingType instead of TreeType $treeType
    • utils\SignText::__construct() now accepts two new optional parameters: ?Color $baseColor and bool $glowing
    • utils\SignText::fromBlob() now accepts two new optional parameters: ?Color $baseColor and bool $glowing
  • The following API methods have been added:
    • protected Block->describeBlockOnlyState(RuntimeDataDescriber $w) : void - describes state properties which are discarded when the block is broken or block-picked, such as facing, powered, etc.
    • public Block->describeBlockItemState(RuntimeDataDescriber $w) : void - describes state properties which are kept by the item when the block is broken or block-picked, such as dye color
    • public Block->generateStatePermutations() : \Generator<int, Block, void, void> - yields all possible states this block type can be in (used for RuntimeBlockStateRegistry)
    • public Block->getTypeTags() : array<string>
    • public Block->hasTypeTag(string $tag) : bool
    • public Block->isFireProofAsItem() : bool
    • public Block->onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void
    • public BlockIdentifier->getBlockTypeId() : int - returns the block's type ID according to BlockTypeIds
    • public Furnace->getFurnaceType() : utils\FurnaceType
    • public GlazedTerracotta->getColor() : utils\DyeColor (from ColoredTrait) - this was previously unsupported due to legacy limitations
    • public GlazedTerracotta->setColor(utils\DyeColor $color) : $this (from ColoredTrait) - this was previously unsupported due to legacy limitations
    • public Leaves->getLeavesType() : utils\LeavesType - returns the type of leaves
    • public Wall->getConnection(int $face) : utils\WallConnectionType
    • public Wall->getConnections() : array<int, utils\WallConnectionType> - returns the wall's connections and their types (see utils\WallConnectionType)
    • public Wall->isPost() : bool
    • public Wall->setConnection(int $face, ?utils\WallConnectionType $type) : $this
    • public Wall->setConnections() - sets the wall's connections and their types (see utils\WallConnectionType)
    • public Wall->setPost(bool $post) : $this
    • public Wood->isStripped() : bool
    • public Wood->setStripped(bool $stripped) : $this
    • public static BlockBreakInfo::axe(float $hardness, ?ToolTier $toolTier = null, ?float $blastResistance = null) : BlockBreakInfo
    • public static BlockBreakInfo::pickaxe(float $hardness, ?ToolTier $toolTier = null, ?float $blastResistance = null) : BlockBreakInfo
    • public static BlockBreakInfo::shovel(float $hardness, ?ToolTier $toolTier = null, ?float $blastResistance = null) : BlockBreakInfo
    • public static BlockBreakInfo::tier(float $hardness, int $toolType, ToolTier $toolTier, ?float $blastResistance = null) : BlockBreakInfo
    • public tile\Spawnable->getRenderUpdateBugWorkaroundStateProperties(Block $block) : array<string, Tag> - allows spawnable tiles to spoof block state properties to work around client-side rendering bugs without actually changing the block server-side
    • public utils\SignText->getBaseColor() : \pocketmine\color\Color
    • public utils\SignText->isGlowing() : bool
  • The following classes now use new traits, adding API methods and/or properties:
    • FenceGate uses utils\WoodTypeTrait
    • GlazedTerracotta uses utils\ColoredTrait
    • Planks uses utils\WoodTypeTrait
    • Wood uses utils\WoodTypeTrait
    • WoodenButton uses utils\WoodTypeTrait
    • WoodenDoor uses utils\WoodTypeTrait
    • WoodenFence uses utils\WoodTypeTrait
    • WoodenPressurePlate uses utils\WoodTypeTrait
    • WoodenSlab uses utils\WoodTypeTrait
    • WoodenStairs uses utils\WoodTypeTrait
    • WoodenTrapdoor uses utils\WoodTypeTrait
  • The following API interface requirements have been added (BC breaking):
    • public utils\Fallable->getFallDamagePerBlock() : float (default implementation provided by utils\FallableTrait)
    • public utils\Fallable->getLandSound() : ?Sound (default implementation provided by utils\FallableTrait)
    • public utils\Fallable->getMaxFallDamage() : float (default implementation provided by utils\FallableTrait)
    • public utils\Fallable->onHitGround(FallingBlock $blockEntity) : bool (default implementation provided by utils\FallableTrait)

pocketmine\command

  • Command permissions are now always checked by the server when running a command.
    • This only affects commands implemented by extending Command. Plugins using PluginBase->onCommand() are not affected by this change, since they already had permissions checked by the server anyway.
    • Previously, direct inheritors of Command were responsible for checking permissions, which required developers to duplicate the same code in every command, and opened lots of potential for security vulnerabilities.
    • If you want to do something on permission denied (e.g. sending a special message, or audit logging), you can do so by overriding Command->testPermission(), instead of baking the code directly into Command->execute().
    • If you don't want to use permissions at all, just create a permission with a default of true (or belonging to pocketmine.group.user) and assign that.
  • SimpleCommandMap now requires all commands to have a permission set when registered.
    • If you actually want to allow everyone to use your command (not advised), you can add a new permission to the pocketmine.group.user group, or use default: true for plugin.yml permissions.
  • The following API methods have changed behaviour:
    • Command->testPermissionSilent() now returns false if there are no permissions associated with the command. This is to prevent commands with no permissions being usable by everyone, which has previously been a source of security issues.
  • The following API methods have been added:
    • public Command->getPermissions() : list<string> - returns a list of permissions which grant usage access to this command. A user with one or more of these permissions will be able to invoke the command's execute() method
    • public Command->setPermissions(list<string> $permissions) : void - sets the permissions which grant usage access to this command. This should be used instead of setPermission() with ; separators (which is now deprecated)

pocketmine\crafting

  • JSON models have been updated to reflect updated crafting data format.
  • The following enum classes have new members:
    • ShapelessRecipeType has new members CARTOGRAPHY and SMITHING
  • The following classes have been added:
    • ExactRecipeIngredient - matches an exact item
    • MetaWildcardRecipeIngredient - matches an item with the given legacy Minecraft ID, but any metadata value
    • RecipeIngredient interface
    • TagWildcardRecipeIngredient - matches an item based on its Minecraft tags, e.g. minecraft:wooden_tier
  • The following API methods have signature changes:
    • FurnaceRecipe->__construct() now accepts RecipeIngredient instead of Item
    • FurnaceRecipe->getInput() now returns RecipeIngredient instead of Item
    • PotionContainerChangeRecipe->__construct() now accepts string, RecipeIngredient, string (using Minecraft string IDs instead of legacy integers).
    • PotionContainerChangeRecipe->getIngredient() now returns RecipeIngredient instead of Item.
    • PotionContainerChangeRecipe->getInputItemId() now returns string (using Minecraft string IDs instead of legacy integers).
    • PotionContainerChangeRecipe->getOutputItemId() now returns string (using Minecraft string IDs instead of legacy integers).
    • PotionTypeRecipe->__construct() now accepts RecipeIngredient instead of Item
    • PotionTypeRecipe->getIngredient() now returns RecipeIngredient instead of Item
    • PotionTypeRecipe->getInput() now returns RecipeIngredient instead of Item
    • ShapedRecipe->__construct() now accepts RecipeIngredient instead of Item
    • ShapedRecipe->getIngredient() now returns ?RecipeIngredient instead of ?Item
    • ShapedRecipe->getIngredientList() now returns RecipeIngredient[] instead of Item[]
    • ShapedRecipe->getIngredientMap() now returns RecipeIngredient[][] instead of Item[][]
    • ShapelessRecipe->__construct() $type parameter is now mandatory.
    • ShapelessRecipe->__construct() now accepts RecipeIngredient instead of Item
    • ShapelessRecipe->getIngredientList() now returns RecipeIngredient[] instead of Item[]

pocketmine\data

  • New packages bedrock\block and bedrock\item have been added. These packages contain all the necessary code for loading and saving Bedrock blocks and items from disk.
  • New package runtime has been added. This package contains code for serializing runtime data for blocks and items.
  • LegacyToStringBidirectionalIdMap has been reduced to LegacyToStringIdMap.
    • Since we never map from string ID to legacy ID, bidirectional mapping is no longer necessary.
    • This affects the following subclasses:
      • LegacyBiomeIdToStringIdMap
      • LegacyBlockIdToStringIdMap
      • LegacyEntityIdToStringIdMap
      • LegacyItemIdToStringIdMap
  • The following internal API methods have been added:
    • public LegacyToStringIdMap->add(string $string, int $legacy) : void - adds a mapping from a custom legacy ID to custom string ID, needed for upgrading old saved data

pocketmine\entity

  • Entity now declares new abstract methods which must be implemented by subclasses:
    • public Entity->getInitialDragMultiplier() : float
    • public Entity->getInitialGravity() : float
  • The following new API methods have been added:
    • public Living->getDisplayName() : string
  • The following API methods have changed signatures:
    • EntityFactory->register() no longer accepts a $legacyMcpeSaveId parameter (now handled by internal conversions instead).
  • The following API methods have been renamed:
    • Entity->isImmobile() -> Entity->hasNoClientPredictions()
    • Entity->setImmobile() -> Entity->setNoClientPredictions()
  • The following internal fields have been renamed:
    • Entity->immobile -> Entity->noClientPredictions
  • The following classes have been removed:
    • EntityLegacyIds

pocketmine\event

  • The following classes have inheritance changes:

    • block\BlockPlaceEvent no longer extends BlockEvent, and therefore no longer has getBlock(). Use getTransaction() instead (may contain multiple blocks).
  • BlockFormEvent now includes information about the block which caused the event.

  • The following new classes have been added:

    • world\WorldDisplayNameChangeEvent - called when a world's display name is changed
  • The following classes have been renamed:

    • entity\ExplosionPrimeEvent -> entity\EntityPreExplodeEvent
  • The following API methods have been added:

    • public block\BlockFormEvent->getCausingBlock() : Block
    • public block\BlockGrowEvent->getPlayer() : ?Player - returns the player that triggered the block growth, or null if it was not triggered by a player
    • public block\BlockPlaceEvent->getTransaction() : BlockTransaction - returns the transaction containing a list of changed block positions and the blockstates they will be changed to
    • public server\DataPacketSendEvent->setPackets(list<ClientboundPacket> $packets) : void
  • The following API methods have changed signatures:

    • block\BlockPlaceEvent->__construct() now accepts BlockTransaction $transaction instead of Block $blockPlace, Block $blockReplace
    • entity\EntityPreExplodeEvent->__construct() has the $force parameter renamed to $radius
    • entity\EntityPreExplodeEvent->getForce() : float -> entity\EntityPreExplodeEvent->getRadius() : float
    • entity\EntityPreExplodeEvent->setForce(float $force) : void -> entity\EntityPreExplodeEvent->setRadius(float $radius) : void
  • The following API methods have been removed:

    • block\BlockPlaceEvent->getBlockReplaced() - this information is now provided in the BlockTransaction object returned by BlockPlaceEvent->getTransaction()
  • The following new API constants have been added:

    • entity\EntityDamageEvent::CAUSE_FALLING_BLOCK
    • entity\EntityDamageEvent::MODIFIER_ARMOR_HELMET

pocketmine\event\player

  • PlayerPreLoginEvent, PlayerDuplicateLoginEvent and PlayerKickEvent now supports setting separate log reasons (disconnect reason) and disconnect screen messages.
  • The following classes have been removed:
    • PlayerCommandPreprocessEvent
  • The following API methods have changed signatures:
    • PlayerDuplicateLoginEvent->getDisconnectMessage() now returns Translatable|string instead of string
    • PlayerDuplicateLoginEvent->setDisconnectMessage() now accepts Translatable|string instead of string
    • PlayerKickEvent->getReason() now returns Translatable|string instead of string
    • PlayerKickEvent->setReason() now accepts Translatable|string instead of string
    • PlayerLoginEvent->getKickMessage() now returns Translatable|string instead of string
    • PlayerLoginEvent->setKickMessage() now accepts Translatable|string instead of string
    • PlayerPreLoginEvent->getFinalKickMessage() now returns Translatable|string instead of string
    • PlayerPreLoginEvent->getKickMessage() now returns Translatable|string|null instead of string|null
    • PlayerPreLoginEvent->setKickFlag() (previously setKickReason()) now accepts Translatable|string $disconnectReason, Translatable|string|null $disconnectScreenMessage = null instead of Translatable|string $message
    • PlayerPreLoginEvent->setKickReason() now accepts Translatable|string for the $message parameter instead of string
    • PlayerQuitEvent->getQuitReason() now returns Translatable|string instead of string
  • The following API methods have been removed:
    • PlayerChatEvent->getFormat() (use PlayerChatEvent->getChatFormatter() instead)
    • PlayerChatEvent->setFormat() (use PlayerChatEvent->setChatFormatter() instead)
    • PlayerDuplicateLoginEvent->getDisconnectMessage() - replaced by getDisconnectReason() and getDisconnectScreenMessage()
    • PlayerDuplicateLoginEvent->setDisconnectMessage() - replaced by setDisconnectReason() and setDisconnectScreenMessage()
    • PlayerKickEvent->getReason() - replaced by getDisconnectReason() and getDisconnectScreenMessage()
    • PlayerKickEvent->setReason() - replaced by setDisconnectReason() and setDisconnectScreenMessage()
  • The following new API methods have been added:
    • public PlayerChatEvent->getChatFormatter() : \pocketmine\player\chat\ChatFormatter - returns the chat formatter to be used for this event
    • public PlayerChatEvent->setChatFormatter(\pocketmine\player\chat\ChatFormatter $formatter) : void - sets the chat formatter to be used for this event
    • public PlayerDeathEvent->getDeathScreenMessage() : Translatable|string - returns the message to be displayed on the death screen
    • public PlayerDeathEvent->setDeathScreenMessage(Translatable|string $deathScreenMessage) : void - sets the message to be displayed on the death screen
    • public PlayerDuplicateLoginEvent->getDisconnectReason() : Translatable|string - returns the reason for the disconnection displayed in the console and server log
    • public PlayerDuplicateLoginEvent->getDisconnectScreenMessage() : Translatable|string|null - returns the message to be displayed on the disconnect screen (the message in getDisconnectReason() is used if null is returned)
    • public PlayerDuplicateLoginEvent->setDisconnectReason(Translatable|string $disconnectReason) : void - sets the reason for the disconnection displayed in the console and server log
    • public PlayerDuplicateLoginEvent->setDisconnectScreenMessage(Translatable|string|null $disconnectScreenMessage) : void - sets the message to be displayed on the disconnect screen (the message in setDisconnectReason() is used if null is passed)
    • public PlayerKickEvent->getDisconnectReason() : Translatable|string - returns the reason for the disconnection displayed in the console and server log
    • public PlayerKickEvent->getDisconnectScreenMessage() : Translatable|string|null - returns the message to be displayed on the disconnect screen (the message in getDisconnectReason() is used if null is returned)
    • public PlayerKickEvent->setDisconnectReason(Translatable|string $disconnectReason) : void - sets the reason for the disconnection displayed in the console and server log
    • public PlayerKickEvent->setDisconnectScreenMessage(Translatable|string|null $disconnectScreenMessage) : void - sets the message to be displayed on the disconnect screen (the message in setDisconnectReason() is used if null is passed)
    • public PlayerPreLoginEvent->getDisconnectScreenMessage(int $flag) : Translatable|string|null - returns the message to be displayed on the disconnect screen for the specified kick flag, if set
    • public PlayerPreLoginEvent->getFinalDisconnectScreenMessage() : Translatable|string|null - returns the message to be displayed on the disconnect screen, taking into account the kick flags set
  • The following classes have inheritance changes:
    • PlayerPreLoginEvent no longer implements Cancellable. This caused unexpected behaviour for most plugin devs due to default-ignoring cancelled events, forcing people to usually have to use @handleCancelled to handle the event when they wanted to use it.
  • The following API constants have been renamed:
    • PlayerPreLoginEvent::KICK_REASON_BANNED -> PlayerPreLoginEvent::KICK_FLAG_BANNED
    • PlayerPreLoginEvent::KICK_REASON_PLUGIN -> PlayerPreLoginEvent::KICK_FLAG_PLUGIN
    • PlayerPreLoginEvent::KICK_REASON_PRIORITY -> PlayerPreLoginEvent::KICK_FLAG_PRIORITY
    • PlayerPreLoginEvent::KICK_REASON_SERVER_FULL -> PlayerPreLoginEvent::KICK_FLAG_SERVER_FULL
    • PlayerPreLoginEvent::KICK_REASON_SERVER_WHITELISTED -> PlayerPreLoginEvent::KICK_FLAG_SERVER_WHITELISTED
  • The following API methods have been renamed:
    • PlayerPreLoginEvent->clearAllKickReasons() -> PlayerPreLoginEvent->clearAllKickFlags()
    • PlayerPreLoginEvent->clearKickReason() -> PlayerPreLoginEvent->clearKickFlag()
    • PlayerPreLoginEvent->getFinalKickMessage() -> PlayerPreLoginEvent->getFinalDisconnectReason() (now used for logs only, if a disconnect screen message is set for the highest priority flag)
    • PlayerPreLoginEvent->getKickMessage() -> PlayerPreLoginEvent->getDisconnectReason() (now used for logs only, if a disconnect screen message is set for the flag)
    • PlayerPreLoginEvent->getKickReasons() -> PlayerPreLoginEvent->getKickFlags()
    • PlayerPreLoginEvent->isKickReasonSet() -> PlayerPreLoginEvent->isKickFlagSet()
    • PlayerPreLoginEvent->setKickReason() -> PlayerPreLoginEvent->setKickFlag()

pocketmine\item

Highlights

  • ItemFactory has been removed. Vanilla item registration is now done via VanillaItems.
    • To get an item at runtime, e.g. iron ingot, use VanillaItems::IRON_INGOT()
    • To get a block as an item, e.g. stone, use VanillaBlocks::STONE()->asItem()
    • To load an item from legacy ID and meta:
      1. Use GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt() to convert the legacy ID and meta to SavedItemStackData
      2. Pass the itemstack data to GlobalItemDataHandlers::getDeserializer() to get an Item instance
  • Items no longer use internal Minecraft string IDs and metadata to identify themselves. All APIs associated with legacy IDs and/or meta have been removed.
  • A new set of runtime item IDs generated from VanillaItems is now used to identify item types. These IDs are defined in ItemTypeIds.
    • These new IDs are primarily intended for type comparison purposes.
    • Item type IDs are used at runtime only. They should NOT be stored in configs or databases, as they are not guaranteed to remain the same between versions.
  • In some cases, items may have additional "type data" which provides extra type information about an item. This replaces item metadata in some cases.
    • Type data may be used to store dynamic type information such as dye colour, potion type, etc.
    • Items must have the same type ID and type data in order to be stackable.
  • Blocks, when represented as items:
    • retain their block type data, but not state data (for example, different colours of concrete don't stack, but things like facing don't affect stackability)
    • use the negative of their block type ID (e.g. a block with type ID 1 will have an item type ID of -1).
  • Durable items (e.g. tools, armour) now use NBT Damage tag to store durability (like Minecraft 1.13+), instead of legacy meta values.
  • &$returnedItems reference parameter is now used in some places (e.g. onInteractBlock()) to enable actions to return items to players without caring about whether they are in creative or anything else.
    • This eliminates boilerplate code of deciding whether to set the player's held item or not, as well as automatically dropping any overflow items that don't fit into the inventory.
    • This is used for things like filling/emptying buckets and bottles, and equipping armor.
  • Blocks which previously had separate items, such as mob heads and beds, no longer do. Their item form can be acquired using Block->asItem() in the same way as every other block. This is facilitated by the new serializer system.

Implementing new items

In a plugin

This follows a similar process to registering blocks.

  1. Get a new type ID using ItemTypeIds::newId() - you'll want to keep this in a property somewhere if you want to compare using getTypeId() later
  2. Set up the item type somewhere - this can be anywhere you like, e.g. a plugin main class property, but using a RegistryTrait class is recommended - you'll need this later to create new instances of the item
  3. Register a deserializer in ItemDeserializer - needed for the item to be recognized when loaded from disk
  4. Register a serializer in ItemSerializer - needed for the item to be saved to disk, and to be sent over the network
  5. Optionally, register a string alias for the item in StringToItemParser - so that it can be given via /give

To see a demo of how to do this in a plugin, see this example plugin.

Again, it's acknowledged this is rather more cumbersome than it should be, but this is an ongoing process.

As a PocketMine-MP core contribution

To register a new vanilla item into the core, the process is slightly different:

  1. Instead of using ItemTypeIds::newId(), add a new constant for the block to ItemTypeIds
  2. Register the new item in VanillaItems
  3. Follow steps 3 onwards from the plugin instructions above

Change list

  • Item is no longer json_encode()-able.
    • The original purpose of this was to allow items to be serialized to JSON for crafting data generated from CraftingDataPacket. Due to changes in the generation methodology, bypassing Items entirely, this is no longer necessary.
    • In addition, jsonSerialize() required the item to know about the method by which it will be serialized (since there is no way to inject context), creating a cyclic dependency between the Item implementation and its serialization method.
    • It's relatively easy to write a replacement method to encode items to JSON as you desire.
    • Item::legacyJsonDeserialize() (previously Item::jsonDeserialize()) is retained to allow loading legacy data, although it may be removed in the future.
  • The following classes have been removed:
    • Bed
    • ItemFactory
    • ItemIds
    • Skull
  • The following classes have been added:
    • BoatType - enum of all boat types
    • CoralFan
    • HoneyBottle
    • MedicineType
    • Medicine
    • Spyglass
    • SuspiciousStewType
    • SuspiciousStew
  • The following API methods have been added:
    • protected Item->describeState(RuntimeDataDescriber $w) : void
    • public Armor->clearCustomColor() : $this - clears the custom color of an armor item
    • public ArmorTypeInfo->getToughness() : int
    • public ArmorTypeInfo->isFireProof() : bool
    • public Boat->getType() : BoatType
    • public Dye->setColor(\pocketmine\block\utils\DyeColor $color) : $this
    • public Item->getStateId() : int - returns a runtime numeric state ID for comparisons including information such as coral type, dye color, etc. - DO NOT save this to disk or databases
    • public Item->getTypeId() : int - returns a runtime numeric type ID for comparisons - DO NOT save this to disk or databases
    • public Item->isFireProof() : bool
    • public ItemIdentifer->getTypeId() : int
    • public Potion->setType(PotionType $type) : $this
    • public SplashPotion->setType(PotionType $type) : $this
    • public StringToItemParser->lookupAliases(Item $item) : list<string> - returns a list of all registered aliases for the given item
    • public StringToItemParser->lookupBlockAliases(Block $block) : list<string> - returns a list of all registered aliases for the given block
    • public static ItemIdentifier::fromBlock(Block $block) : self
  • The following API methods have been removed:
    • Boat->getWoodType()
    • Item->getId() - for type comparisons, use Item->getTypeId() instead
    • Item->getMeta() - use the item's specific API methods to compare information such as colour, potion type etc.
    • Item->hasAnyDamageValue() - for meta wildcard recipe ingredients, use pocketmine\crafting\MetaWildcardRecipeIngredient instead
    • ItemIdentifier->getId()
    • ItemIdentifier->getMeta()
  • The following API methods have been renamed:
    • Item::jsonDeserialize() -> Item::legacyJsonDeserialize()
  • The following API methods have signature changes:
    • ArmorTypeInfo->__construct() now accepts optional parameters int $toughness and bool $fireProof
    • BoatType::__construct() now accepts BoatType $boatType instead of TreeType $woodType.
    • Item->onAttackEntity() now accepts array<Item> &$returnedItems reference parameter.
    • Item->onClickAir() now accepts array<Item> &$returnedItems reference parameter.
    • Item->onDestroyBlock() now accepts array<Item> &$returnedItems reference parameter.
    • Item->onInteractBlock() now accepts array<Item> &$returnedItems reference parameter.
    • Item->onReleaseUsing() now accepts array<Item> &$returnedItems reference parameter.
    • ItemIdentifier->__construct() no longer accepts a $variant parameter, and now expects an item type ID for the ID parameter
    • LegacyStringToItemParser->addMapping() now accepts a string for ID, instead of an integer
  • The following API methods have behaviour changes:
    • Item->equals()'s $checkDamage parameter is now ignored, as tool damage is now stored as an NBT tag. This parameter wasn't removed due to being followed by a second bool parameter, which would potentially end up in the wrong place and silently cause bugs in updated plugins.
    • Item->equals()'s $checkTags parameter will now cause tool and armor damage to be checked if true.
  • The following enums have new members:
    • ToolTier has new member NETHERITE

pocketmine\network

  • The following API methods have changed signatures:
    • NetworkSessionManager->close() now accepts an additional Translatable|string|null $disconnectScreenMessage parameter.
  • The following API methods have changed signatures:
    • query\QueryInfo->getPlayerList() now returns list<string> instead of list<Player>
    • query\QueryInfo->setPlayerList() now accepts list<string> instead of list<Player>

pocketmine\player

  • The following API methods have changed signatures:
    • Player->disconnect() now accepts Translatable|string for $reason instead of string (to allow localized disconnect messages)
    • Player->disconnect() now accepts an additional Translatable|string|null $disconnectScreenMessage parameter, which is the message to be displayed on the disconnect screen (the message in $reason is used if null is passed)
    • Player->kick() now accepts Translatable|string for $reason instead of string (to allow localized kick messages)
    • Player->kick() now accepts an additional Translatable|string|null $disconnectScreenMessage parameter, which is the message to be displayed on the disconnect screen (the message in $reason is used if null is passed)
    • Player->sendJukeboxPopup() now accepts Translatable|string instead of string, string[]
  • The following classes have been removed:
    • PlayerChunkLoader - deprecated in 4.19.0 (this was technically internal, but never marked as such)

pocketmine\player\chat

  • The following new classes have been added:
    • ChatFormatter - interface implemented by chat formatters - this is far more powerful than the old API
    • LegacyRawChatFormatter - implements the same behaviour previously used by PlayerChatEvent->setFormat()
    • StandardChatFormatter - formats chat messages in the vanilla Minecraft style

pocketmine\scheduler

  • AsyncTask->setResult() now works with thread-safe objects. This was previously not possible due to limitations in the pthreads extension.

pocketmine\world

  • The following API methods have been added:
    • public World->setDisplayName(string $name) : void
  • The following API methods have changed signatures:
    • Explosion->__construct() has the $size parameter renamed to $radius
  • The following public properties have been renamed:
    • Explosion->size -> Explosion->radius

pocketmine\world\format

  • Chunks are now considered dirty (modified) by default, unless loaded from a WorldProvider by World. Previously, chunks were considered unmodified by default, which allowed several pathways to bugs.
  • The following classes have been added:
    • io\GlobalBlockStateHandlers - gives access to block data serializer, deserializer, and upgraders
    • io\GlobalItemDataHandlers - gives access to item data serializer, deserializer, and upgraders
    • io\LoadedChunkData - represents a chunk loaded from disk, along with information such as whether the chunk was upgraded and what fixes it requires
  • The following new API methods have been added:
    • public SubChunk->getBiomeArray() : PalettedBlockArray
  • The following classes have been removed:
    • BiomeArray - PalettedBlockArray is now used for 3D biome data
  • The following API methods have changed signatures:
    • Chunk->getBiomeId() now accepts int $x, int $y, int $z instead of int $x, int $z
    • Chunk->setBiomeId() now accepts int $x, int $y, int $z instead of int $x, int $z
    • Chunk->__construct() no longer accepts BiomeArray as a parameter (contained in each subchunk instead)
    • SubChunk->__construct() now accepts int $emptyBlockId, list<PalettedBlockArray> $blockLayers, PalettedBlockArray $biomes, ?LightArray $blockLight, ?LightArray $skyLight instead of int, list<PalettedBlockArray>, ?LightArray, ?LightArray
    • io\WorldProvider->loadChunk() now returns LoadedChunkData instead of ChunkData
    • io\WorldProvider->getAllChunks() now yields LoadedChunkData instead of ChunkData
    • io\ChunkData->__construct() now accepts array<int, SubChunk>, bool $populated instead of Chunk $chunk
  • The following API methods have been renamed:
    • Chunk->getFullBlock() -> Chunk->getBlockStateId()
    • Chunk->setFullBlock() -> Chunk->setBlockStateId()
    • SubChunk->getFullBlock() -> SubChunk->getBlockStateId()
    • SubChunk->setFullBlock() -> SubChunk->setBlockStateId()
  • The following API interface requirements have been added:
    • public io\data\WorldData->setDisplayName(string $value) : void

pocketmine\world\generator\object

  • The following API methods have been removed:
    • TreeType::fromMagicNumber()
    • TreeType->getMagicNumber()

pocketmine\world\sound

  • The following classes have been added:
    • CopperWaxApplySound
    • CopperWaxRemoveSound
    • DyeUseSound
    • InkSacUseSound
  • The following enums have new members:
    • NoteInstrument has new members BELL, FLUTE, CHIME, XYLOPHONE, IRON_XYLOPHONE, COW_BELL, DIDGERIDOO, BIT, BANJO, PLING
  • The following API methods have been removed:
    • NoteInstrument::fromMagicNumber()
    • NoteInstrument->getMagicNumber()

Internals

  • All external usages of KnownTranslationKeys are now removed. All localized messages are now sent using Translatable objects (usually from KnownTranslationFactory).
  • All usages of NBT keys now use class constants instead of hardcoded strings (except for an occasional overlooked one).
  • Built-in commands now declare their names inside the class constructor, rather than accepting them as parameters. This improves code consistency.
  • Commands now use an array for permissions internally, instead of a string separated by ;.
  • Make use of Item->canStackWith() instead of Item->equals() wherever possible, to make the code more readable.
  • Moved command timings to Timings.
  • Overriding of serializers and deserializers of items and blocks is now consistently disallowed. Since overriding stuff is non-cooperative, it doesn't make any sense in plugins, which must coexist with other plugins. If you want to modify the functionality of built-in stuff, you have several alternative options:
    • Use existing API (e.g. events, API methods) - most uses of overrides in PM4 and earlier were abuses that could have been done with events
    • Submit feature proposals or pull requests for new API to be added (e.g. new events)
    • Register completely custom items, and reuse behaviour from the item you want to mimic
    • Fork PocketMine-MP and alter the code directly - this way your plugins aren't pretending to be cooperative with other plugins
  • level.dat, block, item, entity, tile and chunk data are now tagged with a version ID as per VersionInfo::WORLD_DATA_VERSION. This allows the server to apply fixes to older worlds if necessary.
  • Protocol creative inventory entries are now cached in CreativeInventoryCache to improve performance of initial join and game mode changes.
  • Singletons in the pocketmine\network\mcpe\convert package have been centralized under TypeConverter. In the future, this will be injected where needed, allowing different converters to be used for different sessions (useful for multiversion).
  • BlockStateDictionary memory usage is now reduced from 9 MB to 3.5 MB using various techniques, including string reuse and binary-encoded states.
  • NetworkSession disconnect APIs now accept Translatable|string instead of string to allow localized disconnect messages.
  • NetworkSession disconnect methods have been altered to allow specifying a different disconnect reason and disconnection screen message.
  • RuntimeBlockMapping has been renamed to BlockTranslator.
@jasonw4331
Copy link

Pass the blockstate ID to BlockFactory::fromStateId() to get a Block instance

I think this is supposed to be RuntimeBlockStateRegistry::fromStateId()

@dktapps
Copy link
Author

dktapps commented May 31, 2023

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment