Merge branch 'minor-next' into major-next

This commit is contained in:
Dylan K. Taylor 2025-02-16 23:18:56 +00:00
commit 694aa17cc9
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
32 changed files with 976 additions and 115 deletions

View File

@ -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;

38
changelogs/5.25.md Normal file
View File

@ -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<CreativeInventoryEntry>` - 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.

View File

@ -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.1.0",

50
composer.lock generated
View File

@ -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": "957c41418e48e3faec0220a11a733f4b",
"content-hash": "663122b8f03ef5ec6718a419923a0851",
"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",

View File

@ -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;
@ -1010,7 +1011,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);

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.24.1";
public const BASE_VERSION = "5.25.1";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";

View File

@ -39,14 +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 CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.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 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';
public const RECIPES = BEDROCK_DATA_PATH . '/recipes';
public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';
}

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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));
}

View File

@ -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());
}

View File

@ -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 */

View File

@ -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
*/

View File

@ -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
)
])),

View File

@ -0,0 +1,34 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
/**
* Available tabs in the creative inventory that an item can be displayed in.
*/
enum CreativeCategory{
case CONSTRUCTION;
case NATURE;
case EQUIPMENT;
case ITEMS;
}

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\lang\Translatable;
use function strlen;
/**
* Info for an item group in the creative inventory menu.
*/
final class CreativeGroup{
/**
* @param Translatable|string $name Tooltip shown to the player on hover
* @param Item $icon Item shown when the group is collapsed
*/
public function __construct(
private readonly Translatable|string $name,
private readonly Item $icon
){
$nameLength = $name instanceof Translatable ? strlen($name->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; }
}

View File

@ -24,21 +24,24 @@ 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<int, Item>
* @var CreativeInventoryEntry[]
* @phpstan-var array<int, CreativeInventoryEntry>
*/
private array $creative = [];
@ -47,17 +50,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(BedrockDataFiles::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 +93,28 @@ final class CreativeInventory{
* @phpstan-return array<int, Item>
*/
public function getAll() : array{
return Utils::cloneObjectArray($this->creative);
return array_map(fn(CreativeInventoryEntry $entry) => $entry->getItem(), $this->creative);
}
/**
* @return CreativeInventoryEntry[]
* @phpstan-return array<int, CreativeInventoryEntry>
*/
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 +126,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();
}

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
final class CreativeInventoryEntry{
private readonly Item $item;
public function __construct(
Item $item,
private readonly CreativeCategory $category,
private readonly ?CreativeGroup $group = null
){
$this->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);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\json;
use pocketmine\crafting\json\ItemStackData;
final class CreativeGroupData{
/** @required */
public string $group_name;
/** @required */
public ?ItemStackData $group_icon;
/**
* @var \pocketmine\crafting\json\ItemStackData[]
* @required
*/
public array $items;
}

View File

@ -143,8 +143,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{
@ -153,8 +154,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;
}
}

View File

@ -897,6 +897,18 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_FROSTWALKER, []);
}
public static function enchantment_heavy_weapon_breach() : Translatable{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_BREACH, []);
}
public static function enchantment_heavy_weapon_density() : Translatable{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_DENSITY, []);
}
public static function enchantment_heavy_weapon_windburst() : Translatable{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_WINDBURST, []);
}
public static function enchantment_knockback() : Translatable{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_KNOCKBACK, []);
}
@ -1108,6 +1120,318 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::ITEM_RECORD_WARD_DESC, []);
}
public static function itemGroup_name_anvil() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ANVIL, []);
}
public static function itemGroup_name_arrow() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ARROW, []);
}
public static function itemGroup_name_axe() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_AXE, []);
}
public static function itemGroup_name_banner() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER, []);
}
public static function itemGroup_name_banner_pattern() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER_PATTERN, []);
}
public static function itemGroup_name_bed() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BED, []);
}
public static function itemGroup_name_boat() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOAT, []);
}
public static function itemGroup_name_boots() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOOTS, []);
}
public static function itemGroup_name_bundles() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUNDLES, []);
}
public static function itemGroup_name_buttons() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUTTONS, []);
}
public static function itemGroup_name_candles() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CANDLES, []);
}
public static function itemGroup_name_chalkboard() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHALKBOARD, []);
}
public static function itemGroup_name_chest() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHEST, []);
}
public static function itemGroup_name_chestboat() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTBOAT, []);
}
public static function itemGroup_name_chestplate() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTPLATE, []);
}
public static function itemGroup_name_compounds() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COMPOUNDS, []);
}
public static function itemGroup_name_concrete() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETE, []);
}
public static function itemGroup_name_concretePowder() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETEPOWDER, []);
}
public static function itemGroup_name_cookedFood() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COOKEDFOOD, []);
}
public static function itemGroup_name_coral() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL, []);
}
public static function itemGroup_name_coral_decorations() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL_DECORATIONS, []);
}
public static function itemGroup_name_crop() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CROP, []);
}
public static function itemGroup_name_door() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DOOR, []);
}
public static function itemGroup_name_dye() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DYE, []);
}
public static function itemGroup_name_enchantedBook() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ENCHANTEDBOOK, []);
}
public static function itemGroup_name_fence() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCE, []);
}
public static function itemGroup_name_fenceGate() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCEGATE, []);
}
public static function itemGroup_name_firework() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORK, []);
}
public static function itemGroup_name_fireworkStars() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORKSTARS, []);
}
public static function itemGroup_name_flower() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FLOWER, []);
}
public static function itemGroup_name_glass() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASS, []);
}
public static function itemGroup_name_glassPane() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASSPANE, []);
}
public static function itemGroup_name_glazedTerracotta() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLAZEDTERRACOTTA, []);
}
public static function itemGroup_name_goatHorn() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GOATHORN, []);
}
public static function itemGroup_name_grass() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GRASS, []);
}
public static function itemGroup_name_helmet() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HELMET, []);
}
public static function itemGroup_name_hoe() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HOE, []);
}
public static function itemGroup_name_horseArmor() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HORSEARMOR, []);
}
public static function itemGroup_name_leaves() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEAVES, []);
}
public static function itemGroup_name_leggings() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEGGINGS, []);
}
public static function itemGroup_name_lingeringPotion() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LINGERINGPOTION, []);
}
public static function itemGroup_name_log() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LOG, []);
}
public static function itemGroup_name_minecart() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MINECART, []);
}
public static function itemGroup_name_miscFood() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MISCFOOD, []);
}
public static function itemGroup_name_mobEgg() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MOBEGG, []);
}
public static function itemGroup_name_monsterStoneEgg() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MONSTERSTONEEGG, []);
}
public static function itemGroup_name_mushroom() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MUSHROOM, []);
}
public static function itemGroup_name_netherWartBlock() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_NETHERWARTBLOCK, []);
}
public static function itemGroup_name_ominousBottle() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_OMINOUSBOTTLE, []);
}
public static function itemGroup_name_ore() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ORE, []);
}
public static function itemGroup_name_permission() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PERMISSION, []);
}
public static function itemGroup_name_pickaxe() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PICKAXE, []);
}
public static function itemGroup_name_planks() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PLANKS, []);
}
public static function itemGroup_name_potion() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_POTION, []);
}
public static function itemGroup_name_pressurePlate() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRESSUREPLATE, []);
}
public static function itemGroup_name_products() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRODUCTS, []);
}
public static function itemGroup_name_rail() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAIL, []);
}
public static function itemGroup_name_rawFood() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAWFOOD, []);
}
public static function itemGroup_name_record() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RECORD, []);
}
public static function itemGroup_name_sandstone() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SANDSTONE, []);
}
public static function itemGroup_name_sapling() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SAPLING, []);
}
public static function itemGroup_name_seed() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SEED, []);
}
public static function itemGroup_name_shovel() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHOVEL, []);
}
public static function itemGroup_name_shulkerBox() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHULKERBOX, []);
}
public static function itemGroup_name_sign() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SIGN, []);
}
public static function itemGroup_name_skull() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SKULL, []);
}
public static function itemGroup_name_slab() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SLAB, []);
}
public static function itemGroup_name_splashPotion() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SPLASHPOTION, []);
}
public static function itemGroup_name_stainedClay() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAINEDCLAY, []);
}
public static function itemGroup_name_stairs() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAIRS, []);
}
public static function itemGroup_name_stone() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONE, []);
}
public static function itemGroup_name_stoneBrick() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONEBRICK, []);
}
public static function itemGroup_name_sword() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SWORD, []);
}
public static function itemGroup_name_trapdoor() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_TRAPDOOR, []);
}
public static function itemGroup_name_walls() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WALLS, []);
}
public static function itemGroup_name_wood() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOD, []);
}
public static function itemGroup_name_wool() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOL, []);
}
public static function itemGroup_name_woolCarpet() : Translatable{
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOLCARPET, []);
}
public static function kick_admin() : Translatable{
return new Translatable(KnownTranslationKeys::KICK_ADMIN, []);
}

View File

@ -194,6 +194,9 @@ final class KnownTranslationKeys{
public const ENCHANTMENT_FIRE = "enchantment.fire";
public const ENCHANTMENT_FISHINGSPEED = "enchantment.fishingSpeed";
public const ENCHANTMENT_FROSTWALKER = "enchantment.frostwalker";
public const ENCHANTMENT_HEAVY_WEAPON_BREACH = "enchantment.heavy_weapon.breach";
public const ENCHANTMENT_HEAVY_WEAPON_DENSITY = "enchantment.heavy_weapon.density";
public const ENCHANTMENT_HEAVY_WEAPON_WINDBURST = "enchantment.heavy_weapon.windburst";
public const ENCHANTMENT_KNOCKBACK = "enchantment.knockback";
public const ENCHANTMENT_LOOTBONUS = "enchantment.lootBonus";
public const ENCHANTMENT_LOOTBONUSDIGGER = "enchantment.lootBonusDigger";
@ -245,6 +248,84 @@ final class KnownTranslationKeys{
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
public const ITEM_RECORD_WAIT_DESC = "item.record_wait.desc";
public const ITEM_RECORD_WARD_DESC = "item.record_ward.desc";
public const ITEMGROUP_NAME_ANVIL = "itemGroup.name.anvil";
public const ITEMGROUP_NAME_ARROW = "itemGroup.name.arrow";
public const ITEMGROUP_NAME_AXE = "itemGroup.name.axe";
public const ITEMGROUP_NAME_BANNER = "itemGroup.name.banner";
public const ITEMGROUP_NAME_BANNER_PATTERN = "itemGroup.name.banner_pattern";
public const ITEMGROUP_NAME_BED = "itemGroup.name.bed";
public const ITEMGROUP_NAME_BOAT = "itemGroup.name.boat";
public const ITEMGROUP_NAME_BOOTS = "itemGroup.name.boots";
public const ITEMGROUP_NAME_BUNDLES = "itemGroup.name.bundles";
public const ITEMGROUP_NAME_BUTTONS = "itemGroup.name.buttons";
public const ITEMGROUP_NAME_CANDLES = "itemGroup.name.candles";
public const ITEMGROUP_NAME_CHALKBOARD = "itemGroup.name.chalkboard";
public const ITEMGROUP_NAME_CHEST = "itemGroup.name.chest";
public const ITEMGROUP_NAME_CHESTBOAT = "itemGroup.name.chestboat";
public const ITEMGROUP_NAME_CHESTPLATE = "itemGroup.name.chestplate";
public const ITEMGROUP_NAME_COMPOUNDS = "itemGroup.name.compounds";
public const ITEMGROUP_NAME_CONCRETE = "itemGroup.name.concrete";
public const ITEMGROUP_NAME_CONCRETEPOWDER = "itemGroup.name.concretePowder";
public const ITEMGROUP_NAME_COOKEDFOOD = "itemGroup.name.cookedFood";
public const ITEMGROUP_NAME_CORAL = "itemGroup.name.coral";
public const ITEMGROUP_NAME_CORAL_DECORATIONS = "itemGroup.name.coral_decorations";
public const ITEMGROUP_NAME_CROP = "itemGroup.name.crop";
public const ITEMGROUP_NAME_DOOR = "itemGroup.name.door";
public const ITEMGROUP_NAME_DYE = "itemGroup.name.dye";
public const ITEMGROUP_NAME_ENCHANTEDBOOK = "itemGroup.name.enchantedBook";
public const ITEMGROUP_NAME_FENCE = "itemGroup.name.fence";
public const ITEMGROUP_NAME_FENCEGATE = "itemGroup.name.fenceGate";
public const ITEMGROUP_NAME_FIREWORK = "itemGroup.name.firework";
public const ITEMGROUP_NAME_FIREWORKSTARS = "itemGroup.name.fireworkStars";
public const ITEMGROUP_NAME_FLOWER = "itemGroup.name.flower";
public const ITEMGROUP_NAME_GLASS = "itemGroup.name.glass";
public const ITEMGROUP_NAME_GLASSPANE = "itemGroup.name.glassPane";
public const ITEMGROUP_NAME_GLAZEDTERRACOTTA = "itemGroup.name.glazedTerracotta";
public const ITEMGROUP_NAME_GOATHORN = "itemGroup.name.goatHorn";
public const ITEMGROUP_NAME_GRASS = "itemGroup.name.grass";
public const ITEMGROUP_NAME_HELMET = "itemGroup.name.helmet";
public const ITEMGROUP_NAME_HOE = "itemGroup.name.hoe";
public const ITEMGROUP_NAME_HORSEARMOR = "itemGroup.name.horseArmor";
public const ITEMGROUP_NAME_LEAVES = "itemGroup.name.leaves";
public const ITEMGROUP_NAME_LEGGINGS = "itemGroup.name.leggings";
public const ITEMGROUP_NAME_LINGERINGPOTION = "itemGroup.name.lingeringPotion";
public const ITEMGROUP_NAME_LOG = "itemGroup.name.log";
public const ITEMGROUP_NAME_MINECART = "itemGroup.name.minecart";
public const ITEMGROUP_NAME_MISCFOOD = "itemGroup.name.miscFood";
public const ITEMGROUP_NAME_MOBEGG = "itemGroup.name.mobEgg";
public const ITEMGROUP_NAME_MONSTERSTONEEGG = "itemGroup.name.monsterStoneEgg";
public const ITEMGROUP_NAME_MUSHROOM = "itemGroup.name.mushroom";
public const ITEMGROUP_NAME_NETHERWARTBLOCK = "itemGroup.name.netherWartBlock";
public const ITEMGROUP_NAME_OMINOUSBOTTLE = "itemGroup.name.ominousBottle";
public const ITEMGROUP_NAME_ORE = "itemGroup.name.ore";
public const ITEMGROUP_NAME_PERMISSION = "itemGroup.name.permission";
public const ITEMGROUP_NAME_PICKAXE = "itemGroup.name.pickaxe";
public const ITEMGROUP_NAME_PLANKS = "itemGroup.name.planks";
public const ITEMGROUP_NAME_POTION = "itemGroup.name.potion";
public const ITEMGROUP_NAME_PRESSUREPLATE = "itemGroup.name.pressurePlate";
public const ITEMGROUP_NAME_PRODUCTS = "itemGroup.name.products";
public const ITEMGROUP_NAME_RAIL = "itemGroup.name.rail";
public const ITEMGROUP_NAME_RAWFOOD = "itemGroup.name.rawFood";
public const ITEMGROUP_NAME_RECORD = "itemGroup.name.record";
public const ITEMGROUP_NAME_SANDSTONE = "itemGroup.name.sandstone";
public const ITEMGROUP_NAME_SAPLING = "itemGroup.name.sapling";
public const ITEMGROUP_NAME_SEED = "itemGroup.name.seed";
public const ITEMGROUP_NAME_SHOVEL = "itemGroup.name.shovel";
public const ITEMGROUP_NAME_SHULKERBOX = "itemGroup.name.shulkerBox";
public const ITEMGROUP_NAME_SIGN = "itemGroup.name.sign";
public const ITEMGROUP_NAME_SKULL = "itemGroup.name.skull";
public const ITEMGROUP_NAME_SLAB = "itemGroup.name.slab";
public const ITEMGROUP_NAME_SPLASHPOTION = "itemGroup.name.splashPotion";
public const ITEMGROUP_NAME_STAINEDCLAY = "itemGroup.name.stainedClay";
public const ITEMGROUP_NAME_STAIRS = "itemGroup.name.stairs";
public const ITEMGROUP_NAME_STONE = "itemGroup.name.stone";
public const ITEMGROUP_NAME_STONEBRICK = "itemGroup.name.stoneBrick";
public const ITEMGROUP_NAME_SWORD = "itemGroup.name.sword";
public const ITEMGROUP_NAME_TRAPDOOR = "itemGroup.name.trapdoor";
public const ITEMGROUP_NAME_WALLS = "itemGroup.name.walls";
public const ITEMGROUP_NAME_WOOD = "itemGroup.name.wood";
public const ITEMGROUP_NAME_WOOL = "itemGroup.name.wool";
public const ITEMGROUP_NAME_WOOLCARPET = "itemGroup.name.woolCarpet";
public const KICK_ADMIN = "kick.admin";
public const KICK_ADMIN_REASON = "kick.admin.reason";
public const KICK_REASON_CHEAT = "kick.reason.cheat";

View File

@ -690,7 +690,7 @@ class InventoryManager{
}
public function syncCreative() : void{
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory()));
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->buildPacket($this->player->getCreativeInventory(), $this->session));
}
/**

View File

@ -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(

View File

@ -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<int, CreativeContentPacket>
* @var CreativeInventoryCacheEntry[]
* @phpstan-var array<int, CreativeInventoryCacheEntry>
*/
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);
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\cache;
use pocketmine\inventory\CreativeCategory;
use pocketmine\inventory\CreativeGroup;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry;
final class CreativeInventoryCacheEntry{
/**
* @param CreativeCategory[] $categories
* @param CreativeGroup[]|null[] $groups
* @param CreativeItemEntry[] $items
*
* @phpstan-param list<CreativeCategory> $categories
* @phpstan-param list<CreativeGroup|null> $groups
* @phpstan-param list<CreativeItemEntry> $items
*/
public function __construct(
public readonly array $categories,
public readonly array $groups,
public readonly array $items,
){
//NOOP
}
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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){

View File

@ -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
];

View File

@ -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;
}