mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Crafting recipe network serialization no longer depends on PM's internal legacy metadata
WOOOOOOOOOOOOOOOOOOOOOOHOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO!!!!!!!!!!!!!!!!!!!!!
This commit is contained in:
parent
8858b16a25
commit
7994da07be
8
composer.lock
generated
8
composer.lock
generated
@ -280,12 +280,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "6a28ede3e9cdf1c548e85ce24382fee5f1bd9d75"
|
||||
"reference": "a546e15f6a8d7498fb25d5a02ce16184a429bb78"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6a28ede3e9cdf1c548e85ce24382fee5f1bd9d75",
|
||||
"reference": "6a28ede3e9cdf1c548e85ce24382fee5f1bd9d75",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/a546e15f6a8d7498fb25d5a02ce16184a429bb78",
|
||||
"reference": "a546e15f6a8d7498fb25d5a02ce16184a429bb78",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -298,7 +298,7 @@
|
||||
"issues": "https://github.com/pmmp/BedrockData/issues",
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/modern-world-support"
|
||||
},
|
||||
"time": "2022-06-08T14:00:34+00:00"
|
||||
"time": "2022-07-02T15:28:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-item-upgrade-schema",
|
||||
|
@ -27,27 +27,53 @@ use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use function array_map;
|
||||
use function get_debug_type;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function json_decode;
|
||||
|
||||
/**
|
||||
* Handles translation of network block runtime IDs into blockstate data, and vice versa
|
||||
*/
|
||||
final class BlockStateDictionary{
|
||||
|
||||
private BlockStateLookupCache $lookupCache;
|
||||
private BlockStateLookupCache $stateDataToStateIdLookupCache;
|
||||
/**
|
||||
* @var int[][]|null
|
||||
* @phpstan-var array<int, array<string, int>>|null
|
||||
*/
|
||||
private ?array $idMetaToStateIdLookupCache = null;
|
||||
|
||||
/**
|
||||
* @param BlockStateData[] $states
|
||||
* @param BlockStateDictionaryEntry[] $states
|
||||
*
|
||||
* @phpstan-param list<BlockStateData> $states
|
||||
* @phpstan-param list<BlockStateDictionaryEntry> $states
|
||||
*/
|
||||
public function __construct(
|
||||
private array $states
|
||||
){
|
||||
$this->lookupCache = new BlockStateLookupCache($this->states);
|
||||
$this->stateDataToStateIdLookupCache = new BlockStateLookupCache(array_map(fn(BlockStateDictionaryEntry $entry) => $entry->getStateData(), $this->states));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[][]
|
||||
* @phpstan-return array<int, array<string, int>>
|
||||
*/
|
||||
private function getIdMetaToStateIdLookup() : array{
|
||||
if($this->idMetaToStateIdLookupCache === null){
|
||||
//TODO: if we ever allow mutating the dictionary, this would need to be rebuilt on modification
|
||||
$this->idMetaToStateIdLookupCache = [];
|
||||
|
||||
foreach($this->states as $i => $state){
|
||||
$this->idMetaToStateIdLookupCache[$state->getMeta()][$state->getStateData()->getName()] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->idMetaToStateIdLookupCache;
|
||||
}
|
||||
|
||||
public function getDataFromStateId(int $networkRuntimeId) : ?BlockStateData{
|
||||
return $this->states[$networkRuntimeId] ?? null;
|
||||
return ($this->states[$networkRuntimeId] ?? null)?->getStateData();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,20 +81,55 @@ final class BlockStateDictionary{
|
||||
* Returns null if there were no matches.
|
||||
*/
|
||||
public function lookupStateIdFromData(BlockStateData $data) : ?int{
|
||||
return $this->lookupCache->lookupStateId($data);
|
||||
return $this->stateDataToStateIdLookupCache->lookupStateId($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blockstate meta value associated with the given blockstate runtime ID.
|
||||
* This is used for serializing crafting recipe inputs.
|
||||
*/
|
||||
public function getMetaFromStateId(int $networkRuntimeId) : ?int{
|
||||
return ($this->states[$networkRuntimeId] ?? null)?->getMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blockstate data associated with the given block ID and meta value.
|
||||
* This is used for deserializing crafting recipe inputs.
|
||||
*/
|
||||
public function lookupStateIdFromIdMeta(string $id, int $meta) : ?int{
|
||||
return $this->getIdMetaToStateIdLookup()[$meta][$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array mapping runtime ID => blockstate data.
|
||||
* @return BlockStateData[]
|
||||
* @phpstan-return array<int, BlockStateData>
|
||||
* @return BlockStateDictionaryEntry[]
|
||||
* @phpstan-return array<int, BlockStateDictionaryEntry>
|
||||
*/
|
||||
public function getStates() : array{ return $this->states; }
|
||||
|
||||
public static function loadFromString(string $contents) : self{
|
||||
return new self(array_map(
|
||||
public static function loadFromString(string $blockPaletteContents, string $metaMapContents) : self{
|
||||
$metaMap = json_decode($metaMapContents, flags: JSON_THROW_ON_ERROR);
|
||||
if(!is_array($metaMap)){
|
||||
throw new \InvalidArgumentException("Invalid metaMap, expected array for root type, got " . get_debug_type($metaMap));
|
||||
}
|
||||
|
||||
$entries = [];
|
||||
$states = array_map(
|
||||
fn(TreeRoot $root) => BlockStateData::fromNbt($root->mustGetCompoundTag()),
|
||||
(new NetworkNbtSerializer())->readMultiple($contents)
|
||||
));
|
||||
(new NetworkNbtSerializer())->readMultiple($blockPaletteContents)
|
||||
);
|
||||
|
||||
foreach($states as $i => $state){
|
||||
$meta = $metaMap[$i] ?? null;
|
||||
if($meta === null){
|
||||
throw new \InvalidArgumentException("Missing associated meta value for state $i (" . $state->toNbt() . ")");
|
||||
}
|
||||
if(!is_int($meta)){
|
||||
throw new \InvalidArgumentException("Invalid metaMap offset $i, expected int, got " . get_debug_type($meta));
|
||||
}
|
||||
$entries[$i] = new BlockStateDictionaryEntry($state, $meta);
|
||||
}
|
||||
|
||||
return new self($entries);
|
||||
}
|
||||
}
|
||||
|
38
src/network/mcpe/convert/BlockStateDictionaryEntry.php
Normal file
38
src/network/mcpe/convert/BlockStateDictionaryEntry.php
Normal 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\network\mcpe\convert;
|
||||
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
|
||||
final class BlockStateDictionaryEntry{
|
||||
|
||||
public function __construct(
|
||||
private BlockStateData $stateData,
|
||||
private int $meta
|
||||
){}
|
||||
|
||||
public function getStateData() : BlockStateData{ return $this->stateData; }
|
||||
|
||||
public function getMeta() : int{ return $this->meta; }
|
||||
}
|
@ -53,9 +53,12 @@ final class RuntimeBlockMapping{
|
||||
|
||||
private static function make() : self{
|
||||
$canonicalBlockStatesFile = Path::join(\pocketmine\BEDROCK_DATA_PATH, "canonical_block_states.nbt");
|
||||
$contents = Utils::assumeNotFalse(file_get_contents($canonicalBlockStatesFile), "Missing required resource file");
|
||||
$canonicalBlockStatesRaw = Utils::assumeNotFalse(file_get_contents($canonicalBlockStatesFile), "Missing required resource file");
|
||||
|
||||
$metaMappingFile = Path::join(\pocketmine\BEDROCK_DATA_PATH, 'block_state_meta_map.json');
|
||||
$metaMappingRaw = Utils::assumeNotFalse(file_get_contents($metaMappingFile), "Missing required resource file");
|
||||
return new self(
|
||||
BlockStateDictionary::loadFromString($contents),
|
||||
BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
|
||||
GlobalBlockStateHandlers::getSerializer()
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\crafting\ExactRecipeIngredient;
|
||||
use pocketmine\crafting\MetaWildcardRecipeIngredient;
|
||||
use pocketmine\crafting\RecipeIngredient;
|
||||
use pocketmine\data\bedrock\item\BlockItemIdMap;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\inventory\transaction\action\CreateItemAction;
|
||||
use pocketmine\inventory\transaction\action\DestroyItemAction;
|
||||
@ -126,11 +127,12 @@ class TypeConverter{
|
||||
$meta = self::RECIPE_INPUT_WILDCARD_META;
|
||||
}elseif($ingredient instanceof ExactRecipeIngredient){
|
||||
$item = $ingredient->getItem();
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($item);
|
||||
if($id < 256){
|
||||
//TODO: this is needed for block crafting recipes to work - we need to replace this with some kind of
|
||||
//blockstate <-> meta mapping table so that we can remove the legacy code from the core
|
||||
$meta = $item->getMeta();
|
||||
[$id, $meta, $blockRuntimeId] = ItemTranslator::getInstance()->toNetworkId($item);
|
||||
if($blockRuntimeId !== ItemTranslator::NO_BLOCK_RUNTIME_ID){
|
||||
$meta = RuntimeBlockMapping::getInstance()->getBlockStateDictionary()->getMetaFromStateId($blockRuntimeId);
|
||||
if($meta === null){
|
||||
throw new AssumptionFailedError("Every block state should have an associated meta value");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
throw new \LogicException("Unsupported recipe ingredient type " . get_class($ingredient) . ", only " . ExactRecipeIngredient::class . " and " . MetaWildcardRecipeIngredient::class . " are supported");
|
||||
@ -143,13 +145,21 @@ class TypeConverter{
|
||||
return null;
|
||||
}
|
||||
|
||||
$itemId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromIntId($ingredient->getId());
|
||||
|
||||
if($ingredient->getMeta() === self::RECIPE_INPUT_WILDCARD_META){
|
||||
$itemId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromIntId($ingredient->getId());
|
||||
return new MetaWildcardRecipeIngredient($itemId);
|
||||
}
|
||||
|
||||
//TODO: this won't be handled properly for blockitems because a block runtimeID is expected rather than a meta value
|
||||
$result = ItemTranslator::getInstance()->fromNetworkId($ingredient->getId(), $ingredient->getMeta(), 0);
|
||||
$meta = $ingredient->getMeta();
|
||||
$blockRuntimeId = null;
|
||||
if(($blockId = BlockItemIdMap::getInstance()->lookupBlockId($itemId)) !== null){
|
||||
$blockRuntimeId = RuntimeBlockMapping::getInstance()->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
|
||||
if($blockRuntimeId !== null){
|
||||
$meta = 0;
|
||||
}
|
||||
}
|
||||
$result = ItemTranslator::getInstance()->fromNetworkId($ingredient->getId(), $meta, $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID);
|
||||
return new ExactRecipeIngredient($result);
|
||||
}
|
||||
|
||||
|
@ -23,14 +23,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\tools\generate_block_palette_spec;
|
||||
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\nbt\NbtException;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\convert\BlockStateDictionary;
|
||||
use pocketmine\network\mcpe\convert\BlockStateDictionaryEntry;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function dirname;
|
||||
@ -51,15 +56,26 @@ if(count($argv) !== 3){
|
||||
[, $inputFile, $outputFile] = $argv;
|
||||
|
||||
try{
|
||||
$palette = BlockStateDictionary::loadFromString(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents($inputFile)));
|
||||
$states = array_map(
|
||||
fn(TreeRoot $root) => BlockStateData::fromNbt($root->mustGetCompoundTag()),
|
||||
(new NetworkNbtSerializer())->readMultiple(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents($inputFile)))
|
||||
);
|
||||
}catch(NbtException){
|
||||
fwrite(STDERR, "Invalid block palette file $argv[1]\n");
|
||||
exit(1);
|
||||
}
|
||||
$entries = [];
|
||||
$fakeMeta = [];
|
||||
foreach($states as $state){
|
||||
$fakeMeta[$state->getName()] ??= 0;
|
||||
$entries[] = new BlockStateDictionaryEntry($state, $fakeMeta[$state->getName()]++);
|
||||
}
|
||||
$palette = new BlockStateDictionary($entries);
|
||||
|
||||
$reportMap = [];
|
||||
|
||||
foreach($palette->getStates() as $state){
|
||||
foreach($palette->getStates() as $entry){
|
||||
$state = $entry->getStateData();
|
||||
$name = $state->getName();
|
||||
foreach($state->getStates() as $propertyName => $value){
|
||||
if($value instanceof IntTag || $value instanceof StringTag){
|
||||
|
Loading…
x
Reference in New Issue
Block a user