diff --git a/.gitignore b/.gitignore index 723ef36..bc8a670 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.idea \ No newline at end of file +.idea/* \ No newline at end of file diff --git a/.poggit.yml b/.poggit.yml index 0f178f9..a7c7c1a 100644 --- a/.poggit.yml +++ b/.poggit.yml @@ -4,4 +4,8 @@ projects: path: "" model: virion type: library + libs: + - src: thebigsmilexd/libblockstate/libblockstate + version: ^0.1.2 + branch: master ... diff --git a/src/xenialdan/dump.md b/src/xenialdan/dump.md index 37095cf..8fe6fb8 100644 --- a/src/xenialdan/dump.md +++ b/src/xenialdan/dump.md @@ -1,75 +1,101 @@ -# NBT format output of exported.mcstructure -``` -TAG_Compound({ - 'structure_world_origin': TAG_List({ - 1, - 4, - 0, - }), - 'format_version': TAG_Int(1), - 'size': TAG_List({ - 1, - 1, - 14, - }), - 'structure': TAG_Compound({ - 'block_indices': TAG_List>({ - { - 0, - 1, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 1, - 0, - 1, - 0, - }, - { - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - }, - }), - 'entities': TAG_List({ - }), - 'palette': TAG_Compound({ - 'default': TAG_Compound({ - 'block_palette': TAG_List({ - { - 'name': TAG_String(minecraft:air), - 'states': TAG_Compound({ - }), - 'version': TAG_Int(17760256), - }, - { - 'name': TAG_String(minecraft:stained_glass), - 'states': TAG_Compound({ - 'color': TAG_String(white), - }), - 'version': TAG_Int(17760256), - }, - }), - 'block_position_data': TAG_Compound({ - }), - }), - }), - }), -}) +# NBT format output of room_1.mcstructure + ``` +TAG_Compound={ + "format_version" => TAG_Int=1 + "size" => TAG_List={ + TAG_Int=2 + TAG_Int=2 + TAG_Int=2 + } + "structure" => TAG_Compound={ + "block_indices" => TAG_List={ + TAG_List={ + TAG_Int=0 + TAG_Int=0 + TAG_Int=1 + TAG_Int=2 + TAG_Int=2 + TAG_Int=2 + TAG_Int=3 + TAG_Int=4 + } + TAG_List={ + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + TAG_Int=-1 + } + } + "entities" => TAG_List={ + } + "palette" => TAG_Compound={ + "default" => TAG_Compound={ + "block_palette" => TAG_List={ + TAG_Compound={ + "name" => TAG_String="minecraft:planks" + "states" => TAG_Compound={ + "wood_type" => TAG_String="oak" + } + "version" => TAG_Int=17959425 + } + TAG_Compound={ + "name" => TAG_String="minecraft:log" + "states" => TAG_Compound={ + "old_log_type" => TAG_String="oak" + "pillar_axis" => TAG_String="y" + } + "version" => TAG_Int=17959425 + } + TAG_Compound={ + "name" => TAG_String="minecraft:stonebrick" + "states" => TAG_Compound={ + "stone_brick_type" => TAG_String="default" + } + "version" => TAG_Int=17959425 + } + TAG_Compound={ + "name" => TAG_String="minecraft:air" + "states" => TAG_Compound={ + } + "version" => TAG_Int=17959425 + } + TAG_Compound={ + "name" => TAG_String="minecraft:jigsaw" + "states" => TAG_Compound={ + "facing_direction" => TAG_Int=4 + "rotation" => TAG_Int=0 + } + "version" => TAG_Int=17959425 + } + } + "block_position_data" => TAG_Compound={ + "1072" => TAG_Compound={ + "block_entity_data" => TAG_Compound={ + "final_state" => TAG_String="minecraft:air" + "id" => TAG_String="JigsawBlock" + "isMovable" => TAG_Byte=1 + "joint" => TAG_String="aligned" + "name" => TAG_String="test:connector" + "target" => TAG_String="test:connector" + "target_pool" => TAG_String="test:tunnel" + "x" => TAG_Int=-18 + "y" => TAG_Int=4 + "z" => TAG_Int=103 + } + } + } + } + } + } + "structure_world_origin" => TAG_List={ + TAG_Int=-32 + TAG_Int=3 + TAG_Int=96 + } +} +``` \ No newline at end of file diff --git a/src/xenialdan/libstructure/PacketListener.php b/src/xenialdan/libstructure/PacketListener.php index 8a9fef0..d1b4bbf 100644 --- a/src/xenialdan/libstructure/PacketListener.php +++ b/src/xenialdan/libstructure/PacketListener.php @@ -10,8 +10,6 @@ use pocketmine\event\Listener; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket; -use pocketmine\network\mcpe\protocol\StructureTemplateDataRequestPacket; -use pocketmine\network\mcpe\protocol\StructureTemplateDataResponsePacket; use pocketmine\plugin\Plugin; use pocketmine\plugin\PluginException; use RuntimeException; @@ -20,55 +18,50 @@ use xenialdan\libstructure\tile\StructureBlockTile; use xenialdan\libstructure\window\StructureBlockInventory; -class PacketListener implements Listener -{ +class PacketListener implements Listener{ /** @var Plugin|null */ private static ?Plugin $registrant = null; - public static function isRegistered(): bool - { + public static function isRegistered() : bool{ return self::$registrant instanceof Plugin; } - public static function getRegistrant(): Plugin - { + public static function getRegistrant() : Plugin{ return self::$registrant; } - public static function unregister(): void - { + public static function unregister() : void{ self::$registrant = null; } /** * @param Plugin $plugin - * @throws PluginException|RuntimeException + * + * @throws PluginException|RuntimeException|InvalidArgumentException */ - public static function register(Plugin $plugin): void - { - if (self::isRegistered()) { + public static function register(Plugin $plugin) : void{ + if(self::isRegistered()){ return;//silent return } self::$registrant = $plugin; - try { - TileFactory::getInstance()->register(StructureBlockTile::class, [StructureBlockTags::TAG_ID, "minecraft:structure_block"]); - BlockFactory::getInstance()->register(new StructureBlock(new BlockIdentifier(BlockLegacyIds::STRUCTURE_BLOCK,0, null, StructureBlockTile::class), "Structure Block"), true); - } catch (InvalidArgumentException) { - } + /** @var TileFactory $tileFactory */ + $tileFactory = TileFactory::getInstance(); + $tileFactory->register(StructureBlockTile::class, [StructureBlockTags::TAG_ID, "minecraft:structure_block"]); + /** @var BlockFactory $blockFactory */ + $blockFactory = BlockFactory::getInstance(); + $blockFactory->register(new StructureBlock(new BlockIdentifier(BlockLegacyIds::STRUCTURE_BLOCK, 0, null, StructureBlockTile::class), "Structure Block"), true); $plugin->getServer()->getPluginManager()->registerEvents(new self, $plugin); } - public function onDataPacketReceiveEvent(DataPacketReceiveEvent $e) - { - if ($e->getPacket() instanceof StructureBlockUpdatePacket) $this->onStructureBlockUpdatePacket($e); + public function onDataPacketReceiveEvent(DataPacketReceiveEvent $e){ + if($e->getPacket() instanceof StructureBlockUpdatePacket) $this->onStructureBlockUpdatePacket($e); //if ($e->getPacket() instanceof StructureTemplateDataRequestPacket) $this->onStructureTemplateDataExportRequestPacket($e); //if ($e->getPacket() instanceof StructureTemplateDataResponsePacket) $this->onStructureTemplateDataExportResponsePacket($e); } - private function onStructureBlockUpdatePacket(DataPacketReceiveEvent $e) - { - if (!$e->getPacket() instanceof StructureBlockUpdatePacket) return; + private function onStructureBlockUpdatePacket(DataPacketReceiveEvent $e){ + if(!$e->getPacket() instanceof StructureBlockUpdatePacket) return; //** @var StructureBlockUpdatePacket $pk */ #var_dump($e->getPacket());//TODO remove $session = $e->getOrigin(); @@ -79,23 +72,21 @@ private function onStructureBlockUpdatePacket(DataPacketReceiveEvent $e) } } - private function onStructureTemplateDataExportRequestPacket(DataPacketReceiveEvent $e) - { - /** @var StructureTemplateDataRequestPacket $pk */ - $pk = $e->getPacket(); - #$player = $e->getOrigin()->getPlayer(); - if ($pk instanceof StructureTemplateDataRequestPacket) { - var_dump($pk);//TODO remove - } - } - - private function onStructureTemplateDataExportResponsePacket(DataPacketReceiveEvent $e) - { - /** @var StructureTemplateDataResponsePacket $pk */ - $pk = $e->getPacket(); - #$player = $e->getOrigin()->getPlayer(); - if ($pk instanceof StructureTemplateDataResponsePacket) { - var_dump($pk);//TODO remove - } - } +// private function onStructureTemplateDataExportRequestPacket(DataPacketReceiveEvent $e){ +// /** @var StructureTemplateDataRequestPacket $pk */ +// $pk = $e->getPacket(); +// #$player = $e->getOrigin()->getPlayer(); +// if($pk instanceof StructureTemplateDataRequestPacket){ +// var_dump($pk);//TODO remove +// } +// } +// +// private function onStructureTemplateDataExportResponsePacket(DataPacketReceiveEvent $e){ +// /** @var StructureTemplateDataResponsePacket $pk */ +// $pk = $e->getPacket(); +// #$player = $e->getOrigin()->getPlayer(); +// if($pk instanceof StructureTemplateDataResponsePacket){ +// var_dump($pk);//TODO remove +// } +// } } \ No newline at end of file diff --git a/src/xenialdan/libstructure/block/StructureBlock.php b/src/xenialdan/libstructure/block/StructureBlock.php index 5732dbe..3e61d7e 100644 --- a/src/xenialdan/libstructure/block/StructureBlock.php +++ b/src/xenialdan/libstructure/block/StructureBlock.php @@ -4,23 +4,27 @@ namespace xenialdan\libstructure\block; +use InvalidArgumentException; +use LogicException; use pocketmine\block\Block; use pocketmine\block\BlockBreakInfo; use pocketmine\block\BlockIdentifier; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\ContainerOpenPacket; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; -use pocketmine\network\mcpe\protocol\types\StructureEditorData; use pocketmine\player\Player; +use pocketmine\utils\AssumptionFailedError; use xenialdan\libstructure\tile\StructureBlockTile as TileStructureBlock; -class StructureBlock extends Block -{ - private int $mode = StructureEditorData::TYPE_SAVE;//TODO validate if correct +class StructureBlock extends Block{ + #private int $mode = StructureEditorData::TYPE_SAVE;//TODO validate if correct - public function __construct(BlockIdentifier $idInfo, string $name, ?BlockBreakInfo $breakInfo = null) - { + /** + * @throws InvalidArgumentException + */ + public function __construct(BlockIdentifier $idInfo, string $name, ?BlockBreakInfo $breakInfo = null){ parent::__construct($idInfo, $name, $breakInfo ?? BlockBreakInfo::indestructible()); } @@ -38,15 +42,17 @@ public function getStateBitmask() : int{ return 0b101; } - public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null): bool - { - if ($player instanceof Player) { + /** + * @throws LogicException|InvalidArgumentException|AssumptionFailedError + */ + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + if($player instanceof Player){ $structureBlock = $this->position->getWorld()->getTile($this->position); - if ($structureBlock instanceof TileStructureBlock and $player->isCreative(true)) { + if($structureBlock instanceof TileStructureBlock and $player->isCreative(true)){ $player->setCurrentWindow($structureBlock->getInventory()); //TODO remove once PMMP allows injecting to InventoryManager::createContainerOpen $id = $player->getNetworkSession()->getInvManager()->getCurrentWindowId(); - $pk = ContainerOpenPacket::blockInvVec3($id, WindowTypes::STRUCTURE_EDITOR, $this->position->asVector3()); + $pk = ContainerOpenPacket::blockInv($id, WindowTypes::STRUCTURE_EDITOR, BlockPosition::fromVector3($this->position->asVector3())); $player->getNetworkSession()->sendDataPacket($pk); } } diff --git a/src/xenialdan/libstructure/exception/StructureException.php b/src/xenialdan/libstructure/exception/StructureException.php index 5314368..1915eb0 100644 --- a/src/xenialdan/libstructure/exception/StructureException.php +++ b/src/xenialdan/libstructure/exception/StructureException.php @@ -4,7 +4,6 @@ use Exception; -abstract class StructureException extends Exception -{ +abstract class StructureException extends Exception{ } \ No newline at end of file diff --git a/src/xenialdan/libstructure/exception/StructureFileException.php b/src/xenialdan/libstructure/exception/StructureFileException.php index a27df12..7dd1785 100644 --- a/src/xenialdan/libstructure/exception/StructureFileException.php +++ b/src/xenialdan/libstructure/exception/StructureFileException.php @@ -4,7 +4,6 @@ namespace xenialdan\libstructure\exception; -class StructureFileException extends StructureException -{ +class StructureFileException extends StructureException{ } \ No newline at end of file diff --git a/src/xenialdan/libstructure/exception/StructureFormatException.php b/src/xenialdan/libstructure/exception/StructureFormatException.php index 2110d56..d013120 100644 --- a/src/xenialdan/libstructure/exception/StructureFormatException.php +++ b/src/xenialdan/libstructure/exception/StructureFormatException.php @@ -6,7 +6,6 @@ use pocketmine\nbt\NbtDataException; -class StructureFormatException extends NbtDataException -{ +class StructureFormatException extends NbtDataException{ } \ No newline at end of file diff --git a/src/xenialdan/libstructure/format/MCStructure.php b/src/xenialdan/libstructure/format/MCStructure.php index b25867b..d916a9c 100644 --- a/src/xenialdan/libstructure/format/MCStructure.php +++ b/src/xenialdan/libstructure/format/MCStructure.php @@ -2,18 +2,16 @@ namespace xenialdan\libstructure\format; -use Closure; -use Exception; use Generator; -use GlobalLogger; use InvalidArgumentException; use OutOfBoundsException; use OutOfRangeException; +use pocketmine\block\Block; use pocketmine\block\BlockFactory; -use pocketmine\block\BlockLegacyIds; use pocketmine\block\tile\Container; use pocketmine\block\tile\Tile; use pocketmine\block\tile\TileFactory; +use pocketmine\data\SavedDataLoadingException; use pocketmine\math\Vector3; use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NBT; @@ -21,21 +19,26 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\TreeRoot; use pocketmine\nbt\UnexpectedTagTypeException; +use pocketmine\network\mcpe\protocol\types\BlockPosition; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; -use pocketmine\utils\TextFormat; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\Position; use pocketmine\world\World; use UnexpectedValueException; +use xenialdan\libblockstate\BlockState; +use xenialdan\libblockstate\BlockStatesParser; use xenialdan\libstructure\exception\StructureFileException; use xenialdan\libstructure\exception\StructureFormatException; -use xenialdan\MagicWE2\helper\BlockStatesEntry; -use xenialdan\MagicWE2\helper\BlockStatesParser; +use xenialdan\libstructure\format\filter\MCStructureFilter; +use function array_key_exists; +use function count; -class MCStructure -{ +class MCStructure{ //https://github.com/df-mc/structure/blob/651c5d323dbfb24991dafdb72129e2a8a478a81b/structure.go#L66-L91 + private const VERSION = 1; public const TAG_STRUCTURE_WORLD_ORIGIN = 'structure_world_origin'; public const TAG_FORMAT_VERSION = 'format_version'; public const TAG_SIZE = 'size'; @@ -48,213 +51,259 @@ class MCStructure public const TAG_PALETTE_BLOCK_POSITION_DATA = 'block_position_data'; public const TAG_PALETTE_BLOCK_ENTITY_DATA = 'block_entity_data'; public const EXTENSION_MCSTRUCTURE = '.mcstructure'; - public const LAYER_BLOCKS = 0; - public const LAYER_LIQUIDS = 1; - /** @var Vector3 */ - private Vector3 $structure_world_origin; - /** @var int */ - private int $format_version; - /** @var Vector3 */ - private Vector3 $size; + + public int $formatVersion; + public BlockPosition $size; + public BlockPosition $origin; + public MCStructureData $structure; + + private string $paletteName; + /** @phpstan-var array|CompoundTag */ + private array $palette;//pointer /** @var PalettedBlockArray[] */ - private array $blockLayers = []; - /** @var array|CompoundTag[] */ - private array $entities = []; - /** @var array|CompoundTag[] */ - private array $blockEntities = []; + private array $layers = []; + private int $activeLayer = 0; + /** @phpstan-var array blockHash => data */ + public array $blockEntities = []; + + public function __construct(MCStructureData $structure, BlockPosition $size, BlockPosition $origin, string $paletteName = self::TAG_PALETTE_DEFAULT){ + $this->formatVersion = self::VERSION; + $this->structure = &$structure; + $this->size = $size; + $this->origin = $origin; + $this->paletteName = $paletteName; + $this->palette = []; + } + + public function check() : bool{ + if($this->formatVersion !== self::VERSION) throw new StructureFileException("Unsupported format version: " . $this->formatVersion); + if(count($this->structure->blockIndices) === 0) throw new StructureFileException("Structure has no blocks in it"); + if(count($this->structure->palettes) === 0) throw new StructureFileException("Structure has no palettes in it"); + $size = $this->size->getX() * $this->size->getY() * $this->size->getZ(); + foreach($this->structure->blockIndices as $layer => $indices){ + if(count($indices) !== $size) throw new StructureFileException("Structure is " . $this->size->getX() . "x" . $this->size->getY() . "x" . $this->size->getZ() . " but has " . count($indices) . " blocks in layer " . $layer); + } + $paletteLen = -1; + foreach($this->structure->palettes as $name => $palette){ + if($paletteLen === -1){ + $paletteLen = count($palette[MCStructure::TAG_PALETTE_BLOCK_PALETTE]); + continue; + } + if(count($palette[MCStructure::TAG_PALETTE_BLOCK_PALETTE]) !== $paletteLen) throw new StructureFileException("Structure palette " . $name . " has " . count($palette[MCStructure::TAG_PALETTE_BLOCK_PALETTE]) . " entries but previous palettes have " . $paletteLen); + } + return true; + } + + + public function usePalette(string $name) : void{ + //FIXME add write mode + $this->paletteName = $name; + if(!array_key_exists($name, $this->structure->palettes)){ + $this->structure->palettes[$name] = [ + self::TAG_PALETTE_BLOCK_PALETTE => [], + self::TAG_PALETTE_BLOCK_POSITION_DATA => new CompoundTag() + ]; + } + $this->palette = &$this->structure->palettes[$name]; + } + + public function lookup(BlockState $properties) : int{ + foreach($this->palette[MCStructure::TAG_PALETTE_BLOCK_PALETTE] as $index => $entry){ + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + $blockState = $blockStatesParser->getFromCompound($entry); + if($blockState instanceof BlockState && $blockState->equals($properties)){ + return $index; + } + } + return -1; + } + /** * Parses a *.mcstructure file + * * @param string $path path to the .mcstructure file - * @throws InvalidArgumentException - * @throws StructureFileException - * @throws StructureFormatException - * @throws UnexpectedTagTypeException - * @throws UnexpectedValueException - * @throws NbtDataException + * + * @throws InvalidArgumentException|StructureFileException|StructureFormatException|UnexpectedTagTypeException|UnexpectedValueException|NbtDataException|OutOfRangeException * @see MCStructure */ - public function parse(string $path): void - { + public static function read(string $path) : self{ $pathext = pathinfo($path, PATHINFO_EXTENSION); - if ('.' . strtolower($pathext) !== self::EXTENSION_MCSTRUCTURE) throw new InvalidArgumentException("File extension $pathext for file $path is not " . self::EXTENSION_MCSTRUCTURE); + if('.' . strtolower($pathext) !== self::EXTENSION_MCSTRUCTURE) throw new InvalidArgumentException("File extension $pathext for file $path is not " . self::EXTENSION_MCSTRUCTURE); $path = Filesystem::cleanPath(realpath($path)); $fread = file_get_contents($path); - if ($fread === false) throw new StructureFileException("Could not read file $path"); + if($fread === false) throw new StructureFileException("Could not read file $path"); $namedTag = (new LittleEndianNBTSerializer())->read($fread)->mustGetCompoundTag(); - #Server::getInstance()->getLogger()->debug($namedTag->toString(2)); - //version - $version = $namedTag->getInt(self::TAG_FORMAT_VERSION); - if ($version === null) throw new StructureFormatException(self::TAG_FORMAT_VERSION . " must be present and valid integer"); - $this->format_version = $version; - //structure origin - $structureWorldOrigin = self::parseVec3($namedTag, self::TAG_STRUCTURE_WORLD_ORIGIN, true);//TODO check if optional (makes it V3{0,0,0}) - $this->structure_world_origin = $structureWorldOrigin; - //size - $size = self::parseVec3($namedTag, self::TAG_SIZE, false); - $this->size = $size; - $this->parseStructure($namedTag->getCompoundTag(self::TAG_STRUCTURE)); + + $structure = new self( + MCStructureData::fromNBT($namedTag->getCompoundTag(self::TAG_STRUCTURE)), + self::parseBlockPosition($namedTag, self::TAG_SIZE, false), + self::parseBlockPosition($namedTag, self::TAG_STRUCTURE_WORLD_ORIGIN, true) + ); + $structure->formatVersion = $namedTag->getInt(self::TAG_FORMAT_VERSION); + + $structure->check(); + + return $structure; + } + + //write file + public function write(string $path, ?MCStructureData $data = null) : void{ + $this->structure ??= $data; + $pathext = pathinfo($path, PATHINFO_EXTENSION); + if('.' . strtolower($pathext) !== self::EXTENSION_MCSTRUCTURE) throw new InvalidArgumentException("File extension $pathext for file $path is not " . self::EXTENSION_MCSTRUCTURE); + $path = Filesystem::cleanPath($path); + $namedTag = new TreeRoot((new CompoundTag()) + ->setInt(self::TAG_FORMAT_VERSION, self::VERSION) + ->setTag(self::TAG_SIZE, new ListTag([ + new IntTag($this->size->getX()), + new IntTag($this->size->getY()), + new IntTag($this->size->getZ()) + ], NBT::TAG_Int)) + ->setTag(self::TAG_STRUCTURE, $this->structure->toNBT()) + ->setTag(self::TAG_STRUCTURE_WORLD_ORIGIN, new ListTag([ + new IntTag($this->origin->getX()), + new IntTag($this->origin->getY()), + new IntTag($this->origin->getZ()) + ], NBT::TAG_Int))); + $serialized = (new LittleEndianNBTSerializer())->write($namedTag); + file_put_contents($path, $serialized); + } + + //parse method + public function parse() : self{ + $this->structure->parse($this); + return $this; } /** * @param CompoundTag $nbt - * @param string $tagName - * @param bool $optional - * @return Vector3 + * @param string $tagName + * @param bool $optional + * + * @return BlockPosition + * @throws UnexpectedTagTypeException * @throws UnexpectedValueException */ - private static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional): Vector3 - { + private static function parseBlockPosition(CompoundTag $nbt, string $tagName, bool $optional) : BlockPosition{ $pos = $nbt->getListTag($tagName); - if ($pos === null and $optional) { - return new Vector3(0, 0, 0); + if($pos === null and $optional){ + return new BlockPosition(0, 0, 0); } - if (!($pos instanceof ListTag) or $pos->getTagType() !== NBT::TAG_Int) { + if(!($pos instanceof ListTag) or $pos->getTagType() !== NBT::TAG_Int){ throw new UnexpectedValueException("'$tagName' should be a List"); } /** @var IntTag[] $values */ $values = $pos->getValue(); - if (count($values) !== 3) { + if(count($values) !== 3){ throw new UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag"); } - return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); + return new BlockPosition($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); } - private function parseStructure(CompoundTag $structure): void - { - $blockIndicesList = $structure->getListTag('block_indices');//list> - #$entitiesList = $structure->getListTag('entities'); - #var_dump($entitiesList->toString(2)); - $paletteCompound = $structure->getCompoundTag('palette'); - #$this->parseEntities($entitiesList);//TODO - $this->parseBlockLayers($paletteCompound, $blockIndicesList); + public function getPaletteName() : string{ + return $this->paletteName; } /** - * @param CompoundTag|null $paletteCompound - * @param ListTag>|null $blockIndicesList - * @throws InvalidArgumentException - * @throws OutOfRangeException + * @param PalettedBlockArray[] $layers */ - private function parseBlockLayers(?CompoundTag $paletteCompound, ?ListTag $blockIndicesList): void - { - /*if($paletteCompound->count() > 1){ - - }*/ - $paletteDefaultTag = $paletteCompound->getCompoundTag(self::TAG_PALETTE_DEFAULT); - $paletteBlocks = new PalettedBlockArray(BlockLegacyIds::AIR << 4); - $paletteLiquids = new PalettedBlockArray(BlockLegacyIds::AIR << 4); - $blockEntities = []; - /** @var BlockStatesEntry[] $paletteArray */ - $paletteArray = []; - /** @var CompoundTag $blockCompoundTag */ - foreach ($paletteDefaultTag->getListTag(self::TAG_PALETTE_BLOCK_PALETTE) as $paletteIndex => $blockCompoundTag) { - $blockState = BlockStatesParser::getInstance()::getStateByCompound($blockCompoundTag); - if ($blockState instanceof BlockStatesEntry) $paletteArray[$paletteIndex] = $blockState; - else print TextFormat::RED . $blockCompoundTag . " is not BlockStatesEntry"; - } - /** @var CompoundTag $blockPositionData */ - $blockPositionData = $paletteDefaultTag->getCompoundTag(self::TAG_PALETTE_BLOCK_POSITION_DATA); - //positions - $l = $this->size->getZ(); - $h = $this->size->getY(); - foreach (range(0, $this->size->getZ() - 1) as $z) { - foreach (range(0, $this->size->getY() - 1) as $y) { - foreach (range(0, $this->size->getX() - 1) as $x) { -// foreach ($blockIndicesList as $layerIndex => $layer) { -// $layer = reset($layer);//only default -// /** @var ListTag $layer */ -// foreach ($layer as $i => $paletteId) { -// /** @var IntTag $paletteId */ -// -// } -// } - $offset = (int)(($x * $l * $h) + ($y * $l) + $z); - /** @var ListTag $tag */ - if(!$tag instanceof ListTag) continue; - - //block layer - if($blockIndicesList->isset(0)){ - $tag = $blockIndicesList->get(0); - $blockLayer = $tag->getAllValues(); - if (($i = $blockLayer[$offset] ?? -1) !== -1) { - if (($statesEntry = $paletteArray[$i] ?? null) !== null) { - try { - $block = $statesEntry->toBlock(); - $paletteBlocks->set($x, $y, $z, $block->getFullId()); - } catch (Exception $e) { - GlobalLogger::get()->logException($e); - } - } - }} - //liquid layer - if($blockIndicesList->isset(1)){ - $tag = $blockIndicesList->get(1); - $liquidLayer = $tag->getAllValues(); - if (($i = $liquidLayer[$offset] ?? -1) !== -1) { - if (($statesEntry = $paletteArray[$i] ?? null) !== null) { - try { - $block = $statesEntry->toBlock(); - $paletteLiquids->set($x, $y, $z, $block->getFullId()); - } catch (Exception $e) { - GlobalLogger::get()->logException($e); - } - } - }} - //nbt - if ($blockPositionData->getTag((string)$offset) !== null) { - /** @var CompoundTag $tag1 */ - $tag1 = $blockPositionData->getCompoundTag((string)$offset); - $blockEntities[World::blockHash($x, $y, $z)] = $tag1->getCompoundTag(self::TAG_PALETTE_BLOCK_ENTITY_DATA); - } - } - } - } + public function setLayers(array $layers) : void{ + $this->layers = $layers; + } - $this->blockLayers = [$paletteBlocks, $paletteLiquids]; - $this->blockEntities = $blockEntities; + /** + * @return PalettedBlockArray[] + */ + public function getLayers() : array{ + return $this->layers; } /** - * @param int $layer Zero = block layer, One = liquid layer - * @return Generator * @throws OutOfBoundsException */ - public function blocks(int $layer = 0): Generator - { - if ($layer > count($this->blockLayers) || $layer < 0) throw new OutOfBoundsException('Layers must be in range 0...' . count($this->blockLayers)); - for ($x = 0; $x < $this->size->getX(); $x++) { - for ($y = 0; $y < $this->size->getY(); $y++) { - for ($z = 0; $z < $this->size->getZ(); $z++) { - $fullState = $this->blockLayers[$layer]->get($x, $y, $z); - $block = BlockFactory::getInstance()->fromFullBlock($fullState); - [$block->getPosition()->x, $block->getPosition()->y, $block->getPosition()->z] = [$x, $y, $z]; - yield $block; + public function setActiveLayer(int $layer) : self{ + if($layer > count($this->layers) || $layer < 0) throw new OutOfBoundsException('Layers must be in range 0...' . count($this->layers)); + $this->activeLayer = $layer; + return $this; + } + + public function getPalettedBlockArray(?int $layer = null) : PalettedBlockArray{ + $this->activeLayer = $layer ?? $this->activeLayer; + return $this->layers[$this->activeLayer]; + } + + /** + * @param PalettedBlockArray|null $palettedBlockArray can be filtered or modified using {@link MCStructureFilter} methods. If null is passed, the current layer will be used. + * + * @return Generator + * @phpstan-return Generator + */ + public function blocks(?PalettedBlockArray $palettedBlockArray = null) : Generator{//TODO offset + for($x = 0; $x < $this->size->getX(); $x++){ + for($y = 0; $y < $this->size->getY(); $y++){ + for($z = 0; $z < $this->size->getZ(); $z++){ + yield $this->get($x, $y, $z, $palettedBlockArray); } } } } - public function translateBlockEntity(Position $position, Vector3 $origin): ?Tile - { + /** + * Get block at position. + * + * @param PalettedBlockArray|null $palettedBlockArray can be filtered or modified using {@link MCStructureFilter} methods. If null is passed, the current layer will be used. + */ + public function get(int $x, int $y, int $z, ?PalettedBlockArray $palettedBlockArray = null) : ?Block{//TODO offset + $palettedBlockArray ??= $this->getPalettedBlockArray(); + $fullId = $palettedBlockArray->get($x, $y, $z); + if($fullId === 0xff_ff_ff_ff) return null; + $block = BlockFactory::getInstance()->fromFullBlock($fullId); + [$block->getPosition()->x, $block->getPosition()->y, $block->getPosition()->z] = [$x, $y, $z]; + return $block; + } + + public function set(int $x, int $y, int $z, ?BlockState $blockState, ?CompoundTag $nbt = null){ + if($blockState === null){ + $this->getPalettedBlockArray()->set($x, $y, $z, 0xff_ff_ff_ff); + return; + } + $this->getPalettedBlockArray()->set($x, $y, $z, $blockState->getFullId()); +// $ptr = $this->lookup($blockState); +// if($ptr === -1){ +// $ptr = count($this->palette[self::TAG_PALETTE_BLOCK_PALETTE]); +// $this->palette[$ptr] = [ +// self::TAG_PALETTE_BLOCK_PALETTE => $blockState, +// self::TAG_PALETTE_BLOCK_POSITION_DATA => $nbt +// ]; +// } +// $l = $this->size->getZ(); +// $h = $this->size->getY(); +// $offset = ($x * $l * $h) + ($y * $l) + $z; +// $this->structure->blockIndices[0][$offset] = $ptr; + } + + /** + * @throws UnexpectedTagTypeException|InvalidArgumentException|AssumptionFailedError|SavedDataLoadingException + */ + public function translateBlockEntity(Position $position, Vector3 $origin) : ?Tile{ $hash = World::blockHash($position->getFloorX(), $position->getFloorY(), $position->getFloorZ()); $data = $this->blockEntities[$hash] ?? null; - if ($data === null) return null; + if($data === null) return null; $instance = TileFactory::getInstance(); $data->setInt(Tile::TAG_X, $origin->getFloorX());//why do i have to manually change that before creation.. it won't work after creation $data->setInt(Tile::TAG_Y, $origin->getFloorY()); $data->setInt(Tile::TAG_Z, $origin->getFloorZ()); //hack to fix container items loading - if (($inventoryTag = $data->getTag(Container::TAG_ITEMS)) instanceof ListTag) { + if(($inventoryTag = $data->getTag(Container::TAG_ITEMS)) instanceof ListTag){ /** @var CompoundTag $itemNBT */ - foreach ($inventoryTag as $itemNBT) { + foreach($inventoryTag as $itemNBT){ $itemNBT->setString("id", $itemNBT->getString("Name", "minecraft:air")); $itemNBT->removeTag("Name"); - if ($itemNBT->getCompoundTag("tag") !== null) { - /** @var CompoundTag $tag */ - $tag = $itemNBT->getCompoundTag("tag"); - if ($tag->getTag("Damage") !== null) $tag->removeTag("Damage"); + if(($tag = $itemNBT->getCompoundTag("tag")) !== null){ + if($tag->getTag("Damage") !== null) $tag->removeTag("Damage"); } } } @@ -275,42 +324,38 @@ public function translateBlockEntity(Position $position, Vector3 $origin): ?Tile // } // } + /** @noinspection PhpInternalEntityUsedInspection */ $tile = $instance->createFromData($position->getWorld(), $data); - if ($tile === null) return null; + if($tile === null) return null;//Might return null if the tile is not registered return $tile; } - /** - * Reads a value of an object, regardless of access modifiers - * @param object $object - * @param string $property - * @return mixed - */ - public static function &readAnyValue(object $object, string $property): mixed - { - $invoke = Closure::bind(function & () use ($property) { - return $this->$property; - }, $object, $object)->__invoke(); - /** @noinspection PhpUnnecessaryLocalVariableInspection */ - $value = &$invoke; - - return $value; + public function getSize() : BlockPosition{ + return $this->size; } - /** - * @return Vector3 - */ - public function getSize(): Vector3 - { - return $this->size; + public function getStructureWorldOrigin() : BlockPosition{ + return $this->origin; + } + + public function getBlockEntitiesRaw() : array{ + return $this->blockEntities; } /** - * @return Vector3 + * @return CompoundTag[] + * @phpstan-return list */ - public function getStructureWorldOrigin(): Vector3 - { - return $this->structure_world_origin; + public function getEntitiesRaw() : array{ + return $this->structure->entities; + } + + public function getBlockLayersCount() : int{ + return count($this->layers); + } + + public function getFormatVersion() : int{ + return $this->formatVersion; } } \ No newline at end of file diff --git a/src/xenialdan/libstructure/format/MCStructureData.php b/src/xenialdan/libstructure/format/MCStructureData.php new file mode 100644 index 0000000..6baf76d --- /dev/null +++ b/src/xenialdan/libstructure/format/MCStructureData.php @@ -0,0 +1,202 @@ +> + * layer => index + */ + public array $blockIndices = []; + /** + * @var CompoundTag[] + * @phpstan-var list + */ + public array $entities = []; + //TODO palettename tagName + /** @phpstan-var array|CompoundTag> */ + public array $palettes = []; + + public static function fromNBT(?CompoundTag $compoundTag) : MCStructureData{ + $data = new MCStructureData(); + $blockIndices = $compoundTag->getListTag(MCStructure::TAG_BLOCK_INDICES); + /** + * @var int $layer + * @var ListTag $list + */ + foreach($blockIndices as $layer => $list){ + $data->blockIndices[$layer] = $list->getAllValues(); + } + $palettes = $compoundTag->getCompoundTag(MCStructure::TAG_PALETTE); + foreach($palettes as $paletteName => $paletteData){ + $data->palettes[$paletteName] = [ + MCStructure::TAG_PALETTE_BLOCK_PALETTE => $paletteData->getListTag(MCStructure::TAG_PALETTE_BLOCK_PALETTE)?->getValue(), + //compound -> int => compound data + MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA => $paletteData->getCompoundTag(MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA) + ]; + } + $data->entities = $compoundTag->getListTag(MCStructure::TAG_ENTITIES)->getValue(); + return $data; + } + + //toNBT + public function toNBT() : CompoundTag{ + $blockIndices = new ListTag(); + foreach($this->blockIndices as $indices){ + $walk = $indices; + array_walk($walk, static function(&$value, $key){ + $value = new IntTag($value); + }); + /** @var ListTag[] $walk */ + $blockIndices->push(new ListTag($walk, NBT::TAG_Int)); + } + $compoundTag = (new CompoundTag())->setTag(MCStructure::TAG_BLOCK_INDICES, $blockIndices); + $palettes = new CompoundTag(); + foreach($this->palettes as $paletteName => $paletteData){ + $palette = (new CompoundTag()) + ->setTag(MCStructure::TAG_PALETTE_BLOCK_PALETTE, new ListTag($paletteData[MCStructure::TAG_PALETTE_BLOCK_PALETTE])) + ->setTag(MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA, $paletteData[MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA]); + $palettes->setTag($paletteName, $palette); + } + $compoundTag->setTag(MCStructure::TAG_ENTITIES, new ListTag($this->entities, NBT::TAG_Compound)); + $compoundTag->setTag(MCStructure::TAG_PALETTE, $palettes); + return $compoundTag; + } + + public function parse(MCStructure $structure) : MCStructure{//TODO layer or palette parameter? + $structure->usePalette(MCStructure::TAG_PALETTE_DEFAULT);//TODO + $paletteName = $structure->getPaletteName();//TODO check if empty/not set + //TODO check if palette was already parsed + /** @var PalettedBlockArray[] $layers */ + $layers = []; + + $palette = $this->parsePalette($paletteName); + + foreach($this->blockIndices as $layer => $indices){ + $layers[$layer] = new PalettedBlockArray(0xff_ff_ff_ff); + + //positions + $l = $structure->size->getZ(); + $h = $structure->size->getY(); + foreach(range(0, $structure->size->getZ() - 1) as $z){ + foreach(range(0, $structure->size->getY() - 1) as $y){ + foreach(range(0, $structure->size->getX() - 1) as $x){ + $offset = (int) (($x * $l * $h) + ($y * $l) + $z); + +// if(($i = $this->blockIndices[$layer][$offset] ?? -1) !== -1){ + if(($i = $indices[$offset] ?? -1) !== -1){ + if(($fullId = $palette[$i] ?? null) !== null){ + try{ + $layers[$layer]->set($x, $y, $z, $fullId); + }catch(Exception $e){ + GlobalLogger::get()->logException($e); + } + } + } + //nbt + if($this->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA]->getTag((string) $offset) !== null){ + $tag1 = $this->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA]->getCompoundTag((string) $offset); + $structure->blockEntities[World::blockHash($x, $y, $z)] = $tag1->getCompoundTag(MCStructure::TAG_PALETTE_BLOCK_ENTITY_DATA); + } + } + } + } + } + + $structure->setLayers($layers); + + return $structure; + } + + //create MCStructureData from MCStructure + public static function fromStructure(MCStructure $structure) : MCStructureData{ + $data = new MCStructureData(); + $paletteName = $structure->getPaletteName(); + /** @phpstan-var list $indices */ + $indices = []; + foreach($structure->getLayers() as $layer => $palettedBlockArray){ + $palettedBlockArray = $structure->getPalettedBlockArray($layer); + $data->writePalette($paletteName, $palettedBlockArray); + + //write block indices + for($x = 0; $x < $structure->size->getX(); $x++){ + for($y = 0; $y < $structure->size->getY(); $y++){ + for($z = 0; $z < $structure->size->getZ(); $z++){ + $fullId = $palettedBlockArray->get($x, $y, $z); + $offset = (int) (($x * $structure->size->getZ() * $structure->size->getY()) + ($y * $structure->size->getZ()) + $z); + if($fullId === 0xff_ff_ff_ff){ + $data->blockIndices[$layer][$offset] = -1; + continue; + } + + $index = array_search($fullId, $indices, true); + if($index === false){ + $index = count($indices); + $indices[$index] = $fullId; + } + $data->blockIndices[$layer][$offset] = $index; + } + } + } + } + + foreach($structure->blockEntities as $hash => $blockEntity){ + World::getBlockXYZ($hash, $x, $y, $z); + $offset = (int) (($x * $structure->size->getZ() * $structure->size->getY()) + ($y * $structure->size->getZ()) + $z); + if(!isset($data->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA])){ + $data->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA] = new CompoundTag(); + } + $data->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_POSITION_DATA]->setTag((string) $offset, (new CompoundTag())->setTag(MCStructure::TAG_PALETTE_BLOCK_ENTITY_DATA, $blockEntity)); + } + + $data->entities = $structure->getEntitiesRaw(); + + return $data; + } + + /** @phpstan-return array */ + private function parsePalette(string $paletteName) : array{ + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + $palette = []; + foreach($this->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_PALETTE] as $index => $blockStateTag){ + $blockState = $blockStatesParser->getFromCompound($blockStateTag); + $palette[$index] = $blockState->getFullId(); + } + return $palette; + } + + /** @phpstan-param array $palette */ + private function writePalette(string $paletteName, PalettedBlockArray $palette) : void{ + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + #$this->palettes[$paletteName] = []; + $index = 0; + $palette->collectGarbage(); + foreach($palette->getPalette() as $fullId){ + if($fullId !== 0xff_ff_ff_ff){ + $blockState = $blockStatesParser->getFullId($fullId); + $this->palettes[$paletteName][MCStructure::TAG_PALETTE_BLOCK_PALETTE][$index] = $blockState->state->getBlockState(); + $index++; + } + } + } + + +} \ No newline at end of file diff --git a/src/xenialdan/libstructure/format/NBTStructure.php b/src/xenialdan/libstructure/format/NBTStructure.php index be8ce54..c3de78a 100644 --- a/src/xenialdan/libstructure/format/NBTStructure.php +++ b/src/xenialdan/libstructure/format/NBTStructure.php @@ -8,6 +8,7 @@ use InvalidArgumentException; use OutOfRangeException; use pocketmine\block\Block; +use pocketmine\block\utils\InvalidBlockStateException; use pocketmine\math\Vector3; use pocketmine\nbt\BigEndianNbtSerializer; use pocketmine\nbt\NBT; @@ -16,21 +17,20 @@ use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\nbt\UnexpectedTagTypeException; +use pocketmine\Server; +use RuntimeException; +use xenialdan\libblockstate\BlockQuery; +use xenialdan\libblockstate\BlockStatesParser; +use xenialdan\libblockstate\exception\BlockQueryParsingFailedException; +use xenialdan\libblockstate\exception\BlockStateNotFoundException; use xenialdan\libstructure\exception\StructureFileException; -use xenialdan\MagicWE2\exception\InvalidBlockStateException; -use xenialdan\MagicWE2\helper\BlockQuery; -use xenialdan\MagicWE2\helper\BlockStatesParser; -use xenialdan\MagicWE2\Loader; use function file_get_contents; use function zlib_decode; -class NBTStructure -{ - /** @var int */ +class NBTStructure{ private int $version; - /** @var string */ private string $author; - /** @var Vector3 */ private Vector3 $size; /** @var ListTag */ private ListTag $palettes; @@ -44,7 +44,7 @@ class NBTStructure * * @param string $file the Schematic output file name */ - public function save(string $file): void//TODO + public function save(string $file) : void//TODO { // $nbt = new TreeRoot( // CompoundTag::create() @@ -63,12 +63,13 @@ public function save(string $file): void//TODO * parse parses a schematic from the file passed. * * @param string $file + * * @throws OutOfRangeException * @throws StructureFileException * @throws NbtDataException + * @throws UnexpectedTagTypeException */ - public function parse(string $file): void - { + public function parse(string $file) : void{ $nbt = (new BigEndianNbtSerializer())->read(zlib_decode(file_get_contents($file))); $nbt = $nbt->getTag(); /** @var CompoundTag $nbt */ @@ -89,21 +90,25 @@ public function parse(string $file): void /** * @param ListTag $paletteList + * * @return Block[] - * @throws InvalidArgumentException - * @throws \pocketmine\block\utils\InvalidBlockStateException + * @throws RuntimeException + * @throws UnexpectedTagTypeException + * @throws BlockQueryParsingFailedException + * @throws BlockStateNotFoundException */ - private function paletteToBlocks(ListTag $paletteList): array - { + private function paletteToBlocks(ListTag $paletteList) : array{ + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); /** @var Block[] $blocks */ $blocks = []; /** @var CompoundTag $blockCompound */ - foreach ($paletteList/*->getValue()*/ as $blockCompound) { + foreach($paletteList/*->getValue()*/ as $blockCompound){ $id = $blockCompound->getString('Name'); $states = []; /** @var CompoundTag $properties */ $properties = $blockCompound->getCompoundTag('Properties'); - if ($properties instanceof CompoundTag) + if($properties instanceof CompoundTag) //Java/legacy hack /*if($properties->getTag('dataID') !== null){ $legacyDataId = $properties->getInt('dataID'); @@ -121,38 +126,40 @@ private function paletteToBlocks(ListTag $paletteList): array }*///TODO java fixes /** - * @var string $name + * @var string $name * @var StringTag $value */ - foreach ($properties->getValue() as $name => $value) { - $valueString = (string)$value->getValue(); + foreach($properties->getValue() as $name => $value){ + $valueString = (string) $value->getValue(); $states[] = $name . '=' . $valueString; } - try { - $fromString = BlockStatesParser::fromString(BlockQuery::fromString($id . '[' . implode(',', $states) . ']')); - } catch (InvalidBlockStateException $e) { - Loader::getInstance()->getLogger()->logException($e); + try{ + $blocks[] = $blockStatesParser->parseQuery(BlockQuery::fromString($id . '[' . implode(',', $states) . ']'))->getBlock(); + }catch(InvalidBlockStateException $e){ + Server::getInstance()->getLogger()->logException($e); } - $blocks[] = reset($fromString); } return $blocks; } /** * returns a generator of blocks found in the schematic opened. - * @param int $palette + * + * @param int $layer + * * @return Generator * @throws OutOfRangeException * @throws InvalidArgumentException - * @throws \pocketmine\block\utils\InvalidBlockStateException + * @throws InvalidBlockStateException + * @throws UnexpectedTagTypeException + * @throws RuntimeException */ - public function blocks(int $palette = 0): Generator - { + public function blocks(int $layer = 0) : Generator{ /** @var ListTag $paletteList */ - $paletteList = $this->palettes->get($palette); + $paletteList = $this->palettes->get($layer); $blockPalette = $this->paletteToBlocks($paletteList); /** @var CompoundTag $blockTag */ - foreach ($this->blocks as $blockTag) { + foreach($this->blocks as $blockTag){ /** @var ListTag $pos */ $pos = $blockTag->getListTag("pos"); $block = $blockPalette[$blockTag->getInt('state')]; @@ -229,15 +236,19 @@ public function blocks(int $palette = 0): Generator // } // } - /** - * @param int $x - * @param int $y - * @param int $z - * - * @return int - */ - protected function blockIndex(int $x, int $y, int $z): int - { + protected function blockIndex(int $x, int $y, int $z) : int{ return ($y * $this->size->getZ() + $z) * $this->size->getX() + $x; } + + public function getVersion() : int{ + return $this->version; + } + + public function getAuthor() : string{ + return $this->author; + } + + public function getSize() : Vector3{ + return $this->size; + } } diff --git a/src/xenialdan/libstructure/format/filter/MCStructureFilter.php b/src/xenialdan/libstructure/format/filter/MCStructureFilter.php new file mode 100644 index 0000000..3e17acb --- /dev/null +++ b/src/xenialdan/libstructure/format/filter/MCStructureFilter.php @@ -0,0 +1,209 @@ + $fullIds + * + * @return PalettedBlockArray + */ + public static function with(PalettedBlockArray $layer, array $fullIds = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + if(!in_array($id, $fullIds, true)){ + $layer->replaceAll($id, -1); + } + } + return $layer; + } + + /** + * Returns all blocks except those contained in $fullIds + * + * @param PalettedBlockArray $layer + * @param array $fullIds + * + * @phpstan-param list $fullIds + * + * @return PalettedBlockArray + */ + public static function except(PalettedBlockArray $layer, array $fullIds = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + if(in_array($id, $fullIds, true)){ + $layer->replaceAll($id, -1); + } + } + return $layer; + } + + /** + * Returns only blocks that have the same string ID as any contained in $blockIds + * + * @param PalettedBlockArray $layer + * @param string[] $blockIds + * + * @phpstan-param list $blockIds + * + * @return PalettedBlockArray + */ + public static function withBlockIds(PalettedBlockArray $layer, array $blockIds = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + $blockState = $blockStatesParser->getFullId($id); + if(!in_array($blockState->state->getId(), $blockIds, true)){ + $layer->replaceAll($id, -1); + } + } + return $layer; + } + + /** + * Returns all blocks except those that have the same string ID as any contained in $blockIds + * + * @param PalettedBlockArray $layer + * @param string[] $blockIds + * + * @phpstan-param list $blockIds + * + * @return PalettedBlockArray + */ + public static function exceptBlockIds(PalettedBlockArray $layer, array $blockIds = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + $blockState = $blockStatesParser->getFullId($id); + if(in_array($blockState->state->getId(), $blockIds, true)){ + $layer->replaceAll($id, -1); + } + } + return $layer; + } + + /** + * Returns only blocks that have any blockstates contained in $blockStates + * + * @param PalettedBlockArray $layer + * @param string[] $blockStates + * + * @phpstan-param list $blockStates + * + * @return PalettedBlockArray + */ + public static function withBlockStates(PalettedBlockArray $layer, array $blockStates = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + $blockState = $blockStatesParser->getFullId($id); + $compoundTag = $blockState->state->getBlockState()->getCompoundTag("states"); + foreach($blockStates as $blockState){ + if($compoundTag->getTag($blockState) === null){ + $layer->replaceAll($id, -1); + } + } + } + return $layer; + } + + /** + * Returns only blocks that have any blockstates contained in $blockStates and their values match + * + * @param PalettedBlockArray $layer + * @param string[] $blockStates + * + * @phpstan-param array $blockStates + * + * @return PalettedBlockArray + */ + public static function withBlockStatesAndValues(PalettedBlockArray $layer, array $blockStates = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + $blockState = $blockStatesParser->getFullId($id); + $compoundTag = $blockState->state->getBlockState()->getCompoundTag("states"); + foreach($blockStates as $stateName => $stateValue){ + if(!(($state = $compoundTag->getTag($stateName)) !== null && $state->getValue() === $stateValue)){ + $layer->replaceAll($id, -1); + } + } + } + return $layer; + } + + /** + * Replaces the $data keys with the $blockIds values + * + * @param PalettedBlockArray $layer + * @param array $blockIds full ids + * + * @phpstan-param array $blockIds full ids + * + * @return PalettedBlockArray + */ + public static function replace(PalettedBlockArray $layer, array $blockIds = []) : PalettedBlockArray{ + $layer = clone $layer; + foreach($blockIds as $from => $to){ + $layer->replaceAll($from, $to); + } + return $layer; + } + + /** + * Replaces all blockstates with new values from $blockStates if the blockstate exits + * + * @param PalettedBlockArray $layer + * @param array[] $blockStates keys are blockstates, values are the new values + * + * @phpstan-param array $blockStates keys are blockstates, values are the new values + * @return PalettedBlockArray + * @throws NoSuchTagException Technically impossible to throw + */ + public static function replaceBlockStates(PalettedBlockArray $layer, array $blockStates = []) : PalettedBlockArray{ + $layer = clone $layer; + $palette = $layer->getPalette(); + /** @var BlockStatesParser $blockStatesParser */ + $blockStatesParser = BlockStatesParser::getInstance(); + foreach($palette as $id){ + if($id === 0xff_ff_ff_ff) continue; + $blockState = $blockStatesParser->getFullId($id); + try{ + $newBlockState = $blockState->replaceBlockStateValues($blockStates, false);//automatically skips if no changes are made + $layer->replaceAll($blockState->getFullId(), $newBlockState->getFullId()); + }catch(UnexpectedTagTypeException | BlockQueryParsingFailedException){ + } + } + return $layer; + } +} \ No newline at end of file diff --git a/src/xenialdan/libstructure/tile/StructureBlockTags.php b/src/xenialdan/libstructure/tile/StructureBlockTags.php index ea4b8c3..a1fac98 100644 --- a/src/xenialdan/libstructure/tile/StructureBlockTags.php +++ b/src/xenialdan/libstructure/tile/StructureBlockTags.php @@ -4,15 +4,14 @@ namespace xenialdan\libstructure\tile; -interface StructureBlockTags -{ - const TAG_ID = "StructureBlock"; - const TAG_DATA = "data"; - const TAG_DATA_INVENTORY_MODEL = 0; - const TAG_DATA_DATA = 1; - const TAG_DATA_SAVE = 2; - const TAG_DATA_LOAD = 3; - const TAG_DATA_CORNER = 4; - const TAG_DATA_EXPORT = 5; +interface StructureBlockTags{ + const TAG_ID = "StructureBlock"; + const TAG_DATA = "data"; + const TAG_DATA_INVENTORY_MODEL = 0; + const TAG_DATA_DATA = 1; + const TAG_DATA_SAVE = 2; + const TAG_DATA_LOAD = 3; + const TAG_DATA_CORNER = 4; + const TAG_DATA_EXPORT = 5; } \ No newline at end of file diff --git a/src/xenialdan/libstructure/tile/StructureBlockTile.php b/src/xenialdan/libstructure/tile/StructureBlockTile.php index d4284c4..cec2568 100644 --- a/src/xenialdan/libstructure/tile/StructureBlockTile.php +++ b/src/xenialdan/libstructure/tile/StructureBlockTile.php @@ -10,13 +10,13 @@ use pocketmine\inventory\InventoryHolder; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\StructureEditorData; use pocketmine\network\mcpe\protocol\types\StructureSettings; use pocketmine\world\World; use xenialdan\libstructure\window\StructureBlockInventory; -class StructureBlockTile extends Spawnable implements Nameable, InventoryHolder -{ +class StructureBlockTile extends Spawnable implements Nameable, InventoryHolder{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; } @@ -33,29 +33,25 @@ class StructureBlockTile extends Spawnable implements Nameable, InventoryHolder private Vector3 $toV3; private ?string $title = null; - public function __construct(World $world, Vector3 $pos) - { + public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->fromV3 = $this->toV3 = $this->position->asVector3(); $this->inventory = new StructureBlockInventory($this->position); } - public function readSaveData(CompoundTag $nbt): void - { + public function readSaveData(CompoundTag $nbt) : void{ //todo read structure block data $this->loadName($nbt); } - protected function writeSaveData(CompoundTag $nbt): void - { + protected function writeSaveData(CompoundTag $nbt) : void{ //~todo~ write structure block data $this->addStructureBlockData($nbt); $nbt->setInt(StructureBlockTags::TAG_DATA, $this->mode); $this->saveName($nbt); } - protected function addAdditionalSpawnData(CompoundTag $nbt): void - { + protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ $this->addStructureBlockData($nbt); $this->addNameSpawnData($nbt); } @@ -63,30 +59,27 @@ protected function addAdditionalSpawnData(CompoundTag $nbt): void /** * @return StructureBlockInventory */ - public function getInventory(): StructureBlockInventory - { + public function getInventory() : StructureBlockInventory{ return $this->inventory; } - public function getDefaultName(): string - { + public function getDefaultName() : string{ return "Structure Block"; } /** * @return Vector3 */ - public function getFromV3(): Vector3 - { + public function getFromV3() : Vector3{ return $this->fromV3; } /** * @param Vector3 $fromV3 + * * @return StructureBlockTile */ - public function setFromV3(Vector3 $fromV3): self - { + public function setFromV3(Vector3 $fromV3) : self{ $this->fromV3 = $fromV3; return $this; } @@ -94,17 +87,16 @@ public function setFromV3(Vector3 $fromV3): self /** * @return Vector3 */ - public function getToV3(): Vector3 - { + public function getToV3() : Vector3{ return $this->toV3; } /** * @param Vector3 $toV3 + * * @return StructureBlockTile */ - public function setToV3(Vector3 $toV3): self - { + public function setToV3(Vector3 $toV3) : self{ $this->toV3 = $toV3; return $this; } @@ -112,83 +104,79 @@ public function setToV3(Vector3 $toV3): self /** * @return string */ - public function getTitle(): string - { + public function getTitle() : string{ return $this->title ?? $this->getName(); } /** * @param string $title + * * @return StructureBlockTile */ - public function setTitle(string $title): self - { + public function setTitle(string $title) : self{ $this->title = $title; return $this; } /** * @param bool $hideStructureBlock + * * @return StructureBlockTile */ - public function setHideStructureBlock(bool $hideStructureBlock): self - { + public function setHideStructureBlock(bool $hideStructureBlock) : self{ $this->hideStructureBlock = $hideStructureBlock; return $this; } /** * @param bool $showPlayers + * * @return StructureBlockTile */ - public function setShowPlayers(bool $showPlayers): self - { + public function setShowPlayers(bool $showPlayers) : self{ $this->showPlayers = $showPlayers; return $this; } /** * @param bool $showEntities + * * @return StructureBlockTile */ - public function setShowEntities(bool $showEntities): self - { + public function setShowEntities(bool $showEntities) : self{ $this->showEntities = $showEntities; return $this; } /** * @param bool $showBlocks + * * @return StructureBlockTile */ - public function setShowBlocks(bool $showBlocks): self - { + public function setShowBlocks(bool $showBlocks) : self{ $this->showBlocks = $showBlocks; return $this; } /** * @param bool $showBoundingBox + * * @return StructureBlockTile */ - public function setShowBoundingBox(bool $showBoundingBox): self - { + public function setShowBoundingBox(bool $showBoundingBox) : self{ $this->showBoundingBox = $showBoundingBox; return $this; } - public function calculateOffset(Vector3 $holderV3): Vector3 - { + public function calculateOffset(Vector3 $holderV3) : Vector3{ return $holderV3->subtractVector(Vector3::minComponents($this->fromV3, $this->toV3))->multiply(-1)->floor(); } - public function calculateSize(): Vector3 - { + public function calculateSize() : Vector3{ return $this->fromV3->subtractVector($this->toV3)->abs()->add(1, 1, 1); } - protected function addStructureBlockData(CompoundTag $nbt): void - { + protected function addStructureBlockData(CompoundTag $nbt) : void{ $pos = $this->getPosition(); $offset = $this->calculateOffset($pos->asVector3()); $size = $this->calculateSize(); @@ -206,85 +194,74 @@ protected function addStructureBlockData(CompoundTag $nbt): void $nbt->setLong("seed", 0); $nbt->setByte("showBoundingBox", $this->showBoundingBox ? 1 : 0); $nbt->setString("structureName", $this->getTitle()); - $nbt->setInt("x", (int)$pos->x); - $nbt->setInt("xStructureOffset", (int)$offset->x); - $nbt->setInt("xStructureSize", (int)$size->x); - $nbt->setInt("y", (int)$pos->y); - $nbt->setInt("yStructureOffset", (int)$offset->y/*+1*/);//TODO remove +1 hack - $nbt->setInt("yStructureSize", (int)$size->y); - $nbt->setInt("z", (int)$pos->z); - $nbt->setInt("zStructureOffset", (int)$offset->z); - $nbt->setInt("zStructureSize", (int)$size->z); + $nbt->setInt("x", (int) $pos->x); + $nbt->setInt("xStructureOffset", (int) $offset->x); + $nbt->setInt("xStructureSize", (int) $size->x); + $nbt->setInt("y", (int) $pos->y); + $nbt->setInt("yStructureOffset", (int) $offset->y/*+1*/);//TODO remove +1 hack + $nbt->setInt("yStructureSize", (int) $size->y); + $nbt->setInt("z", (int) $pos->z); + $nbt->setInt("zStructureOffset", (int) $offset->z); + $nbt->setInt("zStructureSize", (int) $size->z); } /** * @return bool */ - public function isShowPlayers(): bool - { + public function isShowPlayers() : bool{ return $this->showPlayers; } /** * @return bool */ - public function isShowEntities(): bool - { + public function isShowEntities() : bool{ return $this->showEntities; } /** * @return bool */ - public function isShowBlocks(): bool - { + public function isShowBlocks() : bool{ return $this->showBlocks; } /** * @return bool */ - public function isShowBoundingBox(): bool - { + public function isShowBoundingBox() : bool{ return $this->showBoundingBox; } /** * @return int */ - public function getMode(): int - { + public function getMode() : int{ return $this->mode; } - public function getStructureEditorData(string $structureName,Vector3 $size, Vector3 $origin): StructureEditorData - { + public function getStructureEditorData(string $structureName, Vector3 $size, Vector3 $origin) : StructureEditorData{ $data = new StructureEditorData(); $data->structureName = $structureName; $data->structureDataField = ""; $data->includePlayers = $this->isShowPlayers(); $data->showBoundingBox = $this->isShowBoundingBox(); $data->structureBlockType = $this->getMode(); - $data->structureSettings = $this->getStructureSettings($size,$origin); + $data->structureSettings = $this->getStructureSettings($size, $origin); $data->structureRedstoneSaveMove = 0; return $data; } - private function getStructureSettings(Vector3 $size, Vector3 $origin): StructureSettings - { + private function getStructureSettings(Vector3 $size, Vector3 $origin) : StructureSettings{ $settings = new StructureSettings(); $settings->paletteName = "default"; $settings->ignoreEntities = !$this->isShowEntities(); $settings->ignoreBlocks = !$this->isShowBlocks(); - $settings->structureSizeX = $size->getFloorX(); - $settings->structureSizeY = $size->getFloorY(); - $settings->structureSizeZ = $size->getFloorZ(); - $settings->structureOffsetX = 0; - $settings->structureOffsetY = 0; - $settings->structureOffsetZ = 0;//TODO position + $settings->dimensions = BlockPosition::fromVector3($size); + $settings->offset = new BlockPosition(0, 0, 0);//TODO position $settings->lastTouchedByPlayerID = -1; $settings->rotation = 0; - $settings->mirror = false; + $settings->mirror = 0; $settings->integrityValue = 1.0; $settings->integritySeed = 0; $settings->pivot = $origin; diff --git a/src/xenialdan/libstructure/window/StructureBlockInventory.php b/src/xenialdan/libstructure/window/StructureBlockInventory.php index 5a4ea11..e0e3d00 100644 --- a/src/xenialdan/libstructure/window/StructureBlockInventory.php +++ b/src/xenialdan/libstructure/window/StructureBlockInventory.php @@ -9,11 +9,10 @@ use pocketmine\inventory\SimpleInventory; use pocketmine\world\Position; -class StructureBlockInventory extends SimpleInventory implements BlockInventory -{ +class StructureBlockInventory extends SimpleInventory implements BlockInventory{ use BlockInventoryTrait; - public function __construct(Position $holder) - { + + public function __construct(Position $holder){ $this->holder = $holder; parent::__construct(0); } diff --git a/virion.yml b/virion.yml index 4b19379..ffe590c 100644 --- a/virion.yml +++ b/virion.yml @@ -1,6 +1,6 @@ name: libstructure -version: 0.1.5 +version: 0.3.0 antigen: xenialdan\libstructure -api: [4.0.0] +api: [ 4.0.0 ] php: [ 8.0 ] author: XenialDan