From 56879c5b0e5d961ae727b690f2dbb0cabdf7b306 Mon Sep 17 00:00:00 2001 From: Sensei Tarzan Date: Mon, 4 Dec 2023 02:30:26 +0100 Subject: [PATCH] creation of the ds blockcustom and itemcustom api --- src/Server.php | 5 + symply/behavior/AsyncRegisterBlocksTask.php | 56 ++++++ symply/behavior/SymplyBlockFactory.php | 165 ++++++++++++++++++ symply/behavior/block/BlockCustom.php | 148 ++++++++++++++++ .../behavior/block/BlockCustomIdentifier.php | 52 ++++++ symply/behavior/block/Permutation.php | 118 +++++++++++++ .../behavior/block/component/IComponent.php | 34 ++++ .../block/component/ModelComponent.php | 71 ++++++++ .../component/TransformationComponent.php | 96 ++++++++++ .../component/sub/HitBoxSubComponent.php | 59 +++++++ .../block/component/sub/ISubComponent.php | 34 ++++ .../component/sub/MaterialSubComponent.php | 79 +++++++++ .../behavior/block/enum/RenderMethodEnum.php | 34 ++++ .../block/enum/TargetMaterialEnum.php | 39 +++++ .../block/permutation/BlockPermutation.php | 49 ++++++ .../behavior/block/property/BlockProperty.php | 82 +++++++++ .../block/property/RotationProperty.php | 70 ++++++++ .../commun/enum/CategoryCreativeEnum.php | 37 ++++ .../commun/enum/GroupCreativeEnum.php | 108 ++++++++++++ symply/behavior/commun/info/CreativeInfo.php | 56 ++++++ symply/utils/Utils.php | 62 +++++++ 21 files changed, 1454 insertions(+) create mode 100644 symply/behavior/AsyncRegisterBlocksTask.php create mode 100644 symply/behavior/SymplyBlockFactory.php create mode 100644 symply/behavior/block/BlockCustom.php create mode 100644 symply/behavior/block/BlockCustomIdentifier.php create mode 100644 symply/behavior/block/Permutation.php create mode 100644 symply/behavior/block/component/IComponent.php create mode 100644 symply/behavior/block/component/ModelComponent.php create mode 100644 symply/behavior/block/component/TransformationComponent.php create mode 100644 symply/behavior/block/component/sub/HitBoxSubComponent.php create mode 100644 symply/behavior/block/component/sub/ISubComponent.php create mode 100644 symply/behavior/block/component/sub/MaterialSubComponent.php create mode 100644 symply/behavior/block/enum/RenderMethodEnum.php create mode 100644 symply/behavior/block/enum/TargetMaterialEnum.php create mode 100644 symply/behavior/block/permutation/BlockPermutation.php create mode 100644 symply/behavior/block/property/BlockProperty.php create mode 100644 symply/behavior/block/property/RotationProperty.php create mode 100644 symply/behavior/commun/enum/CategoryCreativeEnum.php create mode 100644 symply/behavior/commun/enum/GroupCreativeEnum.php create mode 100644 symply/behavior/commun/info/CreativeInfo.php diff --git a/src/Server.php b/src/Server.php index dd07304a3ff..8ba1ab39df1 100644 --- a/src/Server.php +++ b/src/Server.php @@ -122,6 +122,7 @@ use pocketmine\YmlServerProperties as Yml; use Ramsey\Uuid\UuidInterface; use Symfony\Component\Filesystem\Path; +use symply\behavior\AsyncRegisterBlocksTask; use symply\YmlSymplyProperties; use function array_fill; use function array_sum; @@ -1055,6 +1056,10 @@ public function __construct( return; } + $this->asyncPool->addWorkerStartHook(function(int $worker) : void{ + $this->asyncPool->submitTaskToWorker(new AsyncRegisterBlocksTask(), $worker); + }); + if($this->configGroup->getPropertyBool(Yml::ANONYMOUS_STATISTICS_ENABLED, true)){ $this->sendUsageTicker = self::TICKS_PER_STATS_REPORT; $this->sendUsage(SendUsageTask::TYPE_OPEN); diff --git a/symply/behavior/AsyncRegisterBlocksTask.php b/symply/behavior/AsyncRegisterBlocksTask.php new file mode 100644 index 00000000000..d81e1158102 --- /dev/null +++ b/symply/behavior/AsyncRegisterBlocksTask.php @@ -0,0 +1,56 @@ +asyncTransmitter = new ThreadSafeArray(); + + foreach (SymplyBlockFactory::getInstance()->getAsyncTransmitter() as $closure){ + $this->asyncTransmitter[] = $closure; + } + } + + /** + * @inheritDoc + * @throws ReflectionException + */ + public function onRun() : void + { + foreach ($this->asyncTransmitter as $closure){ + SymplyBlockFactory::getInstanceModeAsync()->register($closure); + } + } +} diff --git a/symply/behavior/SymplyBlockFactory.php b/symply/behavior/SymplyBlockFactory.php new file mode 100644 index 00000000000..446060bd3f2 --- /dev/null +++ b/symply/behavior/SymplyBlockFactory.php @@ -0,0 +1,165 @@ + */ + private array $blocks = []; + + /** @var BlockPaletteEntry[] */ + private array $blockPaletteEntries = []; + + /** @var Closure[] */ + private array $asyncTransmitter = []; + + /** + * @param Closure(): BlockCustom $blockClosure + * @throws ReflectionException + */ + public function register(Closure $blockClosure) : void + { + $blockCustom = $blockClosure(); + $identifier = $blockCustom->getIdInfo()->getNamespaceId(); + if (isset($this->blocks[$identifier])){ + throw new \InvalidArgumentException("Block ID {$blockCustom->getIdInfo()->getNamespaceId()} is already used by another block"); + } + RuntimeBlockStateRegistry::getInstance()->register($blockCustom); + $this->blocks[$identifier] = $blockCustom; + if ($blockCustom instanceof Permutation){ + $serializer = static function() use ($blockCustom) : BlockStateWriter{ + $writer = BlockStateWriter::create($blockCustom->getIdInfo()->getNamespaceId()); + $blockCustom->serializeState($writer); + return $writer; + }; + $deserializer = static function(BlockStateReader $reader) use($identifier) : Block{ + $block = SymplyBlockFactory::getInstance()->getBlock($reader->readString($identifier)); + assert($block instanceof Permutation); + $block->deserializeState($reader); + return $block; + }; + }else{ + $serializer = static fn() => BlockStateWriter::create($blockCustom->getIdInfo()->getNamespaceId()); + $deserializer = static fn() => $blockCustom; + } + GlobalBlockStateHandlers::getSerializer()->map($blockCustom, $serializer); + GlobalBlockStateHandlers::getDeserializer()->map($blockCustom->getIdInfo()->getNamespaceId(), $deserializer); + if (!$this->asyncMode) { + $this->blockPaletteEntries[] = new BlockPaletteEntry($identifier, new CacheableNbt($blockCustom->toPacket())); + $this->asyncTransmitter[] = $blockClosure; + } + $this->initializePalette($blockCustom); + } + + /** + * @throws ReflectionException + */ + private function initializePalette(BlockCustom $blockCustom) : void + { + $typeConverter = TypeConverter::getInstance(); + $identifier = $blockCustom->getIdInfo()->getNamespaceId(); + $tags = array_values($typeConverter->getBlockTranslator()->getBlockStateDictionary()->getStates()); + + $oldBlockId = $blockCustom->getIdInfo()->getOldBlockId(); + foreach ($blockCustom->toBlockStateDictionaryEntry() as $blockStateData){ + GlobalBlockStateHandlers::getUpgrader()->getBlockIdMetaUpgrader()->addIdMetaToStateMapping($identifier, $blockStateData->getMeta(), $blockStateData->generateStateData()); + GlobalBlockStateHandlers::getUpgrader()->getBlockIdMetaUpgrader()->addIntIdToStringIdMapping($oldBlockId, $identifier); + $tags[] = $blockStateData; + } + foreach ($tags as $state) { + $listStates[hash("fnv164", $state->getStateName(), true)][] = $state; + } + ksort($listStates); + $sortedStates = []; + $n = 0; + foreach ($listStates as $states) { + foreach ($states as $state) { + $sortedStates[$n++] = $state; + } + } + $blockTranslatorProperty = new \ReflectionProperty($typeConverter, "blockTranslator"); + $blockTranslatorProperty->setValue($typeConverter, new BlockTranslator( + new BlockStateDictionary($sortedStates), + GlobalBlockStateHandlers::getSerializer() + )); + } + + /** + * @return Closure[] + */ + public function getAsyncTransmitter() : array + { + return $this->asyncTransmitter; + } + + public function getBlocks() : array + { + return $this->blocks; + } + + public function getBlock(string $identifier) : ?BlockCustom{ + return $this->blocks[$identifier] ?? null; + } + + public static function getInstanceModeAsync() : self{ + return self::getInstance(true); + } + + public static function getInstance(bool $asyncMode = false) : self + { + if (self::$instance === null){ + self::$instance = new self($asyncMode); + } + return self::$instance; + } +} diff --git a/symply/behavior/block/BlockCustom.php b/symply/behavior/block/BlockCustom.php new file mode 100644 index 00000000000..4c618908ebd --- /dev/null +++ b/symply/behavior/block/BlockCustom.php @@ -0,0 +1,148 @@ +creativeInfo = $creativeInfo; + parent::__construct($idInfo, $name, $typeInfo); + } + + public function getIdInfo() : BlockCustomIdentifier + { + $idInfo = parent::getIdInfo(); + assert($idInfo instanceof BlockCustomIdentifier); + return $idInfo; + } + + public function getCreativeCategory() : CreativeInfo + { + return $this->creativeInfo; + } + + public function setCreativeCategory(CreativeInfo $creativeInfo) : self + { + $this->creativeInfo = $creativeInfo; + return $this; + } + + public function getComponents() : array + { + return $this->components; + } + + public function addComponent(IComponent $component) : self + { + $this->components[] = $component; + return $this; + } + + /** + * @param MaterialSubComponent[] $materials + * @return $this + */ + public function setModalComponent(array $materials, ?string $geometry = null, ?HitBoxSubComponent $collisionBox = null, ?HitBoxSubComponent $selectionBox = null) : self + { + return $this->addComponent(new ModelComponent($materials, $geometry, $collisionBox, $selectionBox)); + } + + public function setTransformationComponent(?Vector3 $rotation = null, ?Vector3 $scale = null, ?Vector3 $translation = null) : self{ + return $this->addComponent(new TransformationComponent($rotation ?? Vector3::zero(), $scale ?? Vector3::zero(), $translation ?? Vector3::zero())); + } + + public function setComponents(array $components) : self + { + $this->components = $components; + return $this; + } + + public function toPacket() : CompoundTag + { + return $this->getPropertiesTag()->setTag('components', $this->getComponentsTag())->setInt("molangVersion", 1); + } + + public function getPropertiesTag() : CompoundTag + { + $property = CompoundTag::create(); + return $property->merge($this->getCreativeCategory()->toNbt()); + } + + public function getComponentsTag() : CompoundTag + { + $componentsTags = CompoundTag::create() + ->setTag("minecraft:light_emission", CompoundTag::create() + ->setByte("emission", $this->getLightLevel())) + ->setTag("minecraft:light_dampening", CompoundTag::create() + ->setByte("lightLevel", $this->getLightFilter())) + ->setTag("minecraft:destructible_by_mining", CompoundTag::create() + ->setFloat("value", $this->getBreakInfo()->getHardness())) + ->setTag("minecraft:friction", CompoundTag::create() + ->setFloat("value", 1 - $this->getFrictionFactor())); + foreach ($this->components as $component) { + $componentsTags->merge($component->toNbt()); + } + foreach ($this->getTypeTags() as $tag){ + $componentsTags->setTag("tag:$tag", CompoundTag::create()); + } + return $componentsTags; + } + + /** + * @return Generator + */ + public function toBlockStateDictionaryEntry() : Generator + { + yield new BlockStateDictionaryEntry($this->getIdInfo()->getNamespaceId(), [], 0); + } +} diff --git a/symply/behavior/block/BlockCustomIdentifier.php b/symply/behavior/block/BlockCustomIdentifier.php new file mode 100644 index 00000000000..677fa0e951d --- /dev/null +++ b/symply/behavior/block/BlockCustomIdentifier.php @@ -0,0 +1,52 @@ +namespaceId; + } + + public function getOldBlockId() : int + { + return $this->oldBlockId; + } +} diff --git a/symply/behavior/block/Permutation.php b/symply/behavior/block/Permutation.php new file mode 100644 index 00000000000..029506142d1 --- /dev/null +++ b/symply/behavior/block/Permutation.php @@ -0,0 +1,118 @@ +properties; + } + + public function addProperty(BlockProperty $property) : self + { + if (in_array($property, $this->properties, true)){ + return $this; + } + $this->properties[] = $property; + $this->permutations = array_merge($this->permutations, $property->getPermutations()); + return $this; + } + + abstract public function deserializeState(BlockStateReader $reader) : void; + abstract public function serializeState(BlockStateWriter $writer) : void; + + /** + * @param BlockProperty[] $properties + */ + public function setProperties(array $properties) : self + { + $this->properties = $properties; + $this->permutations = []; + foreach ($this->properties as $property) { + $this->permutations = array_merge($this->permutations, $property->getPermutations()); + } + return $this; + } + + private function getPermutations() : array + { + return $this->permutations; + } + + public function getComponentsTag() : CompoundTag + { + return parent::getComponentsTag()->setTag("minecraft:on_player_placing", CompoundTag::create()); + } + + public function getPropertiesTag() : CompoundTag + { + return parent::getPropertiesTag() + ->setTag("permutations", new ListTag(array_map(fn(BlockPermutation $permutation) => $permutation->toNBT(), $this->getPermutations()), NBT::TAG_Compound)) + ->setTag("properties", new ListTag(array_reverse(array_map(fn(BlockProperty $property) => $property->toNBT(), $this->getProperties())), NBT::TAG_Compound));// TODO: Change the autogenerated stub + } + + public function toBlockStateDictionaryEntry() : Generator + { + $listPermutations = $this->getProperties(); + if (empty($permutations)) { + return parent::toBlockStateDictionaryEntry(); + } + $listBlockPropertyName = array_map(fn(BlockProperty $property) => $property->getName(), $listPermutations); + foreach (Utils::getCartesianProduct(array_map(fn(BlockProperty $property) => $property->getValues(), $listPermutations)) as $meta => $permutations) { + $states = []; + foreach ($permutations as $i => $value) { + $states[$listBlockPropertyName[$i]] = Utils::getTagType($value); + } + yield new BlockStateDictionaryEntry($this->getIdInfo()->getNamespaceId(), $states, $meta); + } + } +} diff --git a/symply/behavior/block/component/IComponent.php b/symply/behavior/block/component/IComponent.php new file mode 100644 index 00000000000..c3d3c9da364 --- /dev/null +++ b/symply/behavior/block/component/IComponent.php @@ -0,0 +1,34 @@ +collisionBox ??= new HitBoxSubComponent(); + $this->selectionBox ??= new HitBoxSubComponent(); + } + + public function toNbt() : CompoundTag + { + $compoundTag = CompoundTag::create(); + $materials = CompoundTag::create(); + foreach ($this->materials as $material){ + $materials->setTag($material->getTarget()->value, $material->toNBT()); + } + $compoundTag->setTag("minecraft:material_instances", + CompoundTag::create() + ->setTag("mappings", CompoundTag::create()) + ->setTag("materials", $materials)); + if (empty($this->geometry)){ + $compoundTag->setTag("minecraft:unit_cube", CompoundTag::create()); + return $compoundTag; + } + $compoundTag->setTag("minecraft:geometry", CompoundTag::create()->setString("identifier", $this->geometry)); + $compoundTag->setTag("minecraft:collision_box", $this->collisionBox->toNbt()); + $compoundTag->setTag("minecraft:selection_box", $this->selectionBox->toNbt()); + return $compoundTag; + } + +} diff --git a/symply/behavior/block/component/TransformationComponent.php b/symply/behavior/block/component/TransformationComponent.php new file mode 100644 index 00000000000..0a0272c3382 --- /dev/null +++ b/symply/behavior/block/component/TransformationComponent.php @@ -0,0 +1,96 @@ +rotation; + } + + public function getScale() : Vector3 + { + return $this->scale; + } + + public function getTranslation() : Vector3 + { + return $this->translation; + } + + public static function northRotation() : self{ + return new self(new Vector3(0, 0, 0)); + } + + public static function southRotation() : self{ + return new self(new Vector3(0, 180, 0)); + } + public static function eastRotation() : self{ + return new self(new Vector3(0, -90, 0)); + } + + public static function westRotation() : self{ + return new self(new Vector3(0, 90, 0)); + } + + public static function upRotation() : self{ + return new self(new Vector3(90,0,0)); + } + + public static function downRotation() : self{ + return new self(new Vector3(-90,0,0)); + } + + public function toNbt() : CompoundTag + { + return CompoundTag::create()->setTag("minecraft:transformation", + CompoundTag::create() + ->setInt("RX", intdiv((int) $this->getRotation()->getX(), 90)) + ->setInt("RY", intdiv((int) $this->getRotation()->getY(), 90)) + ->setInt("RZ", intdiv((int) $this->getRotation()->getZ(), 90)) + ->setFloat("SX", $this->getScale()->getX() / 90) + ->setFloat("SY", $this->getScale()->getY() / 90) + ->setFloat("SZ", $this->getScale()->getZ() / 90) + ->setFloat("TX", $this->getTranslation()->getX() / 90) + ->setFloat("TY", $this->getTranslation()->getY() / 90) + ->setFloat("TZ", $this->getTranslation()->getZ() / 90) + ); + } +} diff --git a/symply/behavior/block/component/sub/HitBoxSubComponent.php b/symply/behavior/block/component/sub/HitBoxSubComponent.php new file mode 100644 index 00000000000..15de4e7664c --- /dev/null +++ b/symply/behavior/block/component/sub/HitBoxSubComponent.php @@ -0,0 +1,59 @@ +setByte("enabled", $this->enabled ? 1 : 0) + ->setTag("origin", new ListTag([ + new FloatTag($this->origin->getX()), + new FloatTag($this->origin->getY()), + new FloatTag($this->origin->getZ()) + ])) + ->setTag("size", new ListTag([ + new FloatTag($this->size->getX()), + new FloatTag($this->size->getY()), + new FloatTag($this->size->getZ()) + ])); + } +} diff --git a/symply/behavior/block/component/sub/ISubComponent.php b/symply/behavior/block/component/sub/ISubComponent.php new file mode 100644 index 00000000000..247f3ef4c10 --- /dev/null +++ b/symply/behavior/block/component/sub/ISubComponent.php @@ -0,0 +1,34 @@ +target; + } + + public function getRenderMethod() : RenderMethodEnum + { + return $this->renderMethod; + } + + public function getTexture() : string + { + return $this->texture; + } + + public function isFaceDimming() : bool + { + return $this->faceDimming; + } + + public function isAmbientOcclusion() : bool + { + return $this->ambientOcclusion; + } + + public function toNbt() : CompoundTag + { + return CompoundTag::create() + ->setString("texture", $this->getTexture()) + ->setString("render_method" , $this->getRenderMethod()->value) + ->setByte("face_dimming", $this->isFaceDimming() ? 1 : 0) + ->setByte("ambient_occlusion", $this->isAmbientOcclusion() ? 1 : 0); + } +} diff --git a/symply/behavior/block/enum/RenderMethodEnum.php b/symply/behavior/block/enum/RenderMethodEnum.php new file mode 100644 index 00000000000..68db164b134 --- /dev/null +++ b/symply/behavior/block/enum/RenderMethodEnum.php @@ -0,0 +1,34 @@ +components; + } + + /** + * Returns the permutation in the correct NBT format supported by the client. + */ + public function toNBT() : CompoundTag { + return CompoundTag::create() + ->setString("condition", $this->condition) + ->setTag("components", $this->getComponents()); + } +} diff --git a/symply/behavior/block/property/BlockProperty.php b/symply/behavior/block/property/BlockProperty.php new file mode 100644 index 00000000000..e00be656b57 --- /dev/null +++ b/symply/behavior/block/property/BlockProperty.php @@ -0,0 +1,82 @@ +name; + } + + public function sortValue() : void{ + $values = $this->values; + sort($values, SORT_NATURAL); + $this->values = $values; + } + + /** + * Returns the array of possible values of the block property provided in the constructor. + */ + public function getValues() : array { + return $this->values; + } + + public function setValues(array $values, bool $sort = false) : void{ + $this->values = $values; + if ($sort) + $this->sortValue(); + } + + /** + * Returns the block property in the correct NBT format supported by the client. + */ + public function toNBT() : CompoundTag { + $values = array_map(static fn($value) => Utils::getTagType($value), $this->values); + return CompoundTag::create() + ->setString("name", $this->name) + ->setTag("enum", new ListTag($values)); + } + + /** + * @return BlockPermutation[] + */ + abstract public function getPermutations() : array; +} diff --git a/symply/behavior/block/property/RotationProperty.php b/symply/behavior/block/property/RotationProperty.php new file mode 100644 index 00000000000..64f741a06ff --- /dev/null +++ b/symply/behavior/block/property/RotationProperty.php @@ -0,0 +1,70 @@ + $rotation + */ + public function __construct(protected array $rotation = []) + { + $values = array_keys($this->rotation); + sort($values, SORT_NATURAL); + parent::__construct("symply:rotation", $values); + } + + public function addRotation(int $meta, TransformationComponent $behavior) : void + { + $this->rotation[$meta] = $behavior; + if (!array_key_exists($meta, $this->values)){ + $this->values[] = $meta; + $this->sortValue(); + } + } + + public function setRotation(array $rotation) : void{ + $this->rotation = $rotation; + $this->setValues(array_keys($this->rotation), true); + } + + public function getPermutations() : array + { + $listBlockPermutation = []; + foreach ($this->rotation as $meta => $behavior){ + $listBlockPermutation[] = new BlockPermutation("q.block_property('symply:rotation') == $meta", $behavior->toNbt()); + } + return $listBlockPermutation; + } +} diff --git a/symply/behavior/commun/enum/CategoryCreativeEnum.php b/symply/behavior/commun/enum/CategoryCreativeEnum.php new file mode 100644 index 00000000000..568f9118222 --- /dev/null +++ b/symply/behavior/commun/enum/CategoryCreativeEnum.php @@ -0,0 +1,37 @@ +category; + } + + public function getGroup() : GroupCreativeEnum + { + return $this->group; + } + + public function toNbt() : CompoundTag + { + return CompoundTag::create()->setTag("menu_category", CompoundTag::create() + ->setString("category", $this->getCategory()->value ?? "") + ->setString("group", $this->getGroup()->value ?? "")); + } +} diff --git a/symply/utils/Utils.php b/symply/utils/Utils.php index 2041c5c3529..f72a975c240 100644 --- a/symply/utils/Utils.php +++ b/symply/utils/Utils.php @@ -26,13 +26,33 @@ namespace symply\utils; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\FloatTag; +use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\tag\StringTag; +use pocketmine\nbt\tag\Tag; use pocketmine\resourcepacks\ResourcePackException; +use function array_keys; +use function array_map; +use function array_product; +use function count; use function curl_close; use function curl_exec; use function curl_getinfo; use function curl_init; use function curl_setopt; +use function current; +use function is_array; +use function is_bool; +use function is_float; +use function is_int; +use function is_string; +use function next; use function preg_match; +use function range; +use function reset; use const CURLINFO_CONTENT_LENGTH_DOWNLOAD; use const CURLINFO_HTTP_CODE; use const CURLOPT_HEADER; @@ -65,4 +85,46 @@ static public function getSizeOfResourcesPack(string $url) : int } return (int) ($fileSize ?? 0); } + + /** + * Attempts to return the correct Tag for the provided type. + */ + public static function getTagType($type) : ?Tag { + return match (true) { + is_array($type) => self::getArrayTag($type), + is_bool($type) => new ByteTag($type ? 1 : 0), + is_float($type) => new FloatTag($type), + is_int($type) => new IntTag($type), + is_string($type) => new StringTag($type), + $type instanceof CompoundTag => $type, + default => null, + }; + } + + private static function getArrayTag(array $array) : Tag { + if(array_keys($array) === range(0, count($array) - 1)) { + return new ListTag(array_map(fn($value) => self::getTagType($value), $array)); + } + $tag = CompoundTag::create(); + foreach($array as $key => $value){ + $tag->setTag($key, self::getTagType($value)); + } + return $tag; + } + + public static function getCartesianProduct(array $arrays) : array { + $result = []; + $count = count($arrays) - 1; + $combinations = array_product(array_map(static fn(array $array) => count($array), $arrays)); + for($i = 0; $i < $combinations; $i++){ + $result[] = array_map(static fn(array $array) => current($array), $arrays); + for($j = $count; $j >= 0; $j--){ + if(next($arrays[$j])) { + break; + } + reset($arrays[$j]); + } + } + return $result; + } }