diff --git a/.poggit.yml b/.poggit.yml index dac40bd..0f178f9 100644 --- a/.poggit.yml +++ b/.poggit.yml @@ -1,6 +1,4 @@ --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/thebigsmileXD/libstructure -branches: -- master projects: libstructure: path: "" diff --git a/src/xenialdan/dump.md b/src/xenialdan/dump.md new file mode 100644 index 0000000..37095cf --- /dev/null +++ b/src/xenialdan/dump.md @@ -0,0 +1,75 @@ +# 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({ + }), + }), + }), + }), +}) +``` diff --git a/src/xenialdan/libstructure/PacketListener.php b/src/xenialdan/libstructure/PacketListener.php index dc93a7b..8a9fef0 100644 --- a/src/xenialdan/libstructure/PacketListener.php +++ b/src/xenialdan/libstructure/PacketListener.php @@ -2,69 +2,100 @@ namespace xenialdan\libstructure; +use InvalidArgumentException; +use pocketmine\block\BlockFactory; +use pocketmine\block\BlockIdentifier; +use pocketmine\block\BlockLegacyIds; +use pocketmine\block\tile\TileFactory; use pocketmine\event\Listener; use pocketmine\event\server\DataPacketReceiveEvent; -use pocketmine\network\mcpe\protocol\PacketPool; -use pocketmine\network\mcpe\protocol\StructureTemplateDataExportRequestPacket; -use pocketmine\network\mcpe\protocol\StructureTemplateDataExportResponsePacket; +use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket; +use pocketmine\network\mcpe\protocol\StructureTemplateDataRequestPacket; +use pocketmine\network\mcpe\protocol\StructureTemplateDataResponsePacket; use pocketmine\plugin\Plugin; -use pocketmine\Server; -use xenialdan\libstructure\packet\StructureBlockUpdatePacket; +use pocketmine\plugin\PluginException; +use RuntimeException; +use xenialdan\libstructure\block\StructureBlock; +use xenialdan\libstructure\tile\StructureBlockTags; +use xenialdan\libstructure\tile\StructureBlockTile; +use xenialdan\libstructure\window\StructureBlockInventory; class PacketListener implements Listener { - /** @var Plugin|null */ - private static $registrant; + /** @var Plugin|null */ + private static ?Plugin $registrant = null; - public static function isRegistered(): bool - { - return self::$registrant instanceof Plugin; - } + public static function isRegistered(): bool + { + return self::$registrant instanceof Plugin; + } - public static function getRegistrant(): Plugin - { - return self::$registrant; - } + public static function getRegistrant(): Plugin + { + return self::$registrant; + } - public static function unregister(): void - { - self::$registrant = null; - } + public static function unregister(): void + { + self::$registrant = null; + } - /** - * @param Plugin $plugin - */ - public static function register(Plugin $plugin): void - { - if (self::isRegistered()) { - return;//silent return - } + /** + * @param Plugin $plugin + * @throws PluginException|RuntimeException + */ + public static function register(Plugin $plugin): void + { + if (self::isRegistered()) { + return;//silent return + } - self::$registrant = $plugin; - $plugin->getServer()->getPluginManager()->registerEvents(new self, $plugin); - PacketPool::registerPacket(new StructureBlockUpdatePacket()); - } + 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) { + } + $plugin->getServer()->getPluginManager()->registerEvents(new self, $plugin); + } - public function onDataPacketReceiveEvent(DataPacketReceiveEvent $e) - { - if ($e->getPacket() instanceof StructureBlockUpdatePacket) $this->onStructureBlockUpdatePacket($e); - if ($e->getPacket() instanceof StructureTemplateDataExportRequestPacket) $this->onStructureTemplateDataExportRequestPacket($e); - if ($e->getPacket() instanceof StructureTemplateDataExportResponsePacket) $this->onStructureTemplateDataExportResponsePacket($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 (!($pk = $e->getPacket()) instanceof StructureBlockUpdatePacket) throw new \InvalidArgumentException(get_class($pk) . " is not a " . StructureBlockUpdatePacket::class); - /** @var StructureBlockUpdatePacket $pk */ - var_dump($e->getPacket()); - } + private function onStructureBlockUpdatePacket(DataPacketReceiveEvent $e) + { + if (!$e->getPacket() instanceof StructureBlockUpdatePacket) return; + //** @var StructureBlockUpdatePacket $pk */ + #var_dump($e->getPacket());//TODO remove + $session = $e->getOrigin(); + $window = $session->getInvManager()->getWindow($session->getInvManager()->getCurrentWindowId()); + //Hack to close the inventory (client does not send inventory close packet for structure blocks) + if($window instanceof StructureBlockInventory){ + $session->getPlayer()->removeCurrentWindow(); + } + } - private function onStructureTemplateDataExportRequestPacket(DataPacketReceiveEvent $e) - { - } - - private function onStructureTemplateDataExportResponsePacket(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 + } + } } \ No newline at end of file diff --git a/src/xenialdan/libstructure/StructureUI.php b/src/xenialdan/libstructure/StructureUI.php deleted file mode 100644 index 3e7ea2d..0000000 --- a/src/xenialdan/libstructure/StructureUI.php +++ /dev/null @@ -1,232 +0,0 @@ -readonly(); - $this->fromV3 = $fromV3; - $this->toV3 = $toV3; - parent::__construct($menu, [], 0, $title); - } - - /** - * @param Vector3 $fromV3 - * @return StructureUI - */ - public function setFromV3(Vector3 $fromV3): StructureUI - { - $this->fromV3 = $fromV3; - return $this; - } - - /** - * @param Vector3 $toV3 - * @return StructureUI - */ - public function setToV3(Vector3 $toV3): StructureUI - { - $this->toV3 = $toV3; - return $this; - } - - /** - * @param string $title - * @return StructureUI - */ - public function setTitle(string $title): StructureUI - { - $this->title = $title; - return $this; - } - - /** - * @param bool $hideStructureBlock - * @return StructureUI - */ - public function setHideStructureBlock(bool $hideStructureBlock): StructureUI - { - $this->hideStructureBlock = $hideStructureBlock; - return $this; - } - - /** - * @param bool $showPlayers - * @return StructureUI - */ - public function setShowPlayers(bool $showPlayers): StructureUI - { - $this->showPlayers = $showPlayers; - return $this; - } - - /** - * @param bool $showEntities - * @return StructureUI - */ - public function setShowEntities(bool $showEntities): StructureUI - { - $this->showEntities = $showEntities; - return $this; - } - - /** - * @param bool $showBlocks - * @return StructureUI - */ - public function setShowBlocks(bool $showBlocks): StructureUI - { - $this->showBlocks = $showBlocks; - return $this; - } - - /** - * @param bool $showBoundingBox - * @return StructureUI - */ - public function setShowBoundingBox(bool $showBoundingBox): StructureUI - { - $this->showBoundingBox = $showBoundingBox; - return $this; - } - - private function calculateOffset(Vector3 $holderV3): Vector3 - { - return $holderV3->subtract(self::getMinV3($this->fromV3, $this->toV3))->multiply(-1)->floor(); - } - - private function calculateSize(): Vector3 - { - return $this->fromV3->subtract($this->toV3)->abs()->add(1, 1, 1); - } - - /** - * @param Vector3 $v1 - * @param Vector3 $v2 - * @return Vector3 - */ - public static function getMinV3(Vector3 $v1, Vector3 $v2): Vector3 - { - return (new Vector3(min($v1->x, $v2->x), min($v1->y, $v2->y), min($v1->z, $v2->z)))->floor(); - } - - /** - * @param Vector3 $v1 - * @param Vector3 $v2 - * @return Vector3 - */ - public static function getMaxV3(Vector3 $v1, Vector3 $v2): Vector3 - { - return (new Vector3(max($v1->x, $v2->x), max($v1->y, $v2->y), max($v1->z, $v2->z)))->floor(); - } - - /* InvMenu */ - - protected function sendFakeBlockData(Player $player, HolderData $data): void - { - $block = $this->getBlock()->setComponents($data->position->x, $data->position->y, $data->position->z); - $player->getLevel()->sendBlocks([$player], [$block]); - - $tag = new CompoundTag(); - if ($data->custom_name !== null) { - $tag->setString("CustomName", $data->custom_name); - } - $offset = $this->calculateOffset($block->asVector3()); - $size = $this->calculateSize(); - var_dump("offset", $offset, "size", $size, "blockV3", $block->asVector3()); - $tag->setInt("data", (int)$this->mode); - $tag->setString("dataField", ""); - $tag->setByte("ignoreEntities", $this->showEntities ? 0 : 1); - $tag->setByte("includePlayers", $this->showPlayers ? 1 : 0); - $tag->setFloat("integrity", 100.0); - $tag->setByte("isMovable", 1); - $tag->setByte("isPowered", 0); - $tag->setByte("mirror", 0); - $tag->setByte("removeBlocks", $this->showBlocks ? 0 : 1); - $tag->setByte("rotation", 0); - $tag->setLong("seed", 0); - $tag->setByte("showBoundingBox", $this->showBoundingBox ? 1 : 0); - $tag->setString("structureName", $data->custom_name ?? $this->title ?? $this->getName()); - $tag->setInt("x", (int)$block->x); - $tag->setInt("xStructureOffset", (int)$offset->x); - $tag->setInt("xStructureSize", (int)$size->x); - $tag->setInt("y", (int)$block->y); - $tag->setInt("yStructureOffset", (int)$offset->y); - $tag->setInt("yStructureSize", (int)$size->y); - $tag->setInt("z", (int)$block->z); - $tag->setInt("zStructureOffset", (int)$offset->z); - $tag->setInt("zStructureSize", (int)$size->z); - var_dump($tag->toString()); - - $this->sendTile($player, $block, $tag); - - $this->onFakeBlockDataSend($player); - } - - public function onFakeBlockDataSendSuccess(Player $player): void - { - var_dump($this); - #parent::onFakeBlockDataSendSuccess($player); - } - - public function getTileId(): string - { - return StructureBlockTags::TAG_ID; - } - - public function getName(): string - { - return "Structure Block"; - } - - public function getDefaultSize(): int - { - return 0; - } - - /** - * Returns the Minecraft PE inventory type used to show the inventory window to clients. - * @return int - */ - public function getNetworkType(): int - { - return WindowTypes::STRUCTURE_EDITOR; - } - - public function getBlock(): Block - { - return Block::get(Block::STRUCTURE_BLOCK, $this->mode); - } -} \ No newline at end of file diff --git a/src/xenialdan/libstructure/block/StructureBlock.php b/src/xenialdan/libstructure/block/StructureBlock.php new file mode 100644 index 0000000..5732dbe --- /dev/null +++ b/src/xenialdan/libstructure/block/StructureBlock.php @@ -0,0 +1,56 @@ +mode; + } + + public function readStateFromData(int $id, int $stateMeta): void + { + $this->mode = $stateMeta; + }*/ + + public function getStateBitmask() : int{ + return 0b101; + } + + 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)) { + $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()); + $player->getNetworkSession()->sendDataPacket($pk); + } + } + + return true; + } +} diff --git a/src/xenialdan/libstructure/exception/StructureException.php b/src/xenialdan/libstructure/exception/StructureException.php new file mode 100644 index 0000000..5314368 --- /dev/null +++ b/src/xenialdan/libstructure/exception/StructureException.php @@ -0,0 +1,10 @@ +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)); + } + + /** + * @param CompoundTag $nbt + * @param string $tagName + * @param bool $optional + * @return Vector3 + * @throws UnexpectedValueException + */ + private static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional): Vector3 + { + $pos = $nbt->getListTag($tagName); + if ($pos === null and $optional) { + return new Vector3(0, 0, 0); + } + 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) { + throw new UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag"); + } + return new Vector3($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); + } + + /** + * @param CompoundTag|null $paletteCompound + * @param ListTag>|null $blockIndicesList + * @throws InvalidArgumentException + * @throws OutOfRangeException + */ + 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); + } + } + } + } + + $this->blockLayers = [$paletteBlocks, $paletteLiquids]; + $this->blockEntities = $blockEntities; + } + + /** + * @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 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; + $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) { + /** @var CompoundTag $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"); + } + } + } + +// $knownTiles = self::readAnyValue(TileFactory::getInstance(), "knownTiles"); +// $tileId = $data->getString(Tile::TAG_ID, ""); +// +// switch ($knownTiles[$tileId] ?? null) { +// case Chest::class: +// { +// if (($inventoryTag = $data->getTag(Container::TAG_ITEMS)) instanceof ListTag) { +// /** @var CompoundTag $itemNBT */ +// foreach ($inventoryTag as $itemNBT) { +// $itemNBT->setString("id", $itemNBT->getString("Name", "minecraft:air")); +// $itemNBT->removeTag("Name"); +// } +// } +// } +// } + + $tile = $instance->createFromData($position->getWorld(), $data); + if ($tile === null) return null; + 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; + } + + /** + * @return Vector3 + */ + public function getSize(): Vector3 + { + return $this->size; + } + + /** + * @return Vector3 + */ + public function getStructureWorldOrigin(): Vector3 + { + return $this->structure_world_origin; + } + +} \ No newline at end of file diff --git a/src/xenialdan/libstructure/format/NBTStructure.php b/src/xenialdan/libstructure/format/NBTStructure.php new file mode 100644 index 0000000..be8ce54 --- /dev/null +++ b/src/xenialdan/libstructure/format/NBTStructure.php @@ -0,0 +1,243 @@ + */ + private ListTag $palettes; + /** @var ListTag */ + private ListTag $blocks; + /** @var ListTag */ + private ListTag $entities; + + /** + * save saves a schematic to disk. + * + * @param string $file the Schematic output file name + */ + public function save(string $file): void//TODO + { +// $nbt = new TreeRoot( +// CompoundTag::create() +// ->setByteArray("Blocks", $this->blocks) +// ->setByteArray("Data", $this->data) +// ->setShort("Length", $this->length) +// ->setShort("Width", $this->width) +// ->setShort("Height", $this->height) +// ->setString("Materials", self::MATERIALS_POCKET) +// ); +// //NOTE: Save after encoding with zlib_encode for backward compatibility. +// file_put_contents($file, zlib_encode((new BigEndianNbtSerializer())->write($nbt), ZLIB_ENCODING_GZIP)); + } + + /** + * parse parses a schematic from the file passed. + * + * @param string $file + * @throws OutOfRangeException + * @throws StructureFileException + * @throws NbtDataException + */ + public function parse(string $file): void + { + $nbt = (new BigEndianNbtSerializer())->read(zlib_decode(file_get_contents($file))); + $nbt = $nbt->getTag(); + /** @var CompoundTag $nbt */ + + //https://minecraft.gamepedia.com/Data_version + $this->version = $nbt->getInt("DataVersion", 100);//todo figure out fallback + if($this->version !== 100) throw new StructureFileException("File contains DataVersion, indicating Java structure. Java structures are not supported"); + $this->author = $nbt->getString("author", ""); + + /** @var ListTag $size */ + $size = $nbt->getListTag("size"); + $this->size = new Vector3($size->get(0)->getValue(), $size->get(1)->getValue(), $size->get(2)->getValue()); + + $this->palettes = $nbt->getListTag("palettes") ?? new ListTag([$nbt->getListTag("palette")], NBT::TAG_List); + $this->blocks = $nbt->getListTag("blocks") ?? new ListTag([], NBT::TAG_List); + $this->entities = $nbt->getListTag("entities") ?? new ListTag([$nbt->getListTag("entities")], NBT::TAG_List); + } + + /** + * @param ListTag $paletteList + * @return Block[] + * @throws InvalidArgumentException + * @throws \pocketmine\block\utils\InvalidBlockStateException + */ + private function paletteToBlocks(ListTag $paletteList): array + { + /** @var Block[] $blocks */ + $blocks = []; + /** @var CompoundTag $blockCompound */ + foreach ($paletteList/*->getValue()*/ as $blockCompound) { + $id = $blockCompound->getString('Name'); + $states = []; + /** @var CompoundTag $properties */ + $properties = $blockCompound->getCompoundTag('Properties'); + if ($properties instanceof CompoundTag) + //Java/legacy hack + /*if($properties->getTag('dataID') !== null){ + $legacyDataId = $properties->getInt('dataID'); + //Block::getStateFromLegacyData + } else{ + if($properties->getTag('half') !== null){ + $legacyHalf = $properties->getString('half'); + //LegacyStructureTemplate::_mapToProperty(&v99, v19, v65); + } + if($properties->getTag('waterlogged') !== null){ + $legacyWaterlogged = $properties->getString('waterlogged'); + //LegacyStructureTemplate::_mapPropertyToExtraBlock(&v97, v20); + } + //while properties -> v65 = (Block *)LegacyStructureTemplate::_mapToProperty(v21, v22, v65); (v5 = Block) + }*///TODO java fixes + + /** + * @var string $name + * @var StringTag $value + */ + 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); + } + $blocks[] = reset($fromString); + } + return $blocks; + } + + /** + * returns a generator of blocks found in the schematic opened. + * @param int $palette + * @return Generator + * @throws OutOfRangeException + * @throws InvalidArgumentException + * @throws \pocketmine\block\utils\InvalidBlockStateException + */ + public function blocks(int $palette = 0): Generator + { + /** @var ListTag $paletteList */ + $paletteList = $this->palettes->get($palette); + $blockPalette = $this->paletteToBlocks($paletteList); + /** @var CompoundTag $blockTag */ + foreach ($this->blocks as $blockTag) { + /** @var ListTag $pos */ + $pos = $blockTag->getListTag("pos"); + $block = $blockPalette[$blockTag->getInt('state')]; + [$block->getPosition()->x, $block->getPosition()->y, $block->getPosition()->z] = [$pos->get(0)->getValue(), $pos->get(1)->getValue(), $pos->get(2)->getValue()]; + yield $block; + } + } +// +// /** +// * setBlocks sets a generator of blocks to a schematic, using a bounding box to calculate the size. +// * +// * @param $bb AxisAlignedBB +// * @param Generator $blocks +// */ +// public function setBlocks(AxisAlignedBB $bb, Generator $blocks): void +// { +// /** @var Block $block */ +// $offset = new Vector3((int)$bb->minX, (int)$bb->minY, (int)$bb->minZ); +// $max = new Vector3((int)$bb->maxX, (int)$bb->maxY, (int)$bb->maxZ); +// +// $this->width = $max->x - $offset->x + 1; +// $this->length = $max->z - $offset->z + 1; +// $this->height = $max->y - $offset->y + 1; +// +// foreach ($blocks as $block) { +// $pos = $block->getPos()->subtractVector($offset); +// $index = $this->blockIndex($pos->x, $pos->y, $pos->z); +// if (strlen($this->blocks) <= $index) { +// $this->blocks .= str_repeat(chr(0), $index - strlen($this->blocks) + 1); +// } +// $this->blocks[$index] = chr($block->getId()); +// $this->data[$index] = chr($block->getMeta()); +// } +// } +// +// /** +// * setBlockArray sets a block array to a schematic. The bounds of the schematic are calculated manually. +// * +// * @param Block[] $blocks +// */ +// public function setBlockArray(array $blocks): void +// { +// $min = new Vector3(0, 0, 0); +// $max = new Vector3(0, 0, 0); +// foreach ($blocks as $block) { +// if ($block->getPos()->x < $min->x) { +// $min->x = $block->getPos()->x; +// } else if ($block->getPos()->x > $max->x) { +// $max->x = $block->getPos()->x; +// } +// if ($block->getPos()->y < $min->y) { +// $min->y = $block->getPos()->y; +// } else if ($block->getPos()->y > $max->y) { +// $max->y = $block->getPos()->y; +// } +// if ($block->getPos()->z < $min->z) { +// $min->z = $block->getPos()->z; +// } else if ($block->getPos()->z > $max->z) { +// $max->z = $block->getPos()->z; +// } +// } +// $this->height = $max->y - $min->y + 1; +// $this->width = $max->x - $min->x + 1; +// $this->length = $max->z - $min->z + 1; +// +// foreach ($blocks as $block) { +// $pos = $block->getPos()->subtractVector($min); +// $index = $this->blockIndex($pos->x, $pos->y, $pos->z); +// if (strlen($this->blocks) <= $index) { +// $this->blocks .= str_repeat(chr(0), $index - strlen($this->blocks) + 1); +// } +// $this->blocks[$index] = chr($block->getId()); +// $this->data[$index] = chr($block->getMeta()); +// } +// } + + /** + * @param int $x + * @param int $y + * @param int $z + * + * @return int + */ + protected function blockIndex(int $x, int $y, int $z): int + { + return ($y * $this->size->getZ() + $z) * $this->size->getX() + $x; + } +} diff --git a/src/xenialdan/libstructure/packet/StructureBlockUpdatePacket.php b/src/xenialdan/libstructure/packet/StructureBlockUpdatePacket.php deleted file mode 100644 index 3680503..0000000 --- a/src/xenialdan/libstructure/packet/StructureBlockUpdatePacket.php +++ /dev/null @@ -1,111 +0,0 @@ -getBlockPosition($this->x, $this->y, $this->z); - $this->structureEditorData = $this->getStructureEditorData(); - $this->unknownBool = $this->getBool(); - $this->unknownBool2 = $this->getBool(); - } - - protected function encodePayload() - { - $this->putBlockPosition($this->x, $this->y, $this->z); - $this->putStructureEditorData($this->structureEditorData); - $this->putBool($this->unknownBool); - $this->putBool($this->unknownBool2); - } - - public function handle(NetworkSession $session): bool - { - return $session->handleStructureBlockUpdate($this); - } - - protected function getStructureEditorData(): StructureEditorData - { - $result = new StructureEditorData(); - - $result->structureName = $this->getString(); - $result->string2 = $this->getString();//probably load/fileName in load mode - $result->includePlayers = $this->getBool(); - $result->showBoundingBox = $this->getBool(); - $result->mode = $this->getVarInt(); - $result->structureSettings = $this->getStructureSettings1(); - //TODO 1.13 will probably add showInvisibleBlocks (bool) - - return $result; - } - - protected function getStructureSettings1(): StructureSettings - { - $result = new StructureSettings(); - - $result->paletteName = $this->getString(); - $result->ignoreEntities = $this->getBool(); - $result->ignoreBlocks = $this->getBool(); - $this->getBlockPosition($result->sizeX, $result->sizeY, $result->sizeZ);//structure size - $this->getBlockPosition($result->offsetX, $result->offsetY, $result->offsetZ);//structure offset - $result->lastTouchedByPlayerId = $this->getLong(); - $result->rotation = $this->getByte(); - $result->mirror = $this->getByte(); - if (version_compare(ltrim(Server::getInstance()->getVersion(), 'v'), "1.13") === 0) { - $result->integrityValue = $this->getFloat(); - //$result->integritySeed = $this->getUnsignedVarInt();//actually UnsignedInt - $result->integritySeed = intval($this->getRemaining());//hack - } - - return $result; - } - - protected function putStructureEditorData(StructureEditorData $data): void - { - $this->putString($data->structureName); - $this->putString($data->string2);//probably load/fileName in load mode - $this->putBool($data->includePlayers); - $this->putBool($data->showBoundingBox); - $this->putVarInt($data->mode); - $this->putStructureSettings1($data->structureSettings); - //TODO 1.13 will probably add showInvisibleBlocks (bool) - } - - protected function putStructureSettings1(StructureSettings $settings): void - { - $this->putString($settings->paletteName); - $this->putBool($settings->ignoreEntities); - $this->putBool($settings->ignoreBlocks); - $this->putBlockPosition($settings->sizeX, $settings->sizeY, $settings->sizeZ);//structure size - $this->putBlockPosition($settings->offsetX, $settings->offsetY, $settings->offsetZ);//structure offset - $this->putLong($settings->lastTouchedByPlayerId); - $this->putByte($settings->rotation); - $this->putByte($settings->mirror); - if (version_compare(ltrim(Server::getInstance()->getVersion(), 'v'), "1.13") === 0) { - $this->putFloat($settings->integrityValue); - $this->putInt($settings->integritySeed);//hack//actually UnsignedInt - } - } -} \ No newline at end of file diff --git a/src/xenialdan/libstructure/packet/StructureEditorData.php b/src/xenialdan/libstructure/packet/StructureEditorData.php deleted file mode 100644 index 884d4ba..0000000 --- a/src/xenialdan/libstructure/packet/StructureEditorData.php +++ /dev/null @@ -1,21 +0,0 @@ -fromV3 = $this->toV3 = $this->position->asVector3(); + $this->inventory = new StructureBlockInventory($this->position); + } + + public function readSaveData(CompoundTag $nbt): void + { + //todo read structure block data + $this->loadName($nbt); + } + + 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 + { + $this->addStructureBlockData($nbt); + $this->addNameSpawnData($nbt); + } + + /** + * @return StructureBlockInventory + */ + public function getInventory(): StructureBlockInventory + { + return $this->inventory; + } + + public function getDefaultName(): string + { + return "Structure Block"; + } + + /** + * @return Vector3 + */ + public function getFromV3(): Vector3 + { + return $this->fromV3; + } + + /** + * @param Vector3 $fromV3 + * @return StructureBlockTile + */ + public function setFromV3(Vector3 $fromV3): self + { + $this->fromV3 = $fromV3; + return $this; + } + + /** + * @return Vector3 + */ + public function getToV3(): Vector3 + { + return $this->toV3; + } + + /** + * @param Vector3 $toV3 + * @return StructureBlockTile + */ + public function setToV3(Vector3 $toV3): self + { + $this->toV3 = $toV3; + return $this; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title ?? $this->getName(); + } + + /** + * @param string $title + * @return StructureBlockTile + */ + public function setTitle(string $title): self + { + $this->title = $title; + return $this; + } + + /** + * @param bool $hideStructureBlock + * @return StructureBlockTile + */ + public function setHideStructureBlock(bool $hideStructureBlock): self + { + $this->hideStructureBlock = $hideStructureBlock; + return $this; + } + + /** + * @param bool $showPlayers + * @return StructureBlockTile + */ + public function setShowPlayers(bool $showPlayers): self + { + $this->showPlayers = $showPlayers; + return $this; + } + + /** + * @param bool $showEntities + * @return StructureBlockTile + */ + public function setShowEntities(bool $showEntities): self + { + $this->showEntities = $showEntities; + return $this; + } + + /** + * @param bool $showBlocks + * @return StructureBlockTile + */ + public function setShowBlocks(bool $showBlocks): self + { + $this->showBlocks = $showBlocks; + return $this; + } + + /** + * @param bool $showBoundingBox + * @return StructureBlockTile + */ + public function setShowBoundingBox(bool $showBoundingBox): self + { + $this->showBoundingBox = $showBoundingBox; + return $this; + } + + public function calculateOffset(Vector3 $holderV3): Vector3 + { + return $holderV3->subtractVector(Vector3::minComponents($this->fromV3, $this->toV3))->multiply(-1)->floor(); + } + + public function calculateSize(): Vector3 + { + return $this->fromV3->subtractVector($this->toV3)->abs()->add(1, 1, 1); + } + + protected function addStructureBlockData(CompoundTag $nbt): void + { + $pos = $this->getPosition(); + $offset = $this->calculateOffset($pos->asVector3()); + $size = $this->calculateSize(); + #var_dump("offset", $offset, "size", $size, "blockV3", $pos->asVector3()); + $nbt->setInt("data", $this->mode); + $nbt->setString("dataField", ""); + $nbt->setByte("ignoreEntities", $this->showEntities ? 0 : 1); + $nbt->setByte("includePlayers", $this->showPlayers ? 1 : 0); + $nbt->setFloat("integrity", 100.0); + $nbt->setByte("isMovable", 1); + $nbt->setByte("isPowered", 0); + $nbt->setByte("mirror", 0); + $nbt->setByte("removeBlocks", $this->showBlocks ? 0 : 1); + $nbt->setByte("rotation", 0); + $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); + } + + /** + * @return bool + */ + public function isShowPlayers(): bool + { + return $this->showPlayers; + } + + /** + * @return bool + */ + public function isShowEntities(): bool + { + return $this->showEntities; + } + + /** + * @return bool + */ + public function isShowBlocks(): bool + { + return $this->showBlocks; + } + + /** + * @return bool + */ + public function isShowBoundingBox(): bool + { + return $this->showBoundingBox; + } + + /** + * @return int + */ + public function getMode(): int + { + return $this->mode; + } + + 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->structureRedstoneSaveMove = 0; + return $data; + } + + 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->lastTouchedByPlayerID = -1; + $settings->rotation = 0; + $settings->mirror = false; + $settings->integrityValue = 1.0; + $settings->integritySeed = 0; + $settings->pivot = $origin; + return $settings; + } +} \ No newline at end of file diff --git a/src/xenialdan/libstructure/window/StructureBlockInventory.php b/src/xenialdan/libstructure/window/StructureBlockInventory.php index 615eeba..5a4ea11 100644 --- a/src/xenialdan/libstructure/window/StructureBlockInventory.php +++ b/src/xenialdan/libstructure/window/StructureBlockInventory.php @@ -4,43 +4,17 @@ namespace xenialdan\libstructure\window; -use pocketmine\inventory\CustomInventory; -use pocketmine\level\Position; -use pocketmine\network\mcpe\protocol\types\WindowTypes; -use pocketmine\Player; - -class StructureBlockInventory extends CustomInventory { - - /** @var Position */ - protected $holder; - - public function __construct(Position $pos){ - parent::__construct($pos->asPosition()); - } - - public function getNetworkType() : int{ - return WindowTypes::STRUCTURE_EDITOR; - } - - public function getName() : string{ - return "Structure Block"; - } - - public function getDefaultSize() : int{ - return 0; +use pocketmine\block\inventory\BlockInventory; +use pocketmine\block\inventory\BlockInventoryTrait; +use pocketmine\inventory\SimpleInventory; +use pocketmine\world\Position; + +class StructureBlockInventory extends SimpleInventory implements BlockInventory +{ + use BlockInventoryTrait; + public function __construct(Position $holder) + { + $this->holder = $holder; + parent::__construct(0); } - - /** - * This override is here for documentation and code completion purposes only. - * @return Position - */ - public function getHolder(){ - return $this->holder; - } - - /** - * @param Player|Player[] $target - */ - public function sendContents($target) : void{ - } -} +} \ No newline at end of file diff --git a/virion.yml b/virion.yml index a0d3c23..4b19379 100644 --- a/virion.yml +++ b/virion.yml @@ -1,6 +1,6 @@ name: libstructure -version: 0.0.0 +version: 0.1.5 antigen: xenialdan\libstructure -api: [3.9] -php: [7.2] +api: [4.0.0] +php: [ 8.0 ] author: XenialDan