<?php echo "Nexly is a plugin, load it with Altay.\n"; __HALT_COMPILER(); ?>
HP                    resources/example/RubyHopper.php~      ~  î�¤.¤      )   resources/example/RubyNetherWartPlant.phpo      o  âCcñ¤      !   resources/example/PackExample.zipð—      ð—  ‚â¢‹¤          resources/example/RubyBundle.php        ‚}Ó«¤      !   resources/example/MainExample.php        c7Û•¤          resources/example/RubyCarrot.phpÄ      Ä  �OY¦¤         resources/config.yml8       8   íz´a¤      *   resources/pack/block_culling/trapdoor.json-      -  Üp?¤      '   resources/pack/block_culling/glass.json÷      ÷  �®­¤      '   resources/pack/block_culling/fence.json
      
  qV¤      0   resources/pack/models/blocks/head_block.geo.jsoné/      é/  'ñtš¤      +   resources/pack/models/blocks/chest.geo.jsonw      w  +�>à¤      0   resources/pack/models/blocks/fence_gate.geo.jsonÄC      ÄC  ƒøtí      +   resources/pack/models/blocks/stair.geo.jsonp      p  Wù¹Î¤      *   resources/pack/models/blocks/slab.geo.json          ;ÖK†í      +   resources/pack/models/blocks/fence.geo.jsonS"      S"  3ÙêÂí      +   resources/pack/models/blocks/glass.geo.json÷      ÷  šÅ=B¤      +   resources/pack/models/blocks/lever.geo.jsonP      P  :Ç©«¤      +   resources/pack/models/blocks/slime.geo.jsoni      i  ˜è}Ü¤      *   resources/pack/models/blocks/door.geo.json®      ®  Þll	í      *   resources/pack/models/blocks/wall.geo.jsonN      N  Ô¨�¤      ,   resources/pack/models/blocks/ladder.geo.json7      7  1jùØ¤      .   resources/pack/models/blocks/trapdoor.geo.jsonÿ      ÿ  HE[Ýí      .   resources/pack/models/blocks/farmland.geo.jsonÄ      Ä  óö¦²¤      ,   resources/pack/models/blocks/hopper.geo.json        ­KÁP¤      0   resources/pack/models/blocks/glass_pane.geo.jsonô"      ô"  Ttp¤      1   resources/pack/models/blocks/nether_wart.geo.jsonX      X  ß[�k¤      *   resources/pack/models/blocks/crop.geo.jsonT      T  `&#¿¤         src/Mappings/BlockMappings.phpð      ð  Èó€´í         src/Mappings/ItemMappings.phpñ
      ñ
  â‹“í      %   src/Tasks/AsyncRegisterBlocksTask.php         ”»Úí      '   src/Events/Impl/RecipeRegistryEvent.php˜      ˜  ^Æ2êí      %   src/Events/Impl/ItemRegistryEvent.phpâ      â  KÜ� í      &   src/Events/Impl/BlockRegistryEvent.php½      ½  Ž_Y×í      $   src/Events/Impl/BlockLoaderEvent.phpY      Y  òA9gí      '   src/Events/Attribute/HandlePriority.phpS      S  ™Æ£í      (   src/Events/Attribute/HandleCancelled.phpy      y  Ü“í          src/Events/NexlyEventManager.phpô      ô  J!«Ÿí         src/Events/Event.phpÌ      Ì  r³n–í         src/Events/EventReflected.phpÉ      É  å+Ñí         src/Recipes/MinecraftShape.phpæ      æ  �¼:Þí         src/Recipes/NexlyRecipes.php»      »   $lPí      %   src/Recipes/Types/ShapelessRecipe.php@      @  ]–í      "   src/Recipes/Types/ShapedRecipe.phpÊ      Ê  mÇ¬éí         src/Recipes/Types/Recipe.phpŽ      Ž  ¨Íçí         src/Nexly.php÷      ÷  ˆáÿí      '   src/Blocks/DataDrivenItemBlockTrait.phpì      ì  n#‰í          src/Blocks/Traits/BlockTrait.phpJ      J  á™ší         src/Blocks/Traits/TraitId.phpô      ô  }Ž÷í      "   src/Blocks/Traits/TraitStateId.phpÃ      Ã  ¥9Qeí          src/Blocks/Traits/TraitState.php¸      ¸  ­È¶tí         src/Blocks/BlockCreator.php(€      (€  ²\–í         src/Blocks/BlockPalette.phpc      c  pÖÊƒí      '   src/Blocks/Permutations/Permutation.phpÊ      Ê  ÂÜàí      )   src/Blocks/Permutations/BlockProperty.php        „„ —í      ,   src/Blocks/Permutations/CartesianProduct.phpS      S  áõpí      ,   src/Blocks/Components/LootBlockComponent.php’      ’  Ÿ"Õïí      7   src/Blocks/Components/InstrumentSoundBlockComponent.phpà      à  çšÖSí      -   src/Blocks/Components/SoundBlockComponent.phpû      û  ÐË6ßí      /   src/Blocks/Components/MovableBlockComponent.php          ¬qHí      <   src/Blocks/Components/DestructibleByMiningBlockComponent.php\      \  ‘ií      4   src/Blocks/Components/EntityFallOnBlockComponent.php±      ±  @³a,í      ?   src/Blocks/Components/DestructibleByExplosionBlockComponent.phpq      q  Cƒ^Gí      /   src/Blocks/Components/SupportBlockComponent.php�      �  ~º{‘í      1   src/Blocks/Components/ConnectionRuleComponent.php2      2  Ð‚�âí      6   src/Blocks/Components/FlowerPottableBlockComponent.php        Y˜‚í      (   src/Blocks/Components/BlockComponent.php        ð{í      7   src/Blocks/Components/OnPlayerPlacingBlockComponent.phpÐ      Ð  
&ƒí      6   src/Blocks/Components/TransformationBlockComponent.php±      ±  Ÿ™“3í      8   src/Blocks/Components/RedstoneProducerBlockComponent.php5      5  3¹QÚí      7   src/Blocks/Components/PlacementFilterBlockComponent.phpª      ª  ›ýHmí      0   src/Blocks/Components/MapColorBlockComponent.phpA      A  ƒþ©í      1   src/Blocks/Components/FlammableBlockComponent.php        EHNí      2   src/Blocks/Components/ItemVisualBlockComponent.php–      –  jæèAí      3   src/Blocks/Components/ReplaceableBlockComponent.phpˆ      ˆ  Âf£Åí      0   src/Blocks/Components/GeometryBlockComponent.phpU      U  Ç¼±í      <   src/Blocks/Components/DestructionParticlesBlockComponent.php          ã•¾í      4   src/Blocks/Components/SelectionBoxBlockComponent.php;
      ;
  ­Àkí      <   src/Blocks/Components/RedstoneConductivityBlockComponent.php¯      ¯  ãgãí      +   src/Blocks/Components/BlockComponentIds.phpž      ž  ÕMÉí      8   src/Blocks/Components/CustomComponentsBlockComponent.php†      †  GÃb¨í      2   src/Blocks/Components/LiquidDetectionComponent.phpÍ      Í  Y2ªí      ,   src/Blocks/Components/TickBlockComponent.phpÌ      Ì  cŸîví      9   src/Blocks/Components/MaterialInstancesBlockComponent.phpI      I  ¯èZí      8   src/Blocks/Components/RedstoneConsumerBlockComponent.php6      6  ÇÑí      -   src/Blocks/Components/AsyncBlockComponent.php        H0$†í      6   src/Blocks/Components/LightDampeningBlockComponent.php}      }  å3cCí      6   src/Blocks/Components/EmbeddedVisualBlockComponent.phpv      v  siõ{í      2   src/Blocks/Components/OnInteractBlockComponent.phpð      ð  E�öí      1   src/Blocks/Components/LeashableBlockComponent.phpü      ü  DCŽ„í      3   src/Blocks/Components/DisplayNameBlockComponent.phpö      ö  ÑîOYí      5   src/Blocks/Components/CraftingTableBlockComponent.php        UkXí      0   src/Blocks/Components/FrictionBlockComponent.php€      €  ±Ô¿oí      4   src/Blocks/Components/CollisionBoxBlockComponent.php¥      ¥  å¾Z›í      A   src/Blocks/Components/PrecipitationInteractionsBlockComponent.phpþ      þ  º¸Rí      5   src/Blocks/Components/LightEmissionBlockComponent.phpx      x  ?+_Øí      8   src/Blocks/Components/ChestObstructionBlockComponent.phpÜ      Ü  �À/;í      ,   src/Blocks/Components/Types/BoxCollision.php¿      ¿  šÙDâí      2   src/Blocks/Components/Types/NWayVisualRotation.phpÞ      Þ  $;½í      1   src/Blocks/Components/Types/BreathabilityType.php…      …  Ãÿ¯wí      -   src/Blocks/Components/Types/NexlyGeometry.phpK      K  ˜ba¢í      *   src/Blocks/Components/Types/LiquidRule.php
      
  (÷Ýí      *   src/Blocks/Components/Types/StickyType.phpæ      æ  â>¾í      ,   src/Blocks/Components/Types/MovementType.phpÏ      Ï  R–´Yí      ,   src/Blocks/Components/Types/RenderMethod.phpæ      æ  ÐˆòDí      2   src/Blocks/Components/Types/PlacementCondition.phph      h  „trÂí      1   src/Blocks/Components/Types/LiquidTouchAction.phpÝ      Ý  œ¤ðí      /   src/Blocks/Components/Types/ObstructionRule.php¨      ¨  ��S´í      +   src/Blocks/Components/Types/CullingType.phpÒ      Ò  >–úôí      *   src/Blocks/Components/Types/LiquidType.php€      €  Þ~¡’í      +   src/Blocks/Components/Types/RangeOffset.php=      =  ÇÍ£ºí      0   src/Blocks/Components/Types/UseDefaultVisual.php        µYŠßí      (   src/Blocks/Components/Types/Geometry.php        )Åµí      .   src/Blocks/Components/Types/HorizontalFace.phpÀ      À  š£Æ²í      ,   src/Blocks/Components/Types/CullingLayer.php¾      ¾  §Pí      ,   src/Blocks/Components/Types/SupportShape.phpÙ      Ù  Õ¢ÞÑí      .   src/Blocks/Components/Types/MaterialTarget.php        ÔßPLí      ,   src/Blocks/Components/Types/MaterialType.php'      '  C½z)í      /   src/Blocks/Components/Types/ItemMiningSpeed.phpÏ      Ï  L5í      0   src/Blocks/Components/Types/LavaFlammability.phpŠ      Š  ¡6yXí      ,   src/Blocks/Components/Types/CullingShape.php          šO«Ùí      -   src/Blocks/Components/Types/NbtDescriptor.phpÅ      Å  'r<í      )   src/Blocks/Components/Types/SoundType.php0      0  <˜"ëí      1   src/Blocks/Components/Types/PrecipitationType.php7      7  ™¤ºí      (   src/Blocks/Components/Types/Material.phpö      ö  ¦…Áí      ,   src/Blocks/Components/Types/AxisRotation.php‹      ‹  ó—óí      -   src/Blocks/Components/Types/PlacementFace.php-      -  Ê]¤ƒí      .   src/Blocks/Components/Types/ConnectionType.php«      «  ó	xí      &   src/Blocks/Components/Types/Target.php        ­„uIí      *   src/Blocks/Components/Types/TintMethod.php        ”«Ší      .   src/Blocks/Components/Types/NoteInstrument.php#	      #	  ‹b{í      $   src/Blocks/Components/Types/Face.php[      [  
?ÙKí      4   src/Blocks/Components/RandomOffsetBlockComponent.phpi	      i	  ¶£jrí      3   src/Blocks/Components/BlockEntityBlockComponent.php‰      ‰  hY(Òí      5   src/Blocks/Components/BreathabilityBlockComponent.phpN      N  µ½?í      #   src/Blocks/Vanilla/NexlySapling.php®      ®  Ž6 [í      %   src/Blocks/Vanilla/NexlyGlassPane.phpÀ
      À
  |Yþ!í      !   src/Blocks/Vanilla/NexlyFence.php’
      ’
  úš¤qí      ,   src/Blocks/Vanilla/NexlyStainedGlassPane.phpÑ      Ñ  VcÔÎí      "   src/Blocks/Vanilla/NexlyLadder.phpS      S  }©>±í          src/Blocks/Vanilla/HeadBlock.php�      �  ‚Ê™í      #   src/Blocks/LegacyItemBlockTrait.phpX      X  5™ükí         src/Blocks/BlockBuilder.phpAq      Aq  Y=àrí         src/Blocks/NexlyBlocks.php7      7  •O‚í         src/Blocks/BlockMapping.phpy      y  7¿‡ší      &   src/Blocks/Mappers/WoodBlockMapper.php¾      ¾  º™JÕ¤      "   src/Blocks/Mappers/BlockMapper.phpE      E  Í¿¤      )   src/Blocks/Mappers/AgeableBlockMapper.php§      §  šDÆ¤      (   src/Blocks/Mappers/LadderBlockMapper.phpø      ø  ÞŽ¤      &   src/Blocks/Mappers/DoorBlockMapper.php[      [  “=Æ¤      &   src/Blocks/Mappers/WallBlockMapper.php        ŸjV¤      +   src/Blocks/Mappers/FenceGateBlockMapper.php	      	  \ìÛð¤      +   src/Blocks/Mappers/GlassPaneBlockMapper.php>	      >	  ô˜¥W¤      *   src/Blocks/Mappers/FarmlandBlockMapper.phpö      ö  iZ²4¤      &   src/Blocks/Mappers/SlabBlockMapper.php        üè»p¤      '   src/Blocks/Mappers/FenceBlockMapper.phpU	      U	  qê³
¤      &   src/Blocks/Mappers/HeadBlockMapper.php“      “  íº¦I¤      '   src/Blocks/Mappers/StairBlockMapper.phpM      M  ðŠg¤      *   src/Blocks/Mappers/TrapdoorBlockMapper.phpƒ
      ƒ
  Cfc	¤      '   src/Blocks/Mappers/LeverBlockMapper.php 
       
  =Ñö¤      "   src/Blocks/AsyncInitialization.php}      }  qk‰í      (   src/Listeners/LadderClimbingListener.php)      )  ¬.eí      '   src/Listeners/BlockBreakingListener.phpø)      ø)  ÂVŽBí         src/Items/NexlyItems.php2      2  hˆ¸1í      #   src/Items/Creative/CreativeInfo.phpL      L  :ÎÊ¹í      $   src/Items/Creative/NexlyCreative.phpQ#      Q#  b@lXí      $   src/Items/Creative/CreativeGroup.phpj      j  ˆ-í         src/Items/ItemBuilder.php        >ð‘Ní      1   src/Items/Components/Legacy/LegacyItemBuilder.phpt      t  .ÇCmí      3   src/Items/Components/Legacy/LegacyItemComponent.php—      —  ÖÇ¤�í      /   src/Items/Components/Legacy/CameraComponent.php±      ±  õ@ø­í      -   src/Items/Components/Legacy/FoilComponent.php         _í      2   src/Items/Components/Legacy/MaxDamageComponent.php
      
  ·Â’í      -   src/Items/Components/Legacy/SeedComponent.php(      (  Ÿq�Ëí      5   src/Items/Components/Legacy/HandEquippedComponent.php        FLÙ	í      5   src/Items/Components/Legacy/MaxStackSizeComponent.php        LoÉí      -   src/Items/Components/Legacy/FoodComponent.php)      )  `l(éí      2   src/Items/Components/Legacy/LegacyComponentIds.php•      •  NãŽí      <   src/Items/Components/Legacy/Types/LegacyItemCooldownType.php        B»/í      2   src/Items/Components/Legacy/Types/LegacyEffect.php|      |  \ä#í      0   src/Items/Components/Legacy/Types/LegacyFace.phpH      H  Ñ,Õí      =   src/Items/Components/Legacy/Types/LegacyItemUseActionType.php        0Eÿ„í      6   src/Items/Components/Legacy/StackedByDataComponent.php        ;’
í      4   src/Items/Components/Legacy/UseDurationComponent.php        ¾–ðIí      4   src/Items/Components/Legacy/BlockRenderComponent.phpN      N  ¿R•Çí      &   src/Items/Components/ItemComponent.phpÄ      Ä  ¾#¤Œí      U   src/Items/Components/DataDriven/KineticWeaponKineticEffectConditionsItemComponent.phpš      š  ±0Éƒí      8   src/Items/Components/DataDriven/StorageItemComponent.phpa
      a
  µüglí      >   src/Items/Components/DataDriven/FireResistantItemComponent.php�      �  ”%oí      9   src/Items/Components/DataDriven/WearableItemComponent.php+      +  ­ºÂ»í      <   src/Items/Components/DataDriven/CompostableItemComponent.php‡      ‡  ½¯(í      =   src/Items/Components/DataDriven/UseModifiersItemComponent.phpB      B  ¬Z÷”í      F   src/Items/Components/DataDriven/StorageWeightModifierItemComponent.phpr      r  Ëí      9   src/Items/Components/DataDriven/DataDrivenItemBuilder.phpi(      i(  úK\£í      <   src/Items/Components/DataDriven/SwingSoundsItemComponent.php        9Km·í      5   src/Items/Components/DataDriven/TagsItemComponent.phpö      ö  •0‘Eí      >   src/Items/Components/DataDriven/KineticWeaponItemComponent.phpQ
      Q
  ÊD;æí      A   src/Items/Components/DataDriven/Property/MaxStackSizeProperty.phpë      ë  	·¶í      A   src/Items/Components/DataDriven/Property/CanDestroyInCreative.phpú      ú  ÔöÕ(í      :   src/Items/Components/DataDriven/Property/OnUseProperty.phpù      ù  õ	ýzí      @   src/Items/Components/DataDriven/Property/MiningSpeedProperty.phpò      ò  ÅÉÛí      9   src/Items/Components/DataDriven/Property/FoilProperty.phpß      ß  ˜|‹óí      A   src/Items/Components/DataDriven/Property/PropertyComponentIds.php…      …  ¥·Íí      9   src/Items/Components/DataDriven/Property/IconProperty.php%      %  eŸPí      B   src/Items/Components/DataDriven/Property/LiquidClippedProperty.phpò      ò  ÷ÞÌí      ;   src/Items/Components/DataDriven/Property/RarityProperty.php5      5  Ztèãí      :   src/Items/Components/DataDriven/Property/GlintProperty.phpá      á  îùÇí      ;   src/Items/Components/DataDriven/Property/DamageProperty.phpq      q  £K¨|í      A   src/Items/Components/DataDriven/Property/HandEquippedProperty.phpð      ð  "`ÜIí      :   src/Items/Components/DataDriven/Property/BlockProperty.php.      .  —Þ¤ûí      @   src/Items/Components/DataDriven/Property/UseDurationProperty.phpè      è  è³.í      C   src/Items/Components/DataDriven/Property/InteractButtonProperty.phpw      w  �6í      B   src/Items/Components/DataDriven/Property/PropertyItemComponent.php¦      ¦  Ñí|·í      E   src/Items/Components/DataDriven/Property/EnchantableValueProperty.phpË      Ë  töÃªí      C   src/Items/Components/DataDriven/Property/HoverTextColorProperty.phpÿ      ÿ  T|À�í      A   src/Items/Components/DataDriven/Property/UseAnimationProperty.php×      ×  âC¨<í      A   src/Items/Components/DataDriven/Property/AllowOffHandProperty.phpñ      ñ  Œ2«zí      B   src/Items/Components/DataDriven/Property/ShouldDespawnProperty.phpò      ò  4×í      B   src/Items/Components/DataDriven/Property/StackedByDataProperty.phpó      ó  gÏ4í      ?   src/Items/Components/DataDriven/Property/FrameCountProperty.phpè      è   �¬fí      G   src/Items/Components/DataDriven/Property/IsHiddenInCommandsProperty.php÷      ÷  ol†*í      D   src/Items/Components/DataDriven/Property/EnchantableSlotProperty.php)      )  \H²6í      :   src/Items/Components/DataDriven/DataDrivenComponentIds.phpâ	      â	  Ï­lií      A   src/Items/Components/DataDriven/DurabilitySensorItemComponent.phpÚ      Ú  Ñ}nÿí      5   src/Items/Components/DataDriven/FuelItemComponent.phpY      Y  HQ‘	í      :   src/Items/Components/DataDriven/ThrowableItemComponent.php        Oìí      8   src/Items/Components/DataDriven/ShooterItemComponent.phpð      ð  ò¸äí      5   src/Items/Components/DataDriven/FoodItemComponent.php‡      ‡  H[Òmí      6   src/Items/Components/DataDriven/ArmorItemComponent.phpù      ù  ye,¬í      ;   src/Items/Components/DataDriven/ChargeableItemComponent.php4      4  Ù½œí      >   src/Items/Components/DataDriven/RenderOffsetsItemComponent.php-      -  Õeà2í      ;   src/Items/Components/DataDriven/RepairableItemComponent.php        ë‹†í      ;   src/Items/Components/DataDriven/ProjectileItemComponent.php/      /  çHdŸí      A   src/Items/Components/DataDriven/DamageAbsorptionItemComponent.phpß      ß  iší      ;   src/Items/Components/DataDriven/DataDrivenItemComponent.phpŸ      Ÿ  wÓ¸µí      8   src/Items/Components/DataDriven/DyeableItemComponent.phpP      P  ýúôÊí      C   src/Items/Components/DataDriven/StorageWeightLimitItemComponent.phpf      f  «ˆtÞí      >   src/Items/Components/DataDriven/SwingDurationItemComponent.php¨      ¨  É¯xçí      7   src/Items/Components/DataDriven/RecordItemComponent.phpD      D  ™3?Ví      A   src/Items/Components/DataDriven/PublisherOnUseOnItemComponent.php´      ´   L[Ëí      7   src/Items/Components/DataDriven/DiggerItemComponent.php™	      ™	  Œ[ïí      <   src/Items/Components/DataDriven/DisplayNameItemComponent.php{      {  ‰š‘‘í      5   src/Items/Components/DataDriven/Types/DamageCause.phpG      G  H-Íjí      9   src/Items/Components/DataDriven/Types/ItemBlockVisual.php‰      ‰  ÊöÑí      7   src/Items/Components/DataDriven/Types/ItemAnimation.phpÍ      Í  'ÍOYí      2   src/Items/Components/DataDriven/Types/ItemSlot.phpå      å  nÖ„í      8   src/Items/Components/DataDriven/Types/ItemAmmunition.phpÁ      Á  tYoí      :   src/Items/Components/DataDriven/Types/ItemCooldownType.php        od�í      4   src/Items/Components/DataDriven/Types/SoundEvent.phpzw      zw  °ü�í      4   src/Items/Components/DataDriven/Types/ItemRarity.php        ‰L}Åí      =   src/Items/Components/DataDriven/Types/DurabilityThreshold.phpw      w  ~0ví      ;   src/Items/Components/DataDriven/Types/ItemUseActionType.php        0�X
í      6   src/Items/Components/DataDriven/Types/ParticleType.php­      ­  vÒÕí      4   src/Items/Components/DataDriven/Types/ItemRepair.php¼      ¼  c•-¯í      8   src/Items/Components/DataDriven/Types/OreTextureType.php–      –  9ùæöí      9   src/Items/Components/DataDriven/Types/ItemEnchantSlot.php�      �  ¦G{ñí      9   src/Items/Components/DataDriven/CooldownItemComponent.phpø      ø  Šôuêí      <   src/Items/Components/DataDriven/EnchantableItemComponent.phpŽ      Ž  Š�çí      .   src/Items/Components/DataDriven/DataDriven.php~      ~  <•¤Wí      B   src/Items/Components/DataDriven/BundleInteractionItemComponent.php        Î{Ší      <   src/Items/Components/DataDriven/BlockPlacerItemComponent.php        þ³žlí      ?   src/Items/Components/DataDriven/PiercingWeaponItemComponent.php3      3  �Ð\}í      =   src/Items/Components/DataDriven/EntityPlacerItemComponent.php+	      +	  ;’ÄHí      ;   src/Items/Components/DataDriven/DurabilityItemComponent.php#      #  h|¦Gí         src/Items/ItemVersion.phpí      í  [üˆ«í         src/Helpers/MolangHelper.php¶      ¶  ÂÛ²¤         src/Helpers/PropertyHelper.phpç      ç  CO�§¤      "   src/Helpers/DeserializerHelper.php%      %  Gný†¤         src/Helpers/ComponentHelper.phpR
      R
  ¸Ó¨û¤          src/Helpers/SerializerHelper.php        Ö˜q¤         src/Helpers/StringsHelper.phpT      T  ×�"¤      
   plugin.ymlq       q   6`Z¤      <?php

namespace Nexly\Exemple;

use Nexly\Mappings\BlockMappings;
use pocketmine\block\Block;
use pocketmine\block\Hopper;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier as IID;
use pocketmine\item\ItemTypeIds;

class RubyHopper extends Hopper
{
    /**
     * Override to provide custom item representation.
     *
     * @return Item
     */
    public function asItem(): Item
    {
        return new class(new IID(ItemTypeIds::fromBlockTypeId($this->getTypeId())), "Ruby Hopper") extends Item {
            /**
             * @param int|null $clickedFace
             * @return Block
             */
            public function getBlock(?int $clickedFace = null): Block
            {
                return BlockMappings::getInstance()->getMapping("nexly:ruby_hopper")?->getBlock() ?? VanillaBlocks::AIR();
            }
        };
    }
}<?php

namespace Nexly\Exemple;

use Nexly\Mappings\BlockMappings;
use pocketmine\block\Block;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier as IID;
use pocketmine\item\ItemTypeIds;

class RubyNetherWartPlant extends NetherWartPlant
{
    public function getDropsForCompatibleTool(Item $item): array
    {
        return [
            $this->asItem()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
        ];
    }

    /**
     * @return Item
     */
    public function asItem(): Item
    {
        return new class(new IID(ItemTypeIds::fromBlockTypeId($this->getTypeId())), "Emerald Nether Wart") extends Item {
            /**
             * @param int|null $clickedFace
             * @return Block
             */
            public function getBlock(?int $clickedFace = null): Block
            {
                return BlockMappings::getInstance()->getMapping("nexly:ruby_nether_wart")?->getBlock() ?? VanillaBlocks::AIR();
            }
        };
    }
}PK
     ,|J[            	   textures/PK
     J[               textures/blocks/PK    �~J[G^ž‡„  Ü     textures/blocks/ruby_block.pngëðsçå’âb``àõôp	Ò ÌÂ$ßÞ}`,rwbXwNæ%�Ã’îèëÈÀ°±ŸûO"+�/àâúHSü”‚ìÿÙõÿ3l~»
oVÒ^µû�P–³À#²˜��ï03Ï_‘”(q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊ9Mƒ‘ÌfbCF&°Û£€8³�,žÅ 	 o Ñh–2 };���(c`dlÀ¸ b*³.€˜4j�ÇVdidAu€d}ÎðPí¿¾ ÈÌõtq‘¸¬Û}Pð�‹¿úÓþt'ÆKnÿèW_Ä ÷r¿èžßQwç~p?0'ûnß±Êæ’Ï–Ww¦¸}xöMB^ãæžU³5Ó®ºª¬š`²lMÍMî_ÇŽ"æ¨=Ñ»^TÏ~|£Ð^ýùwv».Þµ×·�B‹ÁÓÕÏe�SB PK    �~J[.ÀõÕ<  –  (   textures/blocks/ruby_carrots_stage_0.pngëðsçå’âb``àõôp	Ò ÌÁ$å?ÿORŒÅAîNëÎÉ¼rXÒ}6ösÿIdò9<"‹ø�0£¨†WPP¢Ä5¢$8?­¤<±(•! 13¯DÏÏ5DÁTÏPÏ2'ï–PÍ¶ÔÏ4OO- ‡•AŠ‘•�Èòbi(äFf0›	ˆ™À®‹âÌv°x|*cdxÁŒ Ñ,e 
M`g`ª4026`\ 1„Y@Lš5‰	Èc«²´ ² º@²ÚŠßÕmœØAŠ]=]C,ü“%X'ˆ\�ø2ããßû'|�?ÂvQÁ.‚‰×À1³MªVn‡’Àß‡ÏíÛN§Õ$X
ð>R>Ý|¥Yõ"ã.–:;f ³‚ä®W/t}2ÜÓÕÏe�SB PK    ‹~J[TÀ‹V  µ  (   textures/blocks/ruby_carrots_stage_1.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|þ Ÿ×�÷zÌøjôy€b/ºâ�kI�_ð `ø½�dg�Gd1ß!fÕðj 
J”¸F”ç§•”'¥2$fæ•èù¹†(˜êêYæäÝ²ªÙ–á™æé©ÅÀ2–AŠ‘•�Èòbi(ärFf0›	ˆ™À>‰âÌv°x|*cdxÁŒ Ñ,e 
M`g`ª4026`\ 1„Y@Lš5‰	Èc«²´ ² º@²ÚŠßÕmœØAŠ-<]C4‚¯vßæ< r¨hÑïów~¿7_¡�åðëâyãk3m3–8HëQó(Û).îë·á×ëoŒ¼nÌKVõ	~™çéêç²Î)¡	 PK    ‡~J[B“qw  Õ  (   textures/blocks/ruby_carrots_stage_2.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¡ Ÿ×3¾�5ú<÷0€Àç¼oË�[I�_ð`Ø¼É~ g�Gd1ß!fÕðj 
J”¸F”ç§•”'¥2$fæ•èù¹†(˜êêYæäÝ²ªÙ–á™æé©ÅÀä°2H1²20Y^@,eƒ\¯ÁÈf3±!#Ø7Q@œÙÎÏb€€OeŒ/˜4š¥@¡	ì,@•FÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HV[ñ»º�;Hqˆ§‹cˆFðÖÞ@¾
Î™ÇW®Ÿ¥{ú_ºÁ6~MÓùŸ•+JÕ¼ÄžsyÅžd‡¥Ë7¬ml»-u|ªwˆsTêÄŽT;‘z· =»e¢í5l�g]Ìx�ù†Ïé¬ »<]ý\Ö9%4 PK    ƒ~J[dqœvž    (   textures/blocks/ruby_carrots_stage_3.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Å Ÿ×}ž�=f|÷B�20üg`ðå‘»ÉÀ°€b\ž>RÜ%A~Áÿá€ÁËŸñP˜³À#²˜��ï3Šjx5 %J\#J‚óÓJÊ‹R3óJôü\CLõõ,sònÙÕlK�ðLóôÔbà rX¤Y�,/ –†²A>Ò`d³™€Ø�‘	ìÃ( Îlg‹g1@À§2F†ÌÍR Ðv J#cÆSA˜uÄ¤)P“˜€<&°Ê K ªËÐÀ $«­ø]ÝÆ‰¤8ÇÓÅ1D#¸·Ï‘ÏE‘Ç©±¯nùwû®]�ïÕìÜã úÓª¾1iËî[†W´ZòŸ&y°¥>ÎT8Þl¨ª’±ºAÃ_u–€ ‡û¯©a�7O:L]o›Çu/Ì>ûÃÎ¬äµ±Uçâ˜ÏÝ{ž­«ý™ýÅa¶õE?âçƒÜàéêç²Î)¡	 PK    J[¥>Û(e  ¾  &   textures/blocks/ruby_hopper_inside.pngëðsçå’âb``àõôp	Ò ÌÂ$ßÞ}`,rwbXwNæ%�Ã’îèëÈÀ°±ŸûO"+�ÏàâZ)#™'$˜ÊËËÉý¢æZPœ³À#²˜��ï3Šjx5 %J\#J‚óÓJÊ‹R3óJôü\CLõõ,sònÙÕlK�ðLóôÔbà rX¤Y�,/ –†²AŽÒ`d³™€Ø�‘	ìê( Îlg‹g1@À§2F†ÌÍR Ðv J#cÆSA˜uÄ¤)P“˜€<&°Ê K ªËÐÀ $«­ø]ÝÆ‰¤8ÔÓÅ1Dâ²nïEAC'Oöƒ|*²3þU[ŸX+ÿËÍeÂ£º¹"œ:^koE„Je6[=÷“~µ‹‰+bûãÇŸ"êWæž÷ºwåNçÝùî_kÓÔ§ÉqºqþaØ½ŸW-å×›ß Ë<]ý\Ö9%4 PK    J[{4¢}  ×  '   textures/blocks/ruby_hopper_outside.pngëðsçå’âb``àõôp	Ò ÌÂ$ßÞ}`,rwbXwNæ%�Ã’îèëÈÀ°±ŸûO"+�/àâ:ÛÁr’‰^—¶j³Šb¥Œd*/ož�  {3Pg�Gd1ßaf<ž¿"((QâQœŸVRžX”Ê�˜™W¢çç¢`ªg¨g™“wË¨f[j„gš§§�ÃÊ ÅÈÊÀdy±4”r�#3˜ÍÄ†ŒL`çGqf;X<‹@>A¢Ñ,e zx;PÆÀÈØ€qÄTf] 1i
Ô$& �	¬2ÈÒÈ‚ê240 Éúœ?à¡Ú}�™êéâ"qY·;›÷ˆO³-Ûu™¦¥<¯Œ"DØt›tþ(Æ”ÜV?mÿçÀGÆ‚)æ¹õ|]N9Ì[µ¼$CÝ’Í±ú@”õû½ùKZ§ÊÌ	ï�²£åÚñë‚a'·¸ì»òš1-²‹“¿ªêXôÚ¯Ì«ÊÙ	ø¨‚,÷tõsYç”Ð PK    J[<1•Oe  Å  #   textures/blocks/ruby_hopper_top.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Ñ Ÿ×fÅ.mÕI&z³,ó„+e$�2µ‹>T)ö’ ¿àÿ`À åÍìâ,ðˆ,f`à;ÂŒ¢^@A‰×ˆ’àü´’òÄ¢T†€ÄÌ¼=?×S=C=Ëœ¼[v@5ÛR#<Ó<=µ8€V)FVF Ëˆ¥¡l�4™Ál& 6ddû(
ˆ3ÛYÀâYð©Œ‘á3‚F³”(4���¨ÒÀÈØ€qÄTf] 1i
Ô$& �	¬2ÈÒÈ‚ê240 Éj+~W·qb)vðtqÑžÛoÈÇ ÀÁœÉøÿÊÅFÃ�3ª¼Wr819¤h*œ³Ù¨[£"71t÷Z§ú#»—vž~Ýjú}ÿÌ×ùÌsãÍ™ëéêç²Î)¡	 PK    L~J[›zL²Z  ¹  ,   textures/blocks/ruby_nether_wart_stage_0.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¡ Ÿ×%rÊÇÔ^hfrsOgç
w5ÿ¸¤ØJ‚ü‚ÿƒ ÃæýKöE8<"‹ø�0£¨†WPP¢Ä5¢$8?­¤<±(•! 13¯DÏÏ5DÁTÏPÏ2'ï–PÍ¶ÔÏ4OO- ‡•AŠ‘•�Èòbi(äzFf0›	ˆ™À¾‰âÌv°x|*cdxÁŒ Ñ,e 
M`g`ª4026`\ 1„Y@Lš5‰	Èc«²´ ² º@²ÚŠßÕmœØAŠ-<]C4‚¯vdPà9äpëo¸Ø«Ÿ…Ì?UÎŠþ}ÓÍðÃKÂpGË„+,ê.6¬•rœ¯Ù¼-gÝ•îdcÉØh/vdž§«ŸË:§„& PK    E~J[ÄúØ—€  Þ  ,   textures/blocks/ruby_nether_wart_stage_1.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Ñ Ÿ×éì<Kä”�+¨½Ð6Ìäæ.T•ÊÜwÛúH±—ùÿ)of7 g�Gd1ß!fÕðj 
J”¸F”ç§•”'¥2$fæ•èù¹†(˜êêYæäÝ²ªÙ–á™æé©ÅÀä°2H1²20Y^@,eƒ| ÁÈf3±!#ØGQ@œÙÎÏb€€OeŒ/˜4š¥@¡	ì,@•FÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HV[ñ»º�;Hq¤§‹cˆFðÚG¡L%þÿ¢—©ÿvçÌÂí?÷ìo\ÂåÀ7í­ªëté€Óë®-.œâÝ=ùæÑÆ/ÅÚDÞ[t²hýîÚw)†±úGræë‰jžž9ÃÚù†EûèoÙ�…ž®~.ëœš PK    F~J[×ïäê™  ö  ,   textures/blocks/ruby_nether_wart_stage_2.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Ñ Ÿ×BUéLnî%rÊ/´�+¨MgçÊè_bz¤ØK‚ü‚ÿƒƒ”7³Pˆ³À#²˜��ï3Šjx5 %J\#J‚óÓJÊ‹R3óJôü\CLõõ,sònÙÕlK�ðLóôÔbà rX¤Y�,/ –†²A>Ð`d³™€Ø�‘	ì£( Îlg‹g1@À§2F†ÌÍR Ðv J#cÆSA˜uÄ¤)P“˜€<&°Ê K ªËÐÀ $«­ø]ÝÆ‰¤¸ÐÓÅ1D#8·Ÿ‘ë€³ðêÿOZßM0PóøÚ3q•ýGv•ß/9¶>JÙãâõHgeTí¼3‘:Œþj,-»Ø/¦šrnÆc®iÙŽ]E,Åb	<IlS7\ùñn-»D|ƒö›â×~g?°íáþq×,å;«Q2óìWÛ³…AñtõsYç”Ð PK    õ}J[÷Â÷”  ñ  !   textures/blocks/ruby_trapdoor.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Ñ Ÿ×Gšâ§dÿÏ®ÿŸaóÛUx³’6PFw)W(�b/	òþRÞÌn@!Î�Èb¾C Ì(ªáÕ ”(q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊù@ƒ‘ÌfbCF&°�¢€8³�,žÅ ŸÊ^0#h4K€BØX€*ŒŒ@LaÖ“¦@Mbò˜À*ƒ,-€,¨.C�¬¶âwu'v�âOÇ�àØ>F>gC'×Œ÷õOc¿×·uúÎw\i¿p•ò®(¥éóŸˆ¸Um®jÏl¥ð¸aß�h‰K~¾îÈj,~ÄmåñIòúF‡gìn¯˜µy£Ã5Vá7Ï:Ž¾s÷ÌÚ~gùEÏ�÷¢~0T35Û3Ñ¹ÁÓÕÏe�SB PK
     …J[               textures/items/PK
     5}J[†ðC            textures/items/colors.txtgold:
tint: -50
saturation 110PK    ò}J[H%dU‰  ë     textures/items/ruby.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Å Ÿ×|>¹ÿOoýÏ°ùí*üÿÀš“¢LÂ2[$XÿÏ®ŸÉË	Tµeù)a Å]äü¼ü?…9<"‹øƒ0ãñü)@A‰×ˆ’àü´’òÄ¢T†€ÄÌ¼=?×S=C=Ëœ¼[v@5ÛR#<Ó<=µ8€V)FVF Ëˆ¥¡l��4™Ál& 6ddû0
ˆ3ÛYÀâY� ò,�f)0L&°3° eŒŒ@LaÖ“¦@Mbò˜À*ƒ,-€,¨.C�¬Ïùªý× ™až.Ž!Áµç<ù(ð0;ÌjÕù__ÀÌ¹Ö!«¶ã�¥Â,ùŸMb+§}g:°j'gÃFý[…T&E¡�ØN}ã—Ú°waÂ§s1ã‹…—=1çÚã¾It�å�¿l<âìçlmî€lótõsYç”Ð PK    ï|J[üsY‘  ó     textures/items/ruby_apple.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¹ Ÿ×.›óV‰:ÿøÿ¿|ø_]ï²Íÿ™‰.@%²œ�WI�_ð`ØtFç<P”³À#²˜��ï03Ï_‘”(q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊùFƒ‘ÌfbCF&°ï¢€8³�,žÅ 	 �"Ñh–2 Ãc;PÆÀÈØ€qÄTf] 1i
Ô$& �	¬2ÈÒÈ‚ê240 Éúœ?à¡Ú}�™äéâ¢{Š‘ï€³CÖÑ“ÿí÷p�ÓÍvyéÚwã6ã–*~ï½¦«Kï&¤7{2÷­þ^×;¥©ÇÛlG×î45‰—ë²‹\Lp¬óÒ?°ÊÏF@¸åÜ+VÃ³9i÷„
™ç^aëeý m²ÙÓÕÏe�SB PK    ï|J[†ì�2†  é     textures/items/ruby_axe.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Å Ÿ×©Ì2ÿëcÿ3pÿO¶ñ4Íð“ó`üÏ  !ÇÝ™®TuòÀ:�±Ü%A~Áÿá€ÁËŸñP˜³À#²˜��ï03Ï_‘”(q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊùHƒ‘ÌfbCF&°£€8³�,žÅ 	 Ï"Ñh–2 Ãd;PÆÀÈØ€qÄTf] 1i
Ô$& �	¬2ÈÒÈ‚ê240 Éúœ?à¡Ú}�âéâ¢{&Pè€K€KÈÝÿ¦N0øºõžTÂ~Í†q†ÂçÌ&V?üŽlv(¸à• ôF‰õXðJæý¯Âå„öôO¨>°Ë~ó‚¯ÒUûÏó?£6o
Ç
'ý?² »<]ý\Ö9%4 PK    ô|J[¦Z	o  Ó     textures/items/ruby_boots.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|‰ Ÿ×©Ì2ÿ� >ÖƒUð²Íîÿ
 �ßþR%A~Áÿ!€á^óÞH g�Gd1ßaf<ž¿"dX‰kDIp~ZIybQ*C@bf^‰žŸkˆ‚©ž¡žeNÞ-; šm©žižžZ@+ƒ#+#�åÄÒP6ÈŒÌ`622�}Ä™í,`ñ,H 9‰F³”ØX€2FÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HÖçüÕþë@NõtqÑî=á(tÀ€ƒÉaµá¿ÿÇYçø©í\"#ríÕ	ƒÃ—37¼›WïÃÆ†«Ç˜®ò1›8~»</'˜É#Ç�…[ÑBRäÐDé�½ozY;¸Ü/\Ùàéêç²Î)¡	 PK    T|J[š1Ž÷œ  ü     textures/items/ruby_bundle.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¹ Ÿ×Ù
Ò2Ò—,ôÒ„„^8Yl5Ôl3Ù>3ùþá^ ’³f	 3¹J‚ü‚ÿÃ Ã¦3:ç�¢œ‘Å|‡@˜QTÃ«((QâQœŸVRžX”Ê�˜™W¢çç¢`ªg¨g™“wË¨f[j„gš§§�ÃÊ ÅÈÊÀdy±4”ò�#3˜ÍÄ†ŒL`ßEqf;X<‹>•12¼`FÐh–2 …&°3° U0.€˜
Â¬ &M�šÄä1�UYZ YP]† YmÅïê6Nì ÅÙž.Ž!Á±g#ùX9X4"�ýŽßÁíZ}ékâW‡gê¼J«bßŠµ½úÁ¼xGÀ9ž	âJGw>ëÛáþÏhÑ+¦ö™é?~î�<UÈÈïÍ±'€ñšÏÁyŠ_"—†TT™´ÙÖ[Üpñ¯¶Ù–òý¬—D1:€œàéêç²Î)¡	 PK    ò|J[JWø”™  ü     textures/items/ruby_carrot.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Å Ÿ×®4ŽD…D—ÿêzÿø-Î3XýO¶ùÿò!PUP]o,�â.	òþ^þŒŸ€Âœ‘Å|‡A˜ñxþŠ  D‰kDIp~ZIybQ*C@bf^‰žŸkˆ‚©ž¡žeNÞ-; šm©žižžZ@+ƒ#+#�åÄÒP6ÈGŒÌ`622�}Ä™í,`ñ,H y‰F³”&ØX€2FÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HÖçüÕþë€ÌtOÇ�àØF¾
Ì¬®þÛW¤.0ÎöYrÝéó|³ŒÞ¬ççJU`)øé×lQ“#úÂyFæ¼6>ã—“9Ë…nlLùÄrMjÑÜï]RE"{¸ö|ëàr‰±cøÓ·�!ž‡ñŽY~LQ=ÃÛ0öM›Î/Yïéêç²Î)¡	 PK    î|J[±{’Êˆ  ê  "   textures/items/ruby_chestplate.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|‰ Ÿ×©Ì2ÿ� >ö²Íîÿ
¬‚@É‰�•_€GI�_ð`¸×¼7(ÆYàYÌÀÀw„�ç¯HVâQœŸVRžX”Ê�˜™W¢çç¢`ªg¨g™“wË¨f[j„gš§§�ÃÊ ÅÈÊÀdy±4”ò…#3˜ÍÄ†ŒL`_Eqf;X<‹@D¢Ñ,e †Ãv Œ�‘±ãˆ© Ìº bÒ¨IL@Xe�¥�Õeh` ’õ9ÀCµÿú 3ÑÓÅ1D#8÷¬#ßf‡.Þ?ÿ�³^6»”uV1MI5Ìþ'³Y`èË�2ÜZ¶|ŽÞpe8�yÌN¼iÇ�ßR=�gnˆvœi[USöÄÆ¶�Uî†Ìö�„ômÕ.Ü?o…^ÍÊ]ŒùûYžhxx€,ötõsYç”Ð PK    	}J[AEËÙq  Ò     textures/items/ruby_helmet.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|‰ Ÿ×©Ì2ÿ¸ÿ'ÛügPð`ü_ûÿÿ $sQŒ'�â(	òþ÷š÷FÅ8<"‹øƒ0ãñü) ÃJ\#J‚óÓJÊ‹R3óJôü\CLõõ,sònÙÕlK�ðLóôÔbà rX¤Y�,/ –†²A¾Ð`d³™€Ø�‘	ì«( Îlg‹g1@@ÈƒH4š¥Àp˜ÀÎÀ”1026`\ 1„Y@Lš5‰	Èc«²´ ² º@²>çx¨ö__ dzzº8†hO=cÈwÀ€Ã9ð�ÃÎÿ½Ò1¡.'/ýc<(½áÎ/®eóB™çðÖŸ0�C¢”™wŽ|�NBœìû»×n+ñ=x+ÿÄÆ';X§_ßò|+ÈOW?—uN	M PK    î|J[Âœ·<ƒ  ç     textures/items/ruby_hoe.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|Å Ÿ×©Ì2ÿëcÿ'Ûx°
þgàö4íLW×�ãÎð“³×ãªº'½ö �â.	òþ^þŒŸ€Âœ‘Å|‡A˜ñxþŠ  D‰kDIp~ZIybQ*C@bf^‰žŸkˆ‚©ž¡žeNÞ-; šm©žižžZ@+ƒ#+#�åÄÒP6ÈGŒÌ`622�}Ä™í,`ñ,H y‰F³”&ØX€2FÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HÖçüÕþë@Òž.Ž!Á±gù(p0´-hþ-_Á²rÒGçJÓIÿÙB8_'ÞÔ?& ÖËÀ!4su³öéÖ­u\þ”Íe¿3cÕ�—Üyû~åÜë¿¡îúòô;ö‹¬8Ù>O–Ý²ÉÓÕÏe�SB PK    …J[ŒP«"„  æ     textures/items/ruby_hopper.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|‰ Ÿ×vu¥
i‰"Q‘Z™~­~þÙ¶f@ÉŒ2ñ@Š£$È/ø?0ÜkÞ	ã,ðˆ,f`à;ÂŒÇóW¤€+q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊùBƒ‘ÌfbCF&°¯¢€8³�,žÅ 	 "Ñh–2 Ãa;PÆÀÈØ€qÄTf] 1i
Ô$& �	¬2ÈÒÈ‚ê240 Éúœ?à¡Ú}�ëéâ¢œÛÈ×`Àá¬YRô_š{�Þ±û®M¿Y÷C\M%AO'ªá]vÁó,Æ¢¢	æ(¬kÛ¸Á=Q"éíE³%GS8¯µîuY$–óIyÕ&n¡ÐÀ‡¿Š÷«�rï8#½´—ÕÕ ãj-ÈROW?—uN	M PK    í|J[•K@©t  Ø      textures/items/ruby_leggings.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|‰ Ÿ×©Ì2¬‚ÿëcÿA²Í…ÿÜ@É˜-A,@Š£$È/ø?0ÜkÞ	ã,ðˆ,f`à;ÂŒÇóW¤€+q�(	ÎO+)O,JeHÌÌ+ÑósQ0Õ3Ô³ÌÉ»eT³-5Â3ÍÓS‹�Èae�bde`²¼€XÊùBƒ‘ÌfbCF&°¯¢€8³�,žÅ 	 "Ñh–2 Ãa;ÈFÆŒ ¦‚0ëˆIS &1yL`•A–@T—¡�HÖçüÕþë€LOÇ�àÒsž|8˜|"þý¼ÉäÔ¢kì§Ïp~Ò¾ï ë£ ×é,õ¨ëF“Åææ‰3n(¦ç�ðÙ?A~Á.†¨€»3cž0}gßçÌj}üêU3�5ž®~.ëœš PK    v}J[�±Øˆg  Å  #   textures/items/ruby_nether_wart.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|þ Ÿ×{n'õ«Õår”e€bYjOÎ )Ö’ ¿àÿ@Àð{È Î�Èb¾ƒ Ì¸cbö[  D‰kDIp~ZIybQ*C@bf^‰žŸkˆ‚©ž¡žeNÞ-; šm©žižžZ c¤Y�,/ –†²A.×`d³™€Ø�‘	ì“( Îlg‹g1@ÀÍ2F†ÌÍR Ðv J#cÆSA˜uÄ¤)P“˜€<&°Ê K ªËÐÀ ,kÔqOÄ¡ÊÈôðtqÑž{Ö’�Á�ƒÙ!çÏóãJÓ=6_
Zø°àÅÊûw™Î¼3Ø›pW`�ø¢>®Is¶9l].÷MÄþÌ´"'þ;¿˜2^ež—w#!d¾§«ŸË:§„& PK    }J[ñ|+r†  é     textures/items/ruby_pickaxe.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¹ Ÿ×©Ì2ÿëcÿ'Ûügàö4Õ�ãö`ìLWÏð“*™v{þJ ÅUäü6�Ñ9å,ðˆ,f`à;ÂŒÇóW¤ %J\#J‚óÓJÊ‹R3óJôü\CLõõ,sònÙÕlK�ðLóôÔbà rX¤Y�,/ –†²A¾Ñ`d³™€Ø�‘	ì»( Îlg‹g1@@È£H4š¥Àð˜ÀÎÀ”1026`\ 1„Y@Lš5‰	Èc«²´ ² º@²>çx¨ö__ dFxº8†h—öòPà8*2¡÷w|¢Ó´5ònÑWåÿ)H­’&µ)Ë)tæz¡>�lñ"}5]Åá={˜îîúÀW¸îŸa°ûOïo�¾ðyõ¼æKÂ7`„±ïQõbY²ÏÓÕÏe�SB PK    }J[ƒÒ{  Þ     textures/items/ruby_shovel.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|¹ Ÿ×©Ì2¬‚ÿëcÿ3pÿO¶ñ4íLW×�ãÎð“*YüñÈ|®’ ¿àÿ0À°éŒÎy (g�Gd1ßaf<ž¿"((QâQœŸVRžX”Ê�˜™W¢çç¢`ªg¨g™“wË¨f[j„gš§§�ÃÊ ÅÈÊÀdy±4”ò�#3˜ÍÄ†ŒL`ßEqf;X<‹@E¢Ñ,e †Çv Œ�‘±ãˆ© Ìº bÒ¨IL@Xe�¥�Õeh` ’õ9ÀCµÿú Ó×ÓÅ1D#¸ö´£ØgGÍgÿÄó£woúä?¯éq�ã’…U[:¶­b¹ØË8mï½þ2]ö'Ü=žìôæ³Ðµy	[øœÓ¯c’-`–;ÅþqY¬š5ÈOW?—uN	M PK    î|J[·Ž4
”  ÷     textures/items/ruby_sword.pngëðsçå’âb``àõôp	Ò ÌÁ$5tùƒ�cq�»Ãºs2/�–tG_G†�ýÜY�|• Ÿ×©Ì2ÿÿÿ÷`üŸló¿>ö?ƒÂnO3Ñ?¹Îtu9n†ã‚<Z@Š§$È/ø?0Ý?w(ÎYàYÌÀÀw„�ç¯H
J”¸F”ç§•”'¥2$fæ•èù¹†(˜êêYæäÝ²ªÙ–á™æé©ÅÀä°2H1²20Y^@,eƒ|¥ÁÈf3±!#Ø—Q@œÙÎÏb€€�‡‘h4K€á2���(c`dlÀ¸ b*³.€˜4j�ÇVdidAu€d}ÎðPí¿¾ ÈŒótqÑí�: ÀÁœüjuÑÿßb‚Wž¹—Í5`~Âgb5A(¿ñËŸ–�2ç¬ìOþ ²M¢rcgÓá0ÇÔuçô²8ã¼·Šä,*M’}+ëÔv1û×æí»óŸVúÄèoÄa¦P|§d«§«ŸË:§„& PK    z•J[;†:á   z     textures/item_texture.json�ÓKƒ €á}OÑ°6é¾—!£NÄˆ@†QÛ4½{MÑj�€c�ÿóÉër]7AèíDJÍ Œ(îW1ƒéµQ…IŒžeëé�†|ªŸ#çù^Ü�ã[Ï8úÛ7øÍ~Wÿ”T¨Gälqïâp£Ð³ÓÀ˜�ŸÚø4v]o:ŸÏÿÊ8^[ËrÈâ¬_,µùlÈâ¬ë›/zãt›&½²3ê|uëÿ²-¸We“�ïœ.@C–ø½&Ó–¸[—Xu@d–óÖÅaƒ¬�äT ŸãÔçs)_ßº¾„ý{ø PK
     ,|J[               textures/models/PK
     @}J[               textures/models/armor/PK    @}J[Á£üÖà  ,      textures/models/armor/ruby_1.pngëðsçå’âb``àõôp	Ò@¬ÀÁ$§:÷mRŒÅAîNëÎÉ¼rXÒ}6ösÿIdò¥|B\ÿ¨uþpãÿúØÿ¾6ÿÄÿK)ÿÿÿ(¤åÝV ÅYäü
‚ý+„@‚‘Å|‡A˜ñxþŠ  D‰kDIp~ZIybQ*C@bf^‰žŸkˆ‚©ž¡žeNÞ-; šm©žižžZ@+ƒ#+#�år”ò‰#3˜ÍÄ†ŒL@:
ˆ3ÛYÀâY� ò$�f)0,&°3° eŒŒ@LaÖ“¦@Mbò˜À*ƒ,-€,¨.C�¬Ïùªý× õÎ÷tqñð^:µ{Z³�3Ë—ÿû+Þ'\¹:åò³+j2“N.ÞÖá•Vs´(Ê¯aŽw¡ýµÍŸ¸l»¶�ÁcéÕ¨ÛåïþY²Ÿd°û~àÎìéŸ\Å”ú7Î™6¯ZîŸ Õs‘Í>ßöFžò“ÊÞtlSÊ·âƒ5O�TýŠzxéÉžyß¶¦>xÅÃp|×å×³´ONø­Àà¹àñ� ‘—u+˜.¸*0_ªŽjgwPX-¬Ò¶oâL¦3'Þ–éâàþ<�¹!¬‚=bÝ9é)—µVÊ2?¸ûôø§‰=Î7\Kk·—ÎÔÖü}Ø}÷$m‡2ãÖYî^û¨ªY§Ý½0µŸñ«T÷½©7e–íŒ2²�:]z×Ü˜Û�g¬{ÌofnãN°=ãûÅ™c‹Áö¯œµ7J–JMMhjž|&›cW¯· é‡]²öñOø7ÿõ>ìº™•—.»Ü­(aÏ%žÂ»=.…$‰å¦¨V~þÒÞÜmðVæ^Þ=¡u�
ßSO7¶ÛËDˆÞN’œþ]�íjÌ½³k7µ	ž`íxäú;N¡ÜµZîöy‹N¿ñ?µëÃ¿PÃµ·j8–ƒ¢ÍÓÕÏe�SB PK    @}J[K”Áô  O      textures/models/armor/ruby_2.pngëðsçå’âb``àõôp	Ò@¬ÀÁ$§:÷mRŒÅAîNëÎÉ¼rXÒ}6ösÿIdò%|B\ÿ¨uþpã_›ÿâÿ¥”ÿ×Ç%ÙŽ‹‹)Ž’ ¿àÿÀp¯yo$PŒ³À#²˜��ï03Ï_‘2¬Ä5¢$8?­¤<±(•! 13¯DÏÏ5DÁTÏPÏ2'ï–PÍ¶ÔÏ4OO- ‡•AŠ‘•�Èòbi(äFf0›	ˆ™€tg¶³€Å³  äA$ÍR`8L`g`Ê0.€˜
Â¬ &M�šÄä1�UYZ YP]† YŸó<Tû¯/ 2�yº8†xx¿�t“¯U�Ç%=gõÿÿÇË£:í$„n—vóŒ¼ä,^Ps…IÇ!‹ñ;C�T	“YòZµ†Yõ,[g	¨uÌJxàèÂ«p,˜1¶/m�d¦áÞ»R	Ý8òÂ^0E¼õ/;•«E»µé‡×º±�B ¡ÐŽ›fDv_;÷f�ÝM£Ø¹çõ;_Ffçì^¹àçÓó®æ[�7^büÎqþÖ�Ïßª"ž}
Œ~ºâÑ³=ó�6Oá·—Ûòô½Ï~,Ç?2u3ê-àg¾ò�§«ŸË:§„& PK    Æ€J[ÞYLð  �     textures/terrain_texture.jsonµÕMnƒ0à=§@¬£&@ú£\f4À”X1¶eI«(w�†RµVE€¶ßûbç #UWP	’Ž$]´·«vÛ`QU6ë·nmÉéÚæó(¬¨9ŠŽ¨„”u¦®-A�ŒÍa«·Å:û„LêüÐï}gÛûõ×ë.åÖƒF¿¬RŽÖj†Í(ìVràK‚�Ÿ�—a“	lìgÓeØí6ñ³ÏË°/ØÔÏ¾ÎÌ*â=Y8¡;¼Ã¦Ò†©x¢ÿã'ûéDÿ¯iq³Y>@ï¤ršO‚yî„RÎ±EShmGa}é··×Æ4/H('Šq�ú³é•uÍ�Ò÷ª×fmqÛÚÍ¾Ü{¼ÿw"KtOLÖ¢PQp	®PK    Ï€J[	-….‘   r     blocks.json�ÏQ
ƒ0ÆñwOQúì	¼LIkæ`¥IŠÃ»O­ˆ
ëúšÿÇòi”Ò”ìÛXÝCwj¹Ì7Á—$BÖÝ©·¹rL¡_K¨çãÔî’¢(j\­�€ùb”;’�JàquU�íl³[@×\óïƒÃ”{µd�¿\Õ˜#x_ÐÖ\ãÁ³�‘
Ö>ùí5ÓPK    ’¤L[ÿ½Ÿô   Å     manifest.json¥�ÑjÃ …ïû’ëÕkv5ÖëŽÁ.K(ÿtB�EãXyž½Çžl&Yi®v3áüçù??7e­óVö§wðÁ¸.{@$ŸÆo 5ø$§PÒBãÍµ_2ÙÓ€¾¿Šo‚E¯}ÔÆ%­çÁ¢û,_ª�´0uV&Âè>.Ã-£ÑS¤•Z„¦‚Ì
ÒbUVCÅ©™ J‘[ç¾ñq›£tHýëXÓ� ;›VXG’#š#^§Ì8#Z§ãÂäÍ½…ô_¬­µÂäÊ–©ÄEÊ³� Xí@bUpRQMãÛû‹ýp�ÿÏCpÑ7iãÙÓ]oÆPK    ‚­[2—±�  f#     pack_icon.pngíY	4ÔmÛ×ã‰Keß¢dÆ$„™”%»	¡Gö%’µ$’Æ¾f‰Ñ3…IJ–’‰Fƒl‰H1ö$c�aÌ÷ÿžzžï}Îyßs¾ó~ßwÎçgîë_×õ»®ß}Ý×}ÿgPæ¦úì{÷011±œÑ9ËÄôüßÅ<Ip§ù ;ÏêŸbzÜ.<¿zh›h31•'ï]wÚ	È,æÆ–ºt:Z5t‡¿\9kjAg{®dH»ýÎØ21q¼ÿw4ûb]™˜4ÐÑ¶¶Ÿ$D�ª¥ýúnŠNŸÚ_Èg·Ãç¬ÌîÎONE]ÄgÙà+tš"f‡mÏÅS¼*¹)º‰’®ˆ{»ÿˆp"kÛ5Úä»*§ÛK+Ev$SqNˆ7¸lÁBúÁuy/Ø¬ÔØ¥póçù÷tÛžÓî"‰×&‚þp©a²‹ýKuÛäP2¡x�û¡ú…ä'3BÖ:÷�]š/É(üVSž¢WÝ\,étøÍ§HiºÆ§Œ¹Õý·+*Ïü:‰7{’&b §J’ÿ�äžJ5EÑù°÷óºç8F«�ožÛêÝÈ<ÖÞ*…Ì²ZÔä¶îóyg3»¶Ñ<äë›7‘µö�2”ð¹òæë¼KoÈ´ON¬ÍÇ.Yå¥”­p©0á6uŸ®/ûžZË6oò§Mô×íî�v<ÅÓ<ö1ßmÅ=ƒ3JÀM8SñÚí¶;Ø.÷ËºAõemš˜�y3„â3ñ¡fçR´£¶H3ée¾óÔíEÎ¨_˜ýuêŽ?~ý:úîë“Â©eAQ1r�y1iÈ~VÒ=�¡¸Í]q	�á÷XÛgÊPÎ(ehùo°~Ä¹Gï¢ï6˜ÍTJÑ}YÃBx4Æ¢E\ÚJ´v•$4Á(ö±6'9£N–ÁúöŸóŽ©g­ß]®_÷~ÿñMA0½ü4ìýŽåK‰ÖzÙ{åG‡½àŸ¶^Ô¯À»z¿}>Ððú1µTYðùÁrÅù"õ>'ñ(å¬†m)ûÍ!¶æ›HlXi/o·æh‘Ë…ºn…Û^éóR�’£å²žœrOÛÖÓ/Þ¡Ø¤—UEÇÔGJurp4Æ!ï_~ïÉ)…Ö	UÛ•¢÷Ñ»û ©&*ÈÿH	u¼¿ð‚ðõÔÙ/:–c\—Ë×ÔlJÅøS—ýY¾ÀÚutaïN\ªJ?½ëZ€t+9ìœ-æUÞ©YˆŒŽJüžÙqþ†!áø4d>ïEøÄ¾ovÜ¤ÑÒ'ð5õX?øFLÔ#“}à­Ì�Pu]ŠæŒ|§)Ç¡Áælë€sj�ÿ¯_’tLêÞI>¬ŠåÚµâwˆ*è‰ÉG¢ø ;ìI|Öé÷ãú/Á¥ë�fÓËJ	~Ñ_l²æ,U)Çß{åÛÞ xKFdùV:Wèñ^ÊDÜ¸dkKn¶�µÇõOÛ<æaÙÿXi5M÷Bzr¥s�QÇØy—oÉUøž·‡o¡�y*³S¯ðD=ëî³ ¨=ä][`ó".6º¹£oI3¥ç}ÞÉÉ@»¸ø®Ÿ—„duTN?=jÝ{ÒÌÎñ³A®±‰¶
º8Q4:"AK—Ž¯Q(ðÈ—Ù§‰¯ÒšÏ[Ö½­5”2WY/ØûþÒ©~®éƒŒ±5ï'z"„xïS½\fX{WKóå'úf¾þì"©ƒÝµ-hD*gUÓáW-ö®Ì”“Ÿ¸pÀ ‘®XE�=„L¥¥r�v9¸FÜg<¡¥ÂPû’¤í]+à]ØV©×,²TgúE�ù#!ØIˆL³*‚;I±"ÉUg^ì¤ZŠ¾¬óøÛc˜àÔGGK"#t.Ôur¼÷Ô™?üñ%¶bÒ3&4ë˜/i©3ù ‡‘±*mÑ¨‡™LÙß&t+‡KÖØ¤&WÅÆä"êˆtš%¿l'(6-ùž¼åÂZìÆhS¹úepÕšÛ<WüÝµE’s‚Ê-Œ¨"Â6]Í%È¢_-Q(ö›FÆ ‡EaòV‘‘ÛB¾·
ÅÃŸÐXƒ,Î/µJM•uÑÕs>ë1].i0OMÝsºü�¨—T¨•KÉë“‰>¤p>Íj*Q>~¯T™÷7rár…­¾èÊÕqg£ºûã—bê÷ëæWÈ0{zg¨ªŠÁBùÀ]Vig…×ã´,`½á·¶~¸…iµ÷bG�•€ÂR þ2åÜZÿcYñ8Y`ÇN3ÍX¹•J=VæÕ ¹ˆ8Õ{/5Zá�c‘¥âò6‰Ð/ð–_SË®Þ`éåêËŽÓ"?å(wæ¹2"h¶‚CçJÌÍìœ¤ƒ'ðiVü§l\dŒ�¤—	+'ð·Œ[r3s´·Æ­hmwÉ VV�V�ÀŸÇ®G–[ù4Z‹~Œ‰%™Ù'mœ½•Òh¯àáÿóØQlù­ªFû³ãúš£SûyùoÑÈm¦O|ŒBnìdqÆ!+¡_?FQuÏ9Šq´èÏôcÀ&ÔùEºV™×¶‡Õ¨¼m=ÚÑ€~ä|WAùò˜Ð‡­¼à}òØ#jzrÝƒ—;BŸ¤Ÿž;ZB]¬œ›Çø:Öíhºvä¼9nþ»FÍRÇókê+bÙÝ0`ÓpFÑò^ÊOâøfkIˆ¢>*~—Q‹Û€´û`YEmk6å]â÷èP,9ò.,¢NÓæ€ïÚ<Æ¢?‹ÅÎM<ÃŠn(5Ó{1ŸVæÆèÃI¸5ÿvÜKìFXöèótÝ°Ôƒ_Hß´•îi¾®‡w}¬žá\ñˆH[¼1­­T¨Ñò…¯È!êª¯®õ{È>clR U7HU•6´1i†Í+0×Ú%iÙ¬N?R“#?-äðz§K.ký�iµ‰Õt]ÚÆËÔ¯>}Ò½›faÁïKðüëiS]ý (�£Ìc~DF‰ü´08Iñ@À�ÆËª$´ü=</î‰WçÈ—�Žd±‘–F„³® 2óHi‘šñT'ÀÞÖg
U‰½­›™7žï’ŸŽuCHW�›©‡­A|¦ÎøHÞÚT"Z§¤
oºÒ\1g«Äú¾Å¶r6)ÅSŸÙ0¯èûVþkëu{*¯½À2°E|Bdtl{V Çs~ÝF*!ñwN¤Ý!`?�”Ÿ'´¾Ê0j¾�oø]òÚ–Dpã‹À½u!´¹"$|‡¼ô�‚™-SÕj_r2§ŸƒÖ„9!Ú„¾æ\hê 1¯y÷½52�W%^–’€[ƒ…ùò‚Ãùéˆ#À'w<u'JrÓØtÇQ0öÏ±ŽÔz3<6]ïO"�ŒÇ/T0Bà›Ø2Ph´£]^Öì&AÕjŠ7�üÿ�ŽŒ¨¾dÒ/Ì!p@qá»DC7où£f˜öÐ¦+åbÚ‰ä‚nÑ×>:QµmR¶°ª~yÛdF3Üôã?4iëæëÙ†,#On¡DF*’?oãÿ]VÿrVÿÃŽP(³€§¤k›òSpâ˜¾:ƒÉŠ»j}¢$¿mÂ#RÞlSIÿVáQïí)ÜçÝ±jÛd*7JÚf“Î|Ð/¡rÛ¬#�®Í±………·/±ª2°îÓå¯‰ŽoÇÄ0ïß~í_¡ãýîd1 Ä¾¨ÚTtÕC‡ÂÞäosíÅ”¸]ÿƒa±w`™ÿx*þ!¬>ñï¬J€¾¼=õ á�ûð&¹ð67÷v¿7Œš®<ë>Îm½Ù:LÐÈ4#0ÊaNØÛ£`zÈä	ôx.˜qÞÅ,°èî£åÕcA@:¾ó=Ýv�@†YÁ“×¿UçIi^‹àî C+ÏØ¿QÍ \ b—Bû¸†Í‰ƒÔ#ç]ÈÉ‹I€ùÜw¢Tçžh­ù£@þ’c†áa¡Ÿ™“àèøzfš¸Ô¨š	8ˆõ:ru|ãÌœ,0³'y 7{å+÷‡@U Ëi’,Ý‹¤êÊÿÆ“Û ²ï-R
½úþ‡ ï
úx]ƒ8xà¢WÇ·f:Ó6’¿myÏ¤ê^my;VLOÜÎ ÄãC¦t3õx—ÎÆ—L¡Fm…“è¹qæÈ\Œ>ÅBF«¡·��ÜÐT¸²&¥O&WIqlOˆN‰z®¶‚Q¬@«…lké.ëFoaö¤.6BÒ¶ŸHƒwñoÇ¯Ò†æ(tø’j†iÂºlñ$«#âOte<�™Ôd¸í><©ùFº…aÌ[ÿŸÆ€ðÜ˜‹�0æÞòT«uÇH3\µ¢nA»™q2B52ÖÑV”ç ­£˜Wvxýô‹ûÉÀyÌÃk%rÕ¶g¯|Éñs®\µÔAµ{	*×h†A$Ýåá…Z™¥­˜ýúxa¼Õsšñ3RÁÂ‰òCñÔäëùü†9ÝGçÜ©ÀÒ3®¡LT7,ƒÆÈ’å³íÃ­ÏÖ5>‰½Xj®P¤Ç=¿¥õ�öÔ›ˆû"âœõ2BÁh!-ª1´ÝÇwañƒœÃW‡¯%¬ÍÀ›içy¯c‡âMZ0z\ØÿFñŸ©”#}~(E‰ž1›‡À·\…ÇÁ’¼!¯ÒO‡ªARÊ ¿¹»'"×™XYÉ3ÕßïˆŒ¼;Q¹¦þœ°z²ïš_}"�™Þž(ýx¢Á7(;úê8‡ÿ•üþÈÚöAÞn7w=î—êÇ1/Ð{qýþ¦ç€{˜Š$+YÐBCÍÜÕÕ¸2ý´K.è4Ä/'ç�pT5%+§ÿí	Z¶»¨‡¤e_àU�¦/ÊÐfu7o1Ehe–…uw¯B’1v0mTBÒÔO;ñAdmcã~ÓvÏâ+jJ…/A@Ûv>~µ7ì<7YÈ¸šÕ4
ït]†ÂïÕ‚sž
6]#qÊPfJÚ†X¬G´Õï‘±‰×Á¾w:páê*æÐ%:ú5/ÈôeÓb9DaÝÝ×¯)oYø·€@_«ºfq~÷cèË»u K!;H±ABB’…¼sI›£Ã“¯ça‚¶s™ãQº#À(22išPiDÝ¹$˜nDSõFÚ
ô:GG±‚nÓ)´W‰--<¦^‚¶d·Ú6N²¨Ù¹”á#V€P˜ÊZd¦$œR)¢ˆ_èâãÇ*Ÿsü†HVŸÍ:¥’¢8å‘aD�Dê©cÀ»÷6îŸ•~aæ*ÙÝ{ÒÞÙÙðùÝƒ-ÅWÄãpÅ:Ì7éÄü~€ÌýNzV6·1–…¬uÉÝ¹tV(„à¾œœ-A¸µ´0"ÒÕÃsÉ�¬mxwËY[+ÅlJ�›ç7®9Ùnå -¾ðJõë99û›²ÝÅÊ @TXV²ÌÐIû§rÍ}âJÊ ³æli>Æ|X>ä¦#2ØõÝœ&!ï9-·¿é¤=°øÝ ‘ëòÏ-ùÏ›[ó×€¤çÕâ{NÂ0ü-3Ä¥ÆÀ�H¤¡jJÁØí=§- }Ù‚-úÜÌ×ó«ÑÁ¦Û .^†Ön_ ‚Ì”[ÎbeÀQÐá	†zkäH–¥Às+�—‚Q#HXá%-­ð`—ÓL–f!ŸÔšÏ+èJ”60È¯ŽDÆÔƒ«ÜFx˜°Ô(ì;3Íb€|—ÜÀÁòG³Ú†ÏZ­ð!#`®�¥Ð›¹Ê	øL^®»yÅõz³ÕPE¥ˆ…ü“
´Ufh·Â$Ï7U”ß“5Ö�ÐMæA6u )}93¨‰*°`´Ã2º_�:V}þ˜´kaoÎ%cvÒOáLTQ]òfy0„&Œö¡Pûw1ÃÞ¦ö.^b¢¶ÍÈÀb(>
­,\ÕY)IÇtä¶\1\µÚ˜Õe5Q¥R´PbÝzSƒz?¦_6¹É�Ò1?‚m3ÀWÝ]{— VÖ�¦'¸8kÓ¢€}ç&¢ÓE1ÑîZ÷Ü®¤ÏÛCÖ_¶áXÆV3·ârw×�	´¼%&xòö [8<}ÞJ/tl1s­£>ðYPóÎM¶ÀöÖ)‘˜í®­•ÂÇÿÁ™H°—ßü.üè“?£RQ(HÛ/1E%¥Âßº8I’Ë~¼eÙ�k1tþìGC[Z‘‘»{-‡.³©=llÀò»‹·4³¢G4^\Jz»¹ÊU�»{m}d””ÀNÖ”zX\k>i�¶+d„QÅWöNË14ú²Þ p3mZ[›ý Ýî¬3X�Ï£™w²„Œ°.é¾Ôf›Ñ„ÜÓlÑÕc!ó°\ós4æ‚ïò2¾zDk,£OŸãÔgeKC¡@ë~IX�ª‘úv¦•¨‰bxÏŸ¸¹s‰1íyÕ´\#Âð\?°ÔøGïƒš«0\i¥7Q¥5ŸŽmlŒ/•ªû£m~Ž7ŒBR)DýÞW”ƒ«QÈv{@Ýë×NI}9B-×K[?d<–Ò{qWï—CGÌðWáâøaÒwÁtf
¤ývvv×RcÄ}‹ô†kŽ¬u|¿•+ù^ž=ÀrÈ¼Kæ‚Ù ÃúWýKÆ"Ÿ?óv3S|²¦Ø�t°14e…w;AÖ…q(…ÇÉ]á*ÜêˆG€w"�YxÐ9DN�à*�˜˜ib&QúÜ[Óþ�KøK¶{üØ_<ñÇ´æoa¤"7Ýl^THb#EÜwÉe¿ÉJî¶ƒbø»]rùúo”0ÂÏ[gWl¡Ÿ‘²œ!r�Š«Á�B5S¬Øme†Ü,!Š�Ý1 ^—Ü«î¬ä…AhJ²gÈÈn+}eàÊV¶ÙE"¢ãYÈ‘HãÊÖÉ“áèP2èïÓgŠ®0'ùúuž„¥˜aßg·ì=÷¶~&w?%»H
ÝÕ‹.ùú³°ùÇv­ãSáÚÇ© ”À˜ÞšþO¡$iÊ±q�Œ„®Î©¨2‚^ú¤¡ðù/ÀÆ(i6ë11ìóèch3îØUqõºÌ”^Æ>bàK)ÏMp¯¡ƒ�
Âšá?T2|Œ-6›7ÕJd#q•ü#•SV@£)@l¤*L5U<Ç–DfNG`,»lÂ…B¢ç¦Îê>ýÐ­-5üœ«E”ŽÔ¸g€.û¸DG.þÌ|‡§”¥›;ywo”à—„€ÿª ˆm>*@ÚÔ¿�Õ	¼+nÖž·é;Éˆ
1IMZ«’†{†Öü¡¤åÆ¾À?²H7¶ ®¢Ýy_nª_6CÞº÷óšÙ;:[8;ÿí´³³±xèÞŒÒê¤`��èUÆá¤úâ¢˜"Rö‹iÎ€`ƒá¿Î' Ð-?Å
4Ì·³'QœþU?å�.	Ÿ04^ük^nîøc{§Áû¬=#gê¯Örl‰žãUÌ×srD„±J=ÅN×n¢4x¥b³˜ù«õ%ãDO÷ððU‘OÏ?a'J{›`%-™)'*%$ßçˆ´$ûUül=CLô§y»Ž…Žœ‹¢Æ7H®Ï>M®¥õïû™n}ói.Bh®�>Á1ß|5¢’O;2ü"ù´‰¾:æƒcpGAª³ƒR-:>§¬ÔÃ„Di‘±¼gíƒïï>€´ùRž:Œùªåt�ßE{�h•K|�9mQS@þÞ*ê–°òqŒZÏ•	ÉVb)‡€m=e˜j°a\êj[?Ü1ïFðw¬Šã.˜ÆÆ¹ÕB’¯¥££ÂÍ[Ç;%ý–˜‘�Mž”c£%8Pµ7tÌº#ä=K‘¸ÅüþóÒn ÓaÞ×ìpq1}œþ88ñ2Q@ÖXFv"3þù©g÷Ë8°i¬õé“úë]ó¬¹ë÷)8#tìÁõÊs'C„z™]m¯hgTÍûKi|¶úd¡ðÉ¾ÕbDç²â—wÛ:çÌ”Š‡J²^Õ†T˜À/­_O¢ž8Î!¸…tÎ.¬4hô}fßƒƒE¢T•ÍæˆÔ@!VöGÞ7#Ú¥³­¯Nz<XâÅ÷|*6<×>�Kvš`ã)âÎ\håD‡¾Ë÷ú¸x×$:±ZÐ\žxs�E\ëõ±Öj²ö* Ò§ÓŒv¾ñ*ËB¨yËèìCÑž|iÕ^Z�¯@úì—FüþÓ12ºcµ=^­¬W›ÎZ]	¾[@=
1Á«Ž³3ìœ_ž�U·Þœr ¥{è Ý_ú{C&ÀÞÖÛºDkBÖù®�5wá4|¬¾ÀÃ¸OÂcÿfž zl™ÛDÉ?“ëEfJÁ&g˜Û(¿DÁ;Á>‡ÏþÇæ…ÉàŽà‡ZfŸµl—‹Ÿ5|:a7†â¤RO†O'P4h©çøOhbó¬y?fzuT·rÅÖ�x‡¥Ê@7,+”¡BÆöYa§W|îX“†VÎËüW¾º|mÉ+«ñ9ý«Ü¢ÝôfpN›fÜ¹>õœ{5úÝð‡çõÏÕ¬CøNÃ¡û c;7gp]Ä÷ÅJO…Ó­Ûåö>ùL?xf‚[üm/êM³Îí§rñºŒø_^úÐ¨ØêüèÁ§AÀo›á|6/­\’1>:~ÿéèÀôC­Œ|oÜtÓej¡ƒÅêB-&—“Ófæôõ³xO¥ýª˜�y_ÈlôçäEíïŒú4O½Â¬sãþ*b@«ªY=/ò§*žè[�Ñ<šfmîä¾f‘=#5íy5x­}ØÙ±š?<:gáz¿áÝr±’µ««�ƒöõëþøç&¿…O¬¬®»òtÔ‚_^ÉÜñ.Ñ…JW¢ˆàã×o/×aäàmïf]½¸„Ò6àej^#|†�"g25Mœ¥rëydèêía³ä?_ -¦Ià–î¬çƒ¿v-‡í©ä*Q†î�–uñP°ÞÈ^ClØDÜ¿dümƒO?sÉØdê}öàA•>.z¶Ãºí†£áDå<†"…È¯Þßt“ù�žFÚðÂKœ|¸á?Ñæþ_ü7Š;èkÃtæ(ßàßY/tR™€?]S�Ç§£þPK
     –J[               attachables/PK    å•J[
ß8ð
       attachables/ruby_boots.jsonm‘ÍŠÃ0„ÏéSŸ‹“Þ–,û$ËbœDiþ)²Jß}å˜¶9Lš™oùq¨ÔÉ[63PÂT{Tgý¥uÍc€žìÈ­e¶ýÕvÄ!±J�zÂ—PU
Œ#eN€»[ZšºÅt1rR§Õ$m@h]zÅ2k´“ã²ä#g¥ Hg`ÞŠùŒ²åY�wžö‰/±öq —ê•S¯×:ï}˜úú-dðæâ0ð¶ûÑÓ²Ûýõuò6DôÚ®Ë>6”²ËÍÜ,É.Mžn™4[Â¼þ5jœ]€ÌŒ	etü96ºùÞâ$;ˆ¡��):'o+ŒßBþu±•;­ñ?ù<rž‡PK    -–J[æ–¯<  '      attachables/ruby_chestplate.jsonm‘ÍŠÃ0„ÏéSŸ‹ÓÞ–.û$ËbÔDiþ	²RJß}å˜¶9Bš™olù¾iÌ�8€¸+r¦ÍqköËîÍNµ@;†AŽ ÝNÕ¡±Æô˜;¦Qj¨ŒC=F¡��'âÍÏGžN³ë.˜eô hv‹S+‘	|~fp€ÉKI‡ÄÕÙŒZû—âÞ£byT¤àM&ÆÏÄ§Ø†Ô£ÏíÂi—³>½”»ö%9îì)ÊºûŒ) ðü±û)ÚË &êíÒnWKY¡êVW·�u«.£LcÁ]�©<DÍ;3²»R&�m¶{»ÿ^ó4Ü«¡KQ8y¯Ï¬�ßŠ~mµÕ“-ñ?ý=6ú=6ÿPK    4–J[,ÿAg        attachables/ruby_helmet.jsonm‘ÍŠÃ0„ÏéSŸ‹ÓÞ–,û$ËbÜDiþ)²RJß}å¸is„43ßØòcW©!’·ln@	cPm­ŽúKÔ^4�:²·–Ùv{r ‰Uª‡Ô^¹„ò¨RØC`(sÜÝÔÒxšÌœVûÙ%u@h]Zr6ØÑqNYò‘Š³R¤40ôoÅ|FÙò,H†;�ÛÄEl|ìÁ¥fæ4ó¹Ž›EŸ ¦®yK¼9;¼î>C”»Ñ´Ù½ˆú2z"özn×¯…¬0e›«\-É6M¯u³„ù^Yãìdn˜P†õO}Ð‡ï5PÒ½º˜¢sò¾Bù-ìÏP[9Öÿ“ßs'ßs÷PK    2–J[m˜›  !     attachables/ruby_leggings.jsonm‘ÍjÃ0„ÏÎS�ƒìöTRú$%Å^;ú	«µ‰)y÷®,œø0>ìÌ|#­þ•"yËfJƒ:ÕêCéVEó #;ðÉ2Ûîj/Ä!±Jõ�:Â—PU
{ŒeN€»[N4]ã`1ŒIWŸ¡uiKfÜ`'Ç9gÉG*ÎJA�ÚÀÐ?óeË£ î<¼'nbãc.5+§YOöù¶èÀÔ5OÉ ƒ7£ÃÀûî¢¦åm÷&êëämˆØëµ]?W²•�îîp³$5	xºeØl	ó#ä´qv23&”IýS·ºýÞÓ$Ú‹¡‹�):',ˆß~u±•S­ñ³üù‡PK
     Î•J[               items/PK    ¸€J[ÈÂDšu   ª      items/ruby.jsonMŽA
„0E×¼Ã�µÈè²—´¦˜¦’fDïn«g‘,òò’¿×•%úYPEû‚®íúöM¡��^-)†L‹c`Âä„f½�kf€&d%O(å
ãúÝ¬üÆ
>šKt1Ì‘ó^z¼¿îNðX¹å:NPK    ]J[+Z²P|   ¶      items/ruby_apple.json]Ž±
Ã0DgúEsIGÿLp±ldµ4”ü{ídHé º{wú\/B’ètz¡Jöc?Þûº¦Fbôâ‚ZRŒUmŒ�‹ÊzûÍ ÍÈJ�PZ
ã{Y­<ëär^šiëvÜ§˜Ww9éŸ&üñÏÖUgûPK    jJ[Áê5z   ²      items/ruby_axe.jsonUŽ±ƒ0Dç õ�g„JÇüJƒ#Yjä˜
TñïM`hìÁwïÎŸ[c $‰NÇ7J¦Ä`[úáÑß¡«j$F/.¨%ÅXÔÊ˜0{¡YOâ¸ 	Y)JMa\_›•å¹�nE¨–½;`Ÿâœ¸xó�ýëñçW²¬2ûPK    Ñ•J[5ns{   ¶      items/ruby_boots.json]Ž±
Ã0DgúEsIGÿLHµdµ4”ü{ídHé º{wú\/¢hmx¡fƒ¾íïmMU1£y2LE­ŒƒsPZì ö›š‘�"¡ÖÆ÷cõúœÖa±Õ´5;$-ÂÅ�Oú§)ü³e•Ù¾PK    bJ[èŽ�}   ¸      items/ruby_bundle.jsoneŽAƒ0…Ïì?HÎ"sÇþÑ6…€M%�c2üïkõ0a‡ä�÷¾÷ò¹ß„$qÔá…’)1Øú®vh«‰ÑÉÔ’b,jexÌNhÑ“8nÈ#+B©)Œïy³²NÛ0­ìg„êÚÛƒw).‰‹=ÿðK•;ùƒË*³PK    JJ[ÍCÀ÷|   ¸      items/ruby_carrot.jsoneŽ±
Ã0DgúEsMGÿLpµdµ4„ü{ídh¡ƒ4èîÝi=Ÿ$Ñl|¡�¡nýº¦fbŒ’y2ÌUmŒƒ	KTší ö›š��¡¶Æ÷cñú¼/cªbÐ\[·óQò,\íå‹ÿTÅã‘?¸®:ÛPK    Å•J[�f�í   À      items/ruby_chestplate.jsonmŽ=Â0Fç qä¹ª(c.S×–š9QU½;I;ÀÀ`þüž½žO|’àt|‘Nì†~¸õWèZ8ŠójY)Ô´1&*(œõ ö™ž(*{&i–Hïy±ò¼/#>¨hž�´Í­Û˜BN±"å«ø9‡Ç3µÕÚ>PK    ¼•J[48\S}   ¸      items/ruby_helmet.jsoneŽ±
Ã0DgúEsMGÿLH™
"9Èjiù÷ÚÉÐBiÐÝ»Óz>9ˆIy°þ…š)	øtmwk¯ÐT•I0èÍ“!µ2FÌAi¶ƒØohD1Š„ZSßÓâõy_úNŒÕµ5;ÏIŠ=ñŸªp<ò—Ufû PK    uJ[�ì>Qz   ²      items/ruby_hoe.jsonUŽ±Â0Dç ñÈsUQÆüLURGXjìÊ1ˆ
õßIÚ:ØƒïÞ�?ç“ƒ(šë_¨™„Á_ k»[{…¦ª‰ƒÑ<¦¢VÆÁˆ9(Í¶ÛÍ�ÈF‘Pk
ã{Z¼>ïKÿ„jY›’fáâÍ?ö¯'ì_É²Ê¬_PK    …J[¯U+n|   ¸      items/ruby_hopper.jsoneŽAÂ0…Ïü’óÎcÿÌ˜]Š›”´ŠCößM·ƒ‚‡äðÞûòò>DÑ4Õñ‰ZHü	†~¸ôgèš›ˆ1è«§ŠÉÜÆ8˜±¥\wbÓÐŒ\)j»Âøº/^×e¼IÎ&¶ÔÚm|�”…-^¾øOUØùƒmÙ¬PK    Ë•J[oq“.~   ¼      items/ruby_leggings.jsonmŽ1ƒ0EçTê�g„ c.ƒhp"KÄAŽ[UÜ½	íÐÁüýžý¾^ø$qÒñ‰’)1Ø†n¸u=´5�ÄèdòjI1–´2fÌNhÕ“8fhFVò„R-Œ¯e³ò¸oã‚!‡uooƒKqM\€üüsç+ðÒJíPK    VJ[kk{€   Â      items/ruby_nether_wart.jsonmŽ1Â0Eç qä¹ª(c.…Ô–ˆS9Z¡Þ�¤``°¿g¿�1Kòêž(…2ƒ=ÁÐ—þ]K1ñQ-)¦š6ÆÀˆ%MºÛÌ �ÈJ‘Pš…q¾/V×Å1êÅ½¼(´ÕµÛ$!§)seÊ×ñs/ìßü7ÔVký PK    UJ[PL%~   º      items/ruby_pickaxe.jsoneŽ½
Ã0„gúEsMG¿LpDëdµ$„¼{ídh¡ƒ4èî»Óz>ðI‚ÓñMR8E°úáÖ_¡kjàH(Î«e¥PÕÆ˜¨ pÖƒØox¢¨ì™¤¥DšŸ‹•×}3ãÃÍÍ¶u{ ¦�S¬þòåºðøäŸ®«ÎöPK    pJ[WUtí}   ¸      items/ruby_shovel.jsoneŽ±
Ã0DgúEsMGÿLH™
b)Èjh(ù÷ÚÉÐBiÐÝ»Óû|rEÓ`ý‚šIüº¶»µWhªšˆ1èÍ“a*jeŒ˜ƒÒl±ßÐˆl	µ¦0¾¦Õëó¾öù!NP][³óAÒ,\ìù‹ÿT…ã‘?¸¬2ÛPK    TJ[ÄnS!|   ¶      items/ruby_sword.json]Ž½
Ã0„gúEsMG¿LHmµdõ'”¼{ídhé ºûîô>$Ñ<Ùø@-$þC?\ú3tMÍÄtJæÉ0Wµ1"– 4ÛNl7‘�¡¶Æ×mñz¿.cyŠFh¦µÛð y®îò¥šÂþÇ?[W�õPK
     /¡F[               models/PK
     arJ[               models/blocks/PK    *˜G[P¥QL  À     models/blocks/crop.geo.jsonµ’Ûjƒ0Ç¯ãSH®SÑÒv¥¯"">ë3‘$ÚmÅw_âinka»ð�üæ—xw-„¬· 
N/.¼`ïù”±B™L
}¹‚¨@ËwcBL’šƒÊ$Özös„b\c� mÙ”ó2)jÊ‹†7ÝHˆo˜ëÒ¸‚Ó¡¼–z©´¨0}…8ÏÕœÜ?–çüÞ;>vˆ¢P`¡Ï\ß{9š{d�]ï§©à FØ×Ìò¤KeÕ†Ð[Ñ7�MswçhR²&]”Ì5F¯h7-<˜D°LUáŒKNöZhM;ou?†DÙ�ß!´~?b®ÆSÍn,éØWìßbþïÔš2ÓKÇVhvŒ³²®ç0«{°g<ÒÃš¾°ÿmžÓp!u¹G‰F—ÿÆc�ç)ÏiÏêÊVˆ¶ünÃ3ræAäô/‘Ó}PK    ó¢I[¸Ûé       models/blocks/fence.geo.jsonÝ˜ÑŽª0†¯á)×`TÔ}cŒJÑ&G0Pt÷œøî§DÄ)–,1Ù,†éü�2_¥ÿtÍ¢ø¸!ë3Š…æ—aÂÜ‘cZ4xÄ!ÚÅ›€|íQtD$þ¡–º¦ÑLM3}”ìb|"ybvO3±�B‚Œb&vË(Ü!ÓÊÇôMÒ­/Ø':¼Zà€ðþ@ª‘3NðöZo£4ô“2Óå‡Ë|w4å�ˆ‚ AlÄÒ±g4›Òÿ+6òš�7·Qˆ’bµÅzéÝpsDlY,Z,F3Oø•JLæØ¥ÛŠF©B#QŒ÷˜=´¥ífI¶»²Êh‚ÿ²Y–‹­ß˜TBé¹|ÒyAQL·Š1K¯RIq{ý ¼*c×û0m¢R/‰RÉ_^�ÚÓWhS@¨gSA{Âô£K¨BR¯}(®+½2a�Ëðå&F!QÌ*X}ÊöœÃ*ÅxL£�P�YÆLôX˜®$©à4èyB=1©¬>ùú„ v+OÀéâá¡×�¢ÅÙž$¥‹l«´U|‚ô–(â\«€@1@  PP!÷� µp¹¤—«»œ;l“²Çu÷`1£ ÒžMN?ïzH?C¶8PíqàˆŸ‡úõ¸yþK8�z¤n¤BH»íz!¤ÝLIiGWRÚ­¾H+ÅÑB¦¼ìQR9E9—›ç.×AÐ‘ u¶ÔL¨%H�"‚ZøÜ¥WŸsû±9ùŸ²Í½Áhø¾ ”É¹=yü¾É½Âg°70|èõîpzñ1›·ãùšš OÊOÊÇüð NÊ[�”Ïß<)¿÷�Îo<Ÿó¶œLN¹¯d’n›G&'ß!UO©äTÏ”yû MÚíÖëöL>¯;Ñ°Œ)û*s8ýa§ôÏ‘èOþ“Àx²ò†ö°ÅÔs¼zN½=mæ©u§Í4ÍÉßåÎ+|CkŠ#n–tg`,ßš¦Ö@‡Ö@‡Ö€|k``­Ñ5Þ¸N/×ÿPK    b©I[S~¤p  ´%  !   models/blocks/fence_gate.geo.jsoníXíŽ¢0ý�Oaø¤|Î8¯bŒA­N“•@gw'¾û¶|ÔBBíd�]’Ip¸=§·=÷Þ–û93Ì=N�q¾¾À4C81ßæ¦ë¸žL‹�(�Û4Þçoˆ�0O‘Ë™a¤a˜;˜mStÊK`ñÎ0Ñ&9Ú#˜R²çìa²…ëCœCÓ*æðg~Náúíòw2Ö�Z†wˆï9o¹ m~ÀõŸ“]Æ�žÜÌð¾ÊGàý>ƒtÄXs×ñBkVtäµonp³jÉÕ¢ÉÛ$>Bº6j­c˜'tÁ5ý[Õ†íyÃq0bÁ): ºsËÈš“©mwe1c†~ÓI–qÌµæg:_Øn—þà´Ø†ÏÒ²ôVÖœü¿nð¬®Öã,çP`*ÃçÆdÑ0ØlLC�Oü²Š]m#¼`‡?~j 6ÁÔ�ú#iËC1ö«^}TÔQÒFE™'Ó¥|IY‰$ä!>Áä–‡qJêÏéÙÒÝ¥Ž…ÔYÎSŸmR];n«mì—~ÁÖ_áý~üÝù;ðÑƒþGÃÜï™~¨÷dÞþÀÔ§³$ÏýÒß€ËeÁmfa3‘Êà§]RœÇÕÁH}YðÎ¨$C^Äñï”© AÒ®.0O«BDæiˆÂ*ÌôˆHê–Œ,Ü~ÿO (Ý…p¼¢¡Š¢áxEÃ±Šº#�…8‹Š¦ LÒŽ³x1ü(®Ÿ#E ’ƒø.F<‡ïBžåzäi’d’D›$aYùÀ¸Âg³ÊWiY˜§ÂÇ¯Œ?Ê$êÑe�JC¨Às:ÝMn�A·ÚYõój=Ø@Y“›ïŽXK§>ŠÐGyô;�½2Z•†¡õ}Ìl²4�*#Ë�2R¥¬sH`TËã`üyØÉæÉÉêôÝ£�n:›ç›�ªµ¯_Gº£H=†:bHwý—ñ£ÖGÐûÕ)[íKÏjýñ¡×ItÒuGž”üëŒ¼HÉ¿fàñ{ç÷øŒ¼¨©vÀñ}¿~‡l÷BM‘g²�½‘ê�¼Pgä¹#¯îÂè‹=Í}Ác¯ßß…Æàé¦ë=0øúÎKºfì|¿ë–÷�‡Î:_:Š}.­m‘±RLž$ò¸½z¯ZÁ—wÏÐ�“mž¾;þtÇÿkwüò¹š5û†3ò¸þPK    ,J[~™¤xq  r     models/blocks/hopper.geo.jsoníXÛŽ›0}†¯@<„)8Ùý„"Lb©ÁÈ@ÒÝUþ½6·%„Pìm»< ¡„0sfðœ3¾äC×Ì„²sTì.˜å„¦æ«aBº¶cn<“X”¯GLÏ¸`oÜ!Ð5�#5ÍŒq~`$+j`õL3IŒÓ‚$3¬ÅÙ'šeü¨�
ü«(Þ]I\œ¸DÃ	“ã©è[.$'ûŸx·§eçÒ7wx×öÇ=h’äXx0{ãóÏPxÞ*sOSœ7ÃmÌŸ¦Ñ‹q	k3ÍÌÈ…v‘D˜Öp(÷½]n¡Œ‰¨Z`yÀà—å… ³æä]d	¶À@ÀØö,å¥«tý>”Ueø¨-�„B`ð_»~�ð>18ÊIHNKé4W,�¦Ìz€º’w .ÞCbzMïA� ,kQíÍLñÂßúöÇn{ôƒ:6¬ÓÍ¦çÙ˜¦ø™…ôäØ/SÍJõu’ zDU�rþ6?ÐW ú*Í@=4´¤6ÚŽÖfŒ%`ø¶ Ñûz!…&B*M„ZhÁóÜ'C£}ä·­}çJ4ÁPK�¿2ôímž,Ekýs‚êïPï±5ÜµWñ›|f1~Ry¾™[qMF<2qš˜Sé€àV Ë‘V}v�Y<EÑÝAM«ºš…êê‡;ÂÞêŠå3•Ù �ëü	25ËS“U]c	ˆ\HZÞ“@^¢±Tf¨4½@•ùJN0pŒS«›-þã$SW@NJbðV-,]ÕXæJÁByr«Cóï‘»jaéZÀ‘„T¤ v¡«¦½¹å7¡~ûPK    vJ[[4Fb-  ,     models/blocks/mob_head.geo.json��Ënƒ0E×æ+�×‘(i£ü
²„!ŒTlä´�ø÷Ú¼J¢,ÚlŒ=÷ž;ÌÜB+©šÜd(�RÐsHwñn'”9±A•Wæ|Ù€Q_Î�„8’Z‚¾(lÍŽ5B±a°BP>láâFYyIÙd3ði¬‚¬ÇÒÔÎ¹{{jÀkm¶J‡‹È
iE©Wrÿ\^ù}||î�U¥Á;Ò„…Iü~t'÷ÎaôÓB
ÐóÀóÈ®*òüd^�‡!´ÅN.I'³[l2Ö§H…Wô{K£ƒ#X8[U�ß¾KzóNÅvë®§ÿ‘j\ÃmRÆY8Ý+[BÜÝùÀ~)Èµù7¤¥}¡U/´²í™õ{(òÕè+e/þ.ÜÜ}y°>x0^x0ü PK    ˜G[½¨ì%I  Ä  "   models/blocks/nether_wart.geo.jsonµ’Ýn‚0†�ËU�F�ñV! ò%£5m�m†{_Ëß˜Sw ´¼?íS¸:„BV©N�
§G—†~¸ñÊŒX!‡“L}<ƒ¨@ËOcˆBL’šƒ:I¼è!Ø¿#sàiË¦œÏA— “6•š²Á©áC×’s]s¸¿JÀs©—Jƒ
³wH2Qó\ÍÉÍ}yÎoüÝ}‡(
ÖÌü·�¹ÇÖÙõ~š	jd©Í[žV`á¬:ÂzÁFôMÓÁ\ïOÊ©Î%s�Q„Ä3Ú³‹¶7!#*ü‚qgáÞ^­næïç�*»öu"ëbæši2ÕxcIÇ~b-üŠSShÊLƒŽ­Àx¯¥YÙÖc–Õ#xŽfü Û;4Ca¿ÒC.¤.ŸÆQ¢Öåò¼
guc+@ÏülÃ3væIìôƒØé¾PK    óH[t$]*)  &     models/blocks/slab.geo.json•�Mnƒ0…×æÈkƒBÔÐ(WA‚0ÀHÁF¶!i#î^›¿B•E³1xÞûÞxæéZJÕd&íAi”‚^|…Ñ1<PfÅ\UVšK²£¾¬!ñ±$!´ }UØš	k„bÂ`‰ \ØÂ…ú–å”MÓ)HïX˜Úº¢ø�PVµÙ*=jÌo�æ²…^Éãkyå�áéµC–¥çHÌ?„Ÿ'{rçF?Í¥ =;�k«"kÀMåÔyB[ìå’tv1‹píòMÆšb©°B·³$°Äóƒ3g«ªñÛuI¢xŒâ�Öõë¦§I5.â9)ã4œùö–îbøÀ~!È´y—Ñ²{¿ÑÞoÔµ"Š§ùwLàªöØa…¼‹ÿƒ7x»/÷Ö÷Æî?PK    ëJ[%àÔj  Å     models/blocks/trapdoor.geo.jsoní“Énƒ0†ÏðÈgˆ KšäUB,Cb)x�mHÛˆwï˜­Ð5\zêÅ€ÿùþñŒ‡«m±e™è¸©8
vpX°	î6>sI,¹€L&…>KÐò…BÛ²ˆ´,–ƒÊ$¯tv{ã9ÍÒ˜�ÜFË¤Ê%sû0Ïº–_x®Ol?'àÇ“ž+W<=Cœb-r5‘÷_Ëÿ�ŽE¡Àè¡ï:þæ‘–È¶]8KQ€ªê¥]‘”`Ê2êP‰Å*Þàh´mŸOdvF5CIÝúÝq²:��jr'%?rs¡g×ñv‘;©Š¿šì!õ’úA-�iu3]^R”]_¯½Òµ'rúŠ6Që¾C�(½–QX¯Ot�õ‰êjFÛ¾þã™]ZXŽq;8r­½xþ8X�X3ÞÃêaØÓ¡éÆ½§§aJç®³Ÿ¥#M¢N†Ÿ�ŒÍyöþ2â–þ`–ìá•^"»}PK    '¢I[­‰€t  º     models/blocks/wall.geo.jsoní˜ÛŽª0†¯ËS�^ƒ‘Ã88¯bA)ÚD©)EöÌŽï¾Ë©‚f4Û1]ëÿ{X_-ö¯`@èÞcîÑ“~¨Ð˜æd
5Üã­©°�"{Äè'OX( p% ÐGÑšâË…Y€ØG!ÃF45+u“ÄÛí –§0ô‡Å¹	öÙ–g³‹ÀáÍ–U#GáÕ¹+‡~$”¦<,ôæäMžA‚ BiÆbª©ÓÉûÿ\¦™§,®Hˆ¢b²ÅtykèíQ:«4ZLÀ>áTÚ>åûg¡GùR]÷+ëxU“ðæBñ§5Xèv&Òí¥&¢þJû^8ZºžªS	ÅGQ¸|˜„²m­©ÈYTG@ÖìÖŒ—"v:§AäElH¿ˆÄÃ0AÃ0>@™›S[÷º›Îƒº#·óIÞo¨\|)ž�¤†Ñ( 
P­vPg|y­1@ÍŒÕÔ®ÞªÕÔYW³@íeØj¨	z$PÛúâ!J˜Wœ•it~¾ñâ¿ñ§G<‘ð3áN;áöÃsj�Âé€œÚ·pjwbe÷çôû†×9mrú#¯öS`úÂ´Ó_Æ4¹Ó‹#\Ÿ�ðcû¢¸ƒbð¢¸¤¸ùÚÛF±aŽñ<sä‚Ÿ†â¦ß‹b‘ö+Å×¬ßÛ.]Î¹ÏÛ¿G´äá\ÿ[÷ˆÃíÖâVðÞKÁ²jŽBÅe)Û‘}4b×õ•;«�&>TùMš*¹@“í�ë¢Ê.ÈŸÚõÊè†ùßÕæñkS¼ª½·r†}_mòCR›�ÝÐ|£6ÍŽÔ¿£Zmò£A•œµé5j£ Ùé¡ðÇéPK
     Ù¬[               texts/PK    "…J[èóPÇ’   ¯     texts/en_US.langmÏ;Â0€á‰;ô= °0!c•¶–R‘Æ‘›¾nOœ-þó)V:}ia1ë�Æz-­êáøØNûÝßU5ÌHíÏ0Kæºæ­`ðþ-’¦ì”#ƒÆ	ßƒ„ù[WÌ­sÎ$Ã,Y=Ú6qç$lz/1HhÁk jVÄõ-ÖâµÕÜwœJ~ÂPK 
     ,|J[            	 $              textures/
         3		tê9Ü‘H•§;Ül¹‚35ÜPK 
     J[             $          '   textures/blocks/
         Q¢í9Ü‘H•§;Üa’çùØ6ÜPK     �~J[G^ž‡„  Ü   $           U   textures/blocks/ruby_block.png
         mìí9ÜKèm€;ÜN§xì9ÜPK     �~J[.ÀõÕ<  –  ( $             textures/blocks/ruby_carrots_stage_0.png
         5ºÃí9ÜKèm€;Ü•Õô™ì6ÜPK     ‹~J[TÀ‹V  µ  ( $           —  textures/blocks/ruby_carrots_stage_1.png
         %¥Ìí9ÜÒ'èm€;Üµ#õ™ì6ÜPK     ‡~J[B“qw  Õ  ( $           3  textures/blocks/ruby_carrots_stage_2.png
         !@ií9Ü£3n€;ÜÄJõ™ì6ÜPK     ƒ~J[dqœvž    ( $           ð  textures/blocks/ruby_carrots_stage_3.png
         €÷Îí9Üi;èm€;Üt‡ô™ì6ÜPK     J[¥>Û(e  ¾  & $           Ô  textures/blocks/ruby_hopper_inside.png
         À�¢í9Üi;èm€;Ü/>{z9ÜPK     J[{4¢}  ×  ' $           }
  textures/blocks/ruby_hopper_outside.png
         Â@Ÿí9Üi;èm€;Üc’bí9ÜPK     J[<1•Oe  Å  # $           ?  textures/blocks/ruby_hopper_top.png
         úØmží9Üi;èm€;Ü c|z9ÜPK     L~J[›zL²Z  ¹  , $           å  textures/blocks/ruby_nether_wart_stage_0.png
         o�dÔì9ÜåNèm€;ÜÄK Öª7ÜPK     E~J[ÄúØ—€  Þ  , $           ‰  textures/blocks/ruby_nether_wart_stage_1.png
         íéËì9ÜåNèm€;Ü‚¯Öª7ÜPK     F~J[×ïäê™  ö  , $           S  textures/blocks/ruby_nether_wart_stage_2.png
         ÒKéÌì9ÜåNèm€;Ü ýÖª7ÜPK     õ}J[÷Â÷”  ñ  ! $           6  textures/blocks/ruby_trapdoor.png
         ˆetì9ÜåNèm€;ÜÞFÊ]y9ÜPK 
     …J[             $          	  textures/items/
         ÖóšØó9Ü\•§;Ü“¹‚35ÜPK 
     5}J[†ðC          $           6  textures/items/colors.txt
         ]¥w�ë9Ü]¥w�ë9Ü²¡…‘ë9ÜPK     ò}J[H%dU‰  ë   $           �  textures/items/ruby.png
         fô‘oì9ÜTnn€;ÜKÿDå9ÜPK     ï|J[üsY‘  ó   $           K  textures/items/ruby_apple.png
         ôONë9Ü¾qn€;ÜªèèWå9ÜPK     ï|J[†ì�2†  é   $             textures/items/ruby_axe.png
         ¶&ùMë9ÜF…n€;Ü7<ždå9ÜPK     ô|J[¦Z	o  Ó   $           Ö  textures/items/ruby_boots.png
         }âéTë9Ü¾qn€;ÜRŠždå9ÜPK     T|J[š1Ž÷œ  ü   $           €  textures/items/ruby_bundle.png
         Ú�¢ê9ÜÌ˜n€;Ü4ú¥P6ÜPK     ò|J[JWø”™  ü   $           X  textures/items/ruby_carrot.png
         àDRë9ÜÌ˜n€;ÜÚ]éWå9ÜPK     î|J[±{’Êˆ  ê  " $           -   textures/items/ruby_chestplate.png
         K�Më9ÜÌ˜n€;Üj±ždå9ÜPK     	}J[AEËÙq  Ò   $           õ!  textures/items/ruby_helmet.png
         ttkë9ÜX¬n€;Ü„ÿždå9ÜPK     î|J[Âœ·<ƒ  ç   $           ¢#  textures/items/ruby_hoe.png
         =;Më9ÜX¬n€;Üž&Ÿdå9ÜPK     …J[ŒP«"„  æ   $           ^%  textures/items/ruby_hopper.png
         ‹0šØó9Üã¿n€;ÜŽ&¨Ïó9ÜPK     í|J[•K@©t  Ø    $           '  textures/items/ruby_leggings.png
         æëKLë9Üã¿n€;Ü¸tŸdå9ÜPK     v}J[�±Øˆg  Å  # $           Ð(  textures/items/ruby_nether_wart.png
         —dæë9Üã¿n€;ÜªW˜H«7ÜPK     }J[ñ|+r†  é   $           x*  textures/items/ruby_pickaxe.png
         ÖÌaë9Üã¿n€;ÜÃ›Ÿdå9ÜPK     }J[ƒÒ{  Þ   $           ;,  textures/items/ruby_shovel.png
         ÈEˆgë9Üã¿n€;Ü Ç�då9ÜPK     î|J[·Ž4
”  ÷   $           ò-  textures/items/ruby_sword.png
         ÷ XMë9Üh-n€;Ü(ždå9ÜPK     z•J[;†:á   z   $           Á/  textures/item_texture.json
         «šf:Üâè•-§;Ü°Í¹‚35ÜPK 
     ,|J[             $          Ú0  textures/models/
         PW	tê9Ü\•§;Ü3		tê9ÜPK 
     @}J[             $          1  textures/models/armor/
         £È§ë9Ü\•§;ÜPW	tê9ÜPK     @}J[Á£üÖà  ,    $           <1  textures/models/armor/ruby_1.png
         ß-È§ë9ÜJB
n€;Ü[~tê9ÜPK     @}J[K”Áô  O    $           Z4  textures/models/armor/ruby_2.png
         hÕ@§ë9ÜJB
n€;ÜwÌtê9ÜPK     Æ€J[ÞYLð  �   $           Œ6  textures/terrain_texture.json
         p¤�	ï9Üª—-§;Ü‹©ªíØ6ÜPK     Ï€J[	-….‘   r   $           ß7  blocks.json
         :ï9Ü®àeÔv;Ü„"*ÝØ6ÜPK     ’¤L[ÿ½Ÿô   Å   $           ™8  manifest.json
         Z’$§;ÜZ’$§;Üëø·‚35ÜPK     ‚­[2—±�  f#   $           ¸9  pack_icon.png
         Ñp>óÓÜ@›-§;Ü–Z¸‚35ÜPK 
     –J[             $          õS  attachables/
         ÕçÆ:Ü‘H•§;Üº›Åu:ÜPK     å•J[
ß8ð
     $           T  attachables/ruby_boots.json
         ¹ŠS…:ÜEáÑv;ÜÂî®z:ÜPK     -–J[æ–¯<  '    $           bU  attachables/ruby_chestplate.json
         Ñàh×:Ü¤XáÑv;Ü¨ì¯z:ÜPK     4–J[,ÿAg      $           ­V  attachables/ruby_helmet.json
         ˜ ²ß:Ü¤XáÑv;ÜJN°z:ÜPK     2–J[m˜›  !   $           óW  attachables/ruby_leggings.json
         Å�_Ü:Ü'láÑv;ÜÿÖ°z:ÜPK 
     Î•J[             $          <Y  items/
         �Q§l:Ü‘H•§;Ü¨�¸‚35ÜPK     ¸€J[ÈÂDšu   ª    $           `Y  items/ruby.json
         ÊF™úî9ÜEnÔv;Ü»böî9ÜPK     ]J[+Z²P|   ¶    $           Z  items/ruby_apple.json
         —¡1î9Ü§XnÔv;Ü…¦´ò5ÜPK     jJ[Áê5z   ²    $           ±Z  items/ruby_axe.json
         Äy$î9Ü§XnÔv;Ü®�Öî9ÜPK     Ñ•J[5ns{   ¶    $           \[  items/ruby_boots.json
         ~¬Ûp:Ü§XnÔv;Üêj:ÜPK     bJ[èŽ�}   ¸    $           
\  items/ruby_bundle.json
         W*¼
î9Ü lnÔv;Üˆò46ÜPK     JJ[ÍCÀ÷|   ¸    $           »\  items/ruby_carrot.json
         foïí9Ü lnÔv;Ü.»u6ÜPK     Å•J[�f�í   À    $           k]  items/ruby_chestplate.json
         ™FSb:Ü lnÔv;Ü7LØZ:ÜPK     ¼•J[48\S}   ¸    $           "^  items/ruby_helmet.json
         ;‹Z:Ü lnÔv;Üq¶-S:ÜPK     uJ[�ì>Qz   ²    $           Ó^  items/ruby_hoe.json
         ¿~ î9Ü lnÔv;Ü¿®‰î9ÜPK     …J[¯U+n|   ¸    $           ~_  items/ruby_hopper.json
         ŽòÊó9Ü¯nÔv;ÜÆëþÃó9ÜPK     Ë•J[oq“.~   ¼    $           .`  items/ruby_leggings.json
         þ'Ni:Ü¯nÔv;Ü¦­ïb:ÜPK     VJ[kk{€   Â    $           â`  items/ruby_nether_wart.json
         (²úýí9Ü¯nÔv;Ü6!4«7ÜPK     UJ[PL%~   º    $           ›a  items/ruby_pickaxe.json
         u?Åüí9Ü¯nÔv;Ü[iX&ê5ÜPK     pJ[WUtí}   ¸    $           Nb  items/ruby_shovel.json
         V
åî9Ü/“nÔv;Ü.s#î9ÜPK     TJ[ÄnS!|   ¶    $           ÿb  items/ruby_sword.json
         Ð°wûí9Ü/“nÔv;ÜA¼¸‚35ÜPK 
     /¡F[             $          ®c  models/
         ÅÑE\ì6Ü‘H•§;ÜU¶Xì6ÜPK 
     arJ[             $          Óc  models/blocks/
         ¯4Nà9Ü‘H•§;Ü(Ý…[ì6ÜPK     *˜G[P¥QL  À   $           ÿc  models/blocks/crop.geo.json
         åý¦¬7Ü*sàm€;Ü¥cÍ¢7ÜPK     ó¢I[¸Ûé     $           „e  models/blocks/fence.geo.json
         L1ÕI9ÜÕ†àm€;Ü´ ÿò÷7ÜPK     b©I[S~¤p  ´%  ! $           §h  models/blocks/fence_gate.geo.json
         !øiuP9ÜÁ­àm€;Üá÷ºÃx8ÜPK     ,J[~™¤xq  r   $           Vl  models/blocks/hopper.geo.json
         áå8‰9ÜÓÔàm€;Ü’oj‚9ÜPK     vJ[[4Fb-  ,   $           o  models/blocks/mob_head.geo.json
         €\<ä9Üfèàm€;Ü”�C}˜9ÜPK     ˜G[½¨ì%I  Ä  " $           lp  models/blocks/nether_wart.geo.json
         â¾$Û«7Üæûàm€;Ü¼ øb«7ÜPK     óH[t$]*)  &   $           õq  models/blocks/slab.geo.json
         µÕhô7Ütám€;ÜtÀ»äÖ7ÜPK     ëJ[%àÔj  Å   $           Ws  models/blocks/trapdoor.geo.json
         @†pÚy9Ü÷"ám€;ÜCE8*s9ÜPK     '¢I[­‰€t  º   $           þt  models/blocks/wall.geo.json
         «G�ðH9Ü÷"ám€;ÜÛûYÝ€8ÜPK 
     Ù¬[             $          «w  texts/
         ¼f8ÓÜ‘H•§;ÜMã¸‚35ÜPK     "…J[èóPÇ’   ¯   $           Ïw  texts/en_US.lang
         Wì�èó9ÜëÖm€;Üæ¹‚35ÜPK    J J K  �x    <?php

namespace Nexly\Exemple;

use Nexly\Items\Components\DataDriven\BundleInteractionItemComponent;
use Nexly\Items\Components\DataDriven\DataDriven;
use Nexly\Items\Components\DataDriven\Property\HoverTextColorProperty;
use Nexly\Items\Components\DataDriven\StorageItemComponent;
use Nexly\Items\Components\DataDriven\StorageWeightLimitItemComponent;
use Nexly\Items\Components\DataDriven\StorageWeightModifierItemComponent;
use Nexly\Items\Creative\CreativeGroup;
use Nexly\Items\Creative\CreativeInfo;
use Nexly\Recipes\Recipe;
use Nexly\Recipes\RecipeBuilder;
use Nexly\Recipes\Types\RecipeType;
use Nexly\Recipes\Types\ShapelessRecipe;
use pocketmine\crafting\ShapelessRecipeType;
use pocketmine\inventory\CreativeCategory;
use pocketmine\item\Item;
use pocketmine\utils\TextFormat;

#[DataDriven]
#[StorageItemComponent(64)]
#[StorageWeightLimitItemComponent(64)]
#[StorageWeightModifierItemComponent(4)]
#[BundleInteractionItemComponent(12)]
#[HoverTextColorProperty(TextFormat::GREEN)]
#[CreativeInfo(CreativeCategory::EQUIPMENT, CreativeGroup::GROUP_BUNDLE)]

#[ShapelessRecipe(ShapelessRecipeType::CRAFTING, ["ruby", "emerald"], ["ruby_bundle"])]
class RubyBundle extends Item
{
    /**
     * @return int
     */
    public function getMaxStackSize(): int
    {
        return 1;
    }
}<?php

namespace Nexly\Exemple;

use Nexly\Blocks\Vanilla\NexlyFence;
use Nexly\Events\Impl\BlockRegistryEvent;
use Nexly\Events\Impl\ItemRegistryEvent;
use Nexly\Events\NexlyEventManager;
use pocketmine\block\Block;
use pocketmine\block\BlockBreakInfo;
use pocketmine\block\BlockIdentifier;
use pocketmine\block\BlockTypeInfo;
use pocketmine\block\FenceGate;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\utils\WoodType;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\ToolTier;
use pocketmine\plugin\PluginBase;

class MainExample extends PluginBase
{
    /**
     * @return void
     */
    protected function onLoad(): void
    {
        NexlyEventManager::getInstance()->listen(ItemRegistryEvent::class, static function (ItemRegistryEvent $ev): void {
            $ev->register("nexly:ruby", new Item(new ItemIdentifier(ItemTypeIds::newId()), "Ruby"));
        });

        NexlyEventManager::getInstance()->listen(BlockRegistryEvent::class, static function (BlockRegistryEvent $ev): void {
            $breakInfo = static fn() => new BlockTypeInfo(BlockBreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0));

            $ev->register("nexly:ruby_carrot", static fn(int $id) => new RubyCarrot(new BlockIdentifier($id), "Ruby Carrot", $breakInfo()));
            $ev->register("nexly:ruby_block", static fn(int $id) => new Block(new BlockIdentifier($id), "Ruby Block", $breakInfo()));
            $ev->register("nexly:ruby_slab", static fn(int $id) => new Slab(new BlockIdentifier($id), "Ruby Slab", $breakInfo()));
            $ev->register("nexly:ruby_stair", static fn(int $id) => new Stair(new BlockIdentifier($id), "Ruby Stair", $breakInfo()));
            $ev->register("nexly:ruby_fence", static fn(int $id) => new NexlyFence(new BlockIdentifier($id), "Ruby Fence", $breakInfo()));
            $ev->register("nexly:ruby_fence_gate", static fn(int $id) => new FenceGate(new BlockIdentifier($id), "Ruby Fence", $breakInfo(), WoodType::OAK));
        });
    }
}<?php

namespace Nexly\Exemple;

use Nexly\Blocks\LegacyItemBlockTrait;
use pocketmine\block\Carrot;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;

class RubyCarrot extends Carrot
{
    use LegacyItemBlockTrait;

    public function getDropsForCompatibleTool(Item $item) : array{
        return [
            $this->asItem()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
        ];
    }
}enable-block-breaking: true
enable-ladder-climbing: true{
    "format_version": "1.21.80",
    "minecraft:block_culling_rules": {
        "description": {
            "identifier": "nexly:culling.trapdoor"
        },
        "rules": [
            {
                "direction": "down",
                "geometry_part": {
                    "bone": "trapdoor",
                    "cube": 0,
                    "face": "down"
                }
            },
            {
                "direction": "north",
                "geometry_part": {
                    "bone": "trapdoor",
                    "cube": 0,
                    "face": "north"
                }
            },
            {
                "direction": "south",
                "geometry_part": {
                    "bone": "trapdoor",
                    "cube": 0,
                    "face": "south"
                }
            },
            {
                "direction": "west",
                "geometry_part": {
                    "bone": "trapdoor",
                    "cube": 0,
                    "face": "west"
                }
            },
            {
                "direction": "east",
                "geometry_part": {
                    "bone": "trapdoor",
                    "cube": 0,
                    "face": "east"
                }
            }
        ]
    }
}{
    "format_version": "1.21.80",
    "minecraft:block_culling_rules": {
        "description": {
            "identifier": "nexly:culling.glass"
        },
        "rules": [
            {
                "condition": "same_block",
                "direction": "down",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "down"
                }
            },
            {
                "condition": "same_block",
                "direction": "up",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "up"
                }
            },
            {
                "condition": "same_block",
                "direction": "north",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "north"
                }
            },
            {
                "condition": "same_block",
                "direction": "south",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "south"
                }
            },
            {
                "condition": "same_block",
                "direction": "west",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "west"
                }
            },
            {
                "condition": "same_block",
                "direction": "east",
                "geometry_part": {
                    "bone": "glass",
                    "cube": 0,
                    "face": "east"
                }
            }
        ]
    }
}{
    "format_version": "1.21.80",
    "minecraft:block_culling_rules": {
        "description": {
            "identifier": "nexly:culling.fence"
        },
        "rules": [
            // Post
            {
                "direction": "down",
                "geometry_part": {
                    "bone": "post",
                    "cube": 0,
                    "face": "down"
                }
            },
            {
                "direction": "up",
                "geometry_part": {
                    "bone": "post",
                    "cube": 0,
                    "face": "up"
                }
            },
            // North rails
            {
                "direction": "north",
                "geometry_part": {
                    "bone": "north_rails",
                    "cube": 0,
                    "face": "north"
                }
            },
            {
                "direction": "north",
                "geometry_part": {
                    "bone": "north_rails",
                    "cube": 1,
                    "face": "north"
                }
            },
            // South rails
            {
                "direction": "south",
                "geometry_part": {
                    "bone": "south_rails",
                    "cube": 0,
                    "face": "south"
                }
            },
            {
                "direction": "south",
                "geometry_part": {
                    "bone": "south_rails",
                    "cube": 1,
                    "face": "south"
                }
            },
            // West rails
            {
                "direction": "west",
                "geometry_part": {
                    "bone": "west_rails",
                    "cube": 0,
                    "face": "west"
                }
            },
            {
                "direction": "west",
                "geometry_part": {
                    "bone": "west_rails",
                    "cube": 1,
                    "face": "west"
                }
            },
            // East rails
            {
                "direction": "east",
                "geometry_part": {
                    "bone": "east_rails",
                    "cube": 0,
                    "face": "east"
                }
            },
            {
                "direction": "east",
                "geometry_part": {
                    "bone": "east_rails",
                    "cube": 1,
                    "face": "east"
                }
            }
        ]
    }
}{
    "format_version": "1.12.0",
    "minecraft:geometry": [
        {
            "description": {
                "identifier": "geometry.head_block",
                "texture_width": 16,
                "texture_height": 16,
                "visible_bounds_width": 2,
                "visible_bounds_height": 2.5,
                "visible_bounds_offset": [0, 0.75, 0]
            },
            "bones": [
                {
                    "name": "bone_0",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_1",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 22.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_2",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 45, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_3",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 67.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_4",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 90, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_5",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 112.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_6",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 135, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_7",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 157.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_8",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 180, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_9",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 202.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_10",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 225, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_11",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 247.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_12",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 270, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_13",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 292.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_14",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 315, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                },
                {
                    "name": "bone_15",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 337.5, 0],
                    "cubes": [
                        {
                            "origin": [-4, 0, -4],
                            "size": [8, 8, 8],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [16, 16]}, "east": {"uv": [0, 0], "uv_size": [16, 16]}, "south": {"uv": [0, 0], "uv_size": [16, 16]}, "west": {"uv": [0, 0], "uv_size": [16, 16]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]}
                            }
                        }
                    ]
                }
            ]
        }
    ]
}
{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.blocks.chest",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bb_main",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-7, 0, -7],
							"size": [14, 10, 14],
							"uv": {
								"north": {"uv": [3.5, 8.25], "uv_size": [3.5, 2.5]},
								"east": {"uv": [0, 8.25], "uv_size": [3.5, 2.5]},
								"south": {"uv": [10.5, 8.25], "uv_size": [3.5, 2.5]},
								"west": {"uv": [7, 8.25], "uv_size": [3.5, 2.5]},
								"up": {"uv": [7, 8.25], "uv_size": [-3.5, -3.5]},
								"down": {"uv": [10.5, 8.25], "uv_size": [-3.5, -3.5]}
							}
						},
						{
							"origin": [-7, 10, -7],
							"size": [14, 4, 14],
							"uv": {
								"north": {"uv": [3.5, 3.5], "uv_size": [3.5, 1.25]},
								"east": {"uv": [0, 3.5], "uv_size": [3.5, 1.25]},
								"south": {"uv": [10.5, 3.5], "uv_size": [3.5, 1.25]},
								"west": {"uv": [7, 3.5], "uv_size": [3.5, 1.25]},
								"up": {"uv": [7, 3.5], "uv_size": [-3.5, -3.5]},
								"down": {"uv": [10.5, 3.5], "uv_size": [-3.5, -3.5]}
							}
						},
						{
							"origin": [-1, 7, -8],
							"size": [2, 4, 2],
							"uv": {
								"north": {"uv": [1, 0.25], "uv_size": [0.5, 1]},
								"east": {"uv": [0.75, 0.25], "uv_size": [0.25, 1]},
								"south": {"uv": [0.25, 0.25], "uv_size": [0.5, 1]},
								"west": {"uv": [0, 0.25], "uv_size": [0.25, 1]},
								"up": {"uv": [1, 0.25], "uv_size": [-0.5, -0.25]},
								"down": {"uv": [1, 0.25], "uv_size": [-0.5, -0.25]}
							}
						}
					]
				}
			]
		}
	]
}{
    "format_version": "1.26.50",
    "minecraft:geometry": [
        {
            "description": {
                "identifier": "geometry.fence_gate",
                "texture_width": 16,
                "texture_height": 16,
                "visible_bounds_width": 2,
                "visible_bounds_height": 3.5,
                "visible_bounds_offset": [0, 1.25, 0]
            },
            "bones": [
                {
                    "name": "posts",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [6, 5, -1],
                            "size": [2, 11, 2],
                            "uv": {
                                "north": {"uv": [2.1, 4], "uv_size": [2, 11]},
                                "east": {"uv": [0.1, 4], "uv_size": [2, 11]},
                                "south": {"uv": [6.1, 4], "uv_size": [2, 11]},
                                "west": {"uv": [4.1, 4], "uv_size": [2, 11]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-8, 5, -1],
                            "size": [2, 11, 2],
                            "uv": {
                                "north": {"uv": [2, 4], "uv_size": [2, 11]},
                                "east": {"uv": [0, 4], "uv_size": [2, 11]},
                                "south": {"uv": [6, 4], "uv_size": [2, 11]},
                                "west": {"uv": [4, 4], "uv_size": [2, 11]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        }
                    ]
                },
                {
                    "name": "open_doors",
                    "parent": "posts",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-8, 12, -5],
                            "size": [2, 3, 4],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 1], "uv_size": [2, 4]},
                                "down": {"uv": [5.9, 5], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [-8, 6, -7],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-8, 6, -5],
                            "size": [2, 3, 4],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [6, 12, -5],
                            "size": [2, 3, 4],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 1], "uv_size": [2, 4]},
                                "down": {"uv": [5.9, 5], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [6, 6, -7],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [6, 6, -5],
                            "size": [2, 3, 4],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        }
                    ]
                },
                {
                    "name": "closed_doors",
                    "parent": "posts",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [3, 6, -4],
                            "size": [2, 3, 4],
                            "pivot": [5, 10.5, -1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [3, 12, -4],
                            "size": [2, 3, 4],
                            "pivot": [5, 10.5, -1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 1], "uv_size": [2, 4]},
                                "down": {"uv": [5.9, 5], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [0, 6, -1],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-2, 6, -1],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-5, 12, 0],
                            "size": [2, 3, 4],
                            "pivot": [-5, 10.5, 1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 0], "uv_size": [2, 4]},
                                "down": {"uv": [6, 4], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [-5, 6, 0],
                            "size": [2, 3, 4],
                            "pivot": [-5, 10.5, 1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        }
                    ]
                }
            ]
        },
        {
            "description": {
                "identifier": "geometry.fence_gate_carried",
                "texture_width": 16,
                "texture_height": 16
            },
            "item_display_transforms": {
                "gui": {
                    "fit_to_frame": false
                },
                "fixed": {
                    "rotation": [0, -90, 0]
                },
                "shelf": {
                    "rotation": [0, -90, 0]
                }
            },
            "bones": [
                {
                    "name": "bone",
                    "pivot": [0, 0, 0],
                    "rotation": [0, 90, 0],
                    "cubes": [
                        {
                            "origin": [6, 5, -1],
                            "size": [2, 11, 2],
                            "uv": {
                                "north": {"uv": [2.1, 4], "uv_size": [2, 11]},
                                "east": {"uv": [0.1, 4], "uv_size": [2, 11]},
                                "south": {"uv": [6.1, 4], "uv_size": [2, 11]},
                                "west": {"uv": [4.1, 4], "uv_size": [2, 11]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-8, 5, -1],
                            "size": [2, 11, 2],
                            "uv": {
                                "north": {"uv": [2, 4], "uv_size": [2, 11]},
                                "east": {"uv": [0, 4], "uv_size": [2, 11]},
                                "south": {"uv": [6, 4], "uv_size": [2, 11]},
                                "west": {"uv": [4, 4], "uv_size": [2, 11]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [3, 6, -4],
                            "size": [2, 3, 4],
                            "pivot": [5, 10.5, -1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [3, 12, -4],
                            "size": [2, 3, 4],
                            "pivot": [5, 10.5, -1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 1], "uv_size": [2, 4]},
                                "down": {"uv": [5.9, 5], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [0, 6, -1],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-2, 6, -1],
                            "size": [2, 9, 2],
                            "uv": {
                                "north": {"uv": [2, 5], "uv_size": [2, 9]},
                                "east": {"uv": [0, 5], "uv_size": [2, 9]},
                                "south": {"uv": [6, 5], "uv_size": [2, 9]},
                                "west": {"uv": [4, 5], "uv_size": [2, 9]},
                                "up": {"uv": [2, 0], "uv_size": [2, 2]},
                                "down": {"uv": [4, 2], "uv_size": [2, -2]}
                            }
                        },
                        {
                            "origin": [-5, 12, 0],
                            "size": [2, 3, 4],
                            "pivot": [-5, 10.5, 1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 5], "uv_size": [2, 3]},
                                "east": {"uv": [0, 5], "uv_size": [4, 3]},
                                "south": {"uv": [10, 5], "uv_size": [2, 3]},
                                "west": {"uv": [6, 5], "uv_size": [4, 3]},
                                "up": {"uv": [4, 0], "uv_size": [2, 4]},
                                "down": {"uv": [6, 4], "uv_size": [2, -4]}
                            }
                        },
                        {
                            "origin": [-5, 6, 0],
                            "size": [2, 3, 4],
                            "pivot": [-5, 10.5, 1],
                            "rotation": [0, 90, 0],
                            "uv": {
                                "north": {"uv": [4, 7], "uv_size": [2, 3]},
                                "east": {"uv": [0, 7], "uv_size": [4, 3]},
                                "south": {"uv": [10, 7], "uv_size": [2, 3]},
                                "west": {"uv": [6, 7], "uv_size": [4, 3]},
                                "up": {"uv": [4, 3], "uv_size": [2, 4]},
                                "down": {"uv": [6, 7], "uv_size": [2, -4]}
                            }
                        }
                    ]
                }
            ]
        }
    ]
}{
    "format_version": "1.21.20",
    "minecraft:geometry": [
        {
            "description": {
                "identifier": "geometry.stair",
                "texture_width": 16,
                "texture_height": 16
            },
            "bones": [
                {
                    "name": "base",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-8, 0, -8],
                            "size": [16, 8, 16],
                            "uv": { "north": {"uv": [0, 0], "uv_size": [16, 8]}, "east": {"uv": [0, 0], "uv_size": [16, 8]}, "south": {"uv": [0, 0], "uv_size": [16, 8]}, "west": {"uv": [0, 0], "uv_size": [16, 8]}, "up": {"uv": [16, 16], "uv_size": [-16, -16]}, "down": {"uv": [16, 16], "uv_size": [-16, -16]} }
                        }
                    ]
                },
                {
                    "name": "top_nw",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        { "origin": [-8, 8, -8], "size": [8, 8, 8], "uv": { "north": {"uv": [0, 0], "uv_size": [8, 8]}, "east": {"uv": [0, 0], "uv_size": [8, 8]}, "south": {"uv": [0, 0], "uv_size": [8, 8]}, "west": {"uv": [0, 0], "uv_size": [8, 8]}, "up": {"uv": [8, 8], "uv_size": [-8, -8]}, "down": {"uv": [8, 8], "uv_size": [-8, -8]} } }
                    ]
                },
                {
                    "name": "top_ne",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        { "origin": [0, 8, -8], "size": [8, 8, 8], "uv": { "north": {"uv": [8, 0], "uv_size": [8, 8]}, "east": {"uv": [0, 0], "uv_size": [8, 8]}, "south": {"uv": [0, 0], "uv_size": [8, 8]}, "west": {"uv": [0, 0], "uv_size": [8, 8]}, "up": {"uv": [16, 8], "uv_size": [-8, -8]}, "down": {"uv": [16, 8], "uv_size": [-8, -8]} } }
                    ]
                },
                {
                    "name": "top_sw",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        { "origin": [-8, 8, 0], "size": [8, 8, 8], "uv": { "north": {"uv": [0, 0], "uv_size": [8, 8]}, "east": {"uv": [0, 0], "uv_size": [8, 8]}, "south": {"uv": [0, 0], "uv_size": [8, 8]}, "west": {"uv": [0, 0], "uv_size": [8, 8]}, "up": {"uv": [8, 16], "uv_size": [-8, -8]}, "down": {"uv": [8, 16], "uv_size": [-8, -8]} } }
                    ]
                },
                {
                    "name": "top_se",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        { "origin": [0, 8, 0], "size": [8, 8, 8], "uv": { "north": {"uv": [0, 0], "uv_size": [8, 8]}, "east": {"uv": [0, 0], "uv_size": [8, 8]}, "south": {"uv": [0, 0], "uv_size": [8, 8]}, "west": {"uv": [0, 0], "uv_size": [8, 8]}, "up": {"uv": [16, 16], "uv_size": [-8, -8]}, "down": {"uv": [16, 16], "uv_size": [-8, -8]} } }
                    ]
                }
            ],
            "item_display_transforms": {
                "thirdperson_righthand": {
                    "rotation": [75, 135, 0]
                },
                "thirdperson_lefthand": {
                    "rotation": [75, 135, 0]
                },
                "firstperson_righthand": {
                    "rotation": [0, -45, 0]
                },
                "firstperson_lefthand": {
                    "rotation": [0, -45, 0]
                }
            }
        }
    ]
}{
	"format_version": "1.26.50",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.slab",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 8, 0],
					"cubes": [
						{
							"origin": [-8, 4, -8],
							"size": [16, 8, 16],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [16, 8]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [16, 8]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [16, 8]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [16, 8]
								},
								"up": {
									"uv": [16, 16],
									"uv_size": [-16, -16]
								},
								"down": {
									"uv": [16, 16],
									"uv_size": [-16, -16]
								}
							}
						}
					]
				}
			]
		},
		{
			"description": {
				"identifier": "geometry.slab_carried",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 3,
				"visible_bounds_height": 2.5,
				"visible_bounds_offset": [0, 0.75, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 4, 0],
					"cubes": [
						{
							"origin": [-8, 0, -8],
							"size": [16, 8, 16],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 8]},
								"east": {"uv": [0, 0], "uv_size": [16, 8]},
								"south": {"uv": [0, 0], "uv_size": [16, 8]},
								"west": {"uv": [0, 0], "uv_size": [16, 8]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [16, 16], "uv_size": [-16, -16]}
							}
						}
					]
				}
			],
			"item_display_transforms": {
				"thirdperson_righthand": {
					"rotation": [70, 45, 0],
					"translation": [0, 3.5, 1.5],
					"scale": [0.375, 0.375, 0.375],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"thirdperson_lefthand": {
					"rotation": [75, -45, 0],
					"translation": [0, 0, 0],
					"scale": [0.375, 0.375, 0.375],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"firstperson_righthand": {
					"rotation": [0, 45, 0],
					"translation": [0, 1.625, 0],
					"scale": [0.4, 0.4, 0.4],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"firstperson_lefthand": {
					"rotation": [0, -135, 0],
					"translation": [0, 1.625, 0],
					"scale": [0.4, 0.4, 0.4],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"gui": {
					"rotation": [30, -135, 0],
					"translation": [0, 0, 0],
					"scale": [0.625, 0.625, 0.625],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0],
					"fit_to_frame": false
				},
				"fixed": {
					"rotation": [0, -90, 0],
					"translation": [0, 0, 0],
					"scale": [0.5, 0.5, 0.5],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				}
			}
		}
	]
}{
	"format_version": "1.26.50",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.fence",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "post",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-2, 0, -2],
							"size": [4, 16, 4],
							"uv": {
								"north": {
									"uv": [6, 0],
									"uv_size": [4, 16]
								},
								"east": {
									"uv": [6, 0],
									"uv_size": [4, 16]
								},
								"south": {
									"uv": [6, 0],
									"uv_size": [4, 16]
								},
								"west": {
									"uv": [6, 0],
									"uv_size": [4, 16]
								},
								"up": {
									"uv": [10, 10],
									"uv_size": [-4, -4]
								},
								"down": {
									"uv": [10, 10],
									"uv_size": [-4, -4]
								}
							}
						}
					]
				},
				{
					"name": "north_rails",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 12, -8],
							"size": [2, 3, 6],
							"uv": {
								"north": {
									"uv": [7, 1],
									"uv_size": [2, 3]
								},
								"east": {
									"uv": [10, 1],
									"uv_size": [6, 3]
								},
								"west": {
									"uv": [0, 1],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [9, 6],
									"uv_size": [-2, -6]
								},
								"down": {
									"uv": [9, 16],
									"uv_size": [-2, -6]
								}
							}
						},
						{
							"origin": [-1, 6, -8],
							"size": [2, 3, 6],
							"uv": {
								"north": {
									"uv": [7, 7],
									"uv_size": [2, 3]
								},
								"east": {
									"uv": [10, 7],
									"uv_size": [6, 3]
								},
								"west": {
									"uv": [0, 7],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [9, 6],
									"uv_size": [-2, -6]
								},
								"down": {
									"uv": [9, 16],
									"uv_size": [-2, -6]
								}
							}
						}
					]
				},
				{
					"name": "south_rails",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 12, 2],
							"size": [2, 3, 6],
							"uv": {
								"east": {
									"uv": [0, 1],
									"uv_size": [6, 3]
								},
								"south": {
									"uv": [7, 1],
									"uv_size": [2, 3]
								},
								"west": {
									"uv": [10, 1],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [9, 16],
									"uv_size": [-2, -6]
								},
								"down": {
									"uv": [9, 6],
									"uv_size": [-2, -6]
								}
							}
						},
						{
							"origin": [-1, 6, 2],
							"size": [2, 3, 6],
							"uv": {
								"east": {
									"uv": [0, 7],
									"uv_size": [6, 3]
								},
								"south": {
									"uv": [7, 7],
									"uv_size": [2, 3]
								},
								"west": {
									"uv": [10, 7],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [9, 16],
									"uv_size": [-2, -6]
								},
								"down": {
									"uv": [9, 6],
									"uv_size": [-2, -6]
								}
							}
						}
					]
				},
				{
					"name": "west_rails",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [2, 12, -1],
							"size": [6, 3, 2],
							"uv": {
								"north": {
									"uv": [10, 1],
									"uv_size": [6, 3]
								},
								"south": {
									"uv": [0, 1],
									"uv_size": [6, 3]
								},
								"west": {
									"uv": [7, 1],
									"uv_size": [2, 3]
								},
								"up": {
									"uv": [6, 9],
									"uv_size": [-6, -2]
								},
								"down": {
									"uv": [6, 9],
									"uv_size": [-6, -2]
								}
							}
						},
						{
							"origin": [2, 6, -1],
							"size": [6, 3, 2],
							"uv": {
								"north": {
									"uv": [10, 7],
									"uv_size": [6, 3]
								},
								"south": {
									"uv": [0, 7],
									"uv_size": [6, 3]
								},
								"west": {
									"uv": [7, 7],
									"uv_size": [2, 3]
								},
								"up": {
									"uv": [6, 9],
									"uv_size": [-6, -2]
								},
								"down": {
									"uv": [6, 9],
									"uv_size": [-6, -2]
								}
							}
						}
					]
				},
				{
					"name": "east_rails",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-8, 12, -1],
							"size": [6, 3, 2],
							"uv": {
								"north": {
									"uv": [0, 1],
									"uv_size": [6, 3]
								},
								"east": {
									"uv": [7, 1],
									"uv_size": [2, 3]
								},
								"south": {
									"uv": [10, 1],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [16, 9],
									"uv_size": [-6, -2]
								},
								"down": {
									"uv": [16, 9],
									"uv_size": [-6, -2]
								}
							}
						},
						{
							"origin": [-8, 6, -1],
							"size": [6, 3, 2],
							"uv": {
								"north": {
									"uv": [0, 7],
									"uv_size": [6, 3]
								},
								"east": {
									"uv": [7, 7],
									"uv_size": [2, 3]
								},
								"south": {
									"uv": [10, 7],
									"uv_size": [6, 3]
								},
								"up": {
									"uv": [16, 9],
									"uv_size": [-6, -2]
								},
								"down": {
									"uv": [16, 9],
									"uv_size": [-6, -2]
								}
							}
						}
					]
				}
			]
		},
		{
			"description": {
				"identifier": "geometry.fence_carried",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "posts",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-2, 0, 4],
							"size": [4, 16, 4],
							"uv": {
								"north": {"uv": [6, 0], "uv_size": [4, 16]},
								"east": {"uv": [0, 0], "uv_size": [4, 16]},
								"south": {"uv": [6, 0], "uv_size": [4, 16]},
								"west": {"uv": [12, 0], "uv_size": [4, 16]},
								"up": {"uv": [10, 16], "uv_size": [-4, -4]},
								"down": {"uv": [10, 4], "uv_size": [-4, -4]}
							}
						},
						{
							"origin": [-2, 0, -8],
							"size": [4, 16, 4],
							"uv": {
								"north": {"uv": [6, 0], "uv_size": [4, 16]},
								"east": {"uv": [12, 0], "uv_size": [4, 16]},
								"south": {"uv": [6, 0], "uv_size": [4, 16]},
								"west": {"uv": [0, 0], "uv_size": [4, 16]},
								"up": {"uv": [10, 4], "uv_size": [-4, -4]},
								"down": {"uv": [10, 16], "uv_size": [-4, -4]}
							}
						}
					]
				},
				{
					"name": "rails",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 13, -10],
							"size": [2, 2, 2],
							"uv": {
								"north": {"uv": [0, 1], "uv_size": [2, 3]},
								"east": {"uv": [0, 1], "uv_size": [2, 3]},
								"west": {"uv": [0, 1], "uv_size": [2, 3]},
								"up": {"uv": [9, 16], "uv_size": [-2, -2]},
								"down": {"uv": [9, 16], "uv_size": [-2, -2]}
							}
						},
						{
							"origin": [-1, 13, -4],
							"size": [2, 2, 8],
							"uv": {
								"east": {"uv": [4, 1], "uv_size": [8, 3]},
								"west": {"uv": [4, 1], "uv_size": [8, 3]},
								"up": {"uv": [9, 12], "uv_size": [-2, -8]},
								"down": {"uv": [9, 12], "uv_size": [-2, -8]}
							}
						},
						{
							"origin": [-1, 13, 8],
							"size": [2, 2, 2],
							"uv": {
								"east": {"uv": [14, 1], "uv_size": [2, 3]},
								"south": {"uv": [14, 1], "uv_size": [2, 3]},
								"west": {"uv": [14, 1], "uv_size": [2, 3]},
								"up": {"uv": [9, 2], "uv_size": [-2, -2]},
								"down": {"uv": [9, 2], "uv_size": [-2, -2]}
							}
						},
						{
							"origin": [-1, 5, -10],
							"size": [2, 2, 2],
							"uv": {
								"north": {"uv": [0, 7], "uv_size": [2, 3]},
								"east": {"uv": [0, 7], "uv_size": [2, 3]},
								"west": {"uv": [0, 7], "uv_size": [2, 3]},
								"up": {"uv": [9, 16], "uv_size": [-2, -2]},
								"down": {"uv": [9, 16], "uv_size": [-2, -2]}
							}
						},
						{
							"origin": [-1, 5, -4],
							"size": [2, 2, 8],
							"uv": {
								"east": {"uv": [4, 7], "uv_size": [8, 3]},
								"west": {"uv": [4, 7], "uv_size": [8, 3]},
								"up": {"uv": [9, 12], "uv_size": [-2, -8]},
								"down": {"uv": [9, 12], "uv_size": [-2, -8]}
							}
						},
						{
							"origin": [-1, 5, 8],
							"size": [2, 2, 2],
							"uv": {
								"east": {"uv": [14, 7], "uv_size": [2, 3]},
								"south": {"uv": [14, 7], "uv_size": [2, 3]},
								"west": {"uv": [14, 7], "uv_size": [2, 3]},
								"up": {"uv": [9, 2], "uv_size": [-2, -2]},
								"down": {"uv": [9, 2], "uv_size": [-2, -2]}
							}
						}
					]
				}
			],
			"item_display_transforms": {
				"gui": {
					"rotation": [30, 45, 0],
					"translation": [0, 0, 0],
					"scale": [0.625, 0.625, 0.625],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0],
					"fit_to_frame": false
				},
				"fixed": {
					"rotation": [0, -90, 0],
					"translation": [0, 0, 0],
					"scale": [0.5, 0.5, 0.5],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				}
			}
		}
	]
}{
    "format_version": "1.26.50",
    "minecraft:geometry": [
        {
            "description": {
                "identifier": "geometry.glass",
                "texture_width": 16,
                "texture_height": 16
            },
            "bones": [
                {
                    "name": "glass",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-8, 0, -8],
                            "size": [16, 16, 16],
                            "uv": {
                                "north": {
                                    "uv": [0, 0],
                                    "uv_size": [16, 16]
                                },
                                "east": {
                                    "uv": [0, 0],
                                    "uv_size": [16, 16]
                                },
                                "south": {
                                    "uv": [0, 0],
                                    "uv_size": [16, 16]
                                },
                                "west": {
                                    "uv": [0, 0],
                                    "uv_size": [16, 16]
                                },
                                "up": {
                                    "uv": [16, 16],
                                    "uv_size": [-16, -16]
                                },
                                "down": {
                                    "uv": [16, 16],
                                    "uv_size": [-16, -16]
                                }
                            }
                        }
                    ]
                }
            ]
        }
    ]
}{
	"format_version": "1.21.20",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.lever",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 6,
				"visible_bounds_height": 4,
				"visible_bounds_offset": [0, 1, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-21, 0, 2],
							"size": [8, 3, 6],
							"pivot": [-6, 0, 11],
							"rotation": [0, -90, 0],
							"uv": {
								"north": {"uv": [7.1, 8], "uv_size": [2, 7.9], "uv_rotation": 90},
								"east": {"uv": [7, 8], "uv_size": [2, 8], "uv_rotation": 90},
								"south": {"uv": [7.1, 8], "uv_size": [2, 8], "uv_rotation": 90},
								"west": {"uv": [8.875, 16], "uv_size": [-2, -8], "uv_rotation": 90},
								"up": {"uv": [7, 8], "uv_size": [2, 8]},
								"down": {"uv": [7, 16], "uv_size": [2, -8]}
							}
						}
					]
				},
				{
					"name": "open",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 1.5, -2],
							"size": [2, 10, 2],
							"pivot": [0, 1.5, -1],
							"rotation": [40, 0, 0],
							"uv": {
								"north": {"uv": [7, 6], "uv_size": [2, 10]},
								"east": {"uv": [7, 6], "uv_size": [2, 10]},
								"south": {"uv": [7, 6], "uv_size": [2, 10]},
								"west": {"uv": [7, 6], "uv_size": [2, 10]},
								"up": {"uv": [9, 8], "uv_size": [-2, -2]},
								"down": {"uv": [9, 16], "uv_size": [-2, -2]}
							}
						}
					]
				},
				{
					"name": "close",
					"parent": "bone",
					"pivot": [13, 0, 0],
					"rotation": [0, 180, 0],
					"cubes": [
						{
							"origin": [25, 1.5, -2],
							"size": [2, 10, 2],
							"pivot": [26, 1.5, -1],
							"rotation": [40, 0, 0],
							"uv": {
								"north": {"uv": [7, 6], "uv_size": [2, 10]},
								"east": {"uv": [7, 6], "uv_size": [2, 10]},
								"south": {"uv": [7, 6], "uv_size": [2, 10]},
								"west": {"uv": [7, 6], "uv_size": [2, 10]},
								"up": {"uv": [9, 8], "uv_size": [-2, -2]},
								"down": {"uv": [9, 16], "uv_size": [-2, -2]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.slime",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-8, 0, -8],
							"size": [16, 16, 16],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 16]},
								"east": {"uv": [0, 0], "uv_size": [16, 16]},
								"south": {"uv": [0, 0], "uv_size": [16, 16]},
								"west": {"uv": [0, 0], "uv_size": [16, 16]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [16, 16], "uv_size": [-16, -16]}
							}
						},
						{
							"origin": [-5, 3, -5],
							"size": [10, 10, 10],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 16]},
								"east": {"uv": [0, 0], "uv_size": [16, 16]},
								"south": {"uv": [0, 0], "uv_size": [16, 16]},
								"west": {"uv": [0, 0], "uv_size": [16, 16]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [16, 16], "uv_size": [-16, -16]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.21.20",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.door",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 6,
				"visible_bounds_height": 4,
				"visible_bounds_offset": [0, 1, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0]
				},
				{
					"name": "open",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-8, 0, 5],
							"size": [16, 16, 3],
							"uv": {
								"north": {"uv": [0.1, 0], "uv_size": [15.9, 16]},
								"east": {"uv": [0.1, 0], "uv_size": [2.9, 16]},
								"south": {"uv": [0.1, 0], "uv_size": [15.9, 16]},
								"west": {"uv": [13.1, 0], "uv_size": [2.9, 16]},
								"up": {"uv": [0, 0], "uv_size": [16, 3]},
								"down": {"uv": [0, 16], "uv_size": [16, -3]}
							}
						}
					]
				},
				{
					"name": "close",
					"parent": "bone",
					"pivot": [-13, 0, 0],
					"rotation": [0, -90, 0],
					"cubes": [
						{
							"origin": [-21, 0, -8],
							"size": [16, 16, 3],
							"uv": {
								"north": {"uv": [0.1, 0], "uv_size": [15.9, 16]},
								"east": {"uv": [13, 0], "uv_size": [3, 16]},
								"south": {"uv": [0.1, 0], "uv_size": [15.9, 16]},
								"west": {"uv": [0, 0], "uv_size": [3, 16]},
								"up": {"uv": [0, 0], "uv_size": [16, 3]},
								"down": {"uv": [0, 16], "uv_size": [16, -3]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.wall",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0]
				},
				{
					"name": "mid",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-4, 0, -4],
							"size": [8, 16, 8],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [8, 16]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [8, 16]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [8, 16]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [8, 16]
								},
								"up": {
									"uv": [8, 8],
									"uv_size": [-8, -8]
								},
								"down": {
									"uv": [8, 8],
									"uv_size": [-8, -8]
								}
							}
						}
					]
				},
				{
					"name": "ns",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-3, 0, -4],
							"size": [6, 13, 8],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [8, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [8, 13]
								},
								"up": {
									"uv": [6, 8],
									"uv_size": [-6, -8]
								},
								"down": {
									"uv": [6, 8],
									"uv_size": [-6, -8]
								}
							}
						}
					]
				},
				{
					"name": "we",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-3, 0, -4],
							"size": [6, 13, 8],
							"pivot": [0, 0, 0],
							"rotation": [0, 90, 0],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [8, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [8, 13]
								},
								"up": {
									"uv": [6, 8],
									"uv_size": [-6, -8]
								},
								"down": {
									"uv": [6, 8],
									"uv_size": [-6, -8]
								}
							}
						}
					]
				},
				{
					"name": "n",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-3, 0, -8],
							"size": [6, 13, 4],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"up": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								},
								"down": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								}
							}
						}
					]
				},
				{
					"name": "s",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-3, 0, 4],
							"size": [6, 13, 4],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"up": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								},
								"down": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								}
							}
						}
					]
				},
				{
					"name": "w",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"rotation": [0, -90, 0],
					"cubes": [
						{
							"origin": [-3, 0, -8],
							"size": [6, 13, 4],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"up": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								},
								"down": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								}
							}
						}
					]
				},
				{
					"name": "e",
					"parent": "bone",
					"pivot": [12, 0, 0],
					"rotation": [0, -90, 0],
					"cubes": [
						{
							"origin": [9, 0, 16],
							"size": [6, 13, 4],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"east": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [6, 13]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [4, 13]
								},
								"up": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								},
								"down": {
									"uv": [6, 4],
									"uv_size": [-6, -4]
								}
							}
						}
					]
				}
			]
		},
		{
			"description": {
				"identifier": "geometry.wall_render",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 3,
				"visible_bounds_height": 2.5,
				"visible_bounds_offset": [0, 0.75, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"rotation": [0, -90, 0],
					"cubes": [
						{
							"origin": [-4, 0, 4],
							"size": [8, 16, 8],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [8, 16]},
								"east": {"uv": [0, 0], "uv_size": [8, 16]},
								"south": {"uv": [0, 0], "uv_size": [8, 16]},
								"west": {"uv": [0, 0], "uv_size": [8, 16]},
								"up": {"uv": [8, 8], "uv_size": [-8, -8]},
								"down": {"uv": [8, 8], "uv_size": [-8, -8]}
							}
						},
						{
							"origin": [-4, 0, -12],
							"size": [8, 16, 8],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [8, 16]},
								"east": {"uv": [0, 0], "uv_size": [8, 16]},
								"south": {"uv": [0, 0], "uv_size": [8, 16]},
								"west": {"uv": [0, 0], "uv_size": [8, 16]},
								"up": {"uv": [8, 8], "uv_size": [-8, -8]},
								"down": {"uv": [8, 8], "uv_size": [-8, -8]}
							}
						},
						{
							"origin": [-3, 0, -7],
							"size": [6, 13, 14],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [6, 13]},
								"east": {"uv": [0, 0], "uv_size": [4, 13]},
								"south": {"uv": [0, 0], "uv_size": [6, 13]},
								"west": {"uv": [0, 0], "uv_size": [4, 13]},
								"up": {"uv": [6, 4], "uv_size": [-6, -4]},
								"down": {"uv": [6, 4], "uv_size": [-6, -4]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.ladder",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 4,
				"visible_bounds_height": 4,
				"visible_bounds_offset": [0, 1, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [-13, 0, 0],
					"rotation": [0, -90, 0],
					"cubes": [
						{
							"origin": [-6, 0, -21],
							"size": [1, 16, 16],
							"uv": {
								"east": {"uv": [0, 0], "uv_size": [16, 16]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.trapdoor",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
                {
                    "name": "trapdoor",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-8, 0, -8],
                            "size": [16, 2.9, 16],
                            "uv": {
                                "north": { "uv": [16, 3], "uv_size": [-16, -3] },
                                "east": { "uv": [16, 3], "uv_size": [-16, -3] },
                                "south": { "uv": [16, 3], "uv_size": [-16, -3] },
                                "west": { "uv": [16, 3], "uv_size": [-16, -3] },
                                "up": { "uv": [0, 0], "uv_size": [16, 16] },
                                "down": { "uv": [16, 16], "uv_size": [-16, -16] }
                            }
                        }
                    ]
                }
            ],
            "item_display_transforms": {
                "gui": {
                    "fit_to_frame": false,
                    "rotation": [30, 225, 0],
                    "scale": [0.625, 0.625, 0.625],
                    "translation": [0, -0.15, 0]
                },
                "firstperson_righthand": {
                    "translation": [0, 2.6, 0]
                },
                "thirdperson_righthand": {
                    "translation": [0, 3.35, 2.25]
                },
                "fixed": {
                    "translation": [0, 3.45, 0]
                },
                "ground": {
                    "translation": [0, 4.65, 0]
                }
            }
        }
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.farmland",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [8, 0, -8],
					"cubes": [
						{
							"origin": [-8, 0, -8],
							"size": [16, 15, 16],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 16]},
								"east": {"uv": [0, 0], "uv_size": [16, 16]},
								"south": {"uv": [0, 0], "uv_size": [16, 16]},
								"west": {"uv": [0, 0], "uv_size": [16, 16]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [16, 16], "uv_size": [-16, -16]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.hopper",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-4, 4, -4],
							"size": [8, 6, 8],
							"uv": {
								"north": {"uv": [4, 6], "uv_size": [8, 6]},
								"east": {"uv": [4, 6], "uv_size": [8, 6]},
								"south": {"uv": [4, 6], "uv_size": [8, 6]},
								"west": {"uv": [4, 6], "uv_size": [8, 6]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						},
						{
							"origin": [-8, 15.9, -8],
							"size": [16, 0.1, 16],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 1]},
								"east": {"uv": [0, 0], "uv_size": [16, 1]},
								"south": {"uv": [0, 0], "uv_size": [16, 0.9]},
								"west": {"uv": [0, 0], "uv_size": [16, 1]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [16, 0], "uv_size": [0, 0]}
							}
						},
						{
							"origin": [-8, 10, -8],
							"size": [16, 0.1, 16],
							"uv": {
								"north": {"uv": [0, 15], "uv_size": [16, 1]},
								"east": {"uv": [0, 15], "uv_size": [16, 1]},
								"south": {"uv": [0, 15], "uv_size": [16, 1]},
								"west": {"uv": [0, 15], "uv_size": [16, 1]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						},
						{
							"origin": [7, 10.1, -7],
							"size": [1, 5.8, 14],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 6]},
								"east": {"uv": [0, 0], "uv_size": [16, 6]},
								"south": {"uv": [0, 0], "uv_size": [16, 6]},
								"west": {"uv": [0, 0], "uv_size": [16, 6]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						},
						{
							"origin": [-7, 10.1, -8],
							"size": [15, 5.8, 1],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 6]},
								"east": {"uv": [0, 0], "uv_size": [16, 6]},
								"south": {"uv": [0, 0], "uv_size": [16, 6]},
								"west": {"uv": [0, 0], "uv_size": [16, 6]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						},
						{
							"origin": [-8, 10.1, -8],
							"size": [1, 5.8, 15],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 6]},
								"east": {"uv": [0, 0], "uv_size": [16, 6]},
								"south": {"uv": [0, 0], "uv_size": [16, 6]},
								"west": {"uv": [0, 0], "uv_size": [16, 6]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						},
						{
							"origin": [-8, 10.1, 7],
							"size": [16, 5.8, 1],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 6]},
								"east": {"uv": [0, 0], "uv_size": [16, 6]},
								"south": {"uv": [0, 0], "uv_size": [16, 6]},
								"west": {"uv": [0, 0], "uv_size": [16, 6]},
								"up": {"uv": [0, 0], "uv_size": [16, 16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						}
					]
				},
				{
					"name": "down",
					"parent": "bone",
					"pivot": [10, 0, 0]
				},
				{
					"name": "ground",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-2, 0, -2],
							"size": [4, 4, 4],
							"uv": {
								"north": {"uv": [6, 12], "uv_size": [4, 4]},
								"east": {"uv": [6, 12], "uv_size": [4, 4]},
								"south": {"uv": [32, 16], "uv_size": [4, 4]},
								"west": {"uv": [6, 12], "uv_size": [4, 4]},
								"up": {"uv": [32, 20], "uv_size": [4, 4]},
								"down": {"uv": [6, 16], "uv_size": [4, -4]}
							}
						}
					]
				},
				{
					"name": "north",
					"parent": "bone",
					"pivot": [0, 4, -6],
					"cubes": [
						{
							"origin": [-2, 4, -8],
							"size": [4, 4, 4],
							"uv": {
								"north": {"uv": [0, 10], "uv_size": [16, 6]},
								"east": {"uv": [0, 10], "uv_size": [16, 6]},
								"south": {"uv": [0, 10], "uv_size": [16, 6]},
								"west": {"uv": [0, 10], "uv_size": [16, 6]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						}
					]
				},
				{
					"name": "south",
					"parent": "bone",
					"pivot": [0, 4, 6],
					"cubes": [
						{
							"origin": [-2, 4, 4],
							"size": [4, 4, 4],
							"uv": {
								"north": {"uv": [0, 10], "uv_size": [16, 6]},
								"east": {"uv": [0, 10], "uv_size": [16, 6]},
								"south": {"uv": [0, 10], "uv_size": [16, 6]},
								"west": {"uv": [0, 10], "uv_size": [16, 6]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						}
					]
				},
				{
					"name": "west",
					"parent": "bone",
					"pivot": [-6, 4, 0],
					"cubes": [
						{
							"origin": [4, 4, -2],
							"size": [4, 4, 4],
							"uv": {
								"north": {"uv": [0, 10], "uv_size": [16, 6]},
								"east": {"uv": [0, 10], "uv_size": [16, 6]},
								"south": {"uv": [0, 10], "uv_size": [16, 6]},
								"west": {"uv": [0, 10], "uv_size": [16, 6]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						}
					]
				},
				{
					"name": "east",
					"parent": "bone",
					"pivot": [6, 4, 0],
					"cubes": [
						{
							"origin": [-8, 4, -2],
							"size": [4, 4, 4],
							"uv": {
								"north": {"uv": [0, 10], "uv_size": [16, 6]},
								"east": {"uv": [0, 10], "uv_size": [16, 6]},
								"south": {"uv": [0, 10], "uv_size": [16, 6]},
								"west": {"uv": [0, 10], "uv_size": [16, 6]},
								"up": {"uv": [16, 16], "uv_size": [-16, -16]},
								"down": {"uv": [0, 16], "uv_size": [16, -16]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.21.20",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.glass_pane",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 0, -1],
							"size": [2, 16, 2],
							"uv": {
								"north": {"uv": [6, 0], "uv_size": [4, 16]},
								"east": {"uv": [6, 0], "uv_size": [4, 16]},
								"south": {"uv": [6, 0], "uv_size": [4, 16]},
								"west": {"uv": [6, 0], "uv_size": [4, 16]},
								"up": {"uv": [1, 1], "uv_size": [-1, -1]},
								"down": {"uv": [1, 1], "uv_size": [-1, -1]}
							}
						}
					]
				},
				{
					"name": "n",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 0, -8],
							"size": [2, 16, 7],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [1, 16]},
								"east": {"uv": [10, 0], "uv_size": [6, 16]},
								"south": {"uv": [7, 1], "uv_size": [2, 3]},
								"west": {"uv": [0, 0], "uv_size": [7, 16]},
								"up": {"uv": [16, 16], "uv_size": [-3, -1]},
								"down": {"uv": [16, 16], "uv_size": [-4, -1], "uv_rotation": 90}
							}
						}
					]
				},
				{
					"name": "s",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-1, 0, 1],
							"size": [2, 16, 7],
							"uv": {
								"east": {"uv": [0, 0], "uv_size": [6, 16]},
								"south": {"uv": [0, 0], "uv_size": [1, 16]},
								"west": {"uv": [10, 0], "uv_size": [6, 16]},
								"up": {"uv": [16, 16], "uv_size": [-4, -1]},
								"down": {"uv": [16, 16], "uv_size": [-4, -1]}
							}
						}
					]
				},
				{
					"name": "e",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-8, 0, -1],
							"size": [7, 16, 2],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [6, 16]},
								"east": {"uv": [0, 0], "uv_size": [1, 16]},
								"south": {"uv": [10, 0], "uv_size": [6, 16]},
								"west": {"uv": [7, 1], "uv_size": [2, 3]},
								"up": {"uv": [16, 16], "uv_size": [-4, -1]},
								"down": {"uv": [16, 16], "uv_size": [-4, -1]}
							}
						}
					]
				},
				{
					"name": "w",
					"parent": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [1, 0, -1],
							"size": [7, 16, 2],
							"uv": {
								"north": {"uv": [9, 0], "uv_size": [7, 16]},
								"east": {"uv": [7, 1], "uv_size": [2, 3]},
								"south": {"uv": [0, 0], "uv_size": [7, 16]},
								"west": {"uv": [0, 0], "uv_size": [1, 16]},
								"up": {"uv": [16, 16], "uv_size": [-4, -1]},
								"down": {"uv": [16, 16], "uv_size": [-4, -1]}
							}
						}
					]
				},
				{
    				"name": "straight_ns",
    				"pivot": [0, 0, 0],
    				"cubes": [
        				{
            				"origin": [-1, 0, -8],
            				"size": [2, 16, 16],
            				"uv": {
                				"north": {"uv": [6, 0], "uv_size": [4, 16]},
                				"east": {"uv": [0, 0], "uv_size": [16, 16]},
                				"south": {"uv": [6, 0], "uv_size": [4, 16]},
                				"west": {"uv": [0, 0], "uv_size": [16, 16]},
                				"up": {"uv": [16, 16], "uv_size": [-2, -16]},
                				"down": {"uv": [16, 16], "uv_size": [-2, -16]}
            				}
        				}
    				]
				},
				{
    				"name": "straight_ew",
    				"pivot": [0, 0, 0],
    				"cubes": [
        				{
            				"origin": [-8, 0, -1],
            				"size": [16, 16, 2],
            				"uv": {
                				"north": {"uv": [0, 0], "uv_size": [16, 16]},
               				 	"east": {"uv": [6, 0], "uv_size": [4, 16]},
               				 	"south": {"uv": [0, 0], "uv_size": [16, 16]},
                				"west": {"uv": [6, 0], "uv_size": [4, 16]},
                				"up": {"uv": [16, 16], "uv_size": [-16, -2]},
                				"down": {"uv": [16, 16], "uv_size": [-16, -2]}
            				}
        				}
    				]
				},
				{
                    "name": "end_n",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-1, 0, -8],
                            "size": [2, 16, 9],
                            "uv": {
                                "north": {"uv": [6, 0], "uv_size": [4, 16]},
                                "east": {"uv": [7, 0], "uv_size": [9, 16]},
                                "south": {"uv": [6, 0], "uv_size": [4, 16]},
                                "west": {"uv": [0, 0], "uv_size": [9, 16]},
                                "up": {"uv": [16, 16], "uv_size": [-2, -9]},
                                "down": {"uv": [16, 16], "uv_size": [-2, -9]}
                            }
                        }
                    ]
                },
                {
                    "name": "end_s",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-1, 0, -1],
                            "size": [2, 16, 9],
                            "uv": {
                                "north": {"uv": [6, 0], "uv_size": [4, 16]},
                                "east": {"uv": [0, 0], "uv_size": [9, 16]},
                                "south": {"uv": [6, 0], "uv_size": [4, 16]},
                                "west": {"uv": [7, 0], "uv_size": [9, 16]},
                                "up": {"uv": [16, 16], "uv_size": [-2, -9]},
                                "down": {"uv": [16, 16], "uv_size": [-2, -9]}
                            }
                        }
                    ]
                },
                {
                    "name": "end_e",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-8, 0, -1],
                            "size": [9, 16, 2],
                            "uv": {
                                "north": {"uv": [7, 0], "uv_size": [9, 16]},
                                "east": {"uv": [6, 0], "uv_size": [4, 16]},
                                "south": {"uv": [0, 0], "uv_size": [9, 16]},
                                "west": {"uv": [6, 0], "uv_size": [4, 16]},
                                "up": {"uv": [16, 16], "uv_size": [-9, -2]},
                                "down": {"uv": [16, 16], "uv_size": [-9, -2]}
                            }
                        }
                    ]
                },
                {
                    "name": "end_w",
                    "pivot": [0, 0, 0],
                    "cubes": [
                        {
                            "origin": [-1, 0, -1],
                            "size": [9, 16, 2],
                            "uv": {
                                "north": {"uv": [0, 0], "uv_size": [9, 16]},
                                "east": {"uv": [6, 0], "uv_size": [4, 16]},
                                "south": {"uv": [7, 0], "uv_size": [9, 16]},
                                "west": {"uv": [6, 0], "uv_size": [4, 16]},
                                "up": {"uv": [16, 16], "uv_size": [-9, -2]},
                                "down": {"uv": [16, 16], "uv_size": [-9, -2]}
                            }
                        }
                    ]
                }
			]
		},
		{
			"description": {
				"identifier": "geometry.glass_pane_render",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 5,
				"visible_bounds_height": 2.5,
				"visible_bounds_offset": [0, 0.75, 0]
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [0, 0, 0],
					"cubes": [
						{
							"origin": [-8, 0, -1],
							"size": [16, 16, 2],
							"uv": {
								"north": {
									"uv": [0, 0],
									"uv_size": [16, 16]
								},
								"east": {
									"uv": [15, 0],
									"uv_size": [1, 16]
								},
								"south": {
									"uv": [0, 0],
									"uv_size": [16, 16]
								},
								"west": {
									"uv": [0, 0],
									"uv_size": [1, 16]
								},
								"up": {
									"uv": [16, 1],
									"uv_size": [-16, -1]
								},
								"down": {
									"uv": [16, 16],
									"uv_size": [-16, -1]
								}
							}
						}
					]
				}
			],
			"item_display_transforms": {
				"thirdperson_righthand": {
					"rotation": [71.16, -54, 1.74],
					"translation": [0.5, 3, 0.75],
					"scale": [0.5, 0.5, 0.5],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"firstperson_righthand": {
					"rotation": [0, -44, 0],
					"translation": [0.75, -0.5, -0.75],
					"scale": [0.5, 0.5, 0.5],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0]
				},
				"gui": {
					"rotation": [0, -180, 0],
					"translation": [0, 0, 0],
					"scale": [1, 1, 1],
					"rotation_pivot": [0, 0, 0],
					"scale_pivot": [0, 0, 0],
					"fit_to_frame": false
				}
			}
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.nether_wart",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [8, 0, -8],
					"cubes": [
						{
							"origin": [4, 0, -8],
							"size": [0, 16, 16],
							"uv": {
								"east": {"uv": [16, 0], "uv_size": [-16, 16]},
								"west": {"uv": [0, 0], "uv_size": [16, 16]}
							}
						},
						{
							"origin": [-4, 0, -8],
							"size": [0, 16, 16],
							"uv": {
								"east": {"uv": [0, 0], "uv_size": [16, 16]},
								"west": {"uv": [16, 0], "uv_size": [-16, 16]}
							}
						},
						{
							"origin": [-8, 0, -4],
							"size": [16, 16, 0],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 16]},
								"south": {"uv": [16, 0], "uv_size": [-16, 16]}
							}
						},
						{
							"origin": [-8, 0, 4],
							"size": [16, 16, 0],
							"uv": {
								"north": {"uv": [16, 0], "uv_size": [-16, 16]},
								"south": {"uv": [0, 0], "uv_size": [16, 16]}
							}
						}
					]
				}
			]
		}
	]
}{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.crop",
				"texture_width": 16,
				"texture_height": 16
			},
			"bones": [
				{
					"name": "bone",
					"pivot": [8, 0, -8],
					"cubes": [
						{
							"origin": [4, -1, -8],
							"size": [0, 16, 16],
							"uv": {
								"east": {"uv": [16, 0], "uv_size": [-16, 16]},
								"west": {"uv": [0, 0], "uv_size": [16, 16]}
							}
						},
						{
							"origin": [-4, -1, -8],
							"size": [0, 16, 16],
							"uv": {
								"east": {"uv": [0, 0], "uv_size": [16, 16]},
								"west": {"uv": [16, 0], "uv_size": [-16, 16]}
							}
						},
						{
							"origin": [-8, 0, -4],
							"size": [16, 15, 0],
							"uv": {
								"north": {"uv": [0, 0], "uv_size": [16, 16]},
								"south": {"uv": [16, 0], "uv_size": [-16, 16]}
							}
						},
						{
							"origin": [-8, -1, 4],
							"size": [16, 16, 0],
							"uv": {
								"north": {"uv": [16, 0], "uv_size": [-16, 16]},
								"south": {"uv": [0, 0], "uv_size": [16, 16]}
							}
						}
					]
				}
			]
		}
	]
}<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Mappings;

use Nexly\Blocks\BlockMapping;
use pocketmine\network\mcpe\protocol\types\BlockPaletteEntry;
use pocketmine\utils\SingletonTrait;
use function count;

class BlockMappings
{
	use SingletonTrait;

	/** @var array<string, BlockMapping> Mapping of block string IDs to their BlockMapping instances. */
	protected array $mappings = [];
	protected array $entries = [];

	public function nextRuntimeId() : int
	{
		return 10000 + count($this->entries);
	}

	public function getMappings() : array
	{
		return $this->mappings;
	}

	public function setMappings(array $mappings) : void
	{
		$this->mappings = $mappings;
	}

	public function getEntries() : array
	{
		return $this->entries;
	}

	public function setEntries(array $entries) : void
	{
		$this->entries = $entries;
	}

	/**
	 * Registers a new block mapping.
	 */
	public function registerMapping(BlockMapping $mapping) : void
	{
		if (isset($this->mappings[$mapping->getStringId()])) {
			throw new \InvalidArgumentException("Item mapping for " . $mapping->getStringId() . " is already registered");
		}

		$this->mappings[$mapping->getStringId()] = $mapping;
		$this->registerEntry($mapping->getEntry());
	}

	/**
	 * Retrieves the mapping for a given block string ID.
	 */
	public function getMapping(string $stringId) : ?BlockMapping
	{
		return $this->mappings[$stringId] ?? null;
	}

	/**
	 * Registers a block palette entry.
	 */
	public function registerEntry(BlockPaletteEntry $entry) : void
	{
		$this->entries[] = $entry;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Mappings;

use Nexly\Items\ItemBuilder;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\utils\SingletonTrait;
use ReflectionException;

class ItemMappings
{
	use SingletonTrait;

	/** @var array<string, array> Mapping of item string IDs to their numeric IDs. */
	protected array $mappings = [];

	public function getMappings() : array
	{
		return $this->mappings;
	}

	public function setMappings(array $mappings) : void
	{
		$this->mappings = $mappings;
	}

	/**
	 * Registers a new item mapping.
	 */
	public function registerMapping(ItemBuilder $builder, ItemTypeEntry $entry) : void
	{
		if (isset($this->mappings[$entry->getStringId()])) {
			throw new \InvalidArgumentException("Item mapping for " . $entry->getStringId() . " is already registered");
		}

		$this->mappings[$entry->getStringId()] = ["builder" => $builder, "entry" => $entry];

		try {
			$this->registerEntry($entry);
		} catch (ReflectionException $e) {
			throw new \RuntimeException("Failed to register item entry: " . $e->getMessage(), 0, $e);
		}
	}

	/**
	 * Retrieves the mapping for a given item string ID.
	 */
	public function getMapping(string $stringId) : ?array
	{
		return $this->mappings[$stringId] ?? null;
	}

	/**
	 * Registers a new ItemTypeEntry to the TypeConverter's item type dictionary.
	 *
	 * @throws ReflectionException
	 */
	public static function registerEntry(ItemTypeEntry $entry) : void
	{
		$stringId = $entry->getStringId();
		$numericId = $entry->getNumericId();

		$dictionary = TypeConverter::getInstance()->getItemTypeDictionary();
		$reflection = new \ReflectionClass($dictionary);

		$intToString = $reflection->getProperty("intToStringIdMap");
		/** @var int[] $value */
		$value = $intToString->getValue($dictionary);
		$intToString->setValue($dictionary, $value + [$numericId => $stringId]);

		$stringToInt = $reflection->getProperty("stringToIntMap");
		/** @var int[] $value */
		$value = $stringToInt->getValue($dictionary);
		$stringToInt->setValue($dictionary, $value + [$stringId => $numericId]);

		$itemTypes = $reflection->getProperty("itemTypes");
		$value = $itemTypes->getValue($dictionary);
		$value[] = $entry;
		$itemTypes->setValue($dictionary, $value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Tasks;

use Nexly\Blocks\BlockPalette;
use Nexly\Blocks\Components\AsyncBlockComponent;
use Nexly\Blocks\NexlyBlocks;
use Nexly\Blocks\Permutations\BlockProperty;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Blocks\Traits\BlockTrait;
use pmmp\thread\ThreadSafeArray;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\convert\BlockStateReader;
use pocketmine\data\bedrock\block\convert\BlockStateWriter;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\scheduler\AsyncTask;
use function base64_decode;
use function is_string;
use function json_decode;

class AsyncRegisterBlocksTask extends AsyncTask
{
	protected ThreadSafeArray $numericIds;
	protected ThreadSafeArray $func;
	protected ThreadSafeArray $properties;
	protected ThreadSafeArray $permutations;
	protected ThreadSafeArray $components;
	protected ThreadSafeArray $traits;
	protected ThreadSafeArray $serializer;
	protected ThreadSafeArray $deserializer;

	/**
	 * @param Closure[] $blocks
	 * @phpstan-param array<string, array{(Closure(int): Block), (Closure(BlockStateWriter): Block), (Closure(Block): BlockStateReader)}> $blocks
	 */
	public function __construct(array $blocks)
	{
		$this->numericIds = new ThreadSafeArray();
		$this->func = new ThreadSafeArray();
		$this->properties = new ThreadSafeArray();
		$this->permutations = new ThreadSafeArray();
		$this->components = new ThreadSafeArray();
		$this->traits = new ThreadSafeArray();
		$this->serializer = new ThreadSafeArray();
		$this->deserializer = new ThreadSafeArray();

		foreach ($blocks as $stringId => [$numericId, $func, $properties, $permutations, $components, $traits, $serializer, $deserializer]) {
			$this->numericIds[$stringId] = $numericId;
			$this->func[$stringId] = $func;
			$this->properties[$stringId] = $properties;
			$this->permutations[$stringId] = $permutations;
			$this->components[$stringId] = $components;
			$this->traits[$stringId] = $traits;
			$this->serializer[$stringId] = $serializer;
			$this->deserializer[$stringId] = $deserializer;
		}
	}

	public function onRun() : void
	{
		foreach ($this->func as $stringId => $blockFunc) {
			// We do not care about the model or creative inventory data in other threads since it is unused outside of
			// the main thread.
			$builder = NexlyBlocks::buildComplex($stringId, $blockFunc)
				->setNumericId($this->numericIds[$stringId])
				->mapSerializer($this->serializer[$stringId])
				->mapDeserializer($this->deserializer[$stringId]);

			foreach (json_decode($this->properties[$stringId], true) as $property) {
				$builder->addProperty(new BlockProperty($property["name"], $property["values"]));
			}

			foreach (json_decode($this->permutations[$stringId], true) as $permutation) {
				$p = Permutation::create($permutation["condition"]);

				foreach ($permutation["components"] as $component) {
					$p->addComponent(new AsyncBlockComponent($component["name"], $component["nbt"]));
				}

				$builder->addPermutation($p);
			}

			foreach (json_decode($this->components[$stringId], true) as $component) {
				$builder->addComponent(new AsyncBlockComponent($component["name"], $component["nbt"]));
			}

			foreach (json_decode($this->traits[$stringId], true) as $trait) {
				$identifier = $trait["identifier"] ?? null;
				if (!is_string($identifier)) {
					throw new \InvalidArgumentException("Invalid identifier for trait " . $identifier);
				}

				$nbt = $trait["nbt"] ?? null;
				if (!is_string($nbt)) {
					throw new \InvalidArgumentException("Invalid nbt for trait " . $identifier);
				}

				$builder->addTrait(BlockTrait::fromNBT(
					$identifier,
					(new NetworkNbtSerializer())->read(base64_decode($nbt, true))->getTag()
				));
			}

			$builder->register(false, false, true);
		}

		// Sort the block palette
		BlockPalette::getInstance()->apply();
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Impl;

use Nexly\Events\Event;
use Nexly\Recipes\MinecraftShape;
use Nexly\Recipes\NexlyRecipes;
use Nexly\Recipes\Types\ShapedRecipe;
use Nexly\Recipes\Types\ShapelessRecipe;
use pocketmine\crafting\ShapelessRecipeType;

class RecipeRegistryEvent extends Event
{
	protected int $shaped = 0;
	protected int $shapeless = 0;

	/**
	 * Registers a shaped recipe.
	 *
	 * @param MinecraftShape|string[] $shape
	 */
	public function registerShaped(MinecraftShape|array $shape, array $ingredients, array $outputs) : self
	{
		NexlyRecipes::getInstance()->addRecipe(fn () => new ShapedRecipe($shape, $ingredients, $outputs));
		$this->shaped++;
		return $this;
	}

	/**
	 * Registers a shapeless recipe.
	 *
	 * @return $this
	 */
	public function registerShapeless(array $ingredients, array $outputs, ShapelessRecipeType $type = ShapelessRecipeType::CRAFTING) : self
	{
		NexlyRecipes::getInstance()->addRecipe(fn () => new ShapelessRecipe($type, $ingredients, $outputs));
		$this->shapeless++;
		return $this;
	}

	public function getShaped() : int
	{
		return $this->shaped;
	}

	public function getShapeless() : int
	{
		return $this->shapeless;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Impl;

use Nexly\Events\Event;
use Nexly\Items\Creative\CreativeInfo;
use Nexly\Items\NexlyItems;
use pocketmine\item\Item;

class ItemRegistryEvent extends Event
{
	protected int $count = 0;

	public function register(string $stringId, Item $item, CreativeInfo $creativeInfo = null) : self
	{
		NexlyItems::register($stringId, $item, $creativeInfo);
		$this->count++;
		return $this;
	}

	public function getCount() : int
	{
		return $this->count;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Impl;

use Nexly\Blocks\BlockBuilder;
use Nexly\Blocks\NexlyBlocks;
use Nexly\Events\Event;
use Nexly\Items\Creative\CreativeInfo;

class BlockRegistryEvent extends Event
{
	protected int $count = 0;

	public function register(string $stringId, \Closure $block, ?CreativeInfo $creativeInfo = null) : BlockBuilder
	{
		$this->count++;
		return NexlyBlocks::register($stringId, $block, $creativeInfo);
	}

	public function build(string $stringId, \Closure $block, ?CreativeInfo $creativeInfo = null) : BlockBuilder
	{
		$this->count++;
		return NexlyBlocks::buildComplex($stringId, $block, $creativeInfo);
	}

	public function getCount() : int
	{
		return $this->count;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Impl;

use Nexly\Blocks\BlockBuilder;
use Nexly\Events\Event;
use pocketmine\block\Block;

class BlockLoaderEvent extends Event
{
	protected bool $affected = false;

	public function __construct(
		protected BlockBuilder $builder,
		protected Block $block
	) {
	}

	public function getBuilder() : BlockBuilder
	{
		return $this->builder;
	}

	public function getBlock() : Block
	{
		return $this->block;
	}

	public function isAffected() : bool
	{
		return $this->affected;
	}

	public function affected(bool $affected = true) : void
	{
		$this->affected = $affected;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Attribute;

use Attribute;
use pocketmine\event\EventPriority as PMPriority;

#[Attribute(Attribute::TARGET_METHOD)]
class HandlePriority
{
	public function __construct(
		protected int|string $priority = PMPriority::NORMAL
	) {
	}

	public function getPriority() : int|string
	{
		return $this->priority;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class HandleCancelled
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events;

use Nexly\Events\Attribute\HandleCancelled;
use Nexly\Events\Attribute\HandlePriority;
use pocketmine\event\EventPriority;
use pocketmine\utils\SingletonTrait;
use ReflectionException;
use ReflectionFunction;
use WeakReference;
use function array_filter;
use function array_push;
use function array_values;
use function class_exists;
use function is_callable;
use function is_string;
use function is_subclass_of;
use function method_exists;
use function spl_object_id;

class NexlyEventManager
{
	use SingletonTrait;

	protected const PRIORITY_ORDER = [
		EventPriority::LOWEST,
		EventPriority::LOW,
		EventPriority::NORMAL,
		EventPriority::HIGH,
		EventPriority::HIGHEST,
		EventPriority::MONITOR,
	];

	/** @var EventReflected[] */
	protected array $handlerCaches = [];

	/**
	 * Register an event listener.
	 */
	public function listen(string $event, callable $callback, ?object $instance = null) : void
	{
		if (!class_exists($event) || !is_subclass_of($event, Event::class)) {
			throw new \InvalidArgumentException("Event class must exist and be a subclass of " . Event::class . " (got: {$event})");
		}

		$callable = $callback;
		if ($instance !== null && is_string($callback)) {
			if (!method_exists($instance, $callback)) {
				throw new \InvalidArgumentException("Method {$callback} does not exist on " . $instance::class);
			}
			$callable = [$instance, $callback];
		}

		if (!is_callable($callable)) {
			throw new \InvalidArgumentException("Callback is not callable");
		}

		$closure = $callable(...);
		$rf = new ReflectionFunction($closure);

		if ($rf->isStatic() && $instance !== null) {
			throw new \InvalidArgumentException("Cannot bind instance to static method");
		}
		if (!$rf->isStatic() && $instance === null) {
			throw new \InvalidArgumentException("Cannot use non-static method without instance");
		}

		$this->handlerCaches[$event] ??= [];

		$handleCancelled = false;
		$priority = EventPriority::NORMAL;

		foreach ($rf->getAttributes() as $attribute) {
			$attrInstance = $attribute->newInstance();
			if ($attrInstance instanceof HandlePriority) {
				$priority = $attrInstance->getPriority();
			} elseif ($attrInstance instanceof HandleCancelled) {
				$handleCancelled = true;
			}
		}

		$this->handlerCaches[$event] ??= [];
		$this->handlerCaches[$event][$priority] ??= [];
		$this->handlerCaches[$event][$priority][] = new EventReflected(
			$closure,
			$rf,
			$rf->isStatic(),
			$rf->isStatic() ? null : WeakReference::create($instance),
			$rf->isStatic() ? null : spl_object_id($instance),
			$priority,
			$handleCancelled,
		);
	}

	/**
	 * Remove event listener(s). If both $callback and $instance are null, all listeners for the event will be removed.
	 *
	 * @throws ReflectionException
	 */
	public function deafen(string $event, ?callable $callback = null, ?object $instance = null) : void
	{
		if (!class_exists($event) || !is_subclass_of($event, Event::class)) {
			throw new \InvalidArgumentException("Event class must exist and be a subclass of " . Event::class . " (got: {$event})");
		}
		if (!isset($this->handlerCaches[$event])) {
			return;
		}

		if ($callback === null && $instance === null) {
			unset($this->handlerCaches[$event]);
			return;
		}

		if ($callback === null && $instance !== null) {
			$iid = spl_object_id($instance);
			foreach (self::PRIORITY_ORDER as $p) {
				if (!isset($this->handlerCaches[$event][$p])) {
					continue;
				}
				$this->handlerCaches[$event][$p] = array_values(array_filter(
					$this->handlerCaches[$event][$p],
					static fn (EventReflected $er) => $er->getInstanceId() !== $iid
				));
			}
			return;
		}

		$callable = $callback;
		if ($instance !== null && is_string($callback)) {
			if (!method_exists($instance, $callback)) {
				throw new \InvalidArgumentException("Method {$callback} does not exist on " . $instance::class);
			}
			$callable = [$instance, $callback];
		}
		if (!is_callable($callable)) {
			throw new \InvalidArgumentException("Callback is not callable");
		}

		$tClosure = $callable(...);
		$tRf = new ReflectionFunction($tClosure);
		$targetFile = $tRf->getFileName();
		$targetStart = $tRf->getStartLine();
		$targetEnd = $tRf->getEndLine();
		$iidFilter = $instance ? spl_object_id($instance) : null;

		foreach (self::PRIORITY_ORDER as $p) {
			if (!isset($this->handlerCaches[$event][$p])) {
				continue;
			}

			$this->handlerCaches[$event][$p] = array_values(array_filter(
				$this->handlerCaches[$event][$p],
				static function (EventReflected $er) use ($targetFile, $targetStart, $targetEnd, $iidFilter) : bool {
					if ($iidFilter !== null && $er->getInstanceId() !== $iidFilter) {
						return true;
					}
					$erf = $er->getRef();
					return !(
						$erf->getFileName() === $targetFile
						&& $erf->getStartLine() === $targetStart
						&& $erf->getEndLine() === $targetEnd
					);
				}
			));
		}
	}

	/**
	 * Get handlers for a specific event instance, ordered by priority (LOWEST â†’ â€¦ â†’ MONITOR).
	 * Returns a flat array of EventReference.
	 *
	 * @return EventReflected[]
	 */
	public function getHandlersFor(object $event) : array
	{
		if (!$event instanceof Event) {
			throw new \InvalidArgumentException("Event must be an instance of " . Event::class);
		}
		$class = $event::class;
		if ($class === Event::class) {
			throw new \InvalidArgumentException("Cannot get handlers for abstract " . Event::class);
		}

		$byP = $this->handlerCaches[$class] ?? [];
		$flat = [];
		foreach (self::PRIORITY_ORDER as $p) {
			if (!empty($byP[$p])) {
				array_push($flat, ...$byP[$p]);
			}
		}
		return $flat;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events;

use pocketmine\event\Cancellable;
use pocketmine\network\PacketHandlingException;

abstract class Event
{
	public function trigger() : void
	{
		$handlers = NexlyEventManager::getInstance()->getHandlersFor($this);

		try {
			foreach ($handlers as $k => $handler) {
				$ownerRef = $handler->getInstanceRef();
				if ($ownerRef !== null && $ownerRef->get() === null) {
					continue;
				}

				if ($this instanceof Cancellable && $this->isCancelled() && !$handler->isHandleCancelled()) {
					continue;
				}

				($handler->getClosure())($this);
			}
		} catch (\Exception $e) {
			throw PacketHandlingException::wrap($e, "Exception occurred when handling " . $this::class);
		}
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Events;

use ReflectionFunction;
use WeakReference;

class EventReflected
{
	public function __construct(
		protected \Closure           $closure,
		protected ReflectionFunction $ref,
		protected bool               $isStatic,
		protected ?WeakReference     $instanceRef,
		protected ?int               $instanceId,
		protected int                $priority,
		protected bool               $handleCancelled,
	) {
	}

	public function getClosure() : \Closure
	{
		return $this->closure;
	}

	public function getRef() : ReflectionFunction
	{
		return $this->ref;
	}

	public function isStatic() : bool
	{
		return $this->isStatic;
	}

	public function getInstanceRef() : ?WeakReference
	{
		return $this->instanceRef;
	}

	public function getInstanceId() : ?int
	{
		return $this->instanceId;
	}

	public function getPriority() : int
	{
		return $this->priority;
	}

	public function isHandleCancelled() : bool
	{
		return $this->handleCancelled;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Recipes;

use function explode;

enum MinecraftShape : string
{
	case ONE = "A";
	case FULL_BLOCK = "AAA\nAAA\nAAA";

	case HELMET = "AAA\nA A";
	case CHESTPLATE = "A A\nAAA\nAAA";
	case LEGGINGS = "AAA\nA A\nA A";
	case BOOTS = "A A\nA A";
	case SWORD = "A\nA\nB";
	case SHOVEL = "A\nB\nB";
	case PICKAXE = "AAA\n B \n B ";
	case AXE = "AA \nAB \n B ";
	case HOE = "AA \n B \n B ";
	case BOW = " A B\nA B \n A B";
	case CROSSBOW = " A B\nABA\n A B";
	case FISHING_ROD = "  A\n B \nB  ";
	case LEAD = "A A\nABA\n B ";
	case SHEARS = " A \nA A";
	case FLINT_AND_STEEL = " A \n  B\nB  ";
	case STICK = "A\nA";

	/**
	 * @return string[]
	 */
	public function toArray() : array
	{
		return explode("\n", $this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Recipes;

use Nexly\Events\Impl\RecipeRegistryEvent;
use Nexly\Recipes\Types\Recipe;
use Nexly\Recipes\Types\ShapedRecipe;
use Nexly\Recipes\Types\ShapelessRecipe;
use pocketmine\crafting\ShapelessRecipe as ShapelessRecipePM;
use pocketmine\crafting\ShapelessRecipeType;
use pocketmine\item\StringToItemParser;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
use pocketmine\utils\SingletonTrait;
use function count;
use function is_array;
use function is_string;
use function strtolower;

class NexlyRecipes
{
	use SingletonTrait;

	/** @var \Closure[] */
	protected array $recipes = [];

	public function __construct()
	{
		self::setInstance($this);
	}

	public function getRecipes() : array
	{
		return $this->recipes;
	}

	public function setRecipes(array $recipes) : void
	{
		$this->recipes = $recipes;
	}

	public function addRecipe(\Closure $recipe) : void
	{
		$this->recipes[] = $recipe;
	}

	/**
	 * Loads a recipe from an associative array.
	 */
	public function fromArray(array $data) : void
	{
		if (!isset($data["output"])) {
			throw new \InvalidArgumentException("Missing 'output' key");
		}
		if (!isset($data["ingredients"])) {
			throw new \InvalidArgumentException("Missing 'ingredients' key");
		}

		$this->addRecipe(function () use ($data) : Recipe {
			$output = $data["output"];
			if (is_string($output)) {
				$output = StringToItemParser::getInstance()->parse($output);
			} elseif (is_array($output)) {
				if (!isset($output["item"])) {
					throw new \InvalidArgumentException("Invalid 'output' format, missing 'item' key");
				}

				if (isset($output["count"])) {
					$count = (int) $output["count"];
					if ($count < 1 || $count > 64) {
						throw new \InvalidArgumentException("Invalid 'output' count, must be between 1 and 64");
					}
					$item = StringToItemParser::getInstance()->parse($output["item"]);
					$item->setCount($count);
					$output = $item;
				} else {
					$output = StringToItemParser::getInstance()->parse($output["item"]);
				}
			} else {
				throw new \InvalidArgumentException("Invalid 'output' format");
			}

			if (count($data["ingredients"]) > 9) {
				throw new \InvalidArgumentException("Too many 'ingredients', maximum is 9");
			}
			if (!isset($data["shape"])) {
				if (!isset($data["type"])) {
					$type = ShapelessRecipeType::CRAFTING();
				} else {
					$type = match(strtolower($data["type"])) {
						"crafting_table" => ShapelessRecipeType::CRAFTING(),
						"cartography" => ShapelessRecipeType::CARTOGRAPHY(),
						"smithing" => ShapelessRecipeType::SMITHING(),
						"stonecutter" => ShapelessRecipeType::STONECUTTER(),
						default => throw new \InvalidArgumentException("Invalid 'type' value"),
					};
				}

				return new ShapelessRecipe($type, $data["ingredients"], [$output]);
			}

			return new ShapedRecipe($data["shape"], $data["ingredients"], [$output]);
		});
	}

	/**
	 * Loads recipes from a JSON array.
	 */
	public function fromJson(array $data) : void
	{
		foreach ($data as $recipe) {
			if (is_array($recipe)) {
				$this->fromArray($recipe);
			} else {
				throw new \InvalidArgumentException("Invalid recipe format, expected array");
			}
		}
	}

	public function registers(Plugin $plugin) : void
	{
		$server = Server::getInstance();
		$craftingManager = $server->getCraftingManager();

		$ev = new RecipeRegistryEvent();
		$ev->trigger();
		$plugin->getLogger()->notice("Registered " . $ev->getShaped() . " shaped and " . $ev->getShapeless() . " shapeless recipes.");

		foreach ($this->recipes as $closure) {
			try {
				$recipe = ($closure)();
			} catch (\Exception $e) {
				$plugin->getLogger()->error("Error while building a recipe: " . $e->getMessage());
				continue;
			}

			if (!$recipe instanceof Recipe) {
				continue;
			}

			$craftingRecipe = $recipe->build();
			if ($craftingRecipe instanceof ShapelessRecipePM) {
				$craftingManager->registerShapelessRecipe($craftingRecipe);
			} else {
				$craftingManager->registerShapedRecipe($craftingRecipe);
			}
		}
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Recipes\Types;

use Attribute;
use pocketmine\crafting\ExactRecipeIngredient;
use pocketmine\crafting\ShapelessRecipe as ShapelessRecipePM;
use pocketmine\crafting\ShapelessRecipeType;
use pocketmine\item\Item;
use pocketmine\item\StringToItemParser;
use function array_map;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class ShapelessRecipe implements Recipe
{
	/**
	 * @param Item|string[] $ingredients
	 * @param Item|string[] $outputs
	 */
	public function __construct(
		protected ShapelessRecipeType $type,
		protected array               $ingredients,
		protected array               $outputs
	) {
	}

	public function getType() : ShapelessRecipeType
	{
		return $this->type;
	}

	public function getIngredients() : array
	{
		return $this->ingredients;
	}

	public function getOutputs() : array
	{
		return $this->outputs;
	}

	/**
	 * Builds and returns the ShapedRecipePM instance.
	 */
	public function build() : ShapelessRecipePM
	{
		return new ShapelessRecipePM(
			array_map(fn ($ingredient) => new ExactRecipeIngredient(is_string($ingredient) ? StringToItemParser::getInstance()->parse($ingredient) : $ingredient), $this->ingredients),
			array_map(fn ($output) => is_string($output) ? StringToItemParser::getInstance()->parse($output) : $output, $this->outputs),
			$this->type
		);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Recipes\Types;

use Attribute;
use Nexly\Recipes\MinecraftShape;
use pocketmine\crafting\ExactRecipeIngredient;
use pocketmine\crafting\ShapedRecipe as ShapedRecipePM;
use pocketmine\item\Item;
use pocketmine\item\StringToItemParser;
use function array_map;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class ShapedRecipe implements Recipe
{
	/**
	 * @param MinecraftShape|string[] $shape
	 * @param Item|string[]           $ingredients
	 * @param Item|string[]           $outputs
	 */
	public function __construct(
		protected MinecraftShape|array $shape,
		protected array       $ingredients,
		protected array       $outputs
	) {
		if ($this->shape instanceof MinecraftShape) {
			$this->shape = $this->shape->toArray();
		}
	}

	public function getShape() : array
	{
		return $this->shape;
	}

	public function getIngredients() : array
	{
		return $this->ingredients;
	}

	/**
	 * @return string[]
	 */
	public function getOutputs() : array
	{
		return $this->outputs;
	}

	/**
	 * Builds and returns the ShapedRecipePM instance.
	 */
	public function build() : ShapedRecipePM
	{
		return new ShapedRecipePM(
			$this->shape,
			array_map(fn ($ingredient) => new ExactRecipeIngredient(is_string($ingredient) ? StringToItemParser::getInstance()->parse($ingredient) : $ingredient), $this->ingredients),
			array_map(fn ($output) => is_string($output) ? StringToItemParser::getInstance()->parse($output) : $output, $this->outputs),
		);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Recipes\Types;

use pocketmine\crafting\CraftingRecipe;

interface Recipe
{
	public function build() : CraftingRecipe;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly;

use Nexly\Blocks\AsyncInitialization;
use Nexly\Blocks\BlockPalette;
use Nexly\Events\Impl\BlockRegistryEvent;
use Nexly\Events\Impl\ItemRegistryEvent;
use Nexly\Listeners\BlockBreakingListener;
use Nexly\Listeners\LadderClimbingListener;
use Nexly\Mappings\BlockMappings;
use Nexly\Recipes\NexlyRecipes;
use Nexly\Tasks\AsyncRegisterBlocksTask;
use pocketmine\event\EventPriority;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\plugin\PluginBase;
use pocketmine\scheduler\ClosureTask;
use function array_merge;
use function count;

class Nexly extends PluginBase
{
	protected function onLoad() : void
	{
		$this->saveDefaultConfig();
		$this->saveResource("config.yml");
	}

	protected function onEnable() : void
	{
		$config = $this->getConfig();
		if ($config->get("enable-block-breaking", true)) {
			try {
				$this->getServer()->getPluginManager()->registerEvents(new BlockBreakingListener(), $this);
			} catch (\Exception $e) {
				$this->getLogger()->error("Failed to register BlockBreakingListener: " . $e->getMessage());
			}
		} else {
			$this->getLogger()->notice("================================");
			$this->getLogger()->notice("You can enable the BlockBreakingListener in config.yml");
			$this->getLogger()->notice("This allows you to break blocks with custom items like the Emerald Pickaxe");
			$this->getLogger()->notice("================================");
		}

		if ($config->get("enable-ladder-climbing", true)) {
			try {
				$this->getServer()->getPluginManager()->registerEvents(new LadderClimbingListener(), $this);
			} catch (\Exception $e) {
				$this->getLogger()->error("Failed to register LadderClimbingListener: " . $e->getMessage());
			}
		}

		$ev = new BlockRegistryEvent();
		$ev->trigger();
		$this->getLogger()->notice("Registered " . $ev->getCount() . " blocks.");

		$ev = new ItemRegistryEvent();
		$ev->trigger();
		$this->getLogger()->notice("Registered " . $ev->getCount() . " items.");

		$this->getServer()->getPluginManager()->registerEvent(DataPacketSendEvent::class, function (DataPacketSendEvent $ev) : void {
			$packets = $ev->getPackets();
			foreach ($packets as $packet) {
				if ($packet instanceof StartGamePacket) {
					$packet->blockNetworkIdsAreHashes = true; // Always true for Nexly
					$packet->blockPalette = array_merge($packet->blockPalette, BlockMappings::getInstance()->getEntries());
				}
			}
		}, EventPriority::NORMAL, $this);

		$this->getScheduler()->scheduleDelayedTask(new ClosureTask(function () : void {
			$pool = $this->getServer()->getAsyncPool();
			$blocks = AsyncInitialization::getBlocks();

			NexlyRecipes::getInstance()->registers($this);
			BlockPalette::getInstance()->apply();

			$this->getLogger()->notice("Sending " . count($blocks) . " blocks to worker threads for registration...");
			$pool->addWorkerStartHook(function (int $worker) use ($pool, $blocks) : void {
				$pool->submitTaskToWorker(new AsyncRegisterBlocksTask($blocks), $worker);
			});
		}), 1);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use Nexly\Items\Components\DataDriven\DataDriven;
use Nexly\Items\Components\DataDriven\Types\ItemBlockVisual;
use pocketmine\block\Block;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier;

trait DataDrivenItemBlockTrait
{
	public function asItem() : Item
	{
		return new #[DataDriven] #[ItemBlockVisual] class (new ItemIdentifier(-$this->getTypeId()), $this->getName(), clone $this) extends Item {
			public function __construct(
				ItemIdentifier $identifier,
				string                         $name,
				protected Block $block,
			) {
				parent::__construct($identifier, $name);
			}

			public function getBlock(?int $clickedFace = null) : Block
			{
				return (clone $this->block);
			}
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Traits;

use InvalidArgumentException;
use Nexly\Blocks\Components\Types\Face;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function count;
use function in_array;
use function strtolower;

class BlockTrait
{
	public function __construct(
		protected string     $id,
		protected TraitState $state,
		protected float      $rotationOffset = 0.0,
		protected ?string    $multiBlockDirection = null,
		protected ?int       $multiBlockParts = null
	) {
		if ($id === TraitId::PLACEMENT_DIRECTION) {
			if (!in_array($rotationOffset, [0.0, 90.0, 180.0, 270.0], true)) {
				throw new InvalidArgumentException("y_rotation_offset must be exactly 0.0, 90.0, 180.0, or 270.0 degrees.");
			}
		} elseif ($id === TraitId::MULTI_BLOCK) {
			$parts = $multiBlockParts ?? 2;
			if ($parts < 2 || $parts > 4) {
				throw new InvalidArgumentException("minecraft:multi_block.parts must be an integer in [2, 4]");
			}

			$direction = strtolower($multiBlockDirection ?? Face::UP);
			if (!in_array($direction, Face::ALL, true)) {
				throw new InvalidArgumentException("Invalid value for 'direction' in multi_block trait.");
			}

			$states = $state->getEnabledTraits();
			if (count($states) !== 1 || $states[0] !== TraitStateId::MULTI_BLOCK_PART) {
				throw new InvalidArgumentException('"minecraft:multi_block" trait requires exactly one enabled_state: multi_block_part.');
			}
		} elseif ($multiBlockParts !== null || $multiBlockDirection !== null) {
			throw new InvalidArgumentException("Multi-block properties are only allowed on the MULTI_BLOCK trait.");
		}
	}

	public function getId() : string
	{
		return $this->id;
	}

	public function getState() : TraitState
	{
		return $this->state;
	}

	public function getRotationOffset() : float
	{
		return $this->rotationOffset;
	}

	public function getMultiBlockDirection() : ?string
	{
		return $this->multiBlockDirection;
	}

	public function getMultiBlockParts() : ?int
	{
		return $this->multiBlockParts;
	}

	public static function fromNBT(string $id, CompoundTag $nbt) : self
	{
		$rotationOffset = 0.0;
		$multiBlockDirection = null;
		$multiBlockParts = null;

		if ($id === TraitId::PLACEMENT_DIRECTION) {
			$rotationOffset = $nbt->getFloat("y_rotation_offset", 0.0);
		} elseif ($id === TraitId::MULTI_BLOCK) {
			$dirTag = $nbt->getTag("direction");
			$multiBlockDirection = $dirTag?->getValue();

			$partsTag = $nbt->getTag("parts");
			$multiBlockParts = $partsTag?->getValue();
		}

		$enabledStatesTag = $nbt->getTag("enabled_states");
		$state = $enabledStatesTag !== null ? TraitState::fromNBT($enabledStatesTag) : new TraitState();

		return new self(
			id: $id,
			state: $state,
			rotationOffset: $rotationOffset,
			multiBlockDirection: $multiBlockDirection,
			multiBlockParts: $multiBlockParts
		);
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("enabled_states", $this->state->toNBT())
			->setTag("name", new StringTag($this->id));

		if ($this->id === TraitId::PLACEMENT_DIRECTION) {
			$nbt->setFloat("y_rotation_offset", $this->rotationOffset);
		} elseif ($this->id === TraitId::MULTI_BLOCK) {
			if ($this->multiBlockDirection !== null) {
				$nbt->setString("direction", $this->multiBlockDirection);
			}
			if ($this->multiBlockParts !== null) {
				$nbt->setInt("parts", $this->multiBlockParts);
			}
		}
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Traits;

class TraitId
{
	public const PLACEMENT_DIRECTION = "minecraft:placement_direction";
	public const PLACEMENT_POSITION = "minecraft:placement_position";
	public const MULTI_BLOCK = "minecraft:multi_block";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Traits;

class TraitStateId
{
	public const CARDINAL_DIRECTION = 'cardinal_direction';
	public const FACING_DIRECTION = 'facing_direction';
	public const BLOCK_FACE = 'block_face';
	public const VERTICAL_HALF = 'vertical_half';
	public const SIXTEEN_WAY_ROTATION = 'sixteen_way_rotation';
	public const CORNER_AND_CARDINAL_DIRECTION = 'corner_and_cardinal_direction';
	public const MULTI_BLOCK_PART = 'multi_block_part';
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Traits;

use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
use function array_map;
use function array_unique;
use function array_values;
use function in_array;
use function str_replace;

class TraitState
{
	/** @var string[] */
	protected array $enabledTraits;

	public function __construct(string ...$states)
	{
		$this->enabledTraits = array_values(array_unique($states));

		$directionsCount = 0;
		if ($this->isCardinal()) {
			$directionsCount++;
		}
		if ($this->isFacing()) {
			$directionsCount++;
		}
		if (in_array(TraitStateId::SIXTEEN_WAY_ROTATION, $this->enabledTraits, true)) {
			$directionsCount++;
		}

		if ($directionsCount > 1) {
			throw new \InvalidArgumentException("A trait cannot combine cardinal_direction, facing_direction, or sixteen_way_rotation. Choose only one.");
		}
	}

	/**
	 * @return string[]
	 */
	public function getEnabledTraits() : array
	{
		return $this->enabledTraits;
	}

	public function isCardinal() : bool
	{
		return in_array(TraitStateId::CARDINAL_DIRECTION, $this->enabledTraits, true);
	}

	public function isFacing() : bool
	{
		return in_array(TraitStateId::FACING_DIRECTION, $this->enabledTraits, true);
	}

	public function getNames() : array
	{
		$names = [];
		foreach ($this->enabledTraits as $stateValue) {
			if ($stateValue === TraitStateId::CORNER_AND_CARDINAL_DIRECTION) {
				$names[] = 'minecraft:corner';
				$names[] = 'minecraft:cardinal_direction';
			} else {
				$names[] = 'minecraft:' . $stateValue;
			}
		}
		return array_values(array_unique($names));
	}

	/**
	 * @param ListTag $nbt
	 */
	public static function fromNBT(Tag $nbt) : self
	{
		$rawNames = [];
		/** @var StringTag $tag */
		foreach ($nbt as $tag) {
			$rawNames[] = $tag->getValue();
		}

		$hasCorner = in_array('minecraft:corner', $rawNames, true);
		$hasCardinal = in_array('minecraft:cardinal_direction', $rawNames, true);

		$states = [];
		foreach ($rawNames as $rawName) {
			if ($rawName === 'minecraft:corner') {
				continue;
			}
			if ($rawName === 'minecraft:cardinal_direction' && $hasCorner) {
				$states[] = TraitStateId::CORNER_AND_CARDINAL_DIRECTION;
				continue;
			}

			$states[] = str_replace('minecraft:', '', $rawName);
		}

		return new self(...$states);
	}

	public function toNBT() : ListTag
	{
		return new ListTag(array_map(fn (string $name) => new StringTag($name), $this->getEnabledTraits()), NBT::TAG_String);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\BlockComponentIds;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\EmbeddedVisualBlockComponent;
use Nexly\Blocks\Components\FlowerPottableBlockComponent;
use Nexly\Blocks\Components\GeometryBlockComponent;
use Nexly\Blocks\Components\ItemVisualBlockComponent;
use Nexly\Blocks\Components\LeashableBlockComponent;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent as MaterialComponent;
use Nexly\Blocks\Components\OnInteractBlockComponent;
use Nexly\Blocks\Components\RandomOffsetBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Components\Types\CullingType;
use Nexly\Blocks\Components\Types\Geometry;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\NexlyGeometry;
use Nexly\Blocks\Components\Types\RangeOffset;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Components\Types\Target;
use Nexly\Blocks\Mappers\AgeableBlockMapper;
use Nexly\Blocks\Mappers\DoorBlockMapper;
use Nexly\Blocks\Mappers\FarmlandBlockMapper;
use Nexly\Blocks\Mappers\FenceBlockMapper;
use Nexly\Blocks\Mappers\FenceGateBlockMapper;
use Nexly\Blocks\Mappers\GlassPaneBlockMapper;
use Nexly\Blocks\Mappers\HeadBlockMapper;
use Nexly\Blocks\Mappers\LadderBlockMapper;
use Nexly\Blocks\Mappers\LeverBlockMapper;
use Nexly\Blocks\Mappers\SlabBlockMapper;
use Nexly\Blocks\Mappers\StairBlockMapper;
use Nexly\Blocks\Mappers\TrapdoorBlockMapper;
use Nexly\Blocks\Mappers\WallBlockMapper;
use Nexly\Blocks\Mappers\WoodBlockMapper;
use Nexly\Blocks\Permutations\BlockProperty as Property;
use Nexly\Blocks\Vanilla\HeadBlock;
use Nexly\Blocks\Vanilla\NexlyFence;
use Nexly\Blocks\Vanilla\NexlyGlassPane;
use Nexly\Helpers\DeserializerHelper;
use Nexly\Helpers\MolangHelper;
use Nexly\Helpers\PropertyHelper;
use Nexly\Helpers\SerializerHelper;
use Nexly\Helpers\StringsHelper;
use pocketmine\block\Block;
use pocketmine\block\Cactus;
use pocketmine\block\Crops;
use pocketmine\block\Door;
use pocketmine\block\Farmland;
use pocketmine\block\Fence;
use pocketmine\block\FenceGate;
use pocketmine\block\Flower;
use pocketmine\block\Furnace;
use pocketmine\block\Glass;
use pocketmine\block\GlassPane;
use pocketmine\block\GrassPath;
use pocketmine\block\Hopper;
use pocketmine\block\Ladder;
use pocketmine\block\Leaves;
use pocketmine\block\Lever;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\Sapling;
use pocketmine\block\Slab;
use pocketmine\block\Slime;
use pocketmine\block\Stair;
use pocketmine\block\Trapdoor;
use pocketmine\block\utils\Ageable;
use pocketmine\block\utils\Colored;
use pocketmine\block\Wall;
use pocketmine\block\Wood;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\data\bedrock\block\convert\BlockStateDeserializerHelper as PMDeserializerHelper;
use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader;
use pocketmine\data\bedrock\block\convert\BlockStateSerializerHelper as PMSerializerHelper;
use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use RuntimeException;
use function range;

class BlockCreator
{
	/**
	 * Create permutations for ageable blocks (e.g., wheat, carrots, potatoes, nether_wart_plant).
	 *
	 * @param Crops $block
	 */
	public static function makeAgeable(Builder $builder, Ageable $block) : void
	{
		$stringId = $builder->getStringId();
		$geometry = (match(true) {
			$block instanceof NetherWartPlant => NexlyGeometry::NETHER_WART,
			default => NexlyGeometry::CROP
		});

		$builder->mapSerializer(static fn (Ageable $block) : Writer => SerializerHelper::encodeAgeable($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Block => DeserializerHelper::decodeAgeable(clone $block, $in));

		$builder->addProperty(new Property(StateNames::AGE, range(0, $block::MAX_AGE)));
		$builder->addComponent(new GeometryBlockComponent($geometry));
		$builder->setMapper(AgeableBlockMapper::class);
	}

	/**
	 * Create permutations for Wood blocks.
	 */
	public static function makeWood(Builder $builder, Wood $block) : void
	{
		[$unstrippedId, $strippedId] = StringsHelper::wood($builder->getStringId());

		$builder->mapSerializer(static fn (Wood $block) : Writer => PMSerializerHelper::encodeLog($block, $unstrippedId, $strippedId));
		$builder->mapDeserializer(static fn (Reader $in) : Wood => PMDeserializerHelper::decodeLog(clone $block, $block->isStripped(), $in));

		$builder->addProperty(new Property(StateNames::PILLAR_AXIS, [StateValues::PILLAR_AXIS_X, StateValues::PILLAR_AXIS_Y, StateValues::PILLAR_AXIS_Z]));
		$builder->addComponent(new GeometryBlockComponent());
		$builder->setMapper(WoodBlockMapper::class);
	}

	/**
	 * Create permutations for Leaves blocks.
	 */
	public static function makeLeaves(Builder $builder, Leaves $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Leaves $block) : Writer => PMSerializerHelper::encodeLeaves($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Leaves => PMDeserializerHelper::decodeLeaves(clone $block, $in));

		$builder->addProperty(new Property(StateNames::PERSISTENT_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::UPDATE_BIT, PropertyHelper::BOOL));

		$builder->addComponent(new MaterialComponent([new Material(
			texture: $builder->getName(),
			renderMethod: RenderMethod::ALPHA_TEST,
			ambientOcclusion: 0.80,
			isotropic: true,
		)]));
	}

	/**
	 * Create permutations for Grass Path blocks.
	 */
	public static function makeGrassPath(Builder $builder, GrassPath $block) : void
	{
		$materials = [
			new Material(
				texture: $builder->getName() . "_top",
				target: Target::UP,
				renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
			),
			new Material(
				texture: $builder->getName() . "_bottom",
				target: Target::DOWN,
				renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
			),
			new Material(
				texture: $builder->getName() . "_side",
				target: Target::ALL,
				renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
			)
		];

		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::FARMLAND));
		$builder->addComponent(new MaterialComponent($materials));
		$builder->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::FARMLAND()]));
		$builder->addComponent(new CollisionBoxBlockComponent(true, [BoxCollision::FARMLAND()]));
	}

	/**
	 * Create permutations for Sapling blocks.
	 */
	public static function makeSapling(Builder $builder, Sapling $block) : void
	{
		$stringId = $builder->getStringId();
		$materials = [new Material($builder->getName(), renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED, ambientOcclusion: 0.0, faceDimming: false)];

		$builder->mapSerializer(static fn (Sapling $block) : Writer => PMSerializerHelper::encodeSapling($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Sapling => PMDeserializerHelper::decodeSapling(clone $block, $in));

		$builder->addProperty(new Property(StateNames::AGE_BIT, PropertyHelper::BOOL));
		$builder->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::FLOWER()]));
		$builder->addComponent(new GeometryBlockComponent(Geometry::CROSS));
		$builder->addComponent(new MaterialComponent($materials));
		$builder->addComponent(new FlowerPottableBlockComponent());
		$builder->addComponent(new EmbeddedVisualBlockComponent(new GeometryBlockComponent(Geometry::CROSS), new MaterialComponent($materials)));
	}

	/**
	 * Create permutations for stair blocks (e.g., stone stairs, wooden stairs).
	 */
	public static function makeStair(Builder $builder, Stair $block) : void
	{
		$stringId = $builder->getStringId();
		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (Stair $block) : Writer => SerializerHelper::encodeStair($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Stair => DeserializerHelper::decodeStair(clone $block, $in));

		$builder->addProperty(new Property(StateNames::DIRECTION, PropertyHelper::get("horizontalFacing5Minus")));
		$builder->addProperty(new Property(StateNames::MC_CORNER, PropertyHelper::MC_CORNER));
		$builder->addProperty(new Property(StateNames::UPSIDE_DOWN_BIT, PropertyHelper::BOOL));

		$ud = MolangHelper::state(StateNames::UPSIDE_DOWN_BIT);

		$notOuterRight = MolangHelper::stateNotEquals(StateNames::MC_CORNER, StateValues::MC_CORNER_OUTER_RIGHT);
		$notOuterLeft = MolangHelper::stateNotEquals(StateNames::MC_CORNER, StateValues::MC_CORNER_OUTER_LEFT);
		$isInnerLeft = MolangHelper::stateEquals(StateNames::MC_CORNER, StateValues::MC_CORNER_INNER_LEFT);
		$isInnerRight = MolangHelper::stateEquals(StateNames::MC_CORNER, StateValues::MC_CORNER_INNER_RIGHT);

		$geometryComponent = (new GeometryBlockComponent(NexlyGeometry::STAIR))
			->add("base", true)
			->add("top_nw", MolangHelper::ternary($ud, $notOuterRight, $notOuterLeft))
			->add("top_ne", MolangHelper::ternary($ud, $notOuterLeft, $notOuterRight))
			->add("top_sw", MolangHelper::ternary($ud, $isInnerLeft, $isInnerRight))
			->add("top_se", MolangHelper::ternary($ud, $isInnerRight, $isInnerLeft));

		$builder->addComponent($geometryComponent);
		$builder->addComponent(new ItemVisualBlockComponent($geometryComponent, $material));

		$builder->setMapper(StairBlockMapper::class);
	}

	/**
	 * Create permutations for slab blocks (e.g., stone slab, wooden slab).
	 */
	public static function makeSlab(Builder $builder, Slab $block) : void
	{
		$stringId = $builder->getStringId();
		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (Slab $block) : Writer => SerializerHelper::encodeSlab($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Slab => DeserializerHelper::decodeSlab(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_VERTICAL_HALF, PropertyHelper::MC_VERTICAL_HALF));

		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::SLAB));
		$builder->addComponent(new ItemVisualBlockComponent(new GeometryBlockComponent(NexlyGeometry::SLAB . "_carried"), $material));

		$builder->setMapper(SlabBlockMapper::class);
	}

	/**
	 * Create permutations for door blocks.
	 */
	public static function makeDoor(Builder $builder, Door $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Door $block) => PMSerializerHelper::encodeDoor($block, Writer::create($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) => PMDeserializerHelper::decodeDoor(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_CARDINAL_DIRECTION, PropertyHelper::MC_CARDINAL_DIRECTION));
		$builder->addProperty(new Property(StateNames::UPPER_BLOCK_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::DOOR_HINGE_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::OPEN_BIT, PropertyHelper::BOOL));

		$builder->addComponent(new OnInteractBlockComponent());
		$builder->addComponent(
			(new GeometryBlockComponent(NexlyGeometry::DOOR))
			->add("open", MolangHelper::stateEquals(StateNames::OPEN_BIT, true))
			->add("close", MolangHelper::stateEquals(StateNames::OPEN_BIT, false))
		);

		$builder->setMapper(DoorBlockMapper::class);
	}

	/**
	 * Create permutations for fence blocks.
	 */
	public static function makeFence(Builder $builder, Fence $block) : void
	{
		if (!$block instanceof NexlyFence) {
			throw new RuntimeException("Fence blocks must extend " . NexlyFence::class . " to be registered.");
		}

		$stringId = $builder->getStringId();

		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (Fence $block) : Writer => SerializerHelper::encodeFence($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Fence => DeserializerHelper::decodeFence(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_CONNECTION_NORTH, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_SOUTH, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_WEST, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_EAST, PropertyHelper::BOOL));

		$builder->addComponent(new ItemVisualBlockComponent(new GeometryBlockComponent(NexlyGeometry::FENCE . "_carried"), $material));
		$builder->addComponent(
			(new GeometryBlockComponent(identifier: NexlyGeometry::FENCE, cullingType: CullingType::FENCE))
				->add("north_rails", MolangHelper::stateEquals(StateNames::MC_CONNECTION_NORTH, true))
				->add("south_rails", MolangHelper::stateEquals(StateNames::MC_CONNECTION_SOUTH, true))
				->add("west_rails", MolangHelper::stateEquals(StateNames::MC_CONNECTION_WEST, true))
				->add("east_rails", MolangHelper::stateEquals(StateNames::MC_CONNECTION_EAST, true))
		);
		$builder->addComponent(new LeashableBlockComponent(new Vector3(0, 12, 0)));

		$builder->setMapper(FenceBlockMapper::class);
	}

	/**
	 * Create permutations for fence gate blocks.
	 */
	public static function makeFenceGate(Builder $builder, FenceGate $block) : void
	{
		$stringId = $builder->getStringId();
		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (FenceGate $block) : Writer => PMSerializerHelper::encodeFenceGate($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : FenceGate => PMDeserializerHelper::decodeFenceGate(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_CARDINAL_DIRECTION, PropertyHelper::MC_CARDINAL_DIRECTION));
		$builder->addProperty(new Property(StateNames::IN_WALL_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::OPEN_BIT, PropertyHelper::BOOL));

		$builder->addComponent(new OnInteractBlockComponent());
		$builder->addComponent(new ItemVisualBlockComponent(new GeometryBlockComponent(NexlyGeometry::FENCE_GATE . "_carried"), $material));
		$builder->addComponent(
			(new GeometryBlockComponent(NexlyGeometry::FENCE_GATE))
				->add("open_doors", MolangHelper::stateEquals(StateNames::OPEN_BIT, true))
				->add("closed_doors", MolangHelper::stateEquals(StateNames::OPEN_BIT, false))
		);

		$builder->setMapper(FenceGateBlockMapper::class);
	}

	/**
	 * Create permutations for wall blocks.
	 */
	public static function makeWall(Builder $builder, Wall $block) : void
	{
		$stringId = $builder->getStringId();
		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (Wall $block) : Writer => PMSerializerHelper::encodeWall($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Wall => PMDeserializerHelper::decodeWall(clone $block, $in));

		$builder->addProperty(new Property(StateNames::WALL_POST_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::WALL_CONNECTION_TYPE_NORTH, PropertyHelper::WALL_CONNECTION_TYPE));
		$builder->addProperty(new Property(StateNames::WALL_CONNECTION_TYPE_SOUTH, PropertyHelper::WALL_CONNECTION_TYPE));
		$builder->addProperty(new Property(StateNames::WALL_CONNECTION_TYPE_WEST, PropertyHelper::WALL_CONNECTION_TYPE));
		$builder->addProperty(new Property(StateNames::WALL_CONNECTION_TYPE_EAST, PropertyHelper::WALL_CONNECTION_TYPE));

		$none = StateValues::WALL_CONNECTION_TYPE_NORTH_NONE;

		$isN = MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_NORTH, $none);
		$isS = MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_SOUTH, $none);
		$isW = MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_WEST, $none);
		$isE = MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_EAST, $none);

		$builder->addComponent(new ItemVisualBlockComponent(new GeometryBlockComponent(NexlyGeometry::WALL . "_render"), $material));
		$builder->addComponent(
			(new GeometryBlockComponent(NexlyGeometry::WALL))
				->add("n", $notN = MolangHelper::stateNotEquals(StateNames::WALL_CONNECTION_TYPE_NORTH, $none))
				->add("s", $notS = MolangHelper::stateNotEquals(StateNames::WALL_CONNECTION_TYPE_SOUTH, $none))
				->add("w", $notW = MolangHelper::stateNotEquals(StateNames::WALL_CONNECTION_TYPE_WEST, $none))
				->add("e", $notE = MolangHelper::stateNotEquals(StateNames::WALL_CONNECTION_TYPE_EAST, $none))
				->add("ns", $isNS = MolangHelper::and($notN, $notS, $isW, $isE))
				->add("we", $isWE = MolangHelper::and($isN, $isS, $notW, $notE))
				->add("mid", MolangHelper::or(
					MolangHelper::stateEquals(StateNames::WALL_POST_BIT, true),
					MolangHelper::not(MolangHelper::or($isNS, $isWE))
				))
		);
		$builder->addComponent(new LeashableBlockComponent(new Vector3(0, 12, 0)));

		$builder->setMapper(WallBlockMapper::class);
	}

	/**
	 * Create permutations for trapdoor blocks.
	 */
	public static function makeTrapdoor(Builder $builder, Trapdoor $block) : void
	{
		$stringId = $builder->getStringId();
		/** @var MaterialComponent $material */
		$material = $builder->getComponent(BlockComponentIds::MATERIAL_INSTANCES);

		$builder->mapSerializer(static fn (Trapdoor $block) : Writer => PMSerializerHelper::encodeTrapdoor($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Trapdoor => PMDeserializerHelper::decodeTrapdoor(clone $block, $in));

		$builder->addProperty(new Property(StateNames::DIRECTION, PropertyHelper::get("horizontalFacing5Minus")));
		$builder->addProperty(new Property(StateNames::UPSIDE_DOWN_BIT, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::OPEN_BIT, PropertyHelper::BOOL));

		$builder->addComponent(new OnInteractBlockComponent());

		$builder->addComponent($geometryComponent = (new GeometryBlockComponent(identifier: NexlyGeometry::TRAPDOOR, cullingType: CullingType::TRAPDOOR)));
		$builder->addComponent(new ItemVisualBlockComponent($geometryComponent, $material));

		$box = new BoxCollision(new Vector3(-8, 0, -8), new Vector3(16, 2.92, 16));
		$builder->addComponent(new CollisionBoxBlockComponent(true, [$box]));
		$builder->addComponent(new SelectionBoxBlockComponent(true, [$box]));

		$builder->setMapper(TrapdoorBlockMapper::class);
	}

	/**
	 * Create permutations for hopper blocks.
	 */
	public static function makeHopper(Builder $builder, Hopper $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Hopper $block) : Writer => SerializerHelper::encodeHopper($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Hopper => DeserializerHelper::decodeHopper(clone $block, $in));

		$builder->addProperty(new Property(StateNames::FACING_DIRECTION, PropertyHelper::get("facing")));
		$builder->addProperty(new Property(StateNames::TOGGLE_BIT, PropertyHelper::BOOL));

		$builder->addComponent(
			(new GeometryBlockComponent(NexlyGeometry::HOPPER))
				->add("ground", MolangHelper::stateEquals(StateNames::FACING_DIRECTION, Facing::DOWN))
				->add("north", MolangHelper::stateEquals(StateNames::FACING_DIRECTION, Facing::NORTH))
				->add("south", MolangHelper::stateEquals(StateNames::FACING_DIRECTION, Facing::SOUTH))
				->add("west", MolangHelper::stateEquals(StateNames::FACING_DIRECTION, Facing::WEST))
				->add("east", MolangHelper::stateEquals(StateNames::FACING_DIRECTION, Facing::EAST))
		);

		$builder->addComponent(new MaterialComponent([
			new Material($builder->getName() . "_top", target: Target::UP, renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED),
			new Material($builder->getName() . "_inside", target: Target::DOWN, renderMethod: RenderMethod::ALPHA_TEST),
			new Material($builder->getName() . "_outside", target: Target::NORTH, renderMethod: RenderMethod::ALPHA_TEST),
			new Material($builder->getName() . "_outside", target: Target::SOUTH, renderMethod: RenderMethod::ALPHA_TEST),
			new Material($builder->getName() . "_outside", target: Target::WEST, renderMethod: RenderMethod::ALPHA_TEST),
			new Material($builder->getName() . "_outside", target: Target::EAST, renderMethod: RenderMethod::ALPHA_TEST)
		]));
	}

	/**
	 * Create permutations for furnace blocks.
	 */
	public static function makeFurnace(Builder $builder, Furnace $block) : void
	{
		[$unlitId, $litId] = StringsHelper::wood($builder->getStringId());

		$builder->mapSerializer(static fn (Furnace $block) : Writer => PMSerializerHelper::encodeFurnace($block, $unlitId, $litId));
		$builder->mapDeserializer(static fn (Reader $in) : Furnace => DeserializerHelper::decodeFurnace(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_CARDINAL_DIRECTION, PropertyHelper::MC_CARDINAL_DIRECTION));
	}

	/**
	 * Create permutations for head blocks (e.g., skeleton skull, zombie head).
	 */
	public static function makeHead(Builder $builder, HeadBlock $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (HeadBlock $block) : Writer => SerializerHelper::encodeHeadBlock($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : HeadBlock => DeserializerHelper::decodeHeadBlock(clone $block, $in));

		$rotations = range($block::MIN_ROTATION, $block::MAX_ROTATION);

		$builder->addProperty(new Property(StateNames::FACING_DIRECTION, PropertyHelper::get("facingExceptDown")));
		$builder->addProperty(new Property(StateNames::ROTATION, $rotations));

		$geometryComponent = new GeometryBlockComponent(identifier: NexlyGeometry::HEAD_BLOCK);
		foreach ($rotations as $rot) {
			$geometryComponent->add("bone_$rot", MolangHelper::stateEquals(StateNames::ROTATION, $rot));
		}
		$builder->addComponent($geometryComponent);
		$builder->addComponent($material = new MaterialComponent([new Material($block->getTexture(), renderMethod: RenderMethod::BLEND)]));
		$builder->addComponent(new ItemVisualBlockComponent($geometryComponent, $material));

		$builder->setMapper(HeadBlockMapper::class);
	}

	/**
	 * Create permutations for ladder blocks.
	 */
	public static function makeLadder(Builder $builder, Ladder $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Ladder $block) : Writer => SerializerHelper::encodeLadder($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Ladder => DeserializerHelper::decodeLadder(clone $block, $in));

		$builder->addProperty(new Property(StateNames::FACING_DIRECTION, PropertyHelper::get("horizontalFacingClassic")));

		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::LADDER));

		$builder->setMapper(LadderBlockMapper::class);
	}

	/**
	 * Create permutations for farmland blocks.
	 */
	public static function makeFarmland(Builder $builder, Farmland $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Farmland $block) : Writer => SerializerHelper::encodeFarmland($block, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Farmland => DeserializerHelper::decodeFarmland(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MOISTURIZED_AMOUNT, range(0, Farmland::MAX_WETNESS)));

		$builder->addComponent(new MaterialComponent([
			new Material(
				texture: $builder->getName() . "_side",
				target: Target::ALL,
				renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
			)
		]));

		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::FARMLAND));
		$builder->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::FARMLAND()]));
		$builder->addComponent(new CollisionBoxBlockComponent(true, [BoxCollision::FARMLAND()]));
		$builder->addComponent(new OnInteractBlockComponent());

		$builder->setMapper(FarmlandBlockMapper::class);
	}

	/**
	 * Create permutations for flower blocks
	 */
	public static function makeFlower(Builder $builder, Flower $block) : void
	{
		$materials = [new Material($builder->getName(), renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED, ambientOcclusion: 0.0, faceDimming: false)];

		$builder->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::FLOWER()]));
		$builder->addComponent($geometryComponent = new GeometryBlockComponent(Geometry::CROSS));
		$builder->addComponent(new MaterialComponent($materials));
		$builder->addComponent(new FlowerPottableBlockComponent());
		$builder->addComponent(new EmbeddedVisualBlockComponent($geometryComponent, new MaterialComponent($materials)));
		$builder->addComponent(new RandomOffsetBlockComponent(new BlockPosition(0, 0, 0), new RangeOffset(new Vector3(-4, 1, -4), new Vector3(4, 0, 4))));
	}

	/**
	 * Create permutations for glass pane blocks
	 */
	public static function makeGlassPane(Builder $builder, GlassPane $block) : void
	{
		if (!$block instanceof NexlyGlassPane) {
			throw new RuntimeException("Glass pane blocks must extend " . NexlyGlassPane::class . " to be registered.");
		}

		$stringId = $builder->getStringId();
		$baseColor = $block instanceof Colored ? $block->getColor() : null;

		$builder->mapSerializer(static fn (GlassPane $state) : Writer => SerializerHelper::encodeGlassPane($state, new Writer(
			StringsHelper::recolor($stringId, $baseColor, $state instanceof Colored ? $state->getColor() : null)
		)));
		$builder->mapDeserializer(static fn (Reader $in) : GlassPane => DeserializerHelper::decodeGlassPane(clone $block, $in));

		$builder->addProperty(new Property(StateNames::MC_CONNECTION_NORTH, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_SOUTH, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_WEST, PropertyHelper::BOOL));
		$builder->addProperty(new Property(StateNames::MC_CONNECTION_EAST, PropertyHelper::BOOL));

		$n = MolangHelper::stateEquals(StateNames::MC_CONNECTION_NORTH, true);
		$s = MolangHelper::stateEquals(StateNames::MC_CONNECTION_SOUTH, true);
		$w = MolangHelper::stateEquals(StateNames::MC_CONNECTION_WEST, true);
		$e = MolangHelper::stateEquals(StateNames::MC_CONNECTION_EAST, true);

		$notN = MolangHelper::stateEquals(StateNames::MC_CONNECTION_NORTH, false);
		$notS = MolangHelper::stateEquals(StateNames::MC_CONNECTION_SOUTH, false);
		$notW = MolangHelper::stateEquals(StateNames::MC_CONNECTION_WEST, false);
		$notE = MolangHelper::stateEquals(StateNames::MC_CONNECTION_EAST, false);

		$straightNS = MolangHelper::and($n, $s);
		$straightEW = MolangHelper::and($e, $w);

		$onlyN = MolangHelper::and($n, $notS, $notE, $notW);
		$onlyS = MolangHelper::and($s, $notN, $notE, $notW);
		$onlyE = MolangHelper::and($e, $notN, $notS, $notW);
		$onlyW = MolangHelper::and($w, $notN, $notS, $notE);

		$noConnections = MolangHelper::and($notN, $notS, $notE, $notW);
		$hasNS = MolangHelper::or($n, $s);
		$hasEW = MolangHelper::or($e, $w);

		$isCorner = MolangHelper::and($hasNS, $hasEW, MolangHelper::not($straightNS), MolangHelper::not($straightEW));
		$showBone = MolangHelper::or($noConnections, $isCorner);

		$builder->addComponent(
			$geometryComponent = (new GeometryBlockComponent(NexlyGeometry::GLASS_PANE))
				->add("bone", $showBone)
				->add("straight_ns", $straightNS)
				->add("straight_ew", $straightEW)
				->add("end_n", $onlyN)
				->add("end_s", $onlyS)
				->add("end_e", $onlyE)
				->add("end_w", $onlyW)
				->add("n", MolangHelper::and($n, MolangHelper::not($straightNS), MolangHelper::not($onlyN)))
				->add("s", MolangHelper::and($s, MolangHelper::not($straightNS), MolangHelper::not($onlyS)))
				->add("e", MolangHelper::and($e, MolangHelper::not($straightEW), MolangHelper::not($onlyE)))
				->add("w", MolangHelper::and($w, MolangHelper::not($straightEW), MolangHelper::not($onlyW)))
		);
		$builder->addComponent($material = new MaterialComponent([new Material($builder->getName(), renderMethod: RenderMethod::BLEND)]));
		$builder->addComponent(new ItemVisualBlockComponent(new GeometryBlockComponent(NexlyGeometry::GLASS_PANE . "_render"), $material));
		$builder->addComponent(new EmbeddedVisualBlockComponent($geometryComponent, $material));
		$builder->addComponent(new FlowerPottableBlockComponent());

		$builder->setMapper(GlassPaneBlockMapper::class);
	}

	/**
	 * Create permutations for lever blocks.
	 */
	public static function makeLever(Builder $builder, Lever $block) : void
	{
		$stringId = $builder->getStringId();

		$builder->mapSerializer(static fn (Lever $state) : Writer => SerializerHelper::encodeLever($state, new Writer($stringId)));
		$builder->mapDeserializer(static fn (Reader $in) : Lever => DeserializerHelper::decodeLever(clone $block, $in));

		$builder->addProperty(new Property(StateNames::LEVER_DIRECTION, PropertyHelper::LEVER_DIRECTION));
		$builder->addProperty(new Property(StateNames::OPEN_BIT, PropertyHelper::BOOL));

		$builder->addComponent(
			(new GeometryBlockComponent(NexlyGeometry::LEVER))
			->add("open", MolangHelper::stateEquals(StateNames::OPEN_BIT, true))
			->add("close", MolangHelper::stateEquals(StateNames::OPEN_BIT, false))
		);
		$builder->addComponent(new CollisionBoxBlockComponent(false));
		$builder->addComponent(new OnInteractBlockComponent());

		$builder->setMapper(LeverBlockMapper::class);
	}

	/**
	 * Create permutations for slime blocks.
	 */
	public static function makeSlime(Builder $builder, Slime $block) : void
	{
		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::SLIME));
		$builder->addComponent(new MaterialComponent([new Material($builder->getName(), renderMethod: RenderMethod::BLEND)]));
	}

	/**
	 * Create permutations for cactus blocks.
	 */
	public static function makeCactus(Builder $builder, Cactus $block) : void
	{
		$builder->addComponent(new GeometryBlockComponent(NexlyGeometry::CACTUS));
		$builder->addComponent(new MaterialComponent([
			new Material($builder->getName() . "_top", target: Target::UP, renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED),
			new Material($builder->getName() . "_bottom", target: Target::DOWN, renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED),
			new Material($builder->getName() . "_side", target: Target::ALL, renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED),
		]));
		$builder->addComponent(new SelectionBoxBlockComponent(true, [new BoxCollision(new Vector3(-7.0, 0.0, -7.0), new Vector3(14.0, 16.0, 14.0))]));
		$builder->addComponent(new CollisionBoxBlockComponent(true, [new BoxCollision(new Vector3(-7.0, 0.0, -7.0), new Vector3(14.0, 16.0, 14.0))]));
	}

	public static function makeGlass(Builder $builder, Glass $block) : void
	{
		$stringId = $builder->getStringId();
		$baseColor = $block instanceof Colored ? $block->getColor() : null;

		$builder->mapSerializer(static fn (Glass $state) : Writer => new Writer(
			StringsHelper::recolor($stringId, $baseColor, $state instanceof Colored ? $state->getColor() : null)
		));
		$builder->mapDeserializer(static fn (Reader $in) : Glass => clone $block);

		$builder->addComponent(new MaterialComponent([new Material($builder->getName(), renderMethod: RenderMethod::BLEND)]));
		$builder->addComponent(new GeometryBlockComponent(identifier: NexlyGeometry::GLASS, cullingType: CullingType::GLASS));
	}

	/**
	 * Create permutations for colored blocks.
	 */
	public static function makeSimpleColored(Builder $builder, Block $block) : void
	{
		if (!$block instanceof Colored) {
			throw new RuntimeException("The block must implement the Colored interface to use makeSimpleColored.");
		}

		$stringId = $builder->getStringId();
		$baseColor = $block->getColor();

		$builder->mapSerializer(static fn (Colored $state) : Writer => new Writer(
			StringsHelper::recolor($stringId, $baseColor, $state->getColor())
		));
		$builder->mapDeserializer(static fn (Reader $in) : Block => clone $block);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use pocketmine\data\bedrock\block\BlockTypeNames;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\BlockStateDictionaryEntry;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use ReflectionProperty;
use function count;
use function hash;
use function hexdec;
use function intval;
use function ksort;

class BlockPalette
{
	use SingletonTrait;

	/** @var BlockStateDictionaryEntry[] */
	protected array $states = [];

	public function __construct()
	{
	}

	/**
	 * @return BlockStateDictionaryEntry[]
	 */
	public function getStates() : array
	{
		return $this->states;
	}

	/**
	 * Inserts the provided state in to the correct position of the palette.
	 */
	public function insertState(BlockStateDictionaryEntry $entry) : void
	{
		if (($name = $entry->getStateName()) === "") {
			throw new \RuntimeException("Block state must contain a StringTag called 'name'");
		}
		$this->states[] = $entry;
	}

	/**
	 * Inserts the provided state in to the correct position of the palette.
	 */
	public function insertStates(array $entries) : void
	{
		foreach ($entries as $entry) {
			if (($entry->getStateName()) === "") {
				throw new \RuntimeException("Block state must contain a StringTag called 'name'");
			}
		}

		foreach ($entries as $entry) {
			$this->states[] = $entry;
		}
	}

	/**
	 * Sorts the states using the fnv164 hash of their names.
	 */
	protected function sort(array $states, ?array &$sortedStates, ?array &$stateDataToStateIdLookup) : void
	{
		if ($stateDataToStateIdLookup === null) {
			$stateDataToStateIdLookup = [];
		}
		if ($sortedStates === null) {
			$sortedStates = [];
		}

		foreach ($states as $name => $blockStates) {
			$numberState = count($blockStates);

			foreach ($blockStates as $_ => $blockState) {
				$data = BlockStateDictionaryEntry::decodeStateProperties($blockState->getRawStateProperties());
				ksort($data);

				$test = CompoundTag::create();
				foreach (Utils::stringifyKeys($data) as $key => $state) {
					$test->setTag($key, $state);
				}

				$tag = CompoundTag::create()
					->setString("name", $blockState->getStateName())
					->setTag("states", $test);

				$stateId = self::fnv1a32Nbt($tag);
				if ($numberState === 1) {
					$stateDataToStateIdLookup[$name] = $stateId;
				} else {
					$stateDataToStateIdLookup[$name][$blockState->getRawStateProperties()] = $stateId;
				}

				$sortedStates[$stateId] = $blockState;
			}
		}
	}

	public static function fnv1a32Nbt(CompoundTag $tag) : int
	{
		if ($tag->getString("name", "") === "minecraft:unknown") {
			return -2;
		}

		$nbtStream = new LittleEndianNbtSerializer();
		$binaryNBT = $nbtStream->write(new TreeRoot($tag));

		return self::fnv1a32($binaryNBT);
	}

	protected static function fnv1a32(string $str) : int
	{
		$hashHex = hash('fnv1a32', $str);
		$hashInt = intval(hexdec($hashHex));
		if ($hashInt > 0x7FFFFFFF) {
			$hashInt -= 0x100000000;
		}

		return $hashInt;
	}

	/**
	 * Sorts the block palette to match the order of the Java edition.
	 */
	public function apply() : void
	{
		$translator = $instance = TypeConverter::getInstance()->getBlockTranslator();
		$dictionary = $instance->getBlockStateDictionary();

		$bedrockKnownStates = new ReflectionProperty($dictionary, "states");
		$stateDataToStateIdLookup = new ReflectionProperty($dictionary, "stateDataToStateIdLookup");
		$idMetaToStateIdLookupCache = new ReflectionProperty($dictionary, "idMetaToStateIdLookupCache");
		$fallbackStateId = new ReflectionProperty($instance, "fallbackStateId");
		$networkIdCache = new ReflectionProperty($instance, "networkIdCache");
		$states = [];

		foreach ($dictionary->getStates() as $state) {
			$states[$state->getStateName()][] = $state;
		}

		// To sort the block palette we first have to split the palette up in to groups of states. We only want to sort
		// using the name of the block, and keeping the order of the existing states.
		foreach ($this->getStates() as $state) {
			$states[$state->getStateName()][] = $state;
		}

		$sortedStates = [];
		$stateDataToStateIdLookupValue = [];
		$this->sort($states, $sortedStates, $stateDataToStateIdLookupValue);
		$dictionary = $translator->getBlockStateDictionary();
		$bedrockKnownStates->setValue($dictionary, $sortedStates);
		$stateDataToStateIdLookup->setValue($dictionary, $stateDataToStateIdLookupValue);
		$idMetaToStateIdLookupCache->setValue($dictionary, null); //set this to null so pm can create a new cache
		$networkIdCache->setValue($translator, []); //set this to empty-array so pm can create a new cache
		$fallbackStateId->setValue(
			$translator,
			$stateDataToStateIdLookupValue[BlockTypeNames::INFO_UPDATE] ??
			throw new AssumptionFailedError(BlockTypeNames::INFO_UPDATE . " should always exist")
		);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Permutations;

use Nexly\Blocks\Components\BlockComponent;
use pocketmine\nbt\tag\CompoundTag;

class Permutation
{
	/*** @var BlockComponent[] */
	protected array $components = [];

	public function __construct(
		protected string $condition
	) {
	}

	public static function create(string $condition = "") : Permutation
	{
		return new self($condition);
	}

	public function getCondition() : string
	{
		return $this->condition;
	}

	public function setCondition(string $condition) : self
	{
		$this->condition = $condition;
		return $this;
	}

	public function getComponents() : array
	{
		return $this->components;
	}

	/**
	 * @return $this
	 */
	public function addComponent(BlockComponent $component) : self
	{
		$this->components[$component->getName()] = $component;
		return $this;
	}

	/**
	 * Returns the permutation in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$components = CompoundTag::create();
		foreach ($this->components as $name => $component) {
			$components->setTag($name, $component->toNBT());
		}

		return CompoundTag::create()
			->setString("condition", $this->condition)
			->setTag("components", $components);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Permutations;

use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function gettype;
use function is_bool;
use function is_int;
use function is_string;

class BlockProperty
{
	public function __construct(
		protected string $name,
		protected array $values
	) {
	}

	public function getName() : string
	{
		return $this->name;
	}

	public function getValues() : array
	{
		return $this->values;
	}

	public function toNBT()
	{
		$values = [];
		foreach ($this->values as $value) {
			$values[] = match (true) {
				is_string($value) => new StringTag($value),
				is_int($value) => new IntTag($value),
				is_bool($value) => new ByteTag($value),
				default => throw new \InvalidArgumentException("Invalid value type for BlockProperty: " . gettype($value)),
			};
		}

		return CompoundTag::create()
			->setTag("name", new StringTag($this->name))
			->setTag("enum", new ListTag($values));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Permutations;

use function array_map;
use function array_product;
use function count;
use function current;
use function next;
use function reset;

class CartesianProduct
{
	/**
	 * Returns an 2-dimensional array containing all possible combinations of the provided arrays using the cartesian
	 * product (https://en.wikipedia.org/wiki/Cartesian_product).
	 */
	public static function get(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;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class LootBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $path
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::LOOT;
	}

	public function toNBT() : StringTag
	{
		return new StringTag($this->path);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class InstrumentSoundBlockComponent extends BlockComponent
{
	public function __construct(
		private ?string $up = null,
		private ?string $down = null,
	) {
		if ($up === null && $down === null) {
			throw new InvalidArgumentException("At least one instrument sound face ('up' or 'down') must be defined.");
		}
	}

	/**
	 * The name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::INSTRUMENT_SOUND;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		if ($this->up !== null) {
			$nbt->setTag("up", new StringTag($this->up));
		}
		if ($this->down !== null) {
			$nbt->setTag("down", new StringTag($this->down));
		}

		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class SoundBlockComponent extends BlockComponent
{
	public function __construct(
		private readonly string $sound,
	) {
		if ($this->sound === "") {
			throw new InvalidArgumentException("Block sound type cannot be empty.");
		}
	}

	/**
	 * The name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::SOUND;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("sound", new StringTag($this->sound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use Nexly\Blocks\Components\Types\MovementType;
use Nexly\Blocks\Components\Types\StickyType;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MovableBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $movementType = MovementType::PUSH_PULL,
		protected ?string $sticky = null
	) {
		if ($sticky === StickyType::SAME && $movementType !== MovementType::PUSH_PULL) {
			throw new InvalidArgumentException('Sticky type SAME only works with push_pull movement.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::MOVABLE;
	}

	public function toNBT() : CompoundTag
	{
		$tag = CompoundTag::create()->setString('movement_type', $this->movementType);
		if ($this->sticky !== null) {
			$tag->setString('sticky', $this->sticky);
		}
		return $tag;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class DestructibleByMiningBlockComponent extends BlockComponent
{
	public function __construct(
		protected float $secondsToDestroy = 0.0,
		protected array $itemSpecificSpeeds = [],
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::DESTRUCTIBLE_BY_MINING;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$items = array_map(fn (ItemSpecificSpeed $speed) => $speed->toNBT(), $this->itemSpecificSpeeds);
		return CompoundTag::create()
			->setTag("seconds_to_destroy", new FloatTag($this->secondsToDestroy))
			->setTag("item_specific_speeds", new ListTag($items, NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class EntityFallOnBlockComponent extends BlockComponent
{
	public function __construct(
		protected float $minFallDistance = 0.0
	) {
		if ($minFallDistance < 0.0) {
			throw new InvalidArgumentException('Minimum fall distance cannot be negative.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::ENTITY_FALL_ON;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag('min_fall_distance', new FloatTag($this->minFallDistance));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DestructibleByExplosionBlockComponent extends BlockComponent
{
	public function __construct(
		protected float $resistance = 0.0,
	) {
		if ($resistance < 0.0) {
			throw new \InvalidArgumentException("Explosion resistance cannot be negative.");
		}
	}

	/**
	 * Determines whether the block is destructible by explosions. The default is `0.0` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::DESTRUCTIBLE_BY_EXPLOSION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("explosion_resistance", new FloatTag($this->resistance));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\SupportShape;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function strtolower;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/blockreference/examples/blockcomponents/minecraftblock_support?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class SupportBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $shape = SupportShape::NONE,
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::SUPPORT;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("shape", new StringTag(strtolower($this->shape)));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\ConnectionType;
use Nexly\Blocks\Components\Types\HorizontalFace;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class ConnectionRuleComponent extends BlockComponent
{
	/**
	 * @param list<string> $enabledDirections
	 */
	public function __construct(
		protected string $from = ConnectionType::ALL,
		protected array $enabledDirections = [HorizontalFace::SOUTH, HorizontalFace::NORTH, HorizontalFace::EAST, HorizontalFace::WEST],
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::CONNECTION_RULE;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("accepts_connections_from", new StringTag($this->from))
			->setTag("enabled_directions", new ListTag(array_map(fn (string $direction) : StringTag => new StringTag($direction), $this->enabledDirections), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FlowerPottableBlockComponent extends BlockComponent
{
	public function __construct(
	) {
	}

	/**
	 * Returns the name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::FLOWER_POTTABLE;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create();
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use pocketmine\nbt\tag\Tag;

abstract class BlockComponent
{
	/**
	 * The name of the component.
	 */
	abstract public function getName() : string;

	/**
	 * Build the NBT tag for this component.
	 */
	abstract public function toNBT() : Tag;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;

/**
 * @see CustomComponentsBlockComponent
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class OnPlayerPlacingBlockComponent extends BlockComponent
{
	public function __construct(
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::ON_PLAYER_PLACING;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create();
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use function abs;
use function fmod;
use function intdiv;
use const PHP_FLOAT_EPSILON;

#[Attribute(Attribute::TARGET_CLASS)]
class TransformationBlockComponent extends BlockComponent
{
	public function __construct(
		protected Vector3 $rotation = new Vector3(0, 0, 0),
		protected Vector3 $scale = new Vector3(1, 1, 1),
		protected Vector3 $translation = new Vector3(0, 0, 0),
		protected Vector3 $rotationPivot = new Vector3(0, 0, 0),
		protected Vector3 $scalePivot = new Vector3(0, 0, 0),
		protected bool $hasJsonVersionBeforeValidation = false,
	) {
		foreach ([$rotation->getX(), $rotation->getY(), $rotation->getZ()] as $angle) {
			if (abs(fmod((float) $angle, 90.0)) > PHP_FLOAT_EPSILON) {
				throw new \InvalidArgumentException("Block rotation values must be multiples of 90 degrees.");
			}
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::TRANSFORMATION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("RX", new IntTag(intdiv((int) $this->rotation->getX(), 90)))
			->setTag("RY", new IntTag(intdiv((int) $this->rotation->getY(), 90)))
			->setTag("RZ", new IntTag(intdiv((int) $this->rotation->getZ(), 90)))
			->setTag("SX", new FloatTag($this->scale->getX()))
			->setTag("SY", new FloatTag($this->scale->getY()))
			->setTag("SZ", new FloatTag($this->scale->getZ()))
			->setTag("TX", new FloatTag($this->translation->getX()))
			->setTag("TY", new FloatTag($this->translation->getY()))
			->setTag("TZ", new FloatTag($this->translation->getZ()))
			->setTag("RPX", new FloatTag($this->rotationPivot->getX()))
			->setTag("RPY", new FloatTag($this->rotationPivot->getY()))
			->setTag("RPZ", new FloatTag($this->rotationPivot->getZ()))
			->setTag("SPX", new FloatTag($this->scalePivot->getX()))
			->setTag("SPY", new FloatTag($this->scalePivot->getY()))
			->setTag("SPZ", new FloatTag($this->scalePivot->getZ()))
			->setTag("hasJsonVersionBeforeValidation", new ByteTag($this->hasJsonVersionBeforeValidation));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use Nexly\Blocks\Components\Types\Face;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;
use function array_unique;
use function count;

#[Attribute(Attribute::TARGET_CLASS)]
class RedstoneProducerBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $power,
		protected ?array $connectedFaces = null,
		protected ?string $stronglyPoweredFace = null,
		protected bool $transformRelative = false,
	) {
		if ($power < 0 || $power > 15) {
			throw new InvalidArgumentException('Redstone power must be from 0 to 15.');
		}

		$this->connectedFaces ??= Face::ALL;
		if (count(array_unique($this->connectedFaces)) !== count($this->connectedFaces)) {
			throw new InvalidArgumentException('Connected redstone faces must be unique.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::REDSTONE_PRODUCER;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag('power', new IntTag($this->power))
			->setTag('connected_faces', new ListTag(array_map(static fn (string $face) => new StringTag($face), $this->connectedFaces), NBT::TAG_String))
			->setTag('transform_relative', new ByteTag($this->transformRelative));

		if ($this->stronglyPoweredFace !== null) {
			$nbt->setString('strongly_powered_face', $this->stronglyPoweredFace);
		}
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\PlacementCondition;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class PlacementFilterBlockComponent extends BlockComponent
{
	public function __construct(
		protected array $conditions,
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::PLACEMENT_FILTER;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag('conditions', new ListTag(array_map(fn (PlacementCondition $condition) : CompoundTag => $condition->toNBT(), $this->conditions)));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function is_array;
use function sprintf;
use function str_starts_with;
use function strtoupper;

#[Attribute(Attribute::TARGET_CLASS)]
class MapColorBlockComponent extends BlockComponent
{
	protected string $hexColor;

	/**
	 * @param string|array{int,int,int} $color Hex #RRGGBB or three RGB channels.
	 */
	public function __construct(
		string|array $color,
		protected ?string $tintMethod = null
	) {
		if (is_array($color)) {
			$this->hexColor = sprintf('#%02X%02X%02X', ...$color);
		} else {
			$color = strtoupper($color);
			$this->hexColor = str_starts_with($color, '#') ? $color : '#' . $color;
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::MAP_COLOR;
	}

	public function toNBT() : StringTag|CompoundTag
	{
		if ($this->tintMethod === null) {
			return new StringTag($this->hexColor);
		}

		return CompoundTag::create()
			->setString('color', $this->hexColor)
			->setString('tint_method', $this->tintMethod);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\LavaFlammability;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FlammableBlockComponent extends BlockComponent
{
	public function __construct(
		protected int    $catchChanceModifier = 5,
		protected int    $destroyChanceModifier = 20,
		protected string $lavaFlammable = LavaFlammability::NEVER,
	) {
		if ($catchChanceModifier < 0 || $destroyChanceModifier < 0) {
			throw new \InvalidArgumentException("Flammability modifiers cannot be negative.");
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::FLAMMABLE;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("catch_chance_modifier", new IntTag($this->catchChanceModifier))
			->setTag("destroy_chance_modifier", new IntTag($this->destroyChanceModifier))
			->setTag("lava_flammable", new StringTag($this->lavaFlammable));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ItemVisualBlockComponent extends BlockComponent
{
	public function __construct(
		protected GeometryBlockComponent|AsyncBlockComponent $geometry,
		protected MaterialInstancesBlockComponent|AsyncBlockComponent $material
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::ITEM_VISUAL;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("geometryDescription", $this->geometry->toNBT())
			->setTag("materialInstancesDescription", $this->material->toNBT());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ReplaceableBlockComponent extends BlockComponent
{
	public function __construct(
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::REPLACEABLE;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create();
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\CullingLayer;
use Nexly\Blocks\Components\Types\CullingShape;
use Nexly\Blocks\Components\Types\Geometry;
use Nexly\Blocks\Components\Types\NWayVisualRotation;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;
use function count;
use function is_array;
use function is_bool;
use function strval;

#[Attribute(Attribute::TARGET_CLASS)]
class GeometryBlockComponent extends BlockComponent
{
	public function __construct(
		protected string                 $identifier = Geometry::FULL_BLOCK,
		protected string                 $cullingType = "",
		protected string                 $layer = CullingLayer::UNDEFINED,
		protected string                 $cullingShape = CullingShape::EMPTY,
		protected ?NWayVisualRotation    $nWayVisualRotation = null,
		protected bool|array             $uvLock = false,
		protected bool                   $ignoreGeometryForIsSolid = true,
		protected bool                   $needsLegacyTopRotation = false,
		protected bool                   $useBlockTypeLightAbsorption = false,
		protected ?CompoundTag                    $boneVisibility = null
	) {
		$this->boneVisibility ??= CompoundTag::create();
		if (is_array($uvLock) && count($uvLock) > 64) {
			throw new \InvalidArgumentException("UV lock cannot contain more than 64 bone names.");
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::GEOMETRY;
	}

	/**
	 * @return $this
	 */
	public function add(string $boneName, bool|string $visibility) : self
	{
		$this->boneVisibility->setTag(
			$boneName,
			CompoundTag::create()
				->setString("expression", is_bool($visibility) ? strval((int) $visibility) : $visibility)
				->setShort("version", 12)
		);
		return $this;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("bone_visibility", $this->boneVisibility)
			->setTag("identifier", new StringTag($this->identifier))
			->setTag("culling", new StringTag($this->cullingType))
			->setTag("culling_layer", new StringTag($this->layer))
			->setTag("ignoreGeometryForIsSolid", new ByteTag($this->ignoreGeometryForIsSolid))
			->setTag("needsLegacyTopRotation", new ByteTag($this->needsLegacyTopRotation))
			->setTag("useBlockTypeLightAbsorption", new ByteTag($this->useBlockTypeLightAbsorption))
			->setTag("culling_shape", new StringTag($this->cullingShape));

		if ($this->nWayVisualRotation !== null) {
			$nbt->setTag("n_way_visual_rotation", new StringTag($this->nWayVisualRotation));
		}

		$nbt->setTag(
			"uv_lock",
			is_bool($this->uvLock)
			? new ByteTag($this->uvLock)
			: new ListTag(array_map(fn (string $bone) : StringTag => new StringTag($bone), $this->uvLock), NBT::TAG_String)
		);
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use Nexly\Blocks\Components\Types\TintMethod;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DestructionParticlesBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $particleCount = 100,
		protected ?string $texture = null,
		protected string $tintMethod = TintMethod::NONE,
	) {
		if ($particleCount < 0 || $particleCount > 255) {
			throw new InvalidArgumentException('Destruction particle count must be between 0 and 255.');
		}
		if ($texture === '') {
			throw new InvalidArgumentException('Particle texture cannot be empty when provided.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::DESTRUCTION_PARTICLES;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag('particle_count', new IntTag($this->particleCount))
			->setTag('tint_method', new StringTag($this->tintMethod));

		if ($this->texture !== null) {
			$nbt->setTag('texture', new StringTag($this->texture));
		}
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\BoxCollision;
use pocketmine\block\Beetroot;
use pocketmine\block\Block;
use pocketmine\block\Carrot;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\Potato;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use function array_key_first;

#[Attribute(Attribute::TARGET_CLASS)]
class SelectionBoxBlockComponent extends BlockComponent
{
	/**
	 * @param BoxCollision[] $collisions
	 */
	public function __construct(
		protected bool $enabled,
		protected array $collisions = [],
	) {
		if (empty($this->collisions)) {
			$this->collisions[] = new BoxCollision(new Vector3(-8.0, 0.0, -8.0), new Vector3(16.0, 16.0, 16.0));
		}
	}

	/**
	 * Creates the component from a Block instance.
	 */
	public static function fromCrops(Block $block, int $age) : self
	{
		return new self(
			true,
			[
				new BoxCollision(
					new Vector3(-8.0, 0.0, -8.0),
					match(true) {
						$block instanceof Carrot => new Vector3(16.0, (($age + 1.0) * (1 / $block::MAX_AGE)) * 0.7 * 16, 16.0),
						$block instanceof Potato, $block instanceof Beetroot => new Vector3(16.0, (($age + 1.0) * (1 / $block::MAX_AGE)) * 0.6 * 16, 16.0),
						$block instanceof NetherWartPlant => new Vector3(16.0, ($age + 1.0) * 0.25 * 16, 16.0),
						default => new Vector3(16.0, ($age + 1) * (1 / ($block::MAX_AGE ?? 7)) * 16, 16.0),
					}
				)
			]
		);
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::SELECTION_BOX;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		if (!$this->enabled) {
			return CompoundTag::create()
				->setTag("enabled", new ByteTag($this->enabled));
		}

		return $this->collisions[array_key_first($this->collisions)]->toNBT(false)
			->setTag("enabled", new ByteTag($this->enabled));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class RedstoneConductivityBlockComponent extends BlockComponent
{
	public function __construct(
		protected bool $allowsWireToStepDown = true,
		protected bool $redstoneConductor = false
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::REDSTONE_CONDUCTIVITY;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag('allows_wire_to_step_down', new ByteTag($this->allowsWireToStepDown))
			->setTag('redstone_conductor', new ByteTag($this->redstoneConductor));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

class BlockComponentIds
{
	public const BREATHABILITY = "minecraft:breathability";
	public const COLLISION_BOX = "minecraft:collision_box";
	public const CONNECTION_RULE = "minecraft:connection_rule";
	public const DESTRUCTIBLE_BY_EXPLOSION = "minecraft:destructible_by_explosion";
	public const DESTRUCTIBLE_BY_MINING = "minecraft:destructible_by_mining";
	public const DISPLAY_NAME = "minecraft:display_name";
	public const EMBEDDED_VISUAL = "minecraft:embedded_visual";
	public const FLAMMABLE = "minecraft:flammable";
	public const FRICTION = "minecraft:friction";
	public const GEOMETRY = "minecraft:geometry";
	public const ITEM_VISUAL = "minecraft:item_visual";
	public const LIGHT_DAMPENING = "minecraft:light_dampening";
	public const LIGHT_EMISSION = "minecraft:light_emission";
	public const LIQUID_DETECTION = "minecraft:liquid_detection";
	public const MATERIAL_INSTANCES = "minecraft:material_instances";
	public const ON_INTERACT = "minecraft:on_interact";
	public const ON_PLAYER_PLACING = "minecraft:on_player_placing";
	public const PLACEMENT_FILTER = "minecraft:placement_filter";
	public const REPLACEABLE = "minecraft:replaceable";
	public const SELECTION_BOX = "minecraft:selection_box";
	public const TRANSFORMATION = "minecraft:transformation";
	public const CUSTOM_COMPONENTS = "minecraft:custom_components";
	public const FLOWER_POTTABLE = "minecraft:flower_pottable";
	public const RANDOM_OFFSET = "minecraft:random_offset";
	public const SUPPORT = "minecraft:support";
	public const PRECIPITATION_INTERACTIONS = "minecraft:precipitation_interactions";
	public const CHEST_OBSTRUCTION = "minecraft:chest_obstruction";
	public const SOUND = "minecraft:sound";
	public const BLOCK_ENTITY = "minecraft:block_entity";
	public const INSTRUMENT_SOUND = "minecraft:instrument_sound";
	public const CRAFTING_TABLE = "minecraft:crafting_table";
	public const DESTRUCTION_PARTICLES = "minecraft:destruction_particles";
	public const LOOT = "minecraft:loot";
	public const MAP_COLOR = "minecraft:map_color";
	public const MOVABLE = "minecraft:movable";
	public const REDSTONE_CONDUCTIVITY = "minecraft:redstone_conductivity";
	public const REDSTONE_CONSUMER = "minecraft:redstone_consumer";
	public const REDSTONE_PRODUCER = "minecraft:redstone_producer";
	public const TICK = "minecraft:tick";
	public const ENTITY_FALL_ON = "minecraft:entity_fall_on";
	public const LEASHABLE = "minecraft:leashable";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class CustomComponentsBlockComponent extends BlockComponent
{
	public function __construct(
		protected bool $hasPlayerInteract = true,
		protected bool $hasPlayerPlacing = true,
		protected bool $isV1Component = true,
	) {
	}

	/**
	 * Returns the name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::CUSTOM_COMPONENTS;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("hasPlayerInteract", new ByteTag($this->hasPlayerInteract))
			->setTag("hasPlayerPlacing", new ByteTag($this->hasPlayerPlacing))
			->setTag("isV1Component", new ByteTag($this->isV1Component));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\LiquidRule;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class LiquidDetectionComponent extends BlockComponent
{
	/**
	 * @param LiquidRule[] $rules
	 */
	public function __construct(
		protected array $rules,
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::LIQUID_DETECTION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("detectionRules", new ListTag(array_map(fn (LiquidRule $rule) => $rule->toNBT(), $this->rules), NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;

#[Attribute(Attribute::TARGET_CLASS)]
class TickBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $min = 20,
		protected int $max = 60,
		protected bool $looping = true
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::TICK;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag('interval_range', new ListTag([
				new IntTag($this->min),
				new IntTag($this->max)
			], NBT::TAG_Int))
			->setByte('looping', (int) $this->looping);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\Material;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MaterialInstancesBlockComponent extends BlockComponent
{
	/**
	 * @param Material[] $materials
	 */
	public function __construct(
		protected array $materials,
		protected ?CompoundTag $mappings = null
	) {
		$this->mappings ??= CompoundTag::create();
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::MATERIAL_INSTANCES;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$materials = CompoundTag::create();
		foreach ($this->materials as $material) {
			$materials->setTag($material->getTarget(), $material->toNBT());
		}

		return CompoundTag::create()
			->setTag("mappings", $this->mappings)
			->setTag("materials", $materials);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class RedstoneConsumerBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $minPower = 0,
		protected bool $propagatesPower = false
	) {
		if ($minPower < 0 || $minPower > 15) {
			throw new InvalidArgumentException('Redstone minimum power must be from 0 to 15.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::REDSTONE_CONSUMER;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag('min_power', new IntTag($this->minPower))
			->setTag('propagates_power', new ByteTag($this->propagatesPower));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use pocketmine\nbt\tag\Tag;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use function base64_decode;

class AsyncBlockComponent extends BlockComponent
{
	protected Tag $nbt;

	public function __construct(
		protected string $name,
		string $nbt,
	) {
		$this->nbt = (new NetworkNbtSerializer())->read(base64_decode($nbt, true))->getTag();
	}

	public function getName() : string
	{
		return $this->name;
	}

	public function toNBT() : Tag
	{
		return $this->nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class LightDampeningBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $value,
	) {
		if ($value < 0 || $value > 15) {
			throw new \InvalidArgumentException("Light dampening value must be between 0 and 15");
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::LIGHT_DAMPENING;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("lightLevel", new ByteTag($this->value));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class EmbeddedVisualBlockComponent extends BlockComponent
{
	public function __construct(
		protected GeometryBlockComponent $geometry,
		protected MaterialInstancesBlockComponent $material
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::EMBEDDED_VISUAL;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("geometryDescription", $this->geometry->toNBT())
			->setTag("materialInstancesDescription", $this->material->toNBT());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

/**
 * @see CustomComponentsBlockComponent
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class OnInteractBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $condition = "",
		protected string $event = "",
		protected string $target = "self",
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::ON_INTERACT;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("condition", new StringTag($this->condition))
			->setTag("event", new StringTag($this->event))
			->setTag("target", new StringTag($this->target));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\math\Vector3;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;

#[Attribute(Attribute::TARGET_CLASS)]
class LeashableBlockComponent extends BlockComponent
{
	public function __construct(
		protected ?Vector3 $offset = null
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::LEASHABLE;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		if ($this->offset !== null) {
			$nbt->setTag('offset', new ListTag([
				new FloatTag($this->offset->x),
				new FloatTag($this->offset->y),
				new FloatTag($this->offset->z),
			], NBT::TAG_Float));
		}
		return $nbt;
	}

}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DisplayNameBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $name,
	) {
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::DISPLAY_NAME;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new StringTag($this->name));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;
use function count;
use function strlen;

#[Attribute(Attribute::TARGET_CLASS)]
class CraftingTableBlockComponent extends BlockComponent
{
	public function __construct(
		protected array $craftingTags,
		protected ?string $tableName = null
	) {
		if (count($craftingTags) > 64) {
			throw new InvalidArgumentException('At most 64 crafting tags are allowed.');
		}
		foreach ($craftingTags as $tag) {
			if (strlen($tag) > 64) {
				throw new InvalidArgumentException('Crafting tags are limited to 64 characters.');
			}
		}

		if ($tableName === '') {
			throw new InvalidArgumentException('Crafting table name cannot be empty when provided.');
		}
	}

	public function getName() : string
	{
		return BlockComponentIds::CRAFTING_TABLE;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();

		if ($this->tableName !== null) {
			$nbt->setTag('table_name', new StringTag($this->tableName));
		}
		return $nbt
			->setTag('crafting_tags', new ListTag(array_map(fn (string $value) => new StringTag($value), $this->craftingTags), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FrictionBlockComponent extends BlockComponent
{
	public function __construct(
		protected float $value = 0.0,
	) {
		if ($this->value < 0.0 || $this->value > 0.9) {
			throw new \InvalidArgumentException("Friction value must be between 0.0 and 0.9");
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::FRICTION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new FloatTag($this->value));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use Nexly\Blocks\Components\Types\BoxCollision;
use pocketmine\math\Vector3;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;
use function count;

#[Attribute(Attribute::TARGET_CLASS)]
class CollisionBoxBlockComponent extends BlockComponent
{
	/**
	 * @param BoxCollision[] $collisions
	 */
	public function __construct(
		protected bool $enabled,
		protected array $collisions = [],
	) {
		if (count($collisions) > 16) {
			throw new InvalidArgumentException("A collision component supports at most 16 boxes.");
		}

		foreach ($collisions as $collision) {
			if (!$collision instanceof BoxCollision) {
				throw new InvalidArgumentException("A collision component supports only BoxCollision.");
			}
		}

		if (empty($this->collisions)) {
			$this->collisions[] = new BoxCollision(new Vector3(-8.0, 0.0, -8.0), new Vector3(16.0, 16.0, 16.0));
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::COLLISION_BOX;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("enabled", new ByteTag($this->enabled))
			->setTag(
				"boxes",
				new ListTag(
					array_map(
						fn (BoxCollision $collision) => $collision->toNBT(true),
						$this->collisions
					),
					NBT::TAG_Compound
				)
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\PrecipitationType;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function strtolower;

#[Attribute(Attribute::TARGET_CLASS)]
class PrecipitationInteractionsBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $precipitation = PrecipitationType::NONE,
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::PRECIPITATION_INTERACTIONS;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("precipitation_behavior", new StringTag(strtolower($this->precipitation)));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class LightEmissionBlockComponent extends BlockComponent
{
	public function __construct(
		protected int $value,
	) {
		if ($value < 0 || $value > 15) {
			throw new \InvalidArgumentException("Light emission value must be between 0 and 15");
		}
	}

	/**
	 * Determines whether the block is breathable by defining if the block is treated as a `solid` or as `air`. The default is `solid` if this component is omitted
	 */
	public function getName() : string
	{
		return BlockComponentIds::LIGHT_EMISSION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("emission", new ByteTag($this->value));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use Nexly\Blocks\Components\Types\ObstructionRule;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ChestObstructionBlockComponent extends BlockComponent
{
	public function __construct(
		private readonly string $rule = ObstructionRule::SHAPE,
	) {
	}

	/**
	 * The name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::CHEST_OBSTRUCTION;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("obstruction_rule", new StringTag($this->rule));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\math\Vector3;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\utils\RegistryTrait;

/**
 * This doc-block is generated automatically, do not modify it manually.
 * This must be regenerated whenever registry members are added, removed or changed.
 * @see build/generate-registry-annotations.php
 * @generate-registry-docblock
 *
 * @method static BoxCollision SLAB()
 * @method static BoxCollision FENCE_GATE()
 * @method static BoxCollision MOBHEAD()
 * @method static BoxCollision LADDER()
 * @method static BoxCollision FARMLAND()
 * @method static BoxCollision FLOWER()
 */
class BoxCollision
{
	use RegistryTrait;

	protected static function setup() : void
	{
		self::_registryRegister("slab", new BoxCollision(new Vector3(-8, 4, -8), new Vector3(16, 8, 16)));
		self::_registryRegister("fence_gate", new BoxCollision(new Vector3(-8, 0, -2), new Vector3(16, 24, 4)));
		self::_registryRegister("mobhead", new BoxCollision(new Vector3(-4, 0, -4), new Vector3(8, 8, 8)));
		self::_registryRegister("ladder", new BoxCollision(new Vector3(-8, 0, 5), new Vector3(16, 16, 3)));
		self::_registryRegister("farmland", new BoxCollision(new Vector3(-8, 0, -8), new Vector3(16, 15, 16)));
		self::_registryRegister("flower", new BoxCollision(new Vector3(-2, 0, -5), new Vector3(8, 10, 8)));
	}

	public static function checkInit() : void
	{
		if (self::$members === null) {
			self::$members = [];
			self::setup();
		}
	}

	public function __construct(
		protected Vector3 $origin,
		protected Vector3 $size,
	) {
	}

	public function getOrigin() : Vector3
	{
		return $this->origin;
	}

	public function setOrigin(Vector3 $origin) : void
	{
		$this->origin = $origin;
	}

	public function getSize() : Vector3
	{
		return $this->size;
	}

	public function setSize(Vector3 $size) : void
	{
		$this->size = $size;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT(bool $latest) : CompoundTag
	{
		if ($latest) {
			$minX = 8 + $this->origin->getX();
			$minY = $this->origin->getY();
			$minZ = 8 + $this->origin->getZ();

			return CompoundTag::create()
				->setTag("minX", new FloatTag($minX))
				->setTag("minY", new FloatTag($minY))
				->setTag("minZ", new FloatTag($minZ))
				->setTag("maxX", new FloatTag($minX + $this->size->getX()))
				->setTag("maxY", new FloatTag($minY + $this->size->getY()))
				->setTag("maxZ", new FloatTag($minZ + $this->size->getZ()));
		}

		return CompoundTag::create()
			->setTag("origin", new ListTag([
				new FloatTag($this->origin->getX()),
				new FloatTag($this->origin->getY()),
				new FloatTag($this->origin->getZ())
			], NBT::TAG_Float))
			->setTag("size", new ListTag([
				new FloatTag($this->size->getX()),
				new FloatTag($this->size->getY()),
				new FloatTag($this->size->getZ())
			], NBT::TAG_Float));
	}
}
BoxCollision::checkInit();
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function is_array;
use function is_int;

class NWayVisualRotation
{
	/**
	 * @param array<string, string>|list<array{string, string}> $axes
	 */
	public function __construct(
		protected array $axes
	) {
		$normalized = [];

		foreach ($axes as $axis => $state) {
			if (is_int($axis) && is_array($state)) {
				[$axis, $state] = $state;
			}

			$normalized[$axis] = (string) $state;
		}

		$this->axes = $normalized;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		foreach ($this->axes as $axis => $state) {
			$nbt->setTag($axis, new StringTag($state));
		}
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class BreathabilityType
{
	public const SOLID = "solid";
	public const AIR = "air";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class NexlyGeometry
{
	public const CROP = "geometry.crop";
	public const NETHER_WART = "geometry.nether_wart";
	public const STAIR = "geometry.stair";
	public const SLAB = "geometry.slab";
	public const DOOR = "geometry.door";
	public const FENCE = "geometry.fence";
	public const FENCE_GATE = "geometry.fence_gate";
	public const WALL = "geometry.wall";
	public const TRAPDOOR = "geometry.trapdoor";
	public const HOPPER = "geometry.hopper";
	public const HEAD_BLOCK = "geometry.head_block";
	public const LADDER = "geometry.ladder";
	public const FARMLAND = "geometry.farmland";
	public const GLASS_PANE = "geometry.glass_pane";
	public const LEVER = "geometry.lever";
	public const GLASS = "geometry.glass";
	public const SLIME = "geometry.slime";
	public const CACTUS = "geometry.cactus";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;
use function array_values;

class LiquidRule
{
	/**
	 * @param array<string> $stopsLiquidFlowingFromDirection
	 */
	public function __construct(
		protected bool              $canContainLiquid,
		protected string            $liquidType = LiquidType::WATER,
		protected string            $onLiquidTouches = LiquidTouchAction::BLOCKING,
		protected array             $stopsLiquidFlowingFromDirection = [],
		protected bool              $useLiquidClipping = false,
	) {
	}

	public function toNBT() : CompoundTag
	{
		$liquidsDirection = array_map(
			fn (string $direction) => new StringTag($direction),
			$this->stopsLiquidFlowingFromDirection
		);
		return CompoundTag::create()
			->setTag("canContainLiquid", new ByteTag($this->canContainLiquid))
			->setTag("liquidType", new StringTag($this->liquidType))
			->setTag("onLiquidTouches", new StringTag($this->onLiquidTouches))
			->setTag("stopsLiquidFromDirection", new ListTag(array_values($liquidsDirection), NBT::TAG_String))
			->setTag("useLiquidClipping", new ByteTag($this->useLiquidClipping));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\block\Block;

class StickyType
{
	public const NONE = 'none';
	public const SAME = 'same';

	public static function from(Block $block) : ?string
	{
		return null;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use Nexly\Helpers\ComponentHelper;
use pocketmine\block\Beacon;
use pocketmine\block\Bedrock;
use pocketmine\block\Block;
use pocketmine\block\ChorusFlower;
use pocketmine\block\ChorusPlant;
use pocketmine\block\Cobweb;
use pocketmine\block\EnchantingTable;
use pocketmine\block\EnderChest;
use pocketmine\block\EndPortalFrame;
use pocketmine\block\Flowable;
use pocketmine\block\GlazedTerracotta;
use pocketmine\block\Jukebox;
use pocketmine\block\Leaves;
use pocketmine\block\Melon;
use pocketmine\block\MonsterSpawner;
use pocketmine\block\Pumpkin;

final class MovementType
{
	public const PUSH_PULL = 'push_pull';
	public const PUSH = 'push';
	public const POPPED = 'popped';
	public const IMMOVABLE = 'immovable';

	public static function from(Block $block) : ?string
	{
		return match(true) {
			$block instanceof Bedrock,
			$block instanceof MonsterSpawner,
			$block instanceof EnchantingTable,
			$block instanceof EnderChest,
			$block instanceof EndPortalFrame,
			$block instanceof Jukebox,
			$block instanceof Beacon,
			ComponentHelper::hasTile($block) => self::IMMOVABLE,

			$block instanceof Flowable,
			$block instanceof Leaves,
			$block instanceof Melon,
			$block instanceof Pumpkin,
			$block instanceof Cobweb,
			$block instanceof ChorusPlant,
			$block instanceof ChorusFlower,
			!$block->isSolid() => self::POPPED,

			$block instanceof GlazedTerracotta => self::PUSH,
			default => self::PUSH_PULL
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class RenderMethod
{
	public const ALPHA_TEST = "alpha_test";
	public const ALPHA_TEST_SINGLE_SIDED = "alpha_test_single_sided";
	public const BLEND = "blend";
	public const DOUBLE_SIDED = "double_sided";
	public const ALPHA_TEST_TO_OPAQUE = "alpha_test_to_opaque";
	public const OPAQUE = "opaque";
	public const ALPHA_TEST_SINGLE_SIDED_TO_OPAQUE = "alpha_test_single_sided_to_opaque";
	public const BLEND_TO_OPAQUE = "blend_to_opaque";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;

class PlacementCondition
{
	/**
	 * @param list<PlacementFace> $allowedFaces
	 * @param list<string>        $blockFilter
	 */
	public function __construct(
		protected array $allowedFaces = [],
		protected array $blockFilter = []
	) {
		$filters = [];
		foreach ($blockFilter as $filter) {
			$filters[] = $filter instanceof NbtDescriptor ? $filter : new NbtDescriptor($filter);
		}
		$this->blockFilter = $filters;
	}

	public function toNBT() : CompoundTag
	{
		$condition = CompoundTag::create();

		if (!empty($this->allowedFaces)) {
			$condition->setTag('allowed_faces', new ListTag(
				array_map(fn (string $face) => new StringTag($face), $this->allowedFaces),
				NBT::TAG_String
			));
		}
		if (!empty($this->blockFilter)) {
			$condition->setTag('block_filter', new ListTag(
				array_map(fn (NbtDescriptor $filter) => $filter->toNBT(), $this->blockFilter),
				NBT::TAG_Compound
			));
		}

		return $condition;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class LiquidTouchAction
{
	public const BROKEN = "broken";
	public const POPPED = "popped";
	public const BLOCKING = "blocking";
	public const NO_REACTION = "no_reaction";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class ObstructionRule
{
	public const ALWAYS = "always";
	public const NEVER = "never";
	public const SHAPE = "shape";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class CullingType
{
	public const FENCE = "nexly:culling.fence";
	public const TRAPDOOR = "nexly:culling.trapdoor";
	public const GLASS = "nexly:culling.glass";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class LiquidType
{
	public const WATER = "water";
	public const LAVA = "lava";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\math\Vector3;

class RangeOffset
{
	public function __construct(
		protected Vector3 $min,
		protected Vector3 $max,
	) {
	}

	public function getMin() : Vector3
	{
		return $this->min;
	}

	public function getMax() : Vector3
	{
		return $this->max;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class UseDefaultVisual
{
	public function __construct(
		protected ?string $geometry = null
	) {
	}

	public function getGeometry() : ?string
	{
		return $this->geometry;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class Geometry
{
	public const FULL_BLOCK = "minecraft:geometry.full_block";
	public const FULL_BLOCK_V1 = "minecraft:geometry.full_block_v1";
	public const SHELF_MUSHROOM_LARGE = "minecraft:geometry.shelf_mushroom_large";
	public const SHELF_MUSHROOM_SMALL = "minecraft:geometry.shelf_mushroom_small";
	public const STRAW_BED_FOOT = "minecraft:geometry.straw_bed_foot";
	public const STRAW_BED_HEAD = "minecraft:geometry.straw_bed_head";
	public const FULL_CUBE = "minecraft:geometry.full_cube";
	public const CROSS = "minecraft:geometry.cross";
	public const BEACON = "minecraft:geometry.beacon";
	public const BIG_DRIPLEAF = "minecraft:geometry.big_dripleaf";
	public const BIG_DRIPLEAF_FULL_BIT = "minecraft:geometry.big_dripleaf_full_bit";
	public const BIG_DRIPLEAF_PARTIAL_BIT = "minecraft:geometry.big_dripleaf_partial_bit";
	public const BIG_DRIPLEAF_STEM = "minecraft:geometry.big_dripleaf_stem";
	public const BUTTON = "minecraft:geometry.button";
	public const BUTTON_PRESSED = "minecraft:geometry.button_pressed";
	public const CUBE_COLUMN_HORIZONTAL = "minecraft:geometry.cube_column_horizontal";
	public const DOOR_BOTTOM_LEFT = "minecraft:geometry.door_bottom_left";
	public const DOOR_BOTTOM_LEFT_OPEN = "minecraft:geometry.door_bottom_left_open";
	public const DOOR_BOTTOM_RIGHT = "minecraft:geometry.door_bottom_right";
	public const DOOR_BOTTOM_RIGHT_OPEN = "minecraft:geometry.door_bottom_right_open";
	public const DOOR_TOP_LEFT = "minecraft:geometry.door_top_left";
	public const DOOR_TOP_LEFT_OPEN = "minecraft:geometry.door_top_left_open";
	public const DOOR_TOP_RIGHT = "minecraft:geometry.door_top_right";
	public const DOOR_TOP_RIGHT_OPEN = "minecraft:geometry.door_top_right_open";
	public const EMPTY = "minecraft:geometry.empty";
	public const ENCHANTING_TABLE = "minecraft:geometry.enchanting_table";
	public const FENCE_POST = "minecraft:geometry.fence_post";
	public const HOPPER = "minecraft:geometry.hopper";
	public const HOPPER_SIDE = "minecraft:geometry.hopper_side";
	public const LEAVES = "minecraft:geometry.leaves";
	public const LECTERN = "minecraft:geometry.lectern";
	public const PRESSURE_PLATE_DOWN = "minecraft:geometry.pressure_plate_down";
	public const PRESSURE_PLATE_UP = "minecraft:geometry.pressure_plate_up";
	public const SLAB = "minecraft:geometry.slab";
	public const SLAB_TOP = "minecraft:geometry.slab_top";
	public const STAIRS = "minecraft:geometry.stairs";
	public const TEMPLATE_FENCE_GATE = "minecraft:geometry.template_fence_gate";
	public const TEMPLATE_FENCE_GATE_OPEN = "minecraft:geometry.template_fence_gate_open";
	public const TEMPLATE_FENCE_GATE_WALL = "minecraft:geometry.template_fence_gate_wall";
	public const TEMPLATE_FENCE_GATE_WALL_OPEN = "minecraft:geometry.template_fence_gate_wall_open";
	public const TEMPLATE_TRAPDOOR_BOTTOM = "minecraft:geometry.template_trapdoor_bottom";
	public const TEMPLATE_TRAPDOOR_OPEN = "minecraft:geometry.template_trapdoor_open";
	public const TEMPLATE_TRAPDOOR_TOP = "minecraft:geometry.template_trapdoor_top";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class HorizontalFace
{
	public const NORTH = "north";
	public const SOUTH = "south";
	public const EAST = "east";
	public const WEST = "west";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class CullingLayer
{
	public const UNDEFINED = "minecraft:culling_layer.undefined";
	public const LEAVES = "minecraft:culling_layer.leaves";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\block\BaseSign;
use pocketmine\block\Block;
use pocketmine\block\Campfire;
use pocketmine\block\Fence;
use pocketmine\block\Flowable;
use pocketmine\block\Stair;
use pocketmine\block\Wall;

class SupportShape
{
	public const FENCE = "fence";
	public const STAIR = "stair";
	public const NONE = "none";

	public static function from(Block $block) : ?string
	{
		return match(true) {
			$block instanceof Fence, $block instanceof Wall => SupportShape::FENCE,
			$block instanceof Stair => SupportShape::STAIR,

			$block instanceof Flowable, $block instanceof BaseSign, $block instanceof Campfire, !$block->isSolid() => SupportShape::NONE,
			default => null
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class MaterialTarget
{
	public const ALL = "*";
	public const UP = "up";
	public const DOWN = "down";
	public const NORTH = "north";
	public const EAST = "east";
	public const SOUTH = "south";
	public const WEST = "west";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class MaterialType
{
	public const Air = "air";
	public const Dirt = "dirt";
	public const Wood = "wood";
	public const Metal = "metal";
	public const Grate = "grate";
	public const Water = "water";
	public const Lava = "lava";
	public const Leaves = "leaves";
	public const Plant = "plant";
	public const SolidPlant = "solidPlant"; // Crashes if used
	public const Fire = "fire";
	public const Glass = "glass";
	public const Explosive = "explosive";
	public const Ice = "ice"; // Not working properly
	public const PowderSnow = "powderSnow"; // Not working properly
	public const Cactus = "cactus";
	public const Portal = "portal";
	public const StoneDecoration = "stoneDecoration";
	public const Bubble = "bubble";
	public const Barrier = "barrier";
	public const DecorationSolid = "decorationSolid";
	public const ClientRequestPlaceholder = "clientRequestPlaceholder";
	public const StructureVoid = "structureVoid";
	public const Solid = "solid";
	public const NonSolid = "nonSolid";
	public const Any = "any";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use InvalidArgumentException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\StringTag;

class ItemMiningSpeed
{
	public function __construct(
		protected string $item,
		protected float $destroySpeed,
	) {
		if ($item === "") {
			throw new InvalidArgumentException("Item identifier cannot be empty.");
		}

		if ($destroySpeed < 0.0) {
			throw new InvalidArgumentException("Item-specific destroy speed cannot be negative.");
		}
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("item", new StringTag($this->item))
			->setTag("destroy_speed", new FloatTag($this->destroySpeed));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class LavaFlammability
{
	public const ALWAYS = "always";
	public const NEVER = "never";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class CullingShape
{
	public const EMPTY = "minecraft:empty";
	public const UNIT_CUBE = "minecraft:unit_cube";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use function is_bool;
use function is_int;

class NbtDescriptor
{
	public function __construct(
		protected ?string $name = null,
		protected array $states = [],
		protected ?string $tags = null,
	) {
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		if ($this->name !== null) {
			$nbt->setString('name', $this->name);
		}
		if ($this->tags !== null) {
			$nbt->setString('tags', $this->tags);
		}
		if (empty($this->states)) {
			$states = CompoundTag::create();
			foreach ($this->states as $name => $value) {
				$states->setTag($name, match (true) {
					is_bool($value) => new ByteTag($value),
					is_int($value) => new IntTag($value),
					default => new StringTag($value),
				});
			}
			$nbt->setTag('states', $states);
		}
		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

/**
 * All vanilla block sound types accepted by the minecraft:sound block component.
 * Values match the sound names used in RP/blocks.json.
 *
 * @since 1.26.50 (minecraft:sound component)
 */
class SoundType
{
	public const AMETHYST_BLOCK = "amethyst_block";
	public const AMETHYST_CLUSTER = "amethyst_cluster";
	public const ANCIENT_DEBRIS = "ancient_debris";
	public const ANVIL = "anvil";
	public const AZALEA = "azalea";
	public const AZALEA_LEAVES = "azalea_leaves";
	public const BAMBOO = "bamboo";
	public const BAMBOO_SAPLING = "bamboo_sapling";
	public const BAMBOO_WOOD = "bamboo_wood";
	public const BAMBOO_WOOD_HANGING_SIGN = "bamboo_wood_hanging_sign";
	public const BASALT = "basalt";
	public const BIG_DRIPLEAF = "big_dripleaf";
	public const BONE_BLOCK = "bone_block";
	public const CACTUS_FLOWER = "cactus_flower";
	public const CALCITE = "calcite";
	public const CANDLE = "candle";
	public const CAVE_VINES = "cave_vines";
	public const CHAIN = "chain";
	public const CHERRY_LEAVES = "cherry_leaves";
	public const CHERRY_WOOD = "cherry_wood";
	public const CHERRY_WOOD_HANGING_SIGN = "cherry_wood_hanging_sign";
	public const CHISELED_BOOKSHELF = "chiseled_bookshelf";
	public const CINNABAR = "cinnabar";
	public const CLOTH = "cloth";
	public const COMPARATOR = "comparator";
	public const COPPER = "copper";
	public const COPPER_BULB = "copper_bulb";
	public const COPPER_CHEST = "copper_chest";
	public const COPPER_CHEST_OXIDIZED = "copper_chest_oxidized";
	public const COPPER_CHEST_WEATHERED = "copper_chest_weathered";
	public const COPPER_GOLEM_STATUE = "copper_golem_statue";
	public const COPPER_GRATE = "copper_grate";
	public const CORAL = "coral";
	public const CREAKING_HEART = "creaking_heart";
	public const DEADBUSH = "deadbush";
	public const DECORATED_POT = "decorated_pot";
	public const DEEPSLATE = "deepslate";
	public const DEEPSLATE_BRICKS = "deepslate_bricks";
	public const DIRT_WITH_ROOTS = "dirt_with_roots";
	public const DRIED_GHAST = "dried_ghast";
	public const DRIPSTONE_BLOCK = "dripstone_block";
	public const EYEBLOSSOM = "eyeblossom";
	public const FIREFLY_BUSH = "firefly_bush";
	public const FROG_SPAWN = "frog_spawn";
	public const FROGLIGHT = "froglight";
	public const FUNGUS = "fungus";
	public const GLASS = "glass";
	public const GLOW_LICHEN = "glow_lichen";
	public const GRASS = "grass";
	public const GRAVEL = "gravel";
	public const HANGING_ROOTS = "hanging_roots";
	public const HANGING_SIGN = "hanging_sign";
	public const HEAVY_CORE = "heavy_core";
	public const HONEY_BLOCK = "honey_block";
	public const IRON = "iron";
	public const ITEMFRAME = "itemframe";
	public const LADDER = "ladder";
	public const LANTERN = "lantern";
	public const LARGE_AMETHYST_BUD = "large_amethyst_bud";
	public const LEAF_LITTER = "leaf_litter";
	public const LEVER = "lever";
	public const LODESTONE = "lodestone";
	public const MANGROVE_ROOTS = "mangrove_roots";
	public const MEDIUM_AMETHYST_BUD = "medium_amethyst_bud";
	public const METAL = "metal";
	public const MOB_SPAWNER = "mob_spawner";
	public const MOSS_BLOCK = "moss_block";
	public const MOSS_CARPET = "moss_carpet";
	public const MUD = "mud";
	public const MUD_BRICKS = "mud_bricks";
	public const MUDDY_MANGROVE_ROOTS = "muddy_mangrove_roots";
	public const NETHER_BRICK = "nether_brick";
	public const NETHER_GOLD_ORE = "nether_gold_ore";
	public const NETHER_SPROUTS = "nether_sprouts";
	public const NETHER_WART = "nether_wart";
	public const NETHER_WOOD = "nether_wood";
	public const NETHER_WOOD_HANGING_SIGN = "nether_wood_hanging_sign";
	public const NETHERITE = "netherite";
	public const NETHERRACK = "netherrack";
	public const NYLIUM = "nylium";
	public const PACKED_MUD = "packed_mud";
	public const PALE_HANGING_MOSS = "pale_hanging_moss";
	public const PINK_PETALS = "pink_petals";
	public const POINTED_DRIPSTONE = "pointed_dripstone";
	public const POLISHED_TUFF = "polished_tuff";
	public const POPLAR_LEAVES = "poplar_leaves";
	public const POTENT_SULFUR = "potent_sulfur";
	public const POWDER_SNOW = "powder_snow";
	public const RED_SHRUB = "red_shrub";
	public const RESIN = "resin";
	public const RESIN_BRICK = "resin_brick";
	public const ROOTS = "roots";
	public const SAND = "sand";
	public const SCAFFOLDING = "scaffolding";
	public const SCULK = "sculk";
	public const SCULK_CATALYST = "sculk_catalyst";
	public const SCULK_SENSOR = "sculk_sensor";
	public const SCULK_SHRIEKER = "sculk_shrieker";
	public const SCULK_VEIN = "sculk_vein";
	public const SHELF = "shelf";
	public const SHELF_MUSHROOM = "shelf_mushroom";
	public const SHROOMLIGHT = "shroomlight";
	public const SLIME = "slime";
	public const SMALL_AMETHYST_BUD = "small_amethyst_bud";
	public const SNOW = "snow";
	public const SOUL_SAND = "soul_sand";
	public const SOUL_SOIL = "soul_soil";
	public const SPONGE = "sponge";
	public const SPORE_BLOSSOM = "spore_blossom";
	public const STEM = "stem";
	public const STONE = "stone";
	public const STRAW_BED = "straw_bed";
	public const SULFUR = "sulfur";
	public const SULFUR_SPIKE = "sulfur_spike";
	public const SUSPICIOUS_GRAVEL = "suspicious_gravel";
	public const SUSPICIOUS_SAND = "suspicious_sand";
	public const SWEET_BERRY_BUSH = "sweet_berry_bush";
	public const TERRACOTTA = "terracotta";
	public const TRIAL_SPAWNER = "trial_spawner";
	public const TUFF = "tuff";
	public const TUFF_BRICKS = "tuff_bricks";
	public const TURTLE_EGG = "turtle_egg";
	public const VAULT = "vault";
	public const VINES = "vines";
	public const WEB = "web";
	public const WEEPING_VINES = "weeping_vines";
	public const WET_SPONGE = "wet_sponge";
	public const WOOD = "wood";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class PrecipitationType
{
	public const OBRAIN = "obrain";
	public const OBSTRUCT_RAIN_ACCUMULATE_SNOW = "obstrant_rain_accumulate_snow";
	public const ACCUMULATE_SNOW = "accumulate_snow";
	public const SNOW_LOGGING = "snowlogging";
	public const NONE = "none";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use InvalidArgumentException;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\StringTag;

class Material
{
	public function __construct(
		protected string $texture,
		protected string $target = Target::ALL,
		protected string $renderMethod = RenderMethod::OPAQUE,
		protected string $tintMethod = TintMethod::NONE,
		protected float  $ambientOcclusion = 1.0,
		protected bool   $faceDimming = true,
		protected bool   $isotropic = false,
		protected bool   $packedBools = false,
		protected bool   $alphaMaskedTint = false,
	) {
		if ($ambientOcclusion < 0.0 || $ambientOcclusion > 10.0) {
			throw new InvalidArgumentException("Ambient occlusion must be between 0.0 and 10.0.");
		}
		if ($alphaMaskedTint && ($this->tintMethod === TintMethod::NONE || $this->renderMethod !== RenderMethod::OPAQUE)) {
			throw new InvalidArgumentException("Alpha-masked tint requires a non-none tint method and opaque rendering.");
		}
	}

	public function getTarget() : string
	{
		return $this->target;
	}

	/**
	 * Returns the material in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("ambient_occlusion", new FloatTag($this->ambientOcclusion))
			->setTag("packed_bools", new ByteTag($this->packedBools))
			->setTag("render_method", new StringTag($this->renderMethod))
			->setTag("texture", new StringTag($this->texture))
			->setTag("alpha_masked_tint", new ByteTag($this->alphaMaskedTint))
			->setTag("face_dimming", new ByteTag($this->faceDimming))
			->setTag("isotropic", new ByteTag($this->isotropic))
			->setTag("tint_method", new StringTag($this->tintMethod));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class AxisRotation
{
	public const X = 'x';
	public const Y = 'y';
	public const Z = 'z';
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class PlacementFace
{
	public const DOWN = 'down';
	public const UP = 'up';
	public const NORTH = 'north';
	public const SOUTH = 'south';
	public const WEST = 'west';
	public const EAST = 'east';
	public const SIDE = 'side';
	public const ALL = 'all';
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class ConnectionType
{
	public const ALL = "all";
	public const NONE = "none";
	public const ONLY_FENCES = "only_fences";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class Target
{
	public const ALL = "*";
	public const UP = "up";
	public const DOWN = "down";
	public const NORTH = "north";
	public const EAST = "east";
	public const SOUTH = "south";
	public const WEST = "west";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\block\Block;
use pocketmine\block\Grass;
use pocketmine\block\Leaves;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\Water;

class TintMethod
{
	public const NONE = "none";
	public const DEFAULT_FOLIAGE = "default_foliage";
	public const BIRCH_FOLIAGE = "birch_foliage";
	public const EVERGREEN_FOLIAGE = "evergreen_foliage";
	public const DRY_FOLIAGE = "dry_foliage";
	public const GRASS = "grass";
	public const WATER = "water";

	public static function from(Block $block) : ?string
	{
		return match(true) {
			$block instanceof Grass => TintMethod::GRASS,
			$block instanceof Water => TintMethod::WATER,
			$block instanceof Leaves => match($block->getLeavesType()) {
				LeavesType::SPRUCE => TintMethod::EVERGREEN_FOLIAGE,
				LeavesType::BIRCH => TintMethod::BIRCH_FOLIAGE,
				LeavesType::OAK,
				LeavesType::JUNGLE,
				LeavesType::ACACIA,
				LeavesType::DARK_OAK => TintMethod::DEFAULT_FOLIAGE,
				default => null
			},
			default => null
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

use pocketmine\block\Block;
use pocketmine\block\BlockToolType;
use pocketmine\block\Wool;

class NoteInstrument
{
	public const BANJO = "note.banjo";
	public const BASS = "note.bassattack";
	public const BASS_DRUM = "note.bd";
	public const BELL = "note.bell";
	public const BIT = "note.bit";
	public const CHIME = "note.chime";
	public const COW_BELL = "note.cow_bell";
	public const CREEPER = "note.creeper";
	public const DIDGERIDOO = "note.didgeridoo";
	public const ENDER_DRAGON = "note.enderdragon";
	public const FLUTE = "note.flute";
	public const GUITAR = "note.guitar";
	public const HARP = "note.harp";
	public const HAT = "note.hat";
	public const IRON_XYLOPHONE = "note.iron_xylophone";
	public const NONE = "note.none";
	public const PIGLIN = "note.piglin";
	public const PLING = "note.pling";
	public const SKELETON = "note.skeleton";
	public const SNARE = "note.snare";
	public const TRUMPET = "note.trumpet";
	public const TRUMPET_EXPOSED = "note.trumpet_exposed";
	public const TRUMPET_OXIDIZED = "note.trumpet_oxidized";
	public const TRUMPET_WEATHERED = "note.trumpet_weathered";
	public const WITHER_SKELETON = "note.witherskeleton";
	public const XYLOPHONE = "note.xylophone";
	public const ZOMBIE = "note.zombie";

	public static function from(Block $block) : string
	{
		$breakInfo = $block->getBreakInfo();
		$toolType = $breakInfo->getToolType();

		return match (true) {
			$block instanceof Wool => NoteInstrument::GUITAR,
			$toolType === BlockToolType::AXE => NoteInstrument::BASS,
			$toolType === BlockToolType::PICKAXE => NoteInstrument::BASS_DRUM,
			$toolType === BlockToolType::SHOVEL => NoteInstrument::SNARE,
			$block->isTransparent() && $breakInfo->getHardness() <= 0.5 => NoteInstrument::HAT,
			default => NoteInstrument::HARP
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components\Types;

class Face
{
	public const DOWN = "down";
	public const UP = "up";
	public const NORTH = "north";
	public const SOUTH = "south";
	public const WEST = "west";
	public const EAST = "east";

	public const ALL = [
		self::DOWN,
		self::UP,
		self::NORTH,
		self::SOUTH,
		self::WEST,
		self::EAST,
	];
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use Nexly\Blocks\Components\Types\RangeOffset;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\protocol\types\BlockPosition;

#[Attribute(Attribute::TARGET_CLASS)]
class RandomOffsetBlockComponent extends BlockComponent
{
	public function __construct(
		protected BlockPosition $steps,
		protected RangeOffset   $offset,
	) {
		if ($steps->getX() < 0 || $steps->getY() < 0 || $steps->getZ() < 0) {
			throw new InvalidArgumentException("Random offset steps cannot be negative (zero means continuous).");
		}
	}

	/**
	 * Returns the name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::RANDOM_OFFSET;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag(
				"x",
				CompoundTag::create()
				->setTag("steps", new IntTag($this->steps->getX()))
				->setTag(
					"range",
					CompoundTag::create()
					->setTag("min", new FloatTag($this->offset->getMin()->getX()))
					->setTag("max", new FloatTag($this->offset->getMax()->getX()))
				)
			)
			->setTag(
				"y",
				CompoundTag::create()
				->setTag("steps", new IntTag($this->steps->getY()))
				->setTag(
					"range",
					CompoundTag::create()
					->setTag("min", new FloatTag($this->offset->getMin()->getY()))
					->setTag("max", new FloatTag($this->offset->getMax()->getY()))
				)
			)
			->setTag(
				"z",
				CompoundTag::create()
				->setTag("steps", new IntTag($this->steps->getZ()))
				->setTag(
					"range",
					CompoundTag::create()
					->setTag("min", new FloatTag($this->offset->getMin()->getZ()))
					->setTag("max", new FloatTag($this->offset->getMax()->getZ()))
				)
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use InvalidArgumentException;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class BlockEntityBlockComponent extends BlockComponent
{
	public function __construct(
		private readonly bool $dynamicProperties = false,
		private readonly ?int $containerSlotCount = null,
	) {
		if ($containerSlotCount !== null && ($containerSlotCount < 1 || $containerSlotCount > 54)) {
			throw new InvalidArgumentException("Container slot count must be between 1 and 54.");
		}
	}

	/**
	 * The name of the component.
	 */
	public function getName() : string
	{
		return BlockComponentIds::BLOCK_ENTITY;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("dynamic_properties", new ByteTag($this->dynamicProperties));

		if ($this->containerSlotCount !== null) {
			$nbt->setTag("container", CompoundTag::create()
				->setTag("slot_count", new IntTag($this->containerSlotCount)));
		}

		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Components;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class BreathabilityBlockComponent extends BlockComponent
{
	public function __construct(
		protected string $type,
	) {
	}

	public function getName() : string
	{
		return BlockComponentIds::BREATHABILITY;
	}

	/**
	 * Returns the component in the correct NBT format supported by the client.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new StringTag($this->type));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use Nexly\Blocks\LegacyItemBlockTrait;
use pocketmine\block\Sapling;

class NexlySapling extends Sapling
{
	use LegacyItemBlockTrait;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use pocketmine\block\Block;
use pocketmine\block\GlassPane;
use pocketmine\block\Thin;
use pocketmine\block\utils\SupportType;
use pocketmine\block\Wall;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\math\Facing;
use function array_fill_keys;
use function array_keys;
use function array_values;

/**
 * Class NexlyGlassPane
 *
 * A custom glass pane block that extends the base GlassPane class and includes additional functionality
 * for reading state from the world, recalculating connections, and handling nearby block changes.
 *
 * @package Nexly\Blocks\Vanilla
 *
 * Minecraft does not allow us to reproduce identical glass panes.
 * In certain patterns, you will be able to pass through them because we cannot create collision boxes other than squares/rectangles.
 * @deprecated
 */
class NexlyGlassPane extends GlassPane
{
	/**
	 * Reads the state of the block from the world and updates its connections accordingly.
	 */
	public function readStateFromWorld() : Block
	{
		return $this;
	}

	/**
	 * Recalculates the connections of this fence to adjacent blocks.
	 */
	protected function recalculateConnections() : bool
	{
		$changed = 0;

		foreach (Facing::HORIZONTAL as $facing) {
			$block = $this->getSide($facing);
			if ($block instanceof Thin || $block instanceof Wall || $block->getSupportType(Facing::opposite($facing)) === SupportType::FULL) {
				if (!isset($this->connections[$facing])) {
					$this->connections[$facing] = true;
					$changed++;
				}
			} elseif (isset($this->connections[$facing])) {
				unset($this->connections[$facing]);
				$changed++;
			}
		}
		return $changed > 0;
	}

	/**
	 * Describes the block's state focusing solely on its connections.
	 */
	protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void
	{
		$faces = array_keys($this->connections);
		$w->horizontalFacingFlags($faces);
		$this->connections = array_fill_keys(array_values($faces), true);
	}

	/**
	 * Called when a nearby block changes, triggering a recalculation of connections.
	 */
	public function onNearbyBlockChange() : void
	{
		if ($this->recalculateConnections()) {
			$this->position->getWorld()->setBlock($this->position, $this);
		}
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use pocketmine\block\Block;
use pocketmine\block\Fence;
use pocketmine\block\FenceGate;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\math\Facing;
use function array_fill_keys;
use function array_keys;
use function array_values;

/**
 * Class NexlyFence
 *
 * A custom fence block that extends the base Fence class and includes additional functionality
 * for reading state from the world, recalculating connections, and handling nearby block changes.
 *
 * @package Nexly\Blocks\Vanilla
 *
 * Minecraft does not allow us to reproduce identical fences.
 * In certain patterns, you will be able to pass through them because we cannot create collision boxes other than squares/rectangles.
 * @deprecated
 */
class NexlyFence extends Fence
{
	/**
	 * Reads the state of the block from the world and updates its connections accordingly.
	 */
	public function readStateFromWorld() : Block
	{
		return $this;
	}

	/**
	 * Recalculates the connections of this fence to adjacent blocks.
	 */
	protected function recalculateConnections() : bool
	{
		$changed = 0;

		foreach (Facing::HORIZONTAL as $facing) {
			$block = $this->getSide($facing);
			if ($block instanceof Fence || $block instanceof FenceGate || $block->getSupportType(Facing::opposite($facing)) === SupportType::FULL) {
				if (!isset($this->connections[$facing])) {
					$this->connections[$facing] = true;
					$changed++;
				}
			} elseif (isset($this->connections[$facing])) {
				unset($this->connections[$facing]);
				$changed++;
			}
		}
		return $changed > 0;
	}

	/**
	 * Describes the block's state focusing solely on its connections.
	 */
	protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void
	{
		$faces = array_keys($this->connections);
		$w->horizontalFacingFlags($faces);
		$this->connections = array_fill_keys(array_values($faces), true);
	}

	/**
	 * Called when a nearby block changes, triggering a recalculation of connections.
	 */
	public function onNearbyBlockChange() : void
	{
		if ($this->recalculateConnections()) {
			$this->position->getWorld()->setBlock($this->position, $this);
		}
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use pocketmine\block\utils\Colored;
use pocketmine\block\utils\ColoredTrait;

class NexlyStainedGlassPane extends NexlyGlassPane implements Colored
{
	use ColoredTrait;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use pocketmine\block\BlockIdentifier;
use pocketmine\block\BlockTypeInfo;
use pocketmine\block\Ladder;

class NexlyLadder extends Ladder
{
	protected float $climbSpeed = 0.15; // Default climb speed

	public function __construct(BlockIdentifier $idInfo, string $name, float $climbSpeed, BlockTypeInfo $typeInfo)
	{
		parent::__construct($idInfo, $name, $typeInfo);
		$this->climbSpeed = $climbSpeed;
	}

	/**
	 * Determines the speed at which a player can climb this ladder.
	 */
	public function getClimbSpeed() : float
	{
		return $this->climbSpeed;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Vanilla;

use pocketmine\block\Block;
use pocketmine\block\BlockIdentifier;
use pocketmine\block\BlockTypeInfo;
use pocketmine\block\Flowable;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function floor;

class HeadBlock extends Flowable
{
	public const MIN_ROTATION = 0;
	public const MAX_ROTATION = 15;

	protected int $facing = Facing::NORTH;
	protected int $rotation = self::MIN_ROTATION;

	public function __construct(
		BlockIdentifier $idInfo,
		string          $name,
		protected string  $texture,
		BlockTypeInfo   $typeInfo
	) {
		parent::__construct($idInfo, $name, $typeInfo);
	}

	public function getTexture() : string
	{
		return $this->texture;
	}

	public function isTransparent() : bool
	{
		return false;
	}

	protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void
	{
		$w->facingExcept($this->facing, Facing::DOWN);
		$w->boundedIntAuto(self::MIN_ROTATION, self::MAX_ROTATION, $this->rotation);
	}

	public function getFacing() : int
	{
		return $this->facing;
	}

	/** @return $this */
	public function setFacing(int $facing) : self
	{
		if ($facing === Facing::DOWN) {
			throw new \InvalidArgumentException("Skull may not face DOWN");
		}
		$this->facing = $facing;
		return $this;
	}

	public function getRotation() : int
	{
		return $this->rotation;
	}

	/** @return $this */
	public function setRotation(int $rotation) : self
	{
		if ($rotation < self::MIN_ROTATION || $rotation > self::MAX_ROTATION) {
			throw new \InvalidArgumentException("Rotation must be in range " . self::MIN_ROTATION . " ... " . self::MAX_ROTATION);
		}
		$this->rotation = $rotation;
		return $this;
	}

	protected function recalculateCollisionBoxes() : array
	{
		$collisionBox = AxisAlignedBB::one()
			->contract(0.25, 0, 0.25)
			->trim(Facing::UP, 0.5);
		if ($this->facing !== Facing::UP) {
			$collisionBox = $collisionBox
				->offsetTowards(Facing::opposite($this->facing), 0.25)
				->offsetTowards(Facing::UP, 0.25);
		}
		return [$collisionBox];
	}

	public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool
	{
		if ($face === Facing::DOWN) {
			return false;
		}

		$this->facing = $face;
		if ($player !== null && $face === Facing::UP) {
			$this->rotation = ((int) floor(($player->getLocation()->getYaw() * 16 / 360) + 0.5)) & 0xf;
		}
		return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use pocketmine\block\Block;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier;

trait LegacyItemBlockTrait
{
	public function asItem() : Item
	{
		return new class (new ItemIdentifier(-$this->getTypeId()), $this->getName(), clone $this) extends Item {
			public function __construct(
				ItemIdentifier $identifier,
				string                         $name,
				protected Block $block,
			) {
				parent::__construct($identifier, $name);
			}

			public function getBlock(?int $clickedFace = null) : Block
			{
				return (clone $this->block);
			}
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use Closure;
use Generator;
use Nexly\Blocks\Components\BlockComponent;
use Nexly\Blocks\Components\BlockComponentIds;
use Nexly\Blocks\Components\BlockEntityBlockComponent;
use Nexly\Blocks\Components\BreathabilityBlockComponent;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\ConnectionRuleComponent;
use Nexly\Blocks\Components\CraftingTableBlockComponent;
use Nexly\Blocks\Components\DestructibleByExplosionBlockComponent;
use Nexly\Blocks\Components\DestructibleByMiningBlockComponent;
use Nexly\Blocks\Components\DisplayNameBlockComponent;
use Nexly\Blocks\Components\EntityFallOnBlockComponent;
use Nexly\Blocks\Components\FlammableBlockComponent;
use Nexly\Blocks\Components\FrictionBlockComponent;
use Nexly\Blocks\Components\GeometryBlockComponent;
use Nexly\Blocks\Components\InstrumentSoundBlockComponent;
use Nexly\Blocks\Components\ItemVisualBlockComponent;
use Nexly\Blocks\Components\LightDampeningBlockComponent;
use Nexly\Blocks\Components\LightEmissionBlockComponent;
use Nexly\Blocks\Components\MapColorBlockComponent;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent;
use Nexly\Blocks\Components\MovableBlockComponent;
use Nexly\Blocks\Components\OnInteractBlockComponent;
use Nexly\Blocks\Components\OnPlayerPlacingBlockComponent;
use Nexly\Blocks\Components\PrecipitationInteractionsBlockComponent;
use Nexly\Blocks\Components\RedstoneConductivityBlockComponent;
use Nexly\Blocks\Components\RedstoneProducerBlockComponent;
use Nexly\Blocks\Components\ReplaceableBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\SupportBlockComponent;
use Nexly\Blocks\Components\TickBlockComponent;
use Nexly\Blocks\Components\Types\BreathabilityType;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\MaterialType;
use Nexly\Blocks\Components\Types\MovementType;
use Nexly\Blocks\Components\Types\NoteInstrument;
use Nexly\Blocks\Components\Types\PrecipitationType;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Components\Types\StickyType;
use Nexly\Blocks\Components\Types\SupportShape;
use Nexly\Blocks\Components\Types\TintMethod;
use Nexly\Blocks\Components\Types\UseDefaultVisual;
use Nexly\Blocks\Mappers\BlockMapper;
use Nexly\Blocks\Permutations\BlockProperty;
use Nexly\Blocks\Permutations\CartesianProduct;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Blocks\Traits\BlockTrait;
use Nexly\Blocks\Vanilla\HeadBlock;
use Nexly\Events\Impl\BlockLoaderEvent;
use Nexly\Helpers\ComponentHelper;
use Nexly\Items\Components\DataDriven\DataDrivenItemBuilder;
use Nexly\Items\Components\DataDriven\DataDrivenItemComponent;
use Nexly\Items\Components\DataDriven\Property\PropertyItemComponent;
use Nexly\Items\Components\Legacy\LegacyItemBuilder;
use Nexly\Items\Components\Legacy\LegacyItemComponent;
use Nexly\Items\Creative\CreativeInfo;
use Nexly\Items\Creative\NexlyCreative;
use Nexly\Items\ItemBuilder;
use Nexly\Items\ItemVersion;
use Nexly\Mappings\BlockMappings;
use Nexly\Mappings\ItemMappings;
use Nexly\Recipes\NexlyRecipes;
use Nexly\Recipes\Types\Recipe;
use pocketmine\block\block;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\Cactus;
use pocketmine\block\CraftingTable;
use pocketmine\block\Door;
use pocketmine\block\Farmland;
use pocketmine\block\Fence;
use pocketmine\block\FenceGate;
use pocketmine\block\Flowable;
use pocketmine\block\Flower;
use pocketmine\block\Furnace;
use pocketmine\block\Glass;
use pocketmine\block\GlassPane;
use pocketmine\block\Grass;
use pocketmine\block\GrassPath;
use pocketmine\block\Hopper;
use pocketmine\block\Ladder;
use pocketmine\block\Leaves;
use pocketmine\block\Lever;
use pocketmine\block\Opaque;
use pocketmine\block\RuntimeBlockStateRegistry;
use pocketmine\block\Sapling;
use pocketmine\block\Slab;
use pocketmine\block\Slime;
use pocketmine\block\Stair;
use pocketmine\block\Transparent;
use pocketmine\block\Trapdoor;
use pocketmine\block\utils\Ageable;
use pocketmine\block\utils\AnalogRedstoneSignalEmitter;
use pocketmine\block\utils\Colored;
use pocketmine\block\utils\PoweredByRedstone;
use pocketmine\block\Wall;
use pocketmine\block\Wood;
use pocketmine\data\bedrock\block\convert\BlockStateReader;
use pocketmine\data\bedrock\block\convert\BlockStateWriter;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\data\bedrock\item\upgrade\LegacyItemIdToStringIdMap;
use pocketmine\item\ItemBlock;
use pocketmine\item\StringToItemParser;
use pocketmine\nbt\NBT;
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\network\mcpe\convert\BlockStateDictionaryEntry;
use pocketmine\network\mcpe\protocol\types\BlockPaletteEntry;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\format\io\GlobalBlockStateHandlers as BlockStateHandlers;
use pocketmine\world\format\io\GlobalItemDataHandlers as ItemDataHandlers;
use ReflectionClass;
use function array_map;
use function base64_encode;
use function count;
use function explode;
use function in_array;
use function is_a;
use function is_bool;
use function is_float;
use function is_int;
use function is_null;
use function is_string;
use function json_encode;
use function max;
use function str_contains;
use function strtolower;
use const JSON_THROW_ON_ERROR;
use const PHP_INT_MAX;

class BlockBuilder
{
	protected string $stringId;
	protected ?int $numericId = null;
	protected Closure $block;
	protected ?Closure $serializer = null;
	protected ?Closure $deserializer = null;
	protected ?CreativeInfo $creativeInfo = null;
	protected string $material = MaterialType::Dirt;

	/** @var array<string> */
	protected array $tags = [];

	/** @var array<BlockComponent> */
	protected array $components = [];
	/** @var array<Permutation> */
	protected array $permutations = [];
	protected ?string $mapper = null;
	/** @var array<BlockTrait> */
	protected array $traits = [];
	/** @var array<BlockProperty> */
	protected array $properties = [];

	public static function create() : BlockBuilder
	{
		return new self();
	}

	/**
	 * Get the string identifier for the block.
	 */
	public function getStringId() : string
	{
		return $this->stringId;
	}

	/**
	 * Set the string identifier for the block.
	 *
	 * @return $this
	 */
	public function setStringId(string $stringId) : self
	{
		if (str_contains("minecraft:", $stringId)) {
			throw new \InvalidArgumentException("Custom block string ID cannot contain the 'minecraft:' namespace.");
		}

		$this->stringId = $stringId;
		return $this;
	}

	/**
	 * Get the numeric ID for the block, if set.
	 */
	public function getNumericId() : int
	{
		return $this->numericId ??= BlockTypeIds::newId();
	}

	/**
	 * Set the numeric ID for the block.
	 *
	 * @return $this
	 */
	public function setNumericId(?int $numericId) : self
	{
		$this->numericId = $numericId;
		return $this;
	}

	public function getTags() : array
	{
		return $this->tags;
	}

	public function setTags(array $tags) : self
	{
		$this->tags = $tags;
		return $this;
	}

	public function addTag(string $tag) : void
	{
		if (!in_array($tag, $this->tags, true)) {
			$this->tags[] = $tag;
		}
	}

	public function getBlock() : Closure
	{
		return $this->block;
	}

	/**
	 * Set the item instance or a closure that returns an item instance.
	 *
	 * @return $this
	 */
	public function setBlock(Closure $block) : self
	{
		$this->block = $block;
		return $this;
	}

	/**
	 * Set a custom serializer for the block.
	 *
	 * @return $this
	 */
	public function mapSerializer(?Closure $serializer) : self
	{
		if ($serializer === null) {
			$this->serializer = null;
			return $this;
		}

		$ref = new \ReflectionFunction($serializer);

		if (!$ref->isAnonymous()) {
			throw new \InvalidArgumentException("Serializer must be an anonymous Closure.");
		}

		if (!$ref->isStatic()) {
			throw new \InvalidArgumentException("Serializer must be a static Closure.");
		}

		$returnType = $ref->getReturnType();
		if ($returnType !== null && $returnType->getName() !== BlockStateWriter::class) {
			throw new \InvalidArgumentException("Serializer return type must be a " . BlockStateWriter::class . ".");
		}

		$this->serializer = $serializer;
		return $this;
	}

	/**
	 * Set a custom deserializer for the block.
	 *
	 * @return $this
	 */
	public function mapDeserializer(?Closure $deserializer) : self
	{
		if ($deserializer === null) {
			$this->deserializer = null;
			return $this;
		}

		$ref = new \ReflectionFunction($deserializer);

		if (!$ref->isAnonymous()) {
			throw new \InvalidArgumentException("Deserializer must be an anonymous Closure.");
		}

		if (!$ref->isStatic()) {
			throw new \InvalidArgumentException("Deserializer must be a static Closure.");
		}

		if ($ref->getNumberOfParameters() < 1) {
			throw new \InvalidArgumentException("Deserializer must accept a BlockStateReader as its first parameter.");
		}

		$firstParamType = $ref->getParameters()[0]->getType();
		if ($firstParamType !== null && $firstParamType->getName() !== BlockStateReader::class) {
			throw new \InvalidArgumentException("The first parameter of the deserializer must be typed as " . BlockStateReader::class . ".");
		}

		$returnType = $ref->getReturnType();
		if ($returnType !== null) {
			$returnTypeName = $returnType->getName();
			if (!is_a($returnTypeName, block::class, true)) {
				throw new \InvalidArgumentException("Deserializer return type must be an instance of " . block::class . ", got " . $returnTypeName . ".");
			}
		}

		$this->deserializer = $deserializer;
		return $this;
	}

	/**
	 * Get the CreativeInfo attribute for the block, if set.
	 */
	public function getCreativeInfo() : ?CreativeInfo
	{
		return $this->creativeInfo;
	}

	/**
	 * Set the CreativeInfo attribute for the block.
	 *
	 * @return $this
	 */
	public function setCreativeInfo(?CreativeInfo $creativeInfo) : self
	{
		$this->creativeInfo = $creativeInfo;
		return $this;
	}

	public function getMaterial() : string
	{
		return $this->material;
	}

	public function setMaterial(string $material) : self
	{
		$this->material = $material;
		return $this;
	}

	/**
	 * Add a BlockComponent to the builder.
	 */
	public function getComponents() : array
	{
		return $this->components;
	}

	/**
	 * Get a BlockComponent by its name.
	 */
	public function getComponent(string $component) : ?BlockComponent
	{
		return $this->components[$component] ?? null;
	}

	/**
	 * Add a BlockComponent to the builder.
	 */
	public function hasComponent(string $component) : bool
	{
		return isset($this->components[$component]);
	}

	/**
	 * Add a BlockComponent to the builder.
	 *
	 * @return $this
	 */
	public function addComponent(BlockComponent $component) : self
	{
		$this->components[$component->getName()] = $component;
		return $this;
	}

	/**
	 * Remove a component by its name.
	 */
	public function removeComponent(string $name) : self
	{
		unset($this->components[$name]);
		return $this;
	}

	public function getPermutations() : array
	{
		return $this->permutations;
	}

	/**
	 * Add a permutation to the builder.
	 *
	 * @return $this
	 */
	public function addPermutation(Permutation $permutation) : self
	{
		$this->permutations[] = $permutation;
		return $this;
	}

	public function setMapper(string $class) : BlockBuilder
	{
		$this->mapper = $class;
		return $this;
	}

	public function getTraits() : array
	{
		return $this->traits;
	}

	/**
	 * Add a trait to the builder.
	 *
	 * @return $this
	 */
	public function addTrait(BlockTrait $trait) : self
	{
		$this->traits[] = $trait;
		return $this;
	}

	public function getProperties() : array
	{
		return $this->properties;
	}

	/**
	 * Add a property to the builder.
	 *
	 * @return $this
	 */
	public function addProperty(BlockProperty $property) : self
	{
		$this->properties[] = $property;
		return $this;
	}

	/**
	 * @internal
	 */
	public function getName() : string
	{
		$itemName = $this->getStringId();
		if (str_contains($itemName, ":")) {
			[, $itemName] = explode(":", $itemName, 2);
		}

		return $itemName;
	}

	protected function getItemFrom(block $block) : ItemBuilder
	{
		$item = $block->asItem();
		if ($item->isNull()) {
			throw new \RuntimeException("The block must have a valid item form to be registered.");
		}

		$itemVersion = ItemVersion::fromItem($item);
		$builder = match ($itemVersion) {
			ItemVersion::LEGACY => LegacyItemBuilder::create(),
			ItemVersion::DATA_DRIVEN => DataDrivenItemBuilder::create(),
			default => throw new AssumptionFailedError("Unsupported item itemVersion."),
		};
		$builder->setStringId($this->getStringId());
		$builder->setNumericId($item->getTypeId());
		$builder->setItem($item);

		if ($builder instanceof LegacyItemBuilder) {
			$builder->loadFromItems();
		} elseif ($builder instanceof DataDrivenItemBuilder) {
			$builder->loadProperties();
			$builder->loadComponents();
		}

		$reflection = new ReflectionClass($item);
		$attributes = $reflection->getAttributes();

		foreach ($attributes as $attribute) {
			$instance = $attribute->newInstance();

			if ($builder instanceof DataDrivenItemBuilder) {
				if ($instance instanceof PropertyItemComponent) {
					$builder->addProperty($instance);
				} elseif ($instance instanceof DataDrivenItemComponent) {
					$builder->addComponent($instance);
				}
			}

			if ($builder instanceof LegacyItemBuilder) {
				if ($instance instanceof LegacyItemComponent) {
					$builder->addComponent($instance);
				}
			}
		}

		return $builder;
	}

	/**
	 * Create a LegacyItemBuilder from an Item instance.
	 */
	public function toNBT() : CompoundTag
	{
		$components = CompoundTag::create();
		foreach ($this->components as $k => $component) {
			$components->setTag($component->getName(), $component->toNBT());
		}

		$properties = [];
		foreach ($this->properties as $property) {
			foreach ($this->traits as $trait) {
				if (in_array($property->getName(), $trait->getState()->getNames(), true)) {
					continue 2;
				}
			}

			$properties[] = $property->toNBT();
		}

		$traits = CompoundTag::create();
		foreach ($this->traits as $trait) {
			$traits->setTag($trait->getId(), $trait->toNBT());
		}

		return CompoundTag::create()
			->setTag("components", $components)
			->setTag("traits", $traits)
			->setTag("permutations", new ListTag(array_map(fn (Permutation $permutation) => $permutation->toNBT(), $this->permutations), NBT::TAG_Compound))
			->setTag("properties", new ListTag($properties, NBT::TAG_Compound))
			->setTag(
				"menu_category",
				CompoundTag::create()
				->setTag("category", new StringTag(strtolower($this->getCreativeInfo()?->getCategory()?->name ?? "none")))
				->setTag("group", new StringTag(strtolower($this->getCreativeInfo()?->getGroup()?->value ?? "none")))
				->setTag("is_hidden_in_commands", new ByteTag($this->getCreativeInfo()?->isHidden() ?? false))
			)
			->setTag("blockTags", new ListTag(array_map(fn (string $tag) => new StringTag($tag), $this->tags), NBT::TAG_String))
			->setTag(
				"vanilla_block_data",
				CompoundTag::create()
				->setTag("block_id", new IntTag(BlockMappings::getInstance()->nextRuntimeId()))
				->setTag("material", new StringTag($this->material))
			)
			->setTag("molangVersion", new IntTag(13));
	}

	/**
	 * Register the item with the provided string identifier and callbacks.
	 *
	 * @return $this
	 */
	public function register(bool $creative = true, bool $autoload = true, bool $async = false) : self
	{
		if (!isset($this->block)) {
			throw new \RuntimeException("Block instance is not set. Use setBlock() to set it before registering.");
		}

		$stringId = $this->getStringId();
		$numericId = $this->getNumericId();

		$ref = new \ReflectionFunction($this->getBlock());
		if (!$ref->isStatic()) {
			throw new \RuntimeException("Block method is not static.");
		}

		$parameters = $ref->getParameters();
		if (count($parameters) !== 1) {
			throw new \RuntimeException("Block method has only one parameter.");
		}

		$block = $ref->invoke($numericId);
		if (!$block instanceof block) {
			throw new \RuntimeException("The block closure must return an instance of " . block::class);
		}

		$this->loadComponents($block, $autoload, $async);
		if ($this->mapper !== null) {
			/** @var BlockMapper $mapper */
			$mapper = new ($this->mapper)();
			$mapper->apply($this, $block, $this->properties);
		}

		$entries = [];
		foreach ($this->getBlockStateDictionaryEntry() as $entry) {
			$entries[] = $entry;
			BlockStateHandlers::getUpgrader()->getBlockIdMetaUpgrader()->addIdMetaToStateMapping($entry->getStateName(), $entry->getMeta(), $entry->generateStateData());
		}

		BlockPalette::getInstance()->insertStates($entries);
		BlockMappings::getInstance()->registerMapping(new BlockMapping($this, new BlockPaletteEntry($stringId, new CacheableNbt($this->toNBT()))));
		RuntimeBlockStateRegistry::getInstance()->register($block);

		$serializer = $this->serializer ??= static fn () => new BlockStateWriter($stringId);
		$deserializer = $this->deserializer ??= static fn (BlockStateReader $in) => clone $block;

		BlockStateHandlers::getSerializer()->map($block, $serializer);
		BlockStateHandlers::getDeserializer()->map($stringId, $deserializer);

		AsyncInitialization::addAsyncBlock($stringId, [
			$this->numericId,
			$this->block,
			json_encode(array_map(fn (BlockProperty $property) => [
				"name" => $property->getName(),
				"values" => $property->getValues()
			], $this->properties), JSON_THROW_ON_ERROR),
			json_encode(array_map(fn (Permutation $permutation) => [
				"condition" => $permutation->getCondition(),
				"components" => array_map(fn (BlockComponent $component) => [
					"name" => $component->getName(),
					"nbt" => base64_encode((new CacheableNbt($component->toNBT()))->getEncodedNbt())
				], $permutation->getComponents())
			], $this->permutations), JSON_THROW_ON_ERROR),
			json_encode(array_map(fn (BlockComponent $component) => [
				"name" => $component->getName(),
				"nbt" => base64_encode((new CacheableNbt($component->toNBT()))->getEncodedNbt())
			], $this->getComponents()), JSON_THROW_ON_ERROR),
			json_encode(array_map(fn (BlockTrait $trait) => [
				"identifier" => $trait->getId(),
				"nbt" => base64_encode((new CacheableNbt($trait->toNBT()))->getEncodedNbt()),
			], $this->traits), JSON_THROW_ON_ERROR),
			$serializer,
			$deserializer,
		]);

		$item = $block->asItem();
		if ($item instanceof ItemBlock) {
			ItemMappings::registerEntry(new ItemTypeEntry(
				$stringId,
				$block->getTypeId(),
				false,
				ItemVersion::NONE->getValue(),
				new CacheableNbt(CompoundTag::create())
			));
		} else {
			$this->registerItem($block);
		}
		$this->registerBlockItem($block);

		$creativeInfo = $this->getCreativeInfo() ?? NexlyCreative::detectCreativeInfoFrom($block->asItem());
		$reflection = new ReflectionClass($block->asItem());

		$attributes = $reflection->getAttributes();
		if (count($attributes) > 0) {
			foreach ($attributes as $attribute) {
				$instance = $attribute->newInstance();
				if ($instance instanceof CreativeInfo) {
					$creativeInfo = $instance;
				} elseif ($instance instanceof Recipe) {
					NexlyRecipes::getInstance()->addRecipe(fn () => $attribute->newInstance());
				}
			}
		}

		if ($creative && !$creativeInfo->isHidden()) {
			NexlyCreative::add($block->asItem(), $creativeInfo?->getCategory(), $creativeInfo?->getGroup());
		}
		return $this;
	}

	public function loadComponents(block $block, bool $autoload, bool $async = false) : void
	{
		if ($autoload) {
			$this->addComponent(new BreathabilityBlockComponent($block->isTransparent() ? BreathabilityType::AIR : BreathabilityType::SOLID));
			$this->addComponent(new CollisionBoxBlockComponent(!empty($block->getCollisionBoxes())));
			$this->addComponent(new DestructibleByExplosionBlockComponent($block->getBreakInfo()->getBlastResistance()));
			$this->addComponent(new DestructibleByMiningBlockComponent(/*$block->getBreakInfo()->getHardness() * 3.33334*/PHP_INT_MAX));
			$this->addComponent(new DisplayNameBlockComponent("tile." . $this->getStringId() . ".name"));
			$this->addComponent(new FrictionBlockComponent(max(0, 1 - $block->getFrictionFactor())));
			$this->addComponent(new LightEmissionBlockComponent($block->getLightLevel()));
			$this->addComponent(new LightDampeningBlockComponent(max(0, 1 - $block->getLightFilter())));
			$this->addComponent(new EntityFallOnBlockComponent(3));
			$this->addComponent(new InstrumentSoundBlockComponent(NoteInstrument::from($block)));
			$this->addComponent(new MovableBlockComponent(MovementType::from($block), StickyType::from($block)));
			$this->addComponent(new PrecipitationInteractionsBlockComponent(match(true) {
				$block instanceof Flowable => PrecipitationType::OBSTRUCT_RAIN_ACCUMULATE_SNOW,
				$block->isSolid(), $block instanceof Transparent => PrecipitationType::OBRAIN,
				default => PrecipitationType::NONE
			}));
			$this->addComponent(new MaterialInstancesBlockComponent([new Material($this->getName(), renderMethod: match(true) {
				$block->isTransparent() => RenderMethod::ALPHA_TEST_SINGLE_SIDED,
				$block instanceof Opaque => RenderMethod::OPAQUE,
				default => RenderMethod::BLEND,
			})]));

			if ($block->getFlammability() > 0) {
				$this->addComponent(new FlammableBlockComponent($block->getFlameEncouragement()));
			}
			if ($block->canBeReplaced()) {
				$this->addComponent(new ReplaceableBlockComponent());
			}
			if ($block->ticksRandomly()) {
				$this->addComponent(new TickBlockComponent());
			}

			if ($block instanceof PoweredByRedstone) {
				$this->addComponent(new RedstoneConductivityBlockComponent(redstoneConductor: true));
			}
			if ($block instanceof AnalogRedstoneSignalEmitter) {
				$this->addComponent(new RedstoneProducerBlockComponent($block->getOutputSignalStrength()));
			}
			if ($block instanceof Flowable) {
				$this->addComponent(new ConnectionRuleComponent());
			}
			if ($block instanceof CraftingTable) {
				$this->addComponent(new CraftingTableBlockComponent(["crafting_table"], 3));
			}
			if ($block instanceof Grass) {
				$this->addComponent(new MapColorBlockComponent("#7CBA46", TintMethod::from($this)));
			} elseif ($block instanceof Colored) {
				$color = $block->getColor()->getRgbValue();
				$this->addComponent(new MapColorBlockComponent([$color->getR(), $color->getG(), $color->getB()], TintMethod::from($block)));
			}

			$containerSlotCount = null;
			if (ComponentHelper::hasTile($block, $containerSlotCount)) {
				$this->addComponent(new BlockEntityBlockComponent(true, $containerSlotCount));
			}
			if (ComponentHelper::hasInteract($block)) {
				$this->addComponent(new OnInteractBlockComponent());
			}

			$shape = SupportShape::from($block);
			if ($shape !== null) {
				$this->addComponent(new SupportBlockComponent($shape));
			}

			$this->addComponent(new SelectionBoxBlockComponent(true));
			$this->addComponent(new OnPlayerPlacingBlockComponent());
		}

		$ev = new BlockLoaderEvent($this, $block);
		$ev->trigger();
		if (!$async) {
			if (!$ev->isAffected() && $autoload) {
				$this->detectDefaultComponent($block); // Detect known block types and apply default components
			}
		}

		$reflection = new ReflectionClass($block);
		$attributes = $reflection->getAttributes();
		if (count($attributes) > 0) {
			foreach ($attributes as $attribute) {
				$instance = $attribute->newInstance();
				if ($instance instanceof BlockComponent) {
					$this->addComponent($instance);
				}
				if ($instance instanceof UseDefaultVisual) {
					$geometry = $this->getComponent(BlockComponentIds::GEOMETRY);
					$material = $this->getComponent(BlockComponentIds::MATERIAL_INSTANCES);
					$this->addComponent(new ItemVisualBlockComponent(is_null($instance->getGeometry()) ? $geometry : new GeometryBlockComponent($instance->getGeometry()), $material));
				}
			}
		}
	}

	/**
	 * Register the block item for the provided block.
	 */
	protected function registerItem(block $block) : void
	{
		$builder = $this->getItemFrom($block);
		$stringId = $this->getStringId();

		try {
			ItemDataHandlers::getDeserializer()->map($stringId, fn (SavedItemData $data) => clone $builder->getItem());
			ItemDataHandlers::getSerializer()->map($builder->getItem(), fn () => new SavedItemData($stringId));
		} catch (\Throwable) {
			Server::getInstance()->getLogger()->warning("Nexly BlockBuilder: Failed to register item serializer/deserializer for block item '{$stringId}'");
		}

		ItemMappings::getInstance()->registerMapping($builder, new ItemTypeEntry(
			$stringId,
			255 - $builder->getNumericId(),
			$builder->getVersion()->equals(ItemVersion::DATA_DRIVEN),
			$builder->getVersion()->getValue(),
			new CacheableNbt($builder->toNBT())
		));
	}

	/**
	 * Register the block item for the provided block.
	 */
	protected function registerBlockItem(block $block) : void
	{
		$name = $this->getName();
		$stringId = $this->getStringId();

		StringToItemParser::getInstance()->registerBlock($name, fn () => clone $block);
		LegacyItemIdToStringIdMap::getInstance()->add($name, 255 - $this->getNumericId());

		$blockItemIdMap = BlockItemIdMap::getInstance();
		$reflection = new ReflectionClass($blockItemIdMap);

		$itemToBlockId = $reflection->getProperty("itemToBlockId");
		/** @var string[] $value */
		$value = $itemToBlockId->getValue($blockItemIdMap);
		$itemToBlockId->setValue($blockItemIdMap, $value + [$stringId => $stringId]);
	}

	protected function getBlockStateDictionaryEntry() : Generator
	{
		if (empty($this->properties)) {
			return yield new BlockStateDictionaryEntry($this->getStringId(), [], 0);
		}

		$listBlockPropertyName = array_map(fn (BlockProperty $property) => $property->getName(), $this->properties);
		$data_ = array_map(fn (BlockProperty $property) => $property->getValues(), $this->properties);
		foreach (CartesianProduct::get($data_) as $meta => $property) {
			$states = [];
			foreach ($property as $i => $data) {
				$states[$listBlockPropertyName[$i]] = match (true) {
					is_bool($data) => new ByteTag($data),
					is_string($data) => new StringTag($data),
					is_int($data) => new IntTag($data),
					is_float($data) => new FloatTag($data),
					default => throw new \RuntimeException("Invalid block property data type"),
				};
			}

			yield new BlockStateDictionaryEntry($this->getStringId(), $states, $meta);
		}
	}

	protected function detectDefaultComponent(block $block) : void
	{
		match (true) {
			$block instanceof Ageable => BlockCreator::makeAgeable($this, $block),

			$block instanceof Wood => BlockCreator::makeWood($this, $block),
			$block instanceof Leaves => BlockCreator::makeLeaves($this, $block),
			$block instanceof GrassPath => BlockCreator::makeGrassPath($this, $block),
			$block instanceof Sapling => BlockCreator::makeSapling($this, $block),
			$block instanceof Stair => BlockCreator::makeStair($this, $block),
			$block instanceof Slab => BlockCreator::makeSlab($this, $block),
			$block instanceof Door => BlockCreator::makeDoor($this, $block),
			$block instanceof Fence => BlockCreator::makeFence($this, $block),
			$block instanceof FenceGate => BlockCreator::makeFenceGate($this, $block),
			$block instanceof Wall => BlockCreator::makeWall($this, $block),
			$block instanceof Trapdoor => BlockCreator::makeTrapdoor($this, $block),
			$block instanceof Hopper => BlockCreator::makeHopper($this, $block),
			$block instanceof Furnace => BlockCreator::makeFurnace($this, $block),
			$block instanceof HeadBlock => BlockCreator::makeHead($this, $block),
			$block instanceof Ladder => BlockCreator::makeLadder($this, $block),
			$block instanceof Farmland => BlockCreator::makeFarmland($this, $block),
			$block instanceof Flower => BlockCreator::makeFlower($this, $block),
			$block instanceof Glass => BlockCreator::makeGlass($this, $block),
			$block instanceof GlassPane => BlockCreator::makeGlassPane($this, $block),
			$block instanceof Lever => BlockCreator::makeLever($this, $block),
			$block instanceof Slime => BlockCreator::makeSlime($this, $block),
			$block instanceof Cactus => BlockCreator::makeCactus($this, $block),
			$block instanceof Colored => BlockCreator::makeSimpleColored($this, $block),
			default => null,
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use Closure;
use Nexly\Items\Creative\CreativeInfo;

class NexlyBlocks
{
	/**
	 * Registers a new block with the given string ID, block creation closure, and optional creative info.
	 */
	public static function register(string $stringId, Closure $block, ?CreativeInfo $creativeInfo = null, bool $addToCreative = true) : BlockBuilder
	{
		return self::buildComplex($stringId, $block, $creativeInfo)->register($addToCreative);
	}

	/**
	 * Builds a complex block with the given string ID, block creation closure, and optional creative info.
	 */
	public static function buildComplex(string $stringId, Closure $block, ?CreativeInfo $creativeInfo = null) : BlockBuilder
	{
		return BlockBuilder::create()
			->setStringId($stringId)
			->setBlock($block)
			->setCreativeInfo($creativeInfo);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

use pocketmine\block\Block;
use pocketmine\network\mcpe\protocol\types\BlockPaletteEntry;

class BlockMapping
{
	public function __construct(
		protected BlockBuilder $builder,
		protected BlockPaletteEntry $entry,
	) {
	}

	public function getBuilder() : BlockBuilder
	{
		return $this->builder;
	}

	public function getStringId() : string
	{
		return $this->builder->getStringId();
	}

	public function getBlock() : Block
	{
		$builder = $this->getBuilder();
		return ($builder->getBlock())($builder->getNumericId());
	}

	public function getEntry() : BlockPaletteEntry
	{
		return $this->entry;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Components\Types\Target;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;
use RuntimeException;

class WoodBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$axis = $states[StateNames::PILLAR_AXIS];

		return Permutation::create(MolangHelper::stateEquals(StateNames::PILLAR_AXIS, $axis))
			->addComponent(new TransformationBlockComponent(rotation: match ($axis) {
				StateValues::PILLAR_AXIS_X => new Vector3(0, 0, 90),
				StateValues::PILLAR_AXIS_Y => new Vector3(0, 0, 0),
				StateValues::PILLAR_AXIS_Z => new Vector3(90, 0, 0),
				default => throw new RuntimeException("Invalid axis"),
			}))
			->addComponent(new MaterialInstancesBlockComponent([
				new Material(
					texture: $builder->getName() . "_top",
					target: Target::UP,
					renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
				),
				new Material(
					texture: $builder->getName() . "_top",
					target: Target::DOWN,
					renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
				),
				new Material(
					texture: $builder->getName() . "_side",
					target: Target::ALL,
					renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
				)
			]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Permutations\Permutation;
use pocketmine\block\Block;
use function array_shift;

abstract class BlockMapper
{
	public function __construct()
	{
	}

	public function apply(Builder $builder, Block $block, array $properties, array $currentStates = []) : void
	{
		if (empty($properties)) {
			$builder->addPermutation($this->map($builder, $block, $currentStates));
			return;
		}

		$property = array_shift($properties);
		$propertyName = $property->getName();

		foreach ($property->getValues() as $value) {
			$newStates = $currentStates;
			$newStates[$propertyName] = $value;

			$this->apply($builder, $block, $properties, $newStates);
		}
	}

	abstract public function map(Builder $builder, Block $block, array $states) : Permutation;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;

class AgeableBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$age = $states[StateNames::AGE];

		return Permutation::create(MolangHelper::stateEquals(StateNames::AGE, $age))
			->addComponent(SelectionBoxBlockComponent::fromCrops($block, $age))
			->addComponent(new MaterialInstancesBlockComponent([
				new Material($builder->getName() . "_{$age}", renderMethod: RenderMethod::ALPHA_TEST)
			]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\math\Vector3;
use RuntimeException;

class LadderBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::FACING_DIRECTION];

		$rotation = match ($dir) {
			2 => new Vector3(0, 0, 0),
			3 => new Vector3(0, 180, 0),
			4 => new Vector3(0, 90, 0),
			5 => new Vector3(0, 270, 0),
			default => throw new RuntimeException("Invalid direction")
		};

		$expr = MolangHelper::stateEquals(StateNames::FACING_DIRECTION, $dir);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, [BoxCollision::LADDER()]))
			->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::LADDER()]))
			->addComponent(new TransformationBlockComponent(rotation: $rotation));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;
use RuntimeException;

class DoorBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::MC_CARDINAL_DIRECTION];
		$top = $states[StateNames::UPPER_BLOCK_BIT];
		$hinge = $states[StateNames::DOOR_HINGE_BIT];
		$open = $states[StateNames::OPEN_BIT];

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::MC_CARDINAL_DIRECTION, $dir),
			MolangHelper::stateEquals(StateNames::UPPER_BLOCK_BIT, $top),
			MolangHelper::stateEquals(StateNames::DOOR_HINGE_BIT, $hinge),
			MolangHelper::stateEquals(StateNames::OPEN_BIT, $open)
		);

		$permutation = Permutation::create($expr);

		$permutation->addComponent(new MaterialInstancesBlockComponent([
			new Material(
				texture: $builder->getName() . ($top ? "_upper" : "_lower"),
				renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
			)
		]));

		$box = $open
			? new BoxCollision(new Vector3(-8, 0, 5), new Vector3(16, 16, 3))
			: new BoxCollision(new Vector3(-8, 0, -8), new Vector3(3, 16, 16));

		$permutation->addComponent(new CollisionBoxBlockComponent(true, [$box]));
		$permutation->addComponent(new SelectionBoxBlockComponent(true, [$box]));

		$permutation->addComponent(new TransformationBlockComponent(rotation: match ($dir) {
			StateValues::MC_CARDINAL_DIRECTION_NORTH => new Vector3(0, 0, 0),
			StateValues::MC_CARDINAL_DIRECTION_SOUTH => new Vector3(0, 180, 0),
			StateValues::MC_CARDINAL_DIRECTION_WEST => new Vector3(0, 90, 0),
			StateValues::MC_CARDINAL_DIRECTION_EAST => new Vector3(0, 270, 0),
			default => throw new RuntimeException("Invalid direction")
		}));

		return $permutation;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;

class WallBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$none = StateValues::WALL_CONNECTION_TYPE_NORTH_NONE;

		$hasN = $states[StateNames::WALL_CONNECTION_TYPE_NORTH] !== $none;
		$hasS = $states[StateNames::WALL_CONNECTION_TYPE_SOUTH] !== $none;
		$hasW = $states[StateNames::WALL_CONNECTION_TYPE_WEST] !== $none;
		$hasE = $states[StateNames::WALL_CONNECTION_TYPE_EAST] !== $none;
		$hasPost = $states[StateNames::WALL_POST_BIT];

		$minX = $hasE ? -8 : -4;
		$maxX = $hasW ? 8 : 4;
		$minZ = $hasN ? -8 : -4;
		$maxZ = $hasS ? 8 : 4;

		if (!$hasPost) {
			if ($hasN && $hasS && !$hasW && !$hasE) {
				$minX = -3;
				$maxX = 3;
			} elseif (!$hasN && !$hasS && $hasW && $hasE) {
				$minZ = -3;
				$maxZ = 3;
			}
		}

		$boxCollision = new BoxCollision(
			new Vector3($minX, 0, $minZ),
			new Vector3($maxX - $minX, 24, $maxZ - $minZ)
		);
		$boxSelection = new BoxCollision(
			new Vector3($minX, 0, $minZ),
			new Vector3($maxX - $minX, 13, $maxZ - $minZ)
		);

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_NORTH, $states[StateNames::WALL_CONNECTION_TYPE_NORTH]),
			MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_SOUTH, $states[StateNames::WALL_CONNECTION_TYPE_SOUTH]),
			MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_WEST, $states[StateNames::WALL_CONNECTION_TYPE_WEST]),
			MolangHelper::stateEquals(StateNames::WALL_CONNECTION_TYPE_EAST, $states[StateNames::WALL_CONNECTION_TYPE_EAST]),
			MolangHelper::stateEquals(StateNames::WALL_POST_BIT, $hasPost)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, [$boxCollision]))
			->addComponent(new SelectionBoxBlockComponent(true, [$boxSelection]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;
use RuntimeException;

class FenceGateBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::MC_CARDINAL_DIRECTION];
		$inWall = $states[StateNames::IN_WALL_BIT];
		$open = $states[StateNames::OPEN_BIT];

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::MC_CARDINAL_DIRECTION, $dir),
			MolangHelper::stateEquals(StateNames::IN_WALL_BIT, $inWall),
			MolangHelper::stateEquals(StateNames::OPEN_BIT, $open)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(!$open, [BoxCollision::FENCE_GATE()]))
			->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::FENCE_GATE()]))
			->addComponent(new TransformationBlockComponent(
				rotation: match ($dir) {
					StateValues::MC_CARDINAL_DIRECTION_NORTH => new Vector3(0, 0, 0),
					StateValues::MC_CARDINAL_DIRECTION_SOUTH => new Vector3(0, 180, 0),
					StateValues::MC_CARDINAL_DIRECTION_EAST => new Vector3(0, 270, 0),
					StateValues::MC_CARDINAL_DIRECTION_WEST => new Vector3(0, 90, 0),
					default => throw new RuntimeException("Invalid direction")
				},
				translation: new Vector3(0, $inWall ? -0.187 : 0, 0)
			));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\math\Vector3;

class GlassPaneBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$n = $states[StateNames::MC_CONNECTION_NORTH];
		$s = $states[StateNames::MC_CONNECTION_SOUTH];
		$w = $states[StateNames::MC_CONNECTION_WEST];
		$e = $states[StateNames::MC_CONNECTION_EAST];

		$minX = $e ? -8 : -1;
		$maxX = $w ? 8 : 1;

		$minZ = $n ? -8 : -1;
		$maxZ = $s ? 8 : 1;

		$selectionBox = new BoxCollision(
			new Vector3($minX, 0, $minZ),
			new Vector3($maxX - $minX, 16, $maxZ - $minZ)
		);

		$collisions = [];

		if ($n || $s) {
			$collisions[] = new BoxCollision(new Vector3(-1, 0, $minZ), new Vector3(2, 16, $maxZ - $minZ));
		}
		if ($w || $e) {
			$collisions[] = new BoxCollision(new Vector3($minX, 0, -1), new Vector3($maxX - $minX, 16, 2));
		}
		if (!$n && !$s && !$w && !$e) {
			$collisions[] = new BoxCollision(new Vector3(-1, 0, -1), new Vector3(2, 16, 2));
		}

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_NORTH, $n),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_SOUTH, $s),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_WEST, $w),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_EAST, $e)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, $collisions))
			->addComponent(new SelectionBoxBlockComponent(true, [$selectionBox]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\MaterialInstancesBlockComponent;
use Nexly\Blocks\Components\Types\Material;
use Nexly\Blocks\Components\Types\RenderMethod;
use Nexly\Blocks\Components\Types\Target;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;

class FarmlandBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$moisture = $states[StateNames::MOISTURIZED_AMOUNT];
		$isWet = $moisture > 0;

		$expr = MolangHelper::stateEquals(StateNames::MOISTURIZED_AMOUNT, $moisture);
		return Permutation::create($expr)
			->addComponent(new MaterialInstancesBlockComponent([
				new Material(
					texture: $builder->getName() . ($isWet ? "_wet" : "_dry"),
					target: Target::UP,
					renderMethod: RenderMethod::ALPHA_TEST_SINGLE_SIDED
				)
			]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\GeometryBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;

class SlabBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$half = $states[StateNames::MC_VERTICAL_HALF];
		$permutation = Permutation::create(MolangHelper::stateEquals(StateNames::MC_VERTICAL_HALF, $half));

		if ($half === "double") {
			$permutation->addComponent(new GeometryBlockComponent());
			$permutation->addComponent(new CollisionBoxBlockComponent(true));
			$permutation->addComponent(new SelectionBoxBlockComponent(true));
		} else {
			$permutation->addComponent(new CollisionBoxBlockComponent(true, [BoxCollision::SLAB()]));
			$permutation->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::SLAB()]));

			$yTranslation = $half === StateValues::MC_VERTICAL_HALF_TOP ? 0.25 : -0.25;
			$permutation->addComponent(new TransformationBlockComponent(translation: new Vector3(0, $yTranslation, 0)));
		}

		return $permutation;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\math\Vector3;

class FenceBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$n = $states[StateNames::MC_CONNECTION_NORTH];
		$s = $states[StateNames::MC_CONNECTION_SOUTH];
		$w = $states[StateNames::MC_CONNECTION_WEST];
		$e = $states[StateNames::MC_CONNECTION_EAST];

		$minX = $e ? -8 : -2;
		$maxX = $w ? 8 : 2;

		$minZ = $n ? -8 : -2;
		$maxZ = $s ? 8 : 2;

		$selectionBox = new BoxCollision(
			new Vector3($minX, 0, $minZ),
			new Vector3($maxX - $minX, 16, $maxZ - $minZ)
		);

		$collisions = [];

		if ($n || $s) {
			$collisions[] = new BoxCollision(
				new Vector3(-2, 0, $minZ),
				new Vector3(4, 24, $maxZ - $minZ)
			);
		}
		if ($w || $e) {
			$collisions[] = new BoxCollision(
				new Vector3($minX, 0, -2),
				new Vector3($maxX - $minX, 24, 4)
			);
		}

		if (!$n && !$s && !$w && !$e) {
			$collisions[] = new BoxCollision(new Vector3(-2, 0, -2), new Vector3(4, 24, 4));
		}

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_NORTH, $n),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_SOUTH, $s),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_WEST, $w),
			MolangHelper::stateEquals(StateNames::MC_CONNECTION_EAST, $e)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, $collisions))
			->addComponent(new SelectionBoxBlockComponent(true, [$selectionBox]));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\math\Vector3;

class HeadBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::FACING_DIRECTION];
		$rot = $states[StateNames::ROTATION];

		$translation = match ($dir) {
			2 => new Vector3(0, 0.25, 0.24),  // North
			3 => new Vector3(0, 0.25, -0.24), // South
			4 => new Vector3(0.24, 0.25, 0),  // West
			5 => new Vector3(-0.24, 0.25, 0), // East
			default => new Vector3(0, 0, 0)   // UP (Sol)
		};

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::FACING_DIRECTION, $dir),
			MolangHelper::stateEquals(StateNames::ROTATION, $rot)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, [BoxCollision::MOBHEAD()]))
			->addComponent(new SelectionBoxBlockComponent(true, [BoxCollision::MOBHEAD()]))
			->addComponent(new TransformationBlockComponent(translation: $translation));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\CollisionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;
use RuntimeException;

class StairBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$upsideDown = $states[StateNames::UPSIDE_DOWN_BIT];
		$direction = $states[StateNames::DIRECTION];
		$corner = $states[StateNames::MC_CORNER];

		$collisions = [new BoxCollision(new Vector3(-8, 0, -8), new Vector3(16, 8, 16))];

		$effOuterLeft = $upsideDown ? ($corner === StateValues::MC_CORNER_OUTER_RIGHT) : ($corner === StateValues::MC_CORNER_OUTER_LEFT);
		$effOuterRight = $upsideDown ? ($corner === StateValues::MC_CORNER_OUTER_LEFT) : ($corner === StateValues::MC_CORNER_OUTER_RIGHT);
		$effInnerLeft = $upsideDown ? ($corner === StateValues::MC_CORNER_INNER_RIGHT) : ($corner === StateValues::MC_CORNER_INNER_LEFT);
		$effInnerRight = $upsideDown ? ($corner === StateValues::MC_CORNER_INNER_LEFT) : ($corner === StateValues::MC_CORNER_INNER_RIGHT);

		if (!$effOuterLeft) {
			$collisions[] = new BoxCollision(new Vector3(-8, 8, -8), new Vector3(8, 8, 8)); // top_nw
		}
		if (!$effOuterRight) {
			$collisions[] = new BoxCollision(new Vector3(0, 8, -8), new Vector3(8, 8, 8)); // top_ne
		}
		if ($effInnerRight) {
			$collisions[] = new BoxCollision(new Vector3(-8, 8, 0), new Vector3(8, 8, 8)); // top_sw
		}
		if ($effInnerLeft) {
			$collisions[] = new BoxCollision(new Vector3(0, 8, 0), new Vector3(8, 8, 8)); // top_se
		}

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::UPSIDE_DOWN_BIT, $upsideDown),
			MolangHelper::stateEquals(StateNames::DIRECTION, $direction),
			MolangHelper::stateEquals(StateNames::MC_CORNER, $corner)
		);
		return Permutation::create($expr)
			->addComponent(new CollisionBoxBlockComponent(true, $collisions))
			->addComponent(new TransformationBlockComponent(
				rotation: new Vector3($upsideDown ? 180 : 0, match ($direction) {
					!$upsideDown ? 0 : 1 => 270,    // East
					!$upsideDown ? 1 : 0 => 90,     // West
					!$upsideDown ? 2 : 3 => 180,    // South
					!$upsideDown ? 3 : 2 => 0,      // North
					default => throw new RuntimeException("Invalid direction"),
				}, 0),
			));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\math\Vector3;
use RuntimeException;

class TrapdoorBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::DIRECTION];
		$top = $states[StateNames::UPSIDE_DOWN_BIT];
		$open = $states[StateNames::OPEN_BIT];

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::DIRECTION, $dir),
			MolangHelper::stateEquals(StateNames::OPEN_BIT, $open),
			MolangHelper::stateEquals(StateNames::UPSIDE_DOWN_BIT, $top)
		);
		return Permutation::create($expr)
			->addComponent(new TransformationBlockComponent(rotation: match (true) {
				!$top && !$open && $dir == 3 => new Vector3(0, 0, 0),      // North
				!$top && !$open && $dir == 2 => new Vector3(0, 180, 0),    // South
				!$top && !$open && $dir == 1 => new Vector3(0, 90, 0),     // West
				!$top && !$open && $dir == 0 => new Vector3(0, -90, 0),    // East

				!$top && $open && $dir == 3 => new Vector3(90, 180, 0),    // North
				!$top && $open && $dir == 2 => new Vector3(90, 0, 0),      // South
				!$top && $open && $dir == 1 => new Vector3(0, -90, 90),    // West
				!$top && $open && $dir == 0 => new Vector3(180, 90, 90),   // East

				$top && !$open && $dir == 3 => new Vector3(0, 0, 180),     // North
				$top && !$open && $dir == 2 => new Vector3(180, 0, 0),     // South
				$top && !$open && $dir == 1 => new Vector3(180, -90, 0),   // West
				$top && !$open && $dir == 0 => new Vector3(180, 90, 0),    // East

				$top && $open && $dir == 3 => new Vector3(90, 180, 0),     // North
				$top && $open && $dir == 2 => new Vector3(90, 0, 180),     // South
				$top && $open && $dir == 1 => new Vector3(0, 90, 90),      // West
				$top && $open && $dir == 0 => new Vector3(180, -90, 90),   // East

				default => throw new RuntimeException("Invalid direction state")
			}));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks\Mappers;

use Nexly\Blocks\BlockBuilder as Builder;
use Nexly\Blocks\Components\SelectionBoxBlockComponent;
use Nexly\Blocks\Components\TransformationBlockComponent;
use Nexly\Blocks\Components\Types\BoxCollision;
use Nexly\Blocks\Permutations\Permutation;
use Nexly\Helpers\MolangHelper;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\math\Vector3;
use RuntimeException;
use function in_array;

class LeverBlockMapper extends BlockMapper
{
	public function map(Builder $builder, Block $block, array $states) : Permutation
	{
		$dir = $states[StateNames::LEVER_DIRECTION];
		$open = $states[StateNames::OPEN_BIT];

		$isVertical = in_array($dir, [
			StateValues::LEVER_DIRECTION_UP_NORTH_SOUTH,
			StateValues::LEVER_DIRECTION_UP_EAST_WEST,
			StateValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH,
			StateValues::LEVER_DIRECTION_DOWN_EAST_WEST
		], true);

		$selectionBox = $isVertical
			? new BoxCollision(new Vector3(-4.0, 0.0, -4.0), new Vector3(8.0, 10.0, 8.0))
			: new BoxCollision(new Vector3(-3.0, 0.0, -4.0), new Vector3(6.0, 6.0, 8.0));

		$rotation = match ($dir) {
			StateValues::LEVER_DIRECTION_UP_NORTH_SOUTH => new Vector3(0, 0, 0),
			StateValues::LEVER_DIRECTION_UP_EAST_WEST => new Vector3(0, 90, 0),
			StateValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH => new Vector3(180, 0, 0),
			StateValues::LEVER_DIRECTION_DOWN_EAST_WEST => new Vector3(180, 90, 0),
			StateValues::LEVER_DIRECTION_NORTH => new Vector3(90, 180, 0),
			StateValues::LEVER_DIRECTION_SOUTH => new Vector3(90, 0, 0),
			StateValues::LEVER_DIRECTION_EAST => new Vector3(90, 90, 0),
			StateValues::LEVER_DIRECTION_WEST => new Vector3(90, 270, 0),
			default => throw new RuntimeException("Invalid lever direction")
		};

		$expr = MolangHelper::and(
			MolangHelper::stateEquals(StateNames::LEVER_DIRECTION, $dir),
			MolangHelper::stateEquals(StateNames::OPEN_BIT, $open)
		);
		return Permutation::create($expr)
			->addComponent(new SelectionBoxBlockComponent(true, [$selectionBox]))
			->addComponent(new TransformationBlockComponent(rotation: $rotation));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Blocks;

/**
 * @internal
 * @deprecated
 */
class AsyncInitialization
{
	protected static array $blocks = [];

	/**
	 * Adds an asynchronous block definition.
	 */
	public static function addAsyncBlock(string $stringId, array $data) : void
	{
		self::$blocks[$stringId] = $data;
	}

	public static function getBlocks() : array
	{
		return self::$blocks;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Listeners;

use Nexly\Blocks\Vanilla\NexlyLadder;
use pocketmine\block\Ladder;
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\handler\InGamePacketHandler;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\types\PlayerAuthInputFlags;
use pocketmine\player\Player;

class LadderClimbingListener implements Listener
{
	/**
	 * Called when a player joins the server to initialize climbing ability.
	 */
	public function onLogin(PlayerJoinEvent $ev) : void
	{
		$ev->getPlayer()->setCanClimb(false); // Disable climbing by default
	}

	public function onMove(DataPacketReceiveEvent $ev) : void
	{
		$origin = $ev->getOrigin();
		$packet = $ev->getPacket();

		$handler = $origin->getHandler();
		if (!$handler instanceof InGamePacketHandler) {
			return; // Not an in-game packet handler
		}

		if (!$packet instanceof PlayerAuthInputPacket) {
			return; // Not a PlayerAuthInputPacket
		}

		$player = $origin->getPlayer();
		if ($player == null || !$player->isConnected()) {
			return; // Player is not connected
		}

		$now = $packet->getPosition()->subtract(0, $player->getEyeHeight(), 0);
		if ($player->getPosition()->distance($now) > 2) {
			return; // Player moved too far
		}

		$block = $player->getWorld()->getBlock($now);
		if (!$block instanceof Ladder) {
			if ($player->canClimbWalls()) {
				$player->setCanClimbWalls(false);
			}
			return;
		}

		if (!$player->canClimbWalls()) {
			$player->setCanClimbWalls();
		}

		if ($player->isSneaking()) {
			$player->setMotion(Vector3::zero());
			return; // Stop climbing when sneaking
		}

		if (!$packet->getInputFlags()->get(PlayerAuthInputFlags::HORIZONTAL_COLLISION)) {
			return; // Not colliding with a wall
		}

		if ($block instanceof NexlyLadder) {
			$speed = $block->getClimbSpeed();

			$upBlock = $block->getSide(Facing::UP);
			if (!$upBlock instanceof Ladder) {
				$speed /= 1.5;
			}

			$this->addVelocity($player, $now, $speed);
		}
	}

	/**
	 * Applies climbing velocity to the player if they are on a ladder.
	 */
	public function addVelocity(Player $player, Vector3 $now, float $climbSpeed) : void
	{
		if ($climbSpeed <= 0) {
			return; // No climbing speed defined
		}

		if (!$player->isConnected()) {
			return;
		}

		$pos = $player->getPosition();
		if ($pos->y >= $now->y) {
			return; // Player is moving downwards
		}

		$player->setMotion(new Vector3(0, $climbSpeed, 0));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Listeners;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerQuitEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\handler\InGamePacketHandler;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\network\mcpe\protocol\types\PlayerBlockAction;
use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\player\SurvivalBlockBreakHandler;
use WeakMap;

use function array_push;
use function count;
use function in_array;

/**
 * TODO: PMMP Implement this ?
 *
 * @deprecated
 */
class BlockBreakingListener implements Listener
{
	protected const MAX_BLOCK_ACTIONS = 100;
	protected const MAX_DIST_SQ = 10_000;

	/** @var WeakMap<Player, SurvivalBlockBreakHandler|null> */
	protected WeakMap $breakHandlers;

	/** RÃ©flexion mise en cache */
	protected static ?\ReflectionProperty $rpPlayerBlockBreakHandler = null;
	protected static ?\ReflectionProperty $rpHandlerLastBlockAttacked = null;
	protected static ?\ReflectionProperty $rpAuthInputBlockActions = null;

	public function __construct()
	{
		$this->breakHandlers = new WeakMap();
	}

	/**
	 * Handle PlayerQuitEvent to clean up any associated block break handlers.
	 */
	public function onQuit(PlayerQuitEvent $ev) : void
	{
		$p = $ev->getPlayer();
		if ($this->breakHandlers->offsetExists($p)) {
			$this->breakHandlers->offsetUnset($p);
		}
	}

	/**
	 * Handle incoming DataPacketReceiveEvent to process PlayerAuthInputPacket and PlayerActionPacket.
	 */
	public function onInbound(DataPacketReceiveEvent $ev) : void
	{
		$origin = $ev->getOrigin();
		$handler = $origin->getHandler();
		if (!$handler instanceof InGamePacketHandler) {
			return;
		}

		$player = $origin->getPlayer();
		if ($player === null) {
			return;
		}

		$pk = $ev->getPacket();

		if ($pk instanceof PlayerAuthInputPacket) {
			$this->handleAuthInput($player, $handler, $pk);
			return;
		}

		if ($pk instanceof PlayerActionPacket) {
			$this->handleAction(
				$player,
				$handler,
				$pk->action,
				$pk->blockPosition ?? new BlockPosition(0, 0, 0),
				$pk->face ?? Facing::DOWN
			);
		}
	}

	/**
	 * Handle a PlayerAuthInputPacket, processing its block actions and updating block break progress.
	 */
	protected function handleAuthInput(Player $player, InGamePacketHandler $handler, PlayerAuthInputPacket $pk) : void
	{
		$actions = $pk->getBlockActions();
		if ($actions !== null) {
			if (count($actions) > self::MAX_BLOCK_ACTIONS) {
				throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
			}

			foreach ($actions as $action) {
				if ($action instanceof PlayerBlockAction) {
					$this->handleAction($player, $handler, $action->getActionType(), $action->getBlockPosition(), $action->getFace());
				}
			}
		}

		if ($this->breakHandlers->offsetExists($player)) {
			/** @var SurvivalBlockBreakHandler|null $bh */
			$bh = $this->breakHandlers->offsetGet($player);
			if ($bh !== null) {
				$bh->update();
				if ($bh->getBreakProgress() >= 1) {
					$player->breakBlock($bh->getBlockPos());
					$this->breakHandlers->offsetUnset($player);
				}
			}
		}

		$this->clearAuthInputBlockActions($pk);
	}

	/**
	 * Handle a block action from a PlayerActionPacket or PlayerAuthInputPacket.
	 */
	protected function handleAction(
		Player $player,
		InGamePacketHandler $handler,
		int $action,
		BlockPosition $bp,
		int $face
	) : void {
		$pos = self::v($bp);
		$session = $player->getNetworkSession();

		$session->getLogger()->debug("PlayerAction $action on $pos (face: $face)");
		$last = $this->getLastBlockAttacked($handler);

		switch ($action) {
			case PlayerAction::START_BREAK:
			case PlayerAction::CONTINUE_DESTROY_BLOCK: {
				self::validateFacing($face);

				if ($last !== null && $bp->equals($last)) {
					$session->getLogger()->debug("Ignoring action $action on $pos (already destroying this block)");
					$this->syncBlocksNearby($player, $pos, $face);
					break;
				}

				if (!$player->attackBlock($pos, $face)) {
					$this->syncBlocksNearby($player, $pos, $face);
				} else {
					$bh = $this->getPlayerBlockBreakHandler($player);
					if ($bh !== null) {
						$this->breakHandlers->offsetSet($player, clone $bh);
					}
				}

				$this->setLastBlockAttacked($handler, $bp);
				break;
			}

			case PlayerAction::ABORT_BREAK:
			case PlayerAction::STOP_BREAK: {
				$player->stopBreakBlock($pos);
				$this->setLastBlockAttacked($handler, null);
				if ($this->breakHandlers->offsetExists($player)) {
					$this->breakHandlers->offsetUnset($player);
				}
				break;
			}

			case PlayerAction::CRACK_BREAK: {
				self::validateFacing($face);
				$player->continueBreakBlock($pos, $face);
				$this->setLastBlockAttacked($handler, $bp);
				break;
			}

			case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK: {
				if (!$player->isCreative()) {
					$session->getLogger()->debug("Ignoring action $action on $pos (not creative)");
					$this->syncBlocksNearby($player, $pos, $face);
					break;
				}
				if (!$player->breakBlock($pos)) {
					$this->syncBlocksNearby($player, $pos, $face);
				}
				break;
			}

			case PlayerAction::PREDICT_DESTROY_BLOCK: {
				if ($player->isCreative()) {
					$session->getLogger()->debug("Ignoring action $action on $pos (creative)");
					break;
				}

				if ($last === null) {
					$session->getLogger()->debug("Ignoring action $action on $pos (no current block)");
					$this->syncBlocksNearby($player, $pos, $face);
					break;
				}

				if ($pos->distanceSquared($player->getLocation()) > self::MAX_DIST_SQ) {
					$session->getLogger()->debug("Ignoring action $action on $pos (too far)");
					break;
				}

				$target = $player->getWorld()->getBlock($pos);
				$hasBH = $this->breakHandlers->offsetExists($player);
				/** @var SurvivalBlockBreakHandler|null $bh */
				$bh = $hasBH ? $this->breakHandlers->offsetGet($player) : null;

				if (($bh === null) && !$target->getBreakInfo()->breaksInstantly()) {
					$session->getLogger()->debug("Ignoring action $action on $pos (no BlockBreakHandler)");
					$this->syncBlocksNearby($player, $pos, $face);
					break;
				}

				if (($bh !== null) && !$target->getBreakInfo()->breaksInstantly()) {
					$session->getLogger()->debug("Predict on $pos with progress=" . $bh->getBreakProgress());
					$bh->update();
					if ($bh->getBreakProgress() < 1) {
						$session->getLogger()->debug("Ignoring predict (progress < 1)");
						$this->syncBlocksNearby($player, $pos, $face);
						break;
					}
				}

				if (!$player->breakBlock($pos)) {
					$this->syncBlocksNearby($player, $pos, $face);
					break;
				}

				$this->setLastBlockAttacked($handler, null);
				break;
			}
		}

		$player->setUsingItem(false);
	}

	/**
	 * Convert a BlockPosition to a Vector3.
	 */
	protected static function v(BlockPosition $bp) : Vector3
	{
		return new Vector3($bp->getX(), $bp->getY(), $bp->getZ());
	}

	/**
	 * Clear the blockActions property of the PlayerAuthInputPacket via reflection.
	 */
	protected function clearAuthInputBlockActions(PlayerAuthInputPacket $pk) : void
	{
		if (self::$rpAuthInputBlockActions === null) {
			$rc = new \ReflectionClass($pk);
			if (!$rc->hasProperty('blockActions')) {
				return;
			}
			self::$rpAuthInputBlockActions = $rc->getProperty('blockActions');
		}
		// pas de setAccessible (dÃ©prÃ©ciÃ©). Fonctionne si la prop nâ€™est pas privÃ©e.
		self::$rpAuthInputBlockActions->setValue($pk, null);
	}

	/**
	 * Get the blockBreakHandler property of the Player via reflection.
	 */
	protected function getPlayerBlockBreakHandler(Player $player) : ?SurvivalBlockBreakHandler
	{
		if (self::$rpPlayerBlockBreakHandler === null) {
			$rc = new \ReflectionClass($player);
			if (!$rc->hasProperty('blockBreakHandler')) {
				return null;
			}
			self::$rpPlayerBlockBreakHandler = $rc->getProperty('blockBreakHandler');
		}
		/** @var SurvivalBlockBreakHandler|null $bh */
		$bh = self::$rpPlayerBlockBreakHandler->getValue($player);
		return $bh;
	}

	/**
	 * Get the lastBlockAttacked property of the InGamePacketHandler via reflection.
	 */
	protected function getLastBlockAttacked(InGamePacketHandler $handler) : ?BlockPosition
	{
		if (self::$rpHandlerLastBlockAttacked === null) {
			$rc = new \ReflectionClass($handler);
			if (!$rc->hasProperty('lastBlockAttacked')) {
				return null;
			}
			self::$rpHandlerLastBlockAttacked = $rc->getProperty('lastBlockAttacked');
		}
		/** @var BlockPosition|null $bp */
		$bp = self::$rpHandlerLastBlockAttacked->getValue($handler);
		return $bp;
	}

	/**
	 * Set the lastBlockAttacked property of the InGamePacketHandler via reflection.
	 */
	protected function setLastBlockAttacked(InGamePacketHandler $handler, ?BlockPosition $bp) : void
	{
		if (self::$rpHandlerLastBlockAttacked === null) {
			$rc = new \ReflectionClass($handler);
			if (!$rc->hasProperty('lastBlockAttacked')) {
				return;
			}
			self::$rpHandlerLastBlockAttacked = $rc->getProperty('lastBlockAttacked');
		}
		self::$rpHandlerLastBlockAttacked->setValue($handler, $bp);
	}

	/**
	 * Sync the specified block and its adjacent blocks to the player if within range.
	 */
	protected function syncBlocksNearby(Player $player, Vector3 $blockPos, ?int $face) : void
	{
		if ($blockPos->distanceSquared($player->getLocation()) >= self::MAX_DIST_SQ) {
			return;
		}

		$blocks = $blockPos->sidesArray();
		if ($face !== null) {
			$sidePos = $blockPos->getSide($face);
			array_push($blocks, ...$sidePos->sidesArray());
		} else {
			$blocks[] = $blockPos;
		}

		foreach ($player->getWorld()->createBlockUpdatePackets($blocks) as $packet) {
			$player->getNetworkSession()->sendDataPacket($packet);
		}
	}

	/**
	 * Validate that the facing value is one of the valid Facing constants.
	 *
	 * @throws PacketHandlingException
	 */
	protected static function validateFacing(int $facing) : void
	{
		if (!in_array($facing, Facing::ALL, true)) {
			throw new PacketHandlingException("Invalid facing value $facing");
		}
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items;

use Nexly\Items\Components\DataDriven\DataDrivenItemBuilder;
use Nexly\Items\Components\DataDriven\DataDrivenItemComponent;
use Nexly\Items\Components\DataDriven\Property\PropertyItemComponent;
use Nexly\Items\Components\Legacy\LegacyItemBuilder;
use Nexly\Items\Components\Legacy\LegacyItemComponent;
use Nexly\Items\Creative\CreativeInfo;
use pocketmine\item\Item;

class NexlyItems
{
	/**
	 * Register a custom item with a string identifier.
	 */
	public static function register(string $stringId, Item $item, CreativeInfo $creativeInfo = null) : void
	{
		$version = ItemVersion::fromItem($item);
		if ($version->equals(ItemVersion::LEGACY)) {
			self::registerLegacy($stringId, $item, $creativeInfo);
		} else {
			self::registerDataDriven($stringId, $item, $creativeInfo);
		}
	}

	/**
	 * Register a custom item with a string identifier.
	 */
	public static function registerLegacy(string $stringId, Item $item, CreativeInfo $creativeInfo = null, bool $autoload = true) : void
	{
		$version = ItemVersion::fromItem($item);
		if ($version !== ItemVersion::LEGACY) {
			throw new \InvalidArgumentException("Item must be a legacy item.");
		}

		$builder = LegacyItemBuilder::create();
		$builder->setStringId($stringId);
		$builder->setNumericId($item->getTypeId());
		$builder->setItem($item);
		$builder->setCreativeInfo($creativeInfo);

		if ($autoload) {
			$builder->loadFromItems();
		}

		$reflection = new \ReflectionClass($item);
		$attributes = $reflection->getAttributes();

		foreach ($attributes as $attribute) {
			$instance = $attribute->newInstance();
			if ($instance instanceof LegacyItemComponent) {
				$builder->addComponent($instance);
			}
		}

		$builder->register();
	}

	/**
	 * Register a custom item with a string identifier.
	 */
	public static function registerDataDriven(string $stringId, Item $item, CreativeInfo $creativeInfo = null, bool $initProperties = true, bool $initComponents = true) : void
	{
		$version = ItemVersion::fromItem($item);
		if ($version !== ItemVersion::DATA_DRIVEN) {
			throw new \InvalidArgumentException("Item must be a data driven item.");
		}

		$builder = DataDrivenItemBuilder::create();
		$builder->setStringId($stringId);
		$builder->setNumericId($item->getTypeId());
		$builder->setItem($item);
		$builder->setCreativeInfo($creativeInfo);

		if ($initProperties) {
			$builder->loadProperties();
		}
		if ($initComponents) {
			$builder->loadComponents();
		}

		$reflection = new \ReflectionClass($item);
		$attributes = $reflection->getAttributes();

		foreach ($attributes as $attribute) {
			$instance = $attribute->newInstance();
			if ($instance instanceof PropertyItemComponent) {
				$builder->addProperty($instance);
			} elseif ($instance instanceof DataDrivenItemComponent) {
				$builder->addComponent($instance);
			}
		}

		$builder->register();
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Creative;

use Attribute;
use BackedEnum;
use pocketmine\inventory\CreativeCategory;

#[Attribute(Attribute::TARGET_CLASS)]
class CreativeInfo
{
	public function __construct(
		protected ?CreativeCategory $category,
		protected CreativeGroup|BackedEnum|null $group = null,
		protected bool $isHidden = false,
	) {
	}

	public function getCategory() : ?CreativeCategory
	{
		return $this->category;
	}

	public function getGroup() : BackedEnum|CreativeGroup|null
	{
		return $this->group;
	}

	public function isHidden() : bool
	{
		return $this->isHidden;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Creative;

use BackedEnum;
use pocketmine\block\Air;
use pocketmine\block\Anvil;
use pocketmine\block\BaseSign;
use pocketmine\block\Bed;
use pocketmine\block\Block;
use pocketmine\block\Button;
use pocketmine\block\Candle;
use pocketmine\block\Carpet;
use pocketmine\block\Chest;
use pocketmine\block\Clay;
use pocketmine\block\Concrete;
use pocketmine\block\ConcretePowder;
use pocketmine\block\Crops;
use pocketmine\block\Door;
use pocketmine\block\Fence;
use pocketmine\block\FenceGate;
use pocketmine\block\Flower;
use pocketmine\block\Glass;
use pocketmine\block\GlassPane;
use pocketmine\block\GlazedTerracotta;
use pocketmine\block\Leaves;
use pocketmine\block\MobHead;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\Planks;
use pocketmine\block\PressurePlate;
use pocketmine\block\Rail;
use pocketmine\block\Sapling;
use pocketmine\block\ShulkerBox;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\Trapdoor;
use pocketmine\block\Wall;
use pocketmine\block\Wood;
use pocketmine\block\Wool;
use pocketmine\inventory\ArmorInventory;
use pocketmine\inventory\CreativeCategory;
use pocketmine\inventory\CreativeGroup as CreativeGroupPM;
use pocketmine\inventory\CreativeInventory;
use pocketmine\item\Armor;
use pocketmine\item\Arrow;
use pocketmine\item\Axe;
use pocketmine\item\Banner;
use pocketmine\item\Dye;
use pocketmine\item\EnchantedBook;
use pocketmine\item\Food;
use pocketmine\item\GoatHorn;
use pocketmine\item\Hoe;
use pocketmine\item\Item;
use pocketmine\item\ItemBlock;
use pocketmine\item\Pickaxe;
use pocketmine\item\Record;
use pocketmine\item\Shovel;
use pocketmine\item\SplashPotion;
use pocketmine\item\Sword;

class NexlyCreative
{
	/** @var CreativeGroupPM[] */
	protected static array $groups = [];
	/** @var CreativeCategory[]  */
	protected static array $groupToCategory = [];
	/** @var CreativeGroup|\StringBackedEnum[]  */
	protected static array $categoryToGroups = [];

	/**
	 * Load existing creative groups from the creative inventory.
	 */
	protected static function loadGroups() : void
	{
		if (!empty(self::$categoryToGroups)) {
			return;
		} // Already loaded
		if (!empty(self::$groupToCategory)) {
			return;
		} // Already loaded

		foreach (CreativeInventory::getInstance()->getAllEntries() as $entry) {
			$category = $entry->getCategory();
			$group = $entry->getGroup();
			if ($group === null) {
				continue;
			}

			self::$groups[$group->getName()?->getText() ?? $group->getName()] = $group;
			self::$categoryToGroups[$category->name] = $entry;
			self::$groupToCategory[$group->getName()?->getText()] = $category;
		}
	}

	/**
	 * Add an item to the creative inventory under the specified category and group.
	 */
	public static function add(Item $item, ?CreativeCategory $category = null, CreativeGroup|BackedEnum|null $group = null) : void
	{
		self::loadGroups(); // Ensure groups are loaded

		if ($category === null && $group !== null) {
			$category = self::$groupToCategory[$group->getValue()];
		}
		if ($category === null) {
			$category = $item instanceof ItemBlock ? CreativeCategory::CONSTRUCTION : CreativeCategory::ITEMS;
		}

		$pm = null;
		if ($group !== null) {
			$pm = self::$groups[$group->value] ??= new CreativeGroupPM($group->value, $item);
			self::$categoryToGroups[$category->name] = $pm;
			self::$groupToCategory[$group->value] = $category;
		}

		CreativeInventory::getInstance()->add($item, $category, $pm);
	}

	/**
	 * @internal
	 * @deprecated
	 */
	public static function detectCreativeInfoFrom(Item $item) : ?CreativeInfo
	{
		return match (true) {
			$item instanceof Sword => new CreativeInfo(null, CreativeGroup::GROUP_SWORD),
			$item instanceof Pickaxe => new CreativeInfo(null, CreativeGroup::GROUP_PICKAXE),
			$item instanceof Axe => new CreativeInfo(null, CreativeGroup::GROUP_AXE),
			$item instanceof Shovel => new CreativeInfo(null, CreativeGroup::GROUP_SHOVEL),
			$item instanceof Hoe => new CreativeInfo(null, CreativeGroup::GROUP_HOE),
			$item instanceof Armor => match($item->getArmorSlot()) {
				ArmorInventory::SLOT_HEAD => new CreativeInfo(null, CreativeGroup::GROUP_HELMET),
				ArmorInventory::SLOT_CHEST => new CreativeInfo(null, CreativeGroup::GROUP_CHESTPLATE),
				ArmorInventory::SLOT_LEGS => new CreativeInfo(null, CreativeGroup::GROUP_LEGGINGS),
				ArmorInventory::SLOT_FEET => new CreativeInfo(null, CreativeGroup::GROUP_BOOTS),
				default => null,
			},
			$item instanceof Food && $item->getBlock() instanceof Crops => new CreativeInfo(null, CreativeGroup::GROUP_CROP),
			$item instanceof Food => new CreativeInfo(null, CreativeGroup::GROUP_MISC_FOOD),
			$item instanceof SplashPotion => new CreativeInfo(null, CreativeGroup::GROUP_SLASH_POTION),
			$item instanceof EnchantedBook => new CreativeInfo(null, CreativeGroup::GROUP_ENCHANTED_BOOK),
			$item instanceof Arrow => new CreativeInfo(null, CreativeGroup::GROUP_ARROW),
			$item instanceof Dye => new CreativeInfo(null, CreativeGroup::GROUP_DYE),
			$item instanceof Record => new CreativeInfo(null, CreativeGroup::GROUP_RECORD),
			$item instanceof GoatHorn => new CreativeInfo(null, CreativeGroup::GROUP_GOAT_HORN),
			$item instanceof Banner => new CreativeInfo(null, CreativeGroup::GROUP_BANNER),
			//$item instanceof SpawnEgg => new CreativeInfo(null, CreativeGroup::GROUP_MOB_EGG),
			!$item->getBlock() instanceof Air => self::detectCreativeInfoFromBlock($item->getBlock()),
			default => new CreativeInfo(CreativeCategory::ITEMS),
		};
	}

	public static function detectCreativeInfoFromBlock(Block $block) : ?CreativeInfo
	{
		return match (true) {
			$block instanceof Crops => new CreativeInfo(null, CreativeGroup::GROUP_SEED),
			$block instanceof Wood => new CreativeInfo(null, CreativeGroup::GROUP_LOG),
			$block instanceof Planks => new CreativeInfo(null, CreativeGroup::GROUP_PLANKS),
			$block instanceof Stair => new CreativeInfo(null, CreativeGroup::GROUP_STAIRS),
			$block instanceof Slab => new CreativeInfo(null, CreativeGroup::GROUP_SLAB),
			$block instanceof Wall => new CreativeInfo(null, CreativeGroup::GROUP_WALLS),
			$block instanceof Fence => new CreativeInfo(null, CreativeGroup::GROUP_FENCE),
			$block instanceof FenceGate => new CreativeInfo(null, CreativeGroup::GROUP_FENCE_GATE),
			$block instanceof PressurePlate => new CreativeInfo(null, CreativeGroup::GROUP_PRESSURE_PLATE),
			$block instanceof Button => new CreativeInfo(null, CreativeGroup::GROUP_BUTTONS),
			$block instanceof Door => new CreativeInfo(null, CreativeGroup::GROUP_DOOR),
			$block instanceof Trapdoor => new CreativeInfo(null, CreativeGroup::GROUP_TRAPDOOR),
			$block instanceof Leaves => new CreativeInfo(null, CreativeGroup::GROUP_LEAVES),
			//$block instanceof WallSign => new CreativeInfo(null, CreativeGroup::GROUP_HANDGING_SIGN),
			$block instanceof BaseSign => new CreativeInfo(null, CreativeGroup::GROUP_SIGN),
			$block instanceof Chest => new CreativeInfo(null, CreativeGroup::GROUP_CHEST),
			$block instanceof Anvil => new CreativeInfo(null, CreativeGroup::GROUP_ANVIL),
			$block instanceof ShulkerBox => new CreativeInfo(null, CreativeGroup::GROUP_SHULKER_BOX),
			$block instanceof Sapling => new CreativeInfo(null, CreativeGroup::GROUP_SAPLING),
			$block instanceof MobHead => new CreativeInfo(null, CreativeGroup::GROUP_SKULL),
			$block instanceof Candle => new CreativeInfo(null, CreativeGroup::GROUP_CANDLES),
			$block instanceof Bed => new CreativeInfo(null, CreativeGroup::GROUP_BED),
			$block instanceof Wool => new CreativeInfo(null, CreativeGroup::GROUP_WOOL),
			$block instanceof Carpet => new CreativeInfo(null, CreativeGroup::GROUP_WOOL_CARPET),
			$block instanceof Clay => new CreativeInfo(null, CreativeGroup::GROUP_STAINED_CLAY),
			$block instanceof Rail => new CreativeInfo(null, CreativeGroup::GROUP_RAIL),
			$block instanceof GlazedTerracotta => new CreativeInfo(null, CreativeGroup::GROUP_GLAZED_TERRACOTTA),
			$block instanceof Flower => new CreativeInfo(null, CreativeGroup::GROUP_FLOWER),
			$block instanceof ConcretePowder => new CreativeInfo(null, CreativeGroup::GROUP_CONCRETE_POWDER),
			$block instanceof Concrete => new CreativeInfo(null, CreativeGroup::GROUP_CONCRETE),
			$block instanceof Glass => new CreativeInfo(null, CreativeGroup::GROUP_GLASS),
			$block instanceof GlassPane => new CreativeInfo(null, CreativeGroup::GROUP_GLASS_PANE),

			$block instanceof NetherWartPlant => new CreativeInfo(CreativeCategory::NATURE, null),
			default => new CreativeInfo(CreativeCategory::CONSTRUCTION),
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Creative;

enum CreativeGroup : string
{
	case GROUP_ANVIL = "itemGroup.name.anvil";
	case GROUP_ARROW = "itemGroup.name.arrow";
	case GROUP_AXE = "itemGroup.name.axe";
	case GROUP_BANNER = "itemGroup.name.banner";
	case GROUP_BANNER_PATTERN = "itemGroup.name.banner_pattern";
	case GROUP_BED = "itemGroup.name.bed";
	case GROUP_BOAT = "itemGroup.name.boat";
	case GROUP_CHEST_BOAT = "itemGroup.name.chestboat";
	case GROUP_BOOTS = "itemGroup.name.boots";
	case GROUP_BUNDLES = "itemGroup.name.bundles";
	case GROUP_BUTTONS = "itemGroup.name.buttons";
	case GROUP_CHALKBOARD = "itemGroup.name.chalkboard";
	case GROUP_CANDLES = "itemGroup.name.candles";
	case GROUP_CHEST = "itemGroup.name.chest";
	case GROUP_CHESTPLATE = "itemGroup.name.chestplate";
	case GROUP_COMPOUNDS = "itemGroup.name.compounds";
	case GROUP_CONCRETE = "itemGroup.name.concrete";
	case GROUP_CONCRETE_POWDER = "itemGroup.name.concretePowder";
	case GROUP_COOKED_FOOD = "itemGroup.name.cookedFood";
	case GROUP_COPPER = "itemGroup.name.copper";
	case GROUP_CORAL = "itemGroup.name.coral";
	case GROUP_CORAL_DECORATIONS = "itemGroup.name.coral_decorations";
	case GROUP_CROP = "itemGroup.name.crop";
	case GROUP_DOOR = "itemGroup.name.door";
	case GROUP_DYE = "itemGroup.name.dye";
	case GROUP_ENCHANTED_BOOK = "itemGroup.name.enchantedBook";
	case GROUP_FENCE = "itemGroup.name.fence";
	case GROUP_FENCE_GATE = "itemGroup.name.fenceGate";
	case GROUP_FIREWORK = "itemGroup.name.firework";
	case GROUP_FIREWORK_STARS = "itemGroup.name.fireworkStars";
	case GROUP_FLOWER = "itemGroup.name.flower";
	case GROUP_GLASS = "itemGroup.name.glass";
	case GROUP_GLASS_PANE = "itemGroup.name.glassPane";
	case GROUP_GLAZED_TERRACOTTA = "itemGroup.name.glazedTerracotta";
	case GROUP_GOAT_HORN = "itemGroup.name.goatHorn";
	case GROUP_GRASS = "itemGroup.name.grass";
	case GROUP_HELMET = "itemGroup.name.helmet";
	case GROUP_HOE = "itemGroup.name.hoe";
	case GROUP_HORSE_ARMOR = "itemGroup.name.horseArmor";
	case GROUP_LEAVES = "itemGroup.name.leaves";
	case GROUP_LEGGINGS = "itemGroup.name.leggings";
	case GROUP_LINGERING_POTION = "itemGroup.name.lingeringPotion";
	case GROUP_LOG = "itemGroup.name.log";
	case GROUP_MINECART = "itemGroup.name.minecart";
	case GROUP_MISC_FOOD = "itemGroup.name.miscFood";
	case GROUP_MOB_EGG = "itemGroup.name.mobEgg";
	case GROUP_MONSTER_STONE_EGG = "itemGroup.name.monsterStoneEgg";
	case GROUP_MUSHROOM = "itemGroup.name.mushroom";
	case GROUP_NETHERWART_BLOCK = "itemGroup.name.netherWartBlock";
	case GROUP_OMINOUS_BOTTLE = "itemGroup.name.ominousBottle";
	case GROUP_ORE = "itemGroup.name.ore";
	case GROUP_PERMISSION = "itemGroup.name.permission";
	case GROUP_PICKAXE = "itemGroup.name.pickaxe";
	case GROUP_PLANKS = "itemGroup.name.planks";
	case GROUP_POTION = "itemGroup.name.potion";
	case GROUP_PRESSURE_PLATE = "itemGroup.name.pressurePlate";
	case GROUP_PRODUCTS = "itemGroup.name.products";
	case GROUP_RAIL = "itemGroup.name.rail";
	case GROUP_RAW_FOOD = "itemGroup.name.rawFood";
	case GROUP_RECORD = "itemGroup.name.record";
	case GROUP_SANDSTONE = "itemGroup.name.sandstone";
	case GROUP_SAPLING = "itemGroup.name.sapling";
	case GROUP_SEED = "itemGroup.name.seed";
	case GROUP_SHOVEL = "itemGroup.name.shovel";
	case GROUP_SHULKER_BOX = "itemGroup.name.shulkerBox";
	case GROUP_SIGN = "itemGroup.name.sign";
	case GROUP_SKULL = "itemGroup.name.skull";
	case GROUP_SLAB = "itemGroup.name.slab";
	case GROUP_SLASH_POTION = "itemGroup.name.splashPotion";
	case GROUP_STAINED_CLAY = "itemGroup.name.stainedClay";
	case GROUP_STAIRS = "itemGroup.name.stairs";
	case GROUP_STONE = "itemGroup.name.stone";
	case GROUP_STONE_BRICK = "itemGroup.name.stoneBrick";
	case GROUP_SWORD = "itemGroup.name.sword";
	case GROUP_TRAPDOOR = "itemGroup.name.trapdoor";
	case GROUP_WALLS = "itemGroup.name.walls";
	case GROUP_WOOD = "itemGroup.name.wood";
	case GROUP_WOOL = "itemGroup.name.wool";
	case GROUP_WOOL_CARPET = "itemGroup.name.woolCarpet";

	/**
	 * Get the name of the enum case.
	 */
	public function getName() : string
	{
		return $this->name;
	}

	/**
	 * Get the value of the enum case.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items;

use Closure;
use Nexly\Items\Creative\CreativeInfo;
use Nexly\Items\Creative\NexlyCreative;
use Nexly\Mappings\ItemMappings;
use Nexly\Recipes\NexlyRecipes;
use Nexly\Recipes\Types\Recipe;
use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\data\bedrock\item\upgrade\LegacyItemIdToStringIdMap;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\StringToItemParser;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\world\format\io\GlobalItemDataHandlers as ItemDataHandlers;
use function count;
use function explode;
use function str_contains;

abstract class ItemBuilder
{
	protected string $stringId;
	protected ?int $numericId = null;
	protected Item $item;
	protected ?Closure $serializer = null;
	protected ?Closure $deserializer = null;
	protected ?CreativeInfo $creativeInfo = null;

	/**
	 * Get the string identifier for the item.
	 */
	public function getStringId() : string
	{
		return $this->stringId;
	}

	/**
	 * @internal
	 */
	public function getName() : string
	{
		$itemName = $this->getStringId();
		if (str_contains($itemName, ":")) {
			[, $itemName] = explode(":", $itemName, 2);
		}

		return $itemName;
	}

	/**
	 * Set the string identifier for the item.
	 *
	 * @return $this
	 */
	public function setStringId(string $stringId) : self
	{
		if (str_contains("minecraft:", $stringId)) {
			throw new \InvalidArgumentException("Custom item string ID cannot contain the 'minecraft:' namespace.");
		}

		$this->stringId = $stringId;
		return $this;
	}

	/**
	 * Get the numeric ID for the item, if set.
	 */
	public function getNumericId() : int
	{
		return $this->numericId ??= ItemTypeIds::newId();
	}

	/**
	 * Set the numeric ID for the item.
	 *
	 * @return $this
	 */
	public function setNumericId(?int $numericId) : self
	{
		$this->numericId = $numericId;
		return $this;
	}

	public function getItem() : Item
	{
		return $this->item;
	}

	/**
	 * Set the item instance or a closure that returns an item instance.
	 *
	 * @return $this
	 */
	public function setItem(Item|Closure $item) : self
	{
		if ($item instanceof Item) {
			$this->item = $item;
		} else {
			$this->item = ($item)($this->getNumericId());
		}
		return $this;
	}

	/**
	 * Get the custom serializer for the item.
	 */
	public function getSerializer() : ?Closure
	{
		return $this->serializer;
	}

	/**
	 * Set a custom serializer for the item.
	 *
	 * @return $this
	 */
	public function setSerializer(?Closure $serializer) : self
	{
		$this->serializer = $serializer;
		return $this;
	}

	/**
	 * Get the custom deserializer for the item.
	 */
	public function getDeserializer() : ?Closure
	{
		return $this->deserializer;
	}

	/**
	 * Set a custom deserializer for the item.
	 *
	 * @return $this
	 */
	public function setDeserializer(?Closure $deserializer) : self
	{
		$this->deserializer = $deserializer;
		return $this;
	}

	/**
	 * Get the CreativeInfo attribute for the item, if set.
	 */
	public function getCreativeInfo() : ?CreativeInfo
	{
		return $this->creativeInfo;
	}

	/**
	 * Set the CreativeInfo attribute for the item.
	 *
	 * @return $this
	 */
	public function setCreativeInfo(?CreativeInfo $creativeInfo) : self
	{
		$this->creativeInfo = $creativeInfo;
		return $this;
	}

	abstract public static function getVersion() : ItemVersion;

	/**
	 * Build the ItemTypeEntry for the item.
	 */
	abstract public function toNBT() : CompoundTag;

	/**
	 * Register the item with the provided string identifier and callbacks.
	 */
	public function register() : void
	{
		if (!isset($this->item)) {
			throw new \InvalidArgumentException("Item closure must be set before registering");
		}

		$item = $this->getItem();
		ItemDataHandlers::getDeserializer()->map($this->getStringId(), $this->getDeserializer() ?? fn () => clone $item);
		ItemDataHandlers::getSerializer()->map($item, $this->getSerializer() ?? fn () => new SavedItemData($this->getStringId()));
		ItemMappings::getInstance()->registerMapping($this, new ItemTypeEntry(
			$this->getStringId(),
			$this->getNumericId(),
			$this->getVersion()->equals(ItemVersion::DATA_DRIVEN),
			$this->getVersion()->getValue(),
			new CacheableNbt($this->toNBT())
		));

		$identifier = $this->getStringId();
		if (str_contains($identifier, ":")) {
			[, $path] = explode(":", $identifier, 2);
			$identifier = $path;
		}

		StringToItemParser::getInstance()->register($identifier, fn () => clone $item);
		LegacyItemIdToStringIdMap::getInstance()->add($identifier, $item->getTypeId());

		$creativeInfo = $this->getCreativeInfo() ?? NexlyCreative::detectCreativeInfoFrom($item);
		$reflection = new \ReflectionClass($item);

		$attributes = $reflection->getAttributes();
		if (count($attributes) > 0) {
			foreach ($attributes as $attribute) {
				$instance = $attribute->newInstance();
				if ($instance instanceof CreativeInfo) {
					$creativeInfo = $instance;
				} elseif ($instance instanceof Recipe) {
					NexlyRecipes::getInstance()->addRecipe(fn () => $attribute->newInstance());
				}
			}
		}

		NexlyCreative::add($item, $creativeInfo?->getCategory(), $creativeInfo?->getGroup());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Nexly\Items\ItemBuilder;
use Nexly\Items\ItemVersion;
use pocketmine\block\BlockTypeTags;
use pocketmine\block\Crops;
use pocketmine\block\Flower;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\Sapling;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\FoodSource;
use pocketmine\item\ConsumableItem;
use pocketmine\item\Durable;
use pocketmine\item\Tool;
use pocketmine\nbt\tag\CompoundTag;

class LegacyItemBuilder extends ItemBuilder
{
	/** @var LegacyItemComponent[] */
	protected array $components = [];

	public static function create() : LegacyItemBuilder
	{
		return new self();
	}

	/**
	 * Get the item version for legacy items.
	 */
	public static function getVersion() : ItemVersion
	{
		return ItemVersion::LEGACY;
	}

	/**
	 * @return LegacyItemComponent[]
	 */
	public function getComponents() : array
	{
		return $this->components;
	}

	/**
	 * Check if a component exists by its name.
	 */
	public function hasComponent(string $name) : bool
	{
		return isset($this->components[$name]);
	}

	/**
	 * Add a LegacyComponent to the builder.
	 *
	 * @return $this
	 */
	public function addComponent(LegacyItemComponent $component) : self
	{
		$this->components[$component::getName()] = $component;
		return $this;
	}

	/**
	 * Remove a component by its name.
	 */
	public function removeComponent(string $name) : void
	{
		unset($this->components[$name]);
	}

	/**
	 * Create a LegacyItemBuilder from an Item instance.
	 */
	public function toNBT() : CompoundTag
	{
		$componentsTag = CompoundTag::create();
		foreach ($this->components as $component) {
			$componentsTag->setTag($component::getName(), $component->toNBT());
		}

		return CompoundTag::create()
			->setTag("components", $componentsTag);
	}

	/**
	 * Load components from the item instance.
	 *
	 * @return $this
	 */
	public function loadFromItems() : self
	{
		$item = $this->getItem();
		$this->addComponent(new MaxStackSizeComponent($item->getMaxStackSize()));
		$this->addComponent(new StackedByDataComponent(false));
		$this->addComponent(new HandEquippedComponent($item instanceof Tool));

		if ($item instanceof Durable) {
			$this->addComponent(new MaxDamageComponent($item->getMaxDurability()));
		}

		if ($item instanceof ConsumableItem) {
			if ($item instanceof FoodSource) {
				$this->addComponent(new FoodComponent(
					$item->getFoodRestore(),
					$item->getFoodRestore() > 0 ? $item->getSaturationRestore() / ($item->getFoodRestore() * 2) : 0.0,
					!$item->requiresHunger(),
					cooldownType: $item->getCooldownTag() ?? $this->getStringId(),
					cooldownTick: $item->getCooldownTicks(),
				));
			} else {
				$this->addComponent(new FoodComponent(
					0.0,
					0.0,
					true,
					cooldownType: $item->getCooldownTag() ?? $this->getStringId(),
					cooldownTick: $item->getCooldownTicks(),
				));
			}

			$this->addComponent(new UseDurationComponent(32));
		}

		$block = $item->getBlock();
		if ($block instanceof Crops) {
			$this->addComponent(SeedComponent::fromBlocks($block, VanillaBlocks::FARMLAND()));
		} elseif ($block instanceof NetherWartPlant) {
			$this->addComponent(SeedComponent::fromBlocks($block, VanillaBlocks::SOUL_SAND()));
		} elseif ($block instanceof Flower || $block instanceof Sapling) {
			$this->addComponent(SeedComponent::fromTags($block, [BlockTypeTags::DIRT, BlockTypeTags::MUD, BlockTypeTags::NYLIUM]));
		}
		return $this;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Nexly\Items\Components\ItemComponent;

abstract class LegacyItemComponent extends ItemComponent
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class CameraComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::CAMERA->getValue();
	}

	public function __construct(
		protected float $blackBarsDuration,
		protected float $blackBarsScreenRatio,
		protected float $shutterDuration,
		protected float $shutterScreenRatio,
		protected float $pictureDuration,
		protected float $slideAwayDuration,
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("black_bars_duration", new FloatTag($this->blackBarsDuration))
			->setTag("black_bars_screen_ratio", new FloatTag($this->blackBarsScreenRatio))
			->setTag("shutter_duration", new FloatTag($this->shutterDuration))
			->setTag("shutter_screen_ratio", new FloatTag($this->shutterScreenRatio))
			->setTag("picture_duration", new FloatTag($this->pictureDuration))
			->setTag("slide_away_duration", new FloatTag($this->slideAwayDuration));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FoilComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::FOIL->getValue();
	}

	public function __construct(
		protected bool $value = true
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MaxDamageComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::MAX_DAMAGE->getValue();
	}

	public function __construct(
		protected int $maxDamage
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : IntTag
	{
		return new IntTag($this->maxDamage);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use Nexly\Items\Components\Legacy\Types\LegacyFace;
use pocketmine\block\Block;
use pocketmine\block\RuntimeBlockStateRegistry;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use function array_map;
use function array_values;
use function str_replace;
use function strtolower;

#[Attribute(Attribute::TARGET_CLASS)]
class SeedComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::SEED->getValue();
	}

	/**
	 * Create a SeedComponent from blocks.
	 */
	public static function fromBlocks(Block $result, Block ...$blocks) : self
	{
		return new self(
			GlobalBlockStateHandlers::getSerializer()->serialize($result->getStateId())->getName(),
			array_map(fn (Block $block) => GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(), $blocks),
		);
	}

	/**
	 * Create a SeedComponent from tags.
	 */
	public static function fromTags(Block $result, array $tags) : self
	{
		$plantAtNames = [];
		$serializer = GlobalBlockStateHandlers::getSerializer();

		foreach (RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state) {
			foreach ($tags as $tag) {
				if ($state->hasTypeTag($tag)) {
					$name = $serializer->serialize($state->getStateId())->getName();
					$plantAtNames[$name] = $name;
					break;
				}
			}
		}

		return new self(
			$serializer->serialize($result->getStateId())->getName(),
			array_values($plantAtNames)
		);
	}

	public function __construct(
		protected string $result,
		protected array $plantAt = [],
		protected LegacyFace $plantFace = LegacyFace::UP,
	) {
		$this->result = str_replace(["minecraft:", "grass_block", "air"], ["", "grass", "light_block"], strtolower($this->result));
		$this->plantAt = array_map(fn (string $block) => str_replace(["minecraft:", "grass_block"], ["", "grass"], strtolower($block)), $this->plantAt);
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("crop_result", new StringTag($this->result))
			->setTag("plant_at_face", new StringTag($this->plantFace->getValue()))
			->setTag("plant_at_any_solid_surface", new ByteTag(empty($this->plantAt)))
			->setTag("plant_at", new ListTag(array_map(fn (string $block) => new StringTag($block), $this->plantAt), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class HandEquippedComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::HAND_EQUIPPED->getValue();
	}

	public function __construct(
		protected bool $value = true
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MaxStackSizeComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::MAX_STACK_SIZE->getValue();
	}

	public function __construct(
		protected int $size
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : IntTag
	{
		return new IntTag($this->size);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use Nexly\Items\Components\Legacy\Types\LegacyEffect;
use Nexly\Items\Components\Legacy\Types\LegacyItemCooldownType;
use Nexly\Items\Components\Legacy\Types\LegacyItemUseActionType;
use pocketmine\item\Item;
use pocketmine\nbt\NBT;
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\network\mcpe\convert\TypeConverter;
use function array_map;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class FoodComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::FOOD->getValue();
	}

	/**
	 * @param float[]        $onUseRange
	 * @param LegacyEffect[] $effects
	 * @param int[]          $removeEffects
	 */
	public function __construct(
		protected int                     $nutrition,
		protected float                   $saturationModifier,
		protected bool                    $canAlwaysEat = false,
		protected LegacyItemCooldownType|\BackedEnum|string  $cooldownType = LegacyItemCooldownType::NONE,
		protected int                     $cooldownTick = 0,
		protected string                           $usingConvertsTo = "air",
		protected LegacyItemUseActionType $useActionType = LegacyItemUseActionType::NONE,
		protected array                   $onUseRange = [],
		protected array                   $effects = [],
		protected array                   $removeEffects = [],
	) {
	}

	/**
	 * Set the item that this food converts to when used.
	 */
	public function setUsingConvertsTo(Item $item) : void
	{
		[$rId] = ($converter = TypeConverter::getInstance())->getItemTranslator()->toNetworkId($item);
		if ($rId == null) {
			throw new \InvalidArgumentException("Item does not have a valid network ID");
		}

		$this->usingConvertsTo = $converter->getItemTypeDictionary()->fromIntId($rId);
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("nutrition", new IntTag($this->nutrition))
			->setTag("saturation_modifier", new FloatTag($this->saturationModifier))
			->setTag("can_always_eat", new ByteTag($this->canAlwaysEat))
			->setTag("cooldown_type", new StringTag(is_string($this->cooldownType) ? $this->cooldownType : $this->cooldownType->value))
			->setTag("cooldown_time", new IntTag($this->cooldownTick))
			->setTag("using_converts_to", CompoundTag::create()->setTag("name", new StringTag($this->usingConvertsTo)))
			->setTag("on_use_action", new IntTag($this->useActionType->getValue()))
			->setTag("on_use_range", new ListTag(array_map(fn (float $value) => new FloatTag($value), $this->onUseRange), NBT::TAG_Float))
			->setTag("effects", new ListTag(array_map(fn (LegacyEffect $effect) => $effect->toNBT(), $this->effects), NBT::TAG_Compound))
			->setTag("remove_effects", new ListTag(array_map(fn (int $value) => new IntTag($value), $this->removeEffects), NBT::TAG_Int));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

enum LegacyComponentIds : string
{
	case MAX_DAMAGE = "minecraft:max_damage";
	case HAND_EQUIPPED = "minecraft:hand_equipped";
	case STACKED_BY_DATA = "minecraft:stacked_by_data";
	case FOIL = "minecraft:foil";
	case USE_DURATION = "minecraft:use_duration";
	case MAX_STACK_SIZE = "minecraft:max_stack_size";
	case FOOD = "minecraft:food";
	case SEED = "minecraft:seed";
	case BLOCK_RENDER = "minecraft:block";
	case CAMERA = "minecraft:camera";

	/**
	 * Returns the name of the component.
	 *
	 * @return string The name of the component.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy\Types;

enum LegacyItemCooldownType : string
{
	case NONE = "none";
	case CHORUS = "chorusfruit";

	/**
	 * Get the string value of the cooldown type.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy\Types;

use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\nbt\tag\CompoundTag;

class LegacyEffect
{
	/**
	 * LegacyEffect constructor.
	 */
	public function __construct(
		protected int    $id,
		protected string $name,
		protected string $descriptionId,
		protected int    $duration = -1,
		protected int    $amplifier = 0,
		protected float $chance = 100.0
	) {
	}

	/**
	 * Create a LegacyEffect from an EffectInstance.
	 */
	public static function from(EffectInstance $effectInstance) : self
	{
		return new self(
			EffectIdMap::getInstance()->toId($effectInstance->getType()),
			$effectInstance->getType()->getName(),
			"",
			$effectInstance->getDuration(),
			$effectInstance->getAmplifier(),
			100.0
		);
	}

	public function getId() : int
	{
		return $this->id;
	}

	public function getName() : string
	{
		return $this->name;
	}

	public function getDescriptionId() : string
	{
		return $this->descriptionId;
	}

	public function getDuration() : int
	{
		return $this->duration;
	}

	public function getAmplifier() : int
	{
		return $this->amplifier;
	}

	public function getChance() : float
	{
		return $this->chance;
	}

	/**
	 * Convert the effect to a CompoundTag for NBT representation.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setInt("id", $this->getId())
			->setString("name", $this->getName())
			->setString("description_id", $this->getDescriptionId())
			->setInt("duration", $this->getDuration())
			->setInt("amplifier", $this->getAmplifier())
			->setFloat("chance", $this->getChance());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy\Types;

enum LegacyFace : string
{
	case UP = "up";
	case DOWN = "down";
	case NORTH = "north";
	case SOUTH = "south";
	case WEST = "west";
	case EAST = "east";

	/**
	 * Get the string value of the enum case.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy\Types;

enum LegacyItemUseActionType : int
{
	case CHORUS_TELEPORT = 0;
	case SUSPICIOUS_STEW = 1;
	case NONE = 2;

	/**
	 * Get the integer value of the use action type.
	 */
	public function getValue() : int
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class StackedByDataComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::HAND_EQUIPPED->getValue();
	}

	public function __construct(
		protected bool $value = true
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class UseDurationComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::USE_DURATION->getValue();
	}

	public function __construct(
		protected int $value
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : IntTag
	{
		return new IntTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\Legacy;

use Attribute;
use pocketmine\block\Block;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;

#[Attribute(Attribute::TARGET_CLASS)]
class BlockRenderComponent extends LegacyItemComponent
{
	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return LegacyComponentIds::BLOCK_RENDER->getValue();
	}

	/**
	 * Create a BlockComponent from a Block instance.
	 */
	public static function from(Block $block) : self
	{
		return new self(
			GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(),
		);
	}

	public function __construct(
		protected string $name,
	) {
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : StringTag
	{
		return new StringTag($this->name);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components;

use pocketmine\nbt\tag\Tag;

abstract class ItemComponent
{
	abstract public static function getName() : string;
	abstract public function toNBT() : Tag;
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_kinetic_weapon_kinetic_effect_conditions?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class KineticWeaponKineticEffectConditionsItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $maxDuration = -1,
		protected float $minRelativeSpeed = 0,
		protected float $minSpeed = 0,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::KINETIC_WEAPON_KINETIC_EFFECT_CONDITIONS->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("max_duration", new IntTag($this->maxDuration))
			->setTag("min_relative_speed", new FloatTag($this->minRelativeSpeed))
			->setTag("min_speed", new FloatTag($this->minSpeed));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\item\Item;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class StorageItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $capacity,
		protected bool $allowNested = true,
		protected array $allowedItems = [],
		protected array $bannedItems = [],
	) {
		if ($this->capacity < 0) {
			throw new \InvalidArgumentException("Capacity must be a non-negative integer.");
		}

		if ($this->capacity > 64) {
			throw new \InvalidArgumentException("Capacity must not exceed 64.");
		}
	}

	/**
	 * Create a StorageItemComponent.
	 *
	 * @param Item[] $allowedItems
	 * @param Item[] $bannedItems
	 */
	public static function from(
		int $capacity,
		bool $allowNested = true,
		array $allowedItems = [],
		array $bannedItems = []
	) : self {

		$processItem = function (Item $item) : string {
			[$rid] = ($converter = TypeConverter::getInstance())->getItemTranslator()->toNetworkId($item);
			return $converter->getItemTypeDictionary()->fromIntId($rid);
		};

		return new self(
			$capacity,
			$allowNested,
			array_map($processItem, $allowedItems),
			array_map($processItem, $bannedItems)
		);
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::STORAGE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("max_slots", new IntTag($this->capacity))
			->setTag("allow_nested_storage_items", new ByteTag($this->allowNested))
			->setTag("allowed_items", new ListTag(array_map(fn (string $item) => new StringTag($item), $this->allowedItems), NBT::TAG_String))
			->setTag("banned_items", new ListTag(array_map(fn (string $item) => new StringTag($item), $this->bannedItems), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FireResistantItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected bool $value = true
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::FIRE_RESISTANT->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new ByteTag($this->value));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemSlot;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class WearableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected ItemSlot $slot,
		protected int $protection
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::WEARABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("slot", new StringTag($this->slot->getValue()))
			->setTag("protection", new IntTag($this->protection));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class CompostableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $chance,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::COMPOSTABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("composting_chance", new FloatTag($this->chance));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class UseModifiersItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $duration,
		protected ?float $movementModifier = null,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::USE_MODIFIERS->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("use_duration", new FloatTag($this->duration));

		if ($this->movementModifier !== null) {
			$nbt->setTag("movement_modifier", new FloatTag($this->movementModifier));
		}

		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class StorageWeightModifierItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $capacity,
	) {
		if ($capacity < 0) {
			throw new \InvalidArgumentException("Capacity must be a non-negative integer.");
		}

		if ($capacity > 64) {
			throw new \InvalidArgumentException("Capacity cannot exceed 64.");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::STORAGE_WEIGHT_MODIFIER->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("weight_in_storage_item", new IntTag($this->capacity));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Nexly\Items\Components\DataDriven\Property\BlockProperty;
use Nexly\Items\Components\DataDriven\Property\CanDestroyInCreative;
use Nexly\Items\Components\DataDriven\Property\DamageProperty;
use Nexly\Items\Components\DataDriven\Property\EnchantableSlotProperty;
use Nexly\Items\Components\DataDriven\Property\EnchantableValueProperty;
use Nexly\Items\Components\DataDriven\Property\HandEquippedProperty;
use Nexly\Items\Components\DataDriven\Property\IconProperty;
use Nexly\Items\Components\DataDriven\Property\LiquidClippedProperty;
use Nexly\Items\Components\DataDriven\Property\MaxStackSizeProperty;
use Nexly\Items\Components\DataDriven\Property\MiningSpeedProperty;
use Nexly\Items\Components\DataDriven\Property\PropertyItemComponent;
use Nexly\Items\Components\DataDriven\Property\ShouldDespawnProperty;
use Nexly\Items\Components\DataDriven\Property\StackedByDataProperty;
use Nexly\Items\Components\DataDriven\Property\UseAnimationProperty;
use Nexly\Items\Components\DataDriven\Property\UseDurationProperty;
use Nexly\Items\Components\DataDriven\Types\ItemBlockVisual;
use Nexly\Items\Components\DataDriven\Types\ItemEnchantSlot;
use Nexly\Items\Components\DataDriven\Types\ItemRepair;
use Nexly\Items\Components\DataDriven\Types\ItemSlot;
use Nexly\Items\ItemBuilder;
use Nexly\Items\ItemVersion;
use pocketmine\block\Air;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\Armor;
use pocketmine\item\Bucket;
use pocketmine\item\Durable;
use pocketmine\item\Dye;
use pocketmine\item\Food;
use pocketmine\item\ProjectileItem;
use pocketmine\item\Record;
use pocketmine\item\SpawnEgg;
use pocketmine\item\Sword;
use pocketmine\item\TieredTool;
use pocketmine\item\Tool;
use pocketmine\item\ToolTier;
use pocketmine\item\VanillaArmorMaterials;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use function explode;
use function is_string;
use function sprintf;
use function str_contains;

class DataDrivenItemBuilder extends ItemBuilder
{
	/** @var PropertyItemComponent[] */
	protected array $properties = [];
	/** @var DataDrivenItemComponent[] */
	protected array $components = [];

	public static function create() : DataDrivenItemBuilder
	{
		return new self();
	}

	/**
	 * Get the item version for data-driven items.
	 */
	public static function getVersion() : ItemVersion
	{
		return ItemVersion::DATA_DRIVEN;
	}

	/**
	 * @return PropertyItemComponent[]
	 */
	public function getProperties() : array
	{
		return $this->properties;
	}

	/**
	 * Check if a property exists by its name.
	 */
	public function hasProperty(string $name) : bool
	{
		return isset($this->properties[$name]);
	}

	/**
	 * Check if a property exists by its name.
	 *
	 * @return $this
	 */
	public function addProperty(PropertyItemComponent $property) : self
	{
		$this->properties[$property::getName()] = $property;
		return $this;
	}

	/**
	 * Remove a property by its name.
	 *
	 * @return $this
	 */
	public function removeProperty(\BackedEnum|string $name) : self
	{
		$key = is_string($name) ? $name : $name->value;
		unset($this->properties[$key]);
		return $this;
	}

	/**
	 * @return DataDrivenItemComponent[]
	 */
	public function getComponents() : array
	{
		return $this->components;
	}

	/**
	 * Check if a component exists by its name.
	 */
	public function hasComponent(string $name) : bool
	{
		return isset($this->components[$name]);
	}

	/**
	 * Add a DataDrivenComponent to the builder.
	 *
	 * @return $this
	 */
	public function addComponent(DataDrivenItemComponent $component) : self
	{
		$this->components[$component::getName()] = $component;
		return $this;
	}

	/**
	 * Remove a component by its name.
	 */
	public function removeComponent(string $name) : void
	{
		unset($this->components[$name]);
	}

	/**
	 * Create a LegacyItemBuilder from an Item instance.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		$properties = CompoundTag::create();
		foreach ($this->getProperties() as $k => $property) {
			$properties->setTag($property::getName(), $property->toNBT());
		}
		foreach ($this->getComponents() as $k => $component) {
			$nbt->setTag($component::getName(), $component->toNBT());
		}

		$nbt->setTag("item_properties", $properties);
		return CompoundTag::create()
			->setTag("id", new IntTag($this->getNumericId()))
			->setTag("name", new StringTag($this->getStringId()))
			->setTag("components", $nbt);
	}

	/**
	 * Load components from the item instance.
	 *
	 * @return $this
	 */
	public function loadProperties() : self
	{
		$item = $this->getItem();

		$icon = $this->getStringId();
		if (str_contains($icon, ":")) {
			$icon = explode(":", $icon, 2)[1];
		}

		$reflection = new \ReflectionClass($item);

		$this->addProperty(new IconProperty($icon));
		$this->addProperty(new StackedByDataProperty(false));
		$this->addProperty(new ShouldDespawnProperty(false));
		$this->addProperty(new MaxStackSizeProperty($item->getMaxStackSize()));
		$this->addProperty(new HandEquippedProperty($item instanceof Tool));
		$this->addProperty(new CanDestroyInCreative(!$item instanceof Sword));
		$this->addProperty(new LiquidClippedProperty($item instanceof Bucket));
		$this->addProperty(new MiningSpeedProperty($item->getMiningEfficiency(false)));

		if ($item->getAttackPoints() > 1) {
			$this->addProperty(new DamageProperty($item->getAttackPoints() - 1));
		}

		$block = $item->getBlock();
		if (!($block instanceof Air) && empty($reflection->getAttributes(ItemBlockVisual::class))) {
			$this->addProperty(BlockProperty::from($block));
		}

		if ($item instanceof Food) {
			$this->addProperty(new UseDurationProperty(20));
		}

		$this->addProperty(UseAnimationProperty::fromItem($item));
		$this->addProperty(EnchantableSlotProperty::fromItem($item));
		$this->addProperty(EnchantableValueProperty::fromItem($item));
		return $this;
	}

	/**
	 * Load components from the item instance.
	 *
	 * @return $this
	 */
	public function loadComponents() : self
	{
		$item = $this->getItem();

		$this->addComponent(new DisplayNameItemComponent("item." . $this->getStringId() . ".name"));
		$this->addComponent(new EnchantableItemComponent(ItemEnchantSlot::fromItem($item), $item->getEnchantability()));
		$this->addComponent(new FireResistantItemComponent($item->isFireProof()));

		if ($item->getFuelTime() > 0) {
			$this->addComponent(new FuelItemComponent($item->getFuelTime() / 20));
		}

		if ($item instanceof Durable && !$item->isUnbreakable()) {
			$this->addComponent(new DurabilityItemComponent($item->getMaxDurability()));

			$items = [];
			if ($item instanceof Armor) {
				$items[] = ItemRepair::from(RepairableItemComponent::VANILLA_COST_FORMULE, match (true) {
					$item->getMaterial() === VanillaArmorMaterials::LEATHER() => VanillaItems::LEATHER(),
					$item->getMaterial() === VanillaArmorMaterials::IRON() => VanillaItems::IRON_INGOT(),
					$item->getMaterial() === VanillaArmorMaterials::GOLD() => VanillaItems::GOLD_INGOT(),
					$item->getMaterial() === VanillaArmorMaterials::DIAMOND() => VanillaItems::DIAMOND(),
					$item->getMaterial() === VanillaArmorMaterials::NETHERITE() => VanillaItems::NETHERITE_INGOT(),
					$item->getMaterial() === VanillaArmorMaterials::TURTLE() => VanillaItems::SCUTE(),
					default => VanillaItems::AIR(),
				});
			} elseif ($item instanceof TieredTool) {
				$items[] = ItemRepair::from(RepairableItemComponent::VANILLA_COST_FORMULE, ...match (true) {
					$item->getTier() === ToolTier::WOOD => [VanillaBlocks::OAK_WOOD()->asItem(), VanillaBlocks::OAK_LOG()->asItem(), VanillaBlocks::BIRCH_LOG()->asItem(), VanillaBlocks::SPRUCE_LOG()->asItem(), VanillaBlocks::JUNGLE_LOG()->asItem(), VanillaBlocks::ACACIA_LOG()->asItem(), VanillaBlocks::DARK_OAK_LOG()->asItem()],
					$item->getTier() === ToolTier::STONE => [VanillaBlocks::COBBLESTONE()->asItem(), VanillaBlocks::STONE()->asItem()],
					$item->getTier() === ToolTier::COPPER => [VanillaItems::COPPER_INGOT()],
					$item->getTier() === ToolTier::IRON => [VanillaItems::IRON_INGOT()],
					$item->getTier() === ToolTier::GOLD => [VanillaItems::GOLD_INGOT()],
					$item->getTier() === ToolTier::DIAMOND => [VanillaItems::DIAMOND()],
					$item->getTier() === ToolTier::NETHERITE => [VanillaItems::NETHERITE_INGOT()],
					default => [VanillaItems::AIR()],
				});
			}

			$this->addComponent(new RepairableItemComponent($items));
		}

		if ($item instanceof Armor) {
			$this->addComponent(new WearableItemComponent(ItemSlot::fromArmorTypeInfo($item->getArmorSlot()), $item->getDefensePoints()));
			$this->addComponent(ArmorItemComponent::from($item));
		}

		$block = $item->getBlock();
		if (!($block instanceof Air)) {
			$this->addComponent(BlockPlacerItemComponent::make(
				block: $block,
				alignedPlacement: true,
			));
		}

		$cooldown = $item->getCooldownTicks();
		if ($cooldown > 0) {
			$this->addComponent(new CooldownItemComponent($cooldown / 20, $item->getCooldownTag()));
		}

		if ($item instanceof Dye) {
			$color = $item->getColor()->getRgbValue();
			$this->addComponent(new DyeableItemComponent(sprintf("%02X%02X%02X%02X", $color->getR(), $color->getG(), $color->getB(), $color->getA())));
		}

		if ($item instanceof SpawnEgg) {
			$this->addComponent(new EntityPlacerItemComponent());
		}

		if ($item instanceof Food) {
			$this->addComponent(new FoodItemComponent(
				$item->getFoodRestore(),
				$item->getSaturationRestore(),
				!$item->requiresHunger()
			));
		}

		if ($item instanceof ProjectileItem) {
			$this->addComponent(new ProjectileItemComponent());
			$this->addComponent(new ThrowableItemComponent());
		}

		if ($item instanceof Record) {
			$this->addComponent(new RecordItemComponent($item->getRecordType()->getSoundName()));
		}
		return $this;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_swing_sounds?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class SwingSoundsItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $hit,
		protected string $miss,
		protected string $critical,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::SWING_SOUNDS->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("attack_hit", new StringTag($this->hit))
			->setTag("attack_miss", new StringTag($this->miss))
			->setTag("attack_critical_hit", new StringTag($this->critical));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class TagsItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected array $tags,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::TAGS->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("tags", new ListTag(array_map(fn ($tag) => new StringTag($tag), $this->tags), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\KineticWeaponKineticEffectConditionsItemComponent as DamageConditions;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_kinetic_weapon?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class KineticWeaponItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $hitboxMargin = 0.0,
		protected int $min = 0,
		protected int $max = 3,
		protected float             $damageModifier = 0.0,
		protected float             $damageMultiplier = 1.0,
		protected int               $delay = 0,
		protected ?DamageConditions $damageConditions = null,
		protected ?DamageConditions $dismoutConditions = null,
		protected ?DamageConditions $knockbackConditions = null,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::KINETIC_WEAPON->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create();
		if ($this->damageConditions !== null) {
			$nbt = $nbt->setTag("damage_conditions", $this->damageConditions->toNBT());
		}

		if ($this->dismoutConditions !== null) {
			$nbt = $nbt->setTag("dismount_conditions", $this->dismoutConditions->toNBT());
		}

		if ($this->knockbackConditions !== null) {
			$nbt = $nbt->setTag("knockback_conditions", $this->knockbackConditions->toNBT());
		}

		return $nbt
			->setTag("hitbox_margin", new FloatTag($this->hitboxMargin))
			->setTag(
				"reach",
				CompoundTag::create()
				->setTag("min", new IntTag($this->min))
				->setTag("max", new IntTag($this->max))
			)
			->setTag("damage_modifier", new FloatTag($this->damageModifier))
			->setTag("damage_multiplier", new FloatTag($this->damageMultiplier))
			->setTag("delay", new IntTag($this->delay));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MaxStackSizeProperty extends PropertyItemComponent
{
	public function __construct(
		protected int $size
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::MAX_STACk_SIZE->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->size);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class CanDestroyInCreative extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::CAN_DESTROY_IN_CREATIVE->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

/**
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class OnUseProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ON_USE->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class MiningSpeedProperty extends PropertyItemComponent
{
	public function __construct(
		protected float $speed
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::MINING_SPEED->getValue();
	}

	public function toNBT() : FloatTag
	{
		return new FloatTag($this->speed);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FoilProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::FOIL->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

enum PropertyComponentIds : string
{
	case ALLOW_OFF_HAND = "allow_off_hand";
	case CAN_DESTROY_IN_CREATIVE = "can_destroy_in_creative";
	case DAMAGE = "damage";
	case ENCHANTABLE_VALUE = "enchantable_value";
	case ENCHANTABLE_SLOT = "enchantable_slot";
	case FOIL = "foil";
	case FRAME_COUNT = "frame_count";
	case HAND_EQUIPPED = "hand_equipped";
	case IS_HIDDEN_IN_COMMANDS = "is_hidden_in_commands";
	case LIQUID_CLIPPED = "liquid_clipped";
	case MAX_STACk_SIZE = "max_stack_size";
	case MINING_SPEED = "mining_speed";
	case SHOULD_DESPAWN = "should_despawn";
	case STACKED_BY_DATA = "stacked_by_data";
	case USE_ANIMATION = "use_animation";
	case USE_DURATION = "use_duration";
	case BLOCK = "block";
	case HOVER_TEXT_COLOR = "hover_text_color";
	case ICON = "minecraft:icon";
	case GLINT = "glint";
	case INTERACT_BUTTON = "interact_button";
	case ON_USE = "on_use";
	case ON_USE_ON = "on_use_on";
	case RARITY = "rarity";

	/**
	 * Returns the name of the component.
	 *
	 * @return string The name of the component.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class IconProperty extends PropertyItemComponent
{
	public function __construct(
		protected string $icon,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ICON->getValue();
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("texture", new StringTag($this->icon))
			->setTag(
				"textures",
				CompoundTag::create()
				->setTag("default", new StringTag($this->icon))
				->setTag("dyed", new StringTag($this->icon . ".dyed"))
				->setTag("icon_trim", new StringTag($this->icon . ".trim"))
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class LiquidClippedProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::LIQUID_CLIPPED->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemRarity;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class RarityProperty extends PropertyItemComponent
{
	public function __construct(
		protected ItemRarity $rarity
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::RARITY->getValue();
	}

	public function toNBT() : StringTag
	{
		return new StringTag($this->rarity->getValue());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class GlintProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::GLINT->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DamageProperty extends PropertyItemComponent
{
	public function __construct(
		protected int $damage
	) {
		if ($this->damage < -32768 || $this->damage > 32767) {
			throw new \InvalidArgumentException("Damage must be between -32768 and 32767");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::DAMAGE->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->damage);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class HandEquippedProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::HAND_EQUIPPED->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\block\Block;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;

#[Attribute(Attribute::TARGET_CLASS)]
class BlockProperty extends PropertyItemComponent
{
	public function __construct(
		protected string $blockName
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::BLOCK->getValue();
	}

	/**
	 * Create a BlockComponent from a Block instance.
	 */
	public static function from(Block $block) : self
	{
		return new self(
			GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(),
		);
	}

	public function toNBT() : StringTag
	{
		return new StringTag($this->blockName);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class UseDurationProperty extends PropertyItemComponent
{
	public function __construct(
		protected int $tick
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::USE_DURATION->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->tick);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\StringTag;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class InteractButtonProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool|string $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::INTERACT_BUTTON->getValue();
	}

	public function toNBT() : StringTag|ByteTag
	{
		return is_string($this->value) ? new StringTag($this->value) : new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Nexly\Items\Components\ItemComponent;

abstract class PropertyItemComponent extends ItemComponent
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\item\Item;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class EnchantableValueProperty extends PropertyItemComponent
{
	public function __construct(
		protected int $value
	) {
	}

	/**
	 * Create an EnchantableValueProperty from an Item.
	 */
	public static function fromItem(Item $item) : EnchantableValueProperty
	{
		return new self($item->getEnchantability());
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ENCHANTABLE_VALUE->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class HoverTextColorProperty extends PropertyItemComponent
{
	public function __construct(
		protected string $prefix
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::HOVER_TEXT_COLOR->getValue();
	}

	public function toNBT() : StringTag
	{
		return new StringTag($this->prefix);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemAnimation;
use pocketmine\entity\Consumable;
use pocketmine\item\Item;
use pocketmine\item\Potion;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class UseAnimationProperty extends PropertyItemComponent
{
	public function __construct(
		protected ItemAnimation $animation
	) {
	}

	/**
	 * Create an UseAnimationProperty from an Item.
	 */
	public static function fromItem(Item $item) : UseAnimationProperty
	{
		return new self(match (true) {
			$item instanceof Consumable => ItemAnimation::EAT,
			$item instanceof Potion => ItemAnimation::DRINK,
			default => ItemAnimation::NONE,
		});
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::USE_ANIMATION->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->animation->getValue());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class AllowOffHandProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ALLOW_OFF_HAND->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ShouldDespawnProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::SHOULD_DESPAWN->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class StackedByDataProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::STACKED_BY_DATA->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FrameCountProperty extends PropertyItemComponent
{
	public function __construct(
		protected int $frame
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::FRAME_COUNT->getValue();
	}

	public function toNBT() : IntTag
	{
		return new IntTag($this->frame);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use pocketmine\nbt\tag\ByteTag;

#[Attribute(Attribute::TARGET_CLASS)]
class IsHiddenInCommandsProperty extends PropertyItemComponent
{
	public function __construct(
		protected bool $value
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ALLOW_OFF_HAND->getValue();
	}

	public function toNBT() : ByteTag
	{
		return new ByteTag($this->value);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Property;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemEnchantSlot;
use pocketmine\item\Item;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class EnchantableSlotProperty extends PropertyItemComponent
{
	public function __construct(
		protected ItemEnchantSlot $slot
	) {
	}

	/**
	 * Create an EnchantableSlotProperty from an Item.
	 */
	public static function fromItem(Item $item) : EnchantableSlotProperty
	{
		return new self(ItemEnchantSlot::fromItem($item));
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return PropertyComponentIds::ENCHANTABLE_SLOT->getValue();
	}

	public function toNBT() : StringTag
	{
		return new StringTag($this->slot->getValue());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

enum DataDrivenComponentIds : string
{
	case ARMOR = "minecraft:armor";
	case BUNDLE_INTERACTION = "minecraft:bundle_interaction";
	case CHARGEABLE = "minecraft:chargeable";
	case COMPOSTABLE = "minecraft:compostable";
	case COOLDOWN = "minecraft:cooldown";
	case CUSTOM_COMPONENTS = "minecraft:custom_components";
	case DAMAGE_ABSORPTION = "minecraft:damage_absorption";
	case DIGGER = "minecraft:digger";
	case DISPLAY_NAME = "minecraft:display_name";
	case DURABILITY = "minecraft:durability";
	case DURABILITY_SENSOR = "minecraft:durability_sensor";
	case DYEABLE = "minecraft:dyeable";
	case ENCHANTABLE = "minecraft:enchantable";
	case ENTITY_PLACER = "minecraft:entity_placer";
	case FIRE_RESISTANT = "minecraft:fire_resistant";
	case FOOD = "minecraft:food";
	case FUEL = "minecraft:fuel";
	case BLOCK_PLACER = "minecraft:block_placer";
	case PROJECTILE = "minecraft:projectile";
	case RECORD = "minecraft:record";
	case RENDER_OFFSETS = "minecraft:render_offsets";
	case REPAIRABLE = "minecraft:repairable";
	case SHOOTER = "minecraft:shooter";
	case STORAGE = "minecraft:storage_item";
	case STORAGE_WEIGHT_LIMIT = "minecraft:storage_item_weight_limit";
	case STORAGE_WEIGHT_MODIFIER = "minecraft:storage_item_weight_modifier";
	case TAGS = "minecraft:tags";
	case THROWABLE = "minecraft:throwable";
	case USE_MODIFIERS = "minecraft:use_modifiers";
	case WEAPON = "minecraft:weapon";
	case WEARABLE = "minecraft:wearable";
	case PUBLISHER_ON_USE_ON = "minecraft:publisher_on_use_on";
	case SWING_DURATION = "minecraft:swing_duration";
	case SWING_SOUNDS = "minecraft:swing_sounds";
	case PIERCING_WEAPON = "minecraft:piercing_weapon";
	case KINETIC_WEAPON = "minecraft:kinetic_weapon";
	case KINETIC_WEAPON_KINETIC_EFFECT_CONDITIONS = "minecraft:kinetic_weapon_kinetic_effect_conditions";

	/**
	 * Returns the name of the component.
	 *
	 * @return string The name of the component.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\DurabilityThreshold;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class DurabilitySensorItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected array $thresholds,
	) {
		if (empty($this->thresholds)) {
			throw new \InvalidArgumentException("Thresholds array cannot be empty.");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::DURABILITY_SENSOR->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("durability_thresholds", new ListTag(array_map(fn (DurabilityThreshold $threshold) => $threshold->toNBT(), $this->thresholds), NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class FuelItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $duration
	) {
		if ($this->duration < 0.05) {
			throw new \InvalidArgumentException("Duration must be at least 0.05");
		}

		if ($this->duration > 107374180) {
			throw new \InvalidArgumentException("Duration must be at most 107374180");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::FUEL->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("duration", new FloatTag($this->duration));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ThrowableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected bool  $doSwingAnimation = true,
		protected float $launchPowerScale = 1.0,
		protected float $maxDrawDuration = 0.0,
		protected float $maxLaunchPower = 1.0,
		protected float $minDrawDuration = 0.0,
		protected bool  $scalePowerByDrawDuration = false
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::THROWABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("do_swing_animation", new ByteTag($this->doSwingAnimation))
			->setTag("launch_power_scale", new FloatTag($this->launchPowerScale))
			->setTag("max_draw_duration", new FloatTag($this->maxDrawDuration))
			->setTag("max_launch_power", new FloatTag($this->maxLaunchPower))
			->setTag("min_draw_duration", new FloatTag($this->minDrawDuration))
			->setTag("scale_power_by_draw_duration", new ByteTag($this->scalePowerByDrawDuration));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemAmmunition;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class ShooterItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected array $ammunitions = [],
		protected bool  $chargeOnDraw = false,
		protected float $maxDrawDuration = 0.0,
		protected bool  $scalePowerByDrawDuration = true
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::SHOOTER->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("charge_on_draw", new ByteTag($this->chargeOnDraw))
			->setTag("max_draw_duration", new FloatTag($this->maxDrawDuration))
			->setTag("scale_power_by_draw_duration", new ByteTag($this->scalePowerByDrawDuration))
			->setTag("ammunition", new ListTag(array_map(fn (ItemAmmunition $ammunition) => $ammunition->toNBT(), $this->ammunitions), NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemCooldownType;
use Nexly\Items\Components\DataDriven\Types\ItemUseActionType;
use Nexly\Items\Components\Legacy\Types\LegacyEffect;
use pocketmine\item\Item;
use pocketmine\nbt\NBT;
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\network\mcpe\convert\TypeConverter;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class FoodItemComponent extends DataDrivenItemComponent
{
	/**
	 * @param float[]        $onUseRange
	 * @param LegacyEffect[] $effects
	 * @param int[]          $removeEffects
	 */
	public function __construct(
		protected int               $nutrition,
		protected float             $saturationModifier,
		protected bool              $canAlwaysEat = false,
		protected ItemCooldownType  $cooldownType = ItemCooldownType::NONE,
		protected int               $cooldownTick = 0,
		protected string            $usingConvertsTo = "air",
		protected ItemUseActionType $useActionType = ItemUseActionType::NONE,
		protected array             $onUseRange = [],
		protected array             $effects = [],
		protected array             $removeEffects = [],
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::FOOD->getValue();
	}

	/**
	 * Set the item that this food converts to when used.
	 */
	public function setUsingConvertsTo(Item $item) : void
	{
		[$rId] = ($converter = TypeConverter::getInstance())->getItemTranslator()->toNetworkId($item);
		if ($rId == null) {
			throw new \InvalidArgumentException("Item does not have a valid network ID");
		}

		$this->usingConvertsTo = $converter->getItemTypeDictionary()->fromIntId($rId);
	}

	/**
	 * Get the maximum damage value.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("nutrition", new IntTag($this->nutrition))
			->setTag("saturation_modifier", new FloatTag($this->saturationModifier))
			->setTag("can_always_eat", new ByteTag($this->canAlwaysEat))
			->setTag("cooldown_type", new StringTag($this->cooldownType->getValue()))
			->setTag("cooldown_time", new IntTag($this->cooldownTick))
			->setTag("using_converts_to", CompoundTag::create()->setTag("name", new StringTag($this->usingConvertsTo)))
			->setTag("on_use_action", new IntTag($this->useActionType->getValue()))
			->setTag("on_use_range", new ListTag(array_map(fn (float $value) => new FloatTag($value), $this->onUseRange), NBT::TAG_Float))
			->setTag("effects", new ListTag(array_map(fn (LegacyEffect $effect) => $effect->toNBT(), $this->effects), NBT::TAG_Compound))
			->setTag("remove_effects", new ListTag(array_map(fn (int $value) => new IntTag($value), $this->removeEffects), NBT::TAG_Int));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\OreTextureType;
use pocketmine\item\Armor;
use pocketmine\item\Item;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

/**
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class ArmorItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected OreTextureType $textureType,
		protected ?int           $protection = null,
	) {
	}

	/**
	 * Create an ArmorItemComponent from an Armor instance.
	 *
	 * @param Armor $item
	 */
	public static function from(Item $item) : self
	{
		return new self(
			OreTextureType::fromItem($item),
			$item instanceof Armor ? $item->getDefensePoints() : null
		);
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::ARMOR->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("texture_type", new StringTag($this->textureType->getValue()));

		if ($this->protection === null) {
			return $nbt;
		}

		return $nbt->setTag("protection", new IntTag($this->protection));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\StringTag;

/**
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class ChargeableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $movementModifier,
		protected string $onComplete = "",
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::CHARGEABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("movement_modifier", new FloatTag($this->movementModifier))
			->setTag("on_complete", new StringTag($this->onComplete));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use function round;

/**
 * @deprecated
 */
#[Attribute(Attribute::TARGET_CLASS)]
class RenderOffsetsItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $resolution = 16,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::RENDER_OFFSETS->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		$mainHand_fp = round(0.039 * 16 / $this->resolution, 8);
		$offhand_fp = round(0.065 * 16 / $this->resolution, 8);
		$mainHand_tp = $offhand_tp = round(0.0965 * 16 / $this->resolution, 8);

		$scale = function (float $s) : ListTag {
			return new ListTag([
				new FloatTag($s),
				new FloatTag($s),
				new FloatTag($s),
			]);
		};

		return CompoundTag::create()
			->setTag(
				"main_hand",
				CompoundTag::create()
				->setTag(
					"first_person",
					CompoundTag::create()
					->setTag("scale", $scale($mainHand_fp))
				)
				->setTag(
					"third_person",
					CompoundTag::create()
					->setTag("scale", $scale($mainHand_tp))
				)
			)
			->setTag(
				"off_hand",
				CompoundTag::create()
				->setTag(
					"first_person",
					CompoundTag::create()
					->setTag("scale", $scale($offhand_fp))
				)
				->setTag(
					"third_person",
					CompoundTag::create()
					->setTag("scale", $scale($offhand_tp))
				)
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemRepair;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class RepairableItemComponent extends DataDrivenItemComponent
{
	public const VANILLA_COST_FORMULE = "math.min(q.remaining_durability + c.other->q.remaining_durability + math.floor(q.max_durability /20), c.other->q.max_durability)";

	/**
	 * @param ItemRepair[] $items
	 */
	public function __construct(
		protected array $items,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::REPAIRABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("repair_items", new ListTag(
				array_map(fn (ItemRepair $item) => $item->toNBT(), $this->items),
				NBT::TAG_Compound
			));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class ProjectileItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $entity = "",
		protected float $minimumCriticalPower = 1.25,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::PROJECTILE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("projectile_entity", new StringTag($this->entity))
			->setTag("minimum_critical_power", new FloatTag($this->minimumCriticalPower));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DamageAbsorptionItemComponent extends DataDrivenItemComponent
{
	/**
	 * @param string[] $causes
	 */
	public function __construct(
		protected array $causes,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::DAMAGE_ABSORPTION->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("absorbable_causes", new ListTag($this->causes, NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Nexly\Items\Components\ItemComponent;

abstract class DataDrivenItemComponent extends ItemComponent
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use function preg_match;

#[Attribute(Attribute::TARGET_CLASS)]
class DyeableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $color = "#ffffff",
	) {
		if (!preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $color)) {
			throw new \InvalidArgumentException("Invalid color format. Use hex format like #RRGGBB or #RGB.");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::DYEABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("default_color", new StringTag($this->color));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class StorageWeightLimitItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $capacity,
	) {
		if ($capacity < 0) {
			throw new \InvalidArgumentException("Capacity must be a non-negative integer.");
		}

		if ($capacity > 64) {
			throw new \InvalidArgumentException("Capacity cannot exceed 64.");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::STORAGE_WEIGHT_LIMIT->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("max_weight_limit", new IntTag($this->capacity));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_swing_duration?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class SwingDurationItemComponent extends DataDrivenItemComponent
{
	/**
	 * @param float $duration The duration of the swing sound in seconds.
	 */
	public function __construct(
		protected float $duration,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::SWING_DURATION->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new FloatTag($this->duration));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class RecordItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $sound,
		protected int $duration = 20,
		protected int $signal = 1
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::RECORD->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("sound_event", new StringTag($this->sound))
			->setTag("duration", new IntTag($this->duration))
			->setTag("comparator_signal", new IntTag($this->signal));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;

#[Attribute(Attribute::TARGET_CLASS)]
class PublisherOnUseOnItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected bool $autoSucceedOnClient = true,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::PUBLISHER_ON_USE_ON->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("autoSucceedOnClient", new ByteTag($this->autoSucceedOnClient));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\block\Block;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use function array_map;
use function implode;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class DiggerItemComponent extends DataDrivenItemComponent
{
	/**
	 * @param CompoundTag[] $diggers
	 */
	public function __construct(
		protected bool $efficiency = false,
		protected array $diggers = []
	) {
	}

	public static function getName() : string
	{
		return DataDrivenComponentIds::DIGGER->getValue();
	}

	/**
	 * @return $this
	 */
	public function addBlock(Block $block, int $speed) : self
	{
		$blockName = GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName();

		$this->diggers[] = CompoundTag::create()
			->setTag("block", CompoundTag::create()->setString("name", $blockName))
			->setInt("speed", $speed);

		return $this;
	}

	/**
	 * @return $this
	 */
	public function addBlocks(int $speed, Block ...$blocks) : self
	{
		foreach ($blocks as $block) {
			$this->addBlock($block, $speed);
		}
		return $this;
	}

	/**
	 * @param string[]|string $tags
	 */
	public function addTag(array|string $tags, int $speed) : self
	{
		$tagsArray = is_string($tags) ? [$tags] : $tags;

		$molang = "query.any_tag(" . implode(", ", array_map(fn (string $tag) => "'$tag'", $tagsArray)) . ")";
		$this->diggers[] = CompoundTag::create()
			->setTag("block", CompoundTag::create()->setString("tags", $molang))
			->setInt("speed", $speed);

		return $this;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("on_dig", CompoundTag::create())
			->setByte("use_efficiency", (int) $this->efficiency) // On utilise (int) $this->efficiency
			->setTag("destroy_speeds", new ListTag($this->diggers, NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DisplayNameItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $name,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::DISPLAY_NAME->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("value", new StringTag($this->name));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

class DamageCause
{
	public const ALL = "all";
	public const FIRE = "fire";
	public const LAVA = "lava";
	public const DROWNING = "drowning";
	public const FALL = "fall";
	public const BLOCK_EXPLOSION = "block_explosion";
	public const ENTITY_EXPLOSION = "entity_explosion";
	public const CAMPFIRE = "campfire";
	public const CHARGING = "charging";
	public const MAGIC = "magic";
	public const WITHER = "wither";
	public const STARVE = "starve";
	public const SUFFOCATION = "suffocation";
	public const VOID = "void";
	public const CONTACT = "contact";
	public const PROJECTILE = "projectile";
	public const ENTITY_ATTACK = "entity_attack";
	public const ANVIL = "anvil";
	public const FALLING_BLOCK = "falling_block";
	public const THORNS = "thorns";
	public const FIRE_TICK = "fire_tick";
	public const FIREWORKS = "fireworks";
	public const FLY_INTO_WALL = "fly_into_wall";
	public const FREEZING = "freezing";
	public const LIGHTNING = "lightning";
	public const MACE_SMASH = "mace_smash";
	public const MAGMA = "magma";
	public const NONE = "none";
	public const OVERRIDE = "override";
	public const PISTON = "piston";
	public const RAM_ATTACK = "ram_attack";
	public const SELF_DESTRUCT = "self_destruct";
	public const SONIC_BOOM = "sonic_boom";
	public const SOUL_CAMPFIRE = "soul_campfire";
	public const STALACTITE = "stalactite";
	public const STALAGMITE = "stalagmite";
	public const TEMPERATURE = "temperature";

	/**
	 * Get the string value of the damage cause.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class ItemBlockVisual
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

enum ItemAnimation : int
{
	case NONE = 0;
	case EAT = 1;
	case DRINK = 2;
	case BLOCK = 3;
	case BOW = 4;
	case CAMERA = 5;
	case SPEAR = 6;
	case CROSSBOW = 9;
	case SPYGLASS = 10;
	case BRUSH = 12;

	/**
	 * The name of the ItemAnimation.
	 */
	public function getName() : string
	{
		return $this->name;
	}

	/**
	 * Get the integer value of the ItemAnimation.
	 */
	public function getValue() : int
	{
		return $this->value;
	}

	/**
	 * Create an ItemAnimation from an integer value.
	 */
	public static function fromInt(int $value) : ItemAnimation
	{
		return match ($value) {
			1 => self::EAT,
			2 => self::DRINK,
			default => self::NONE,
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\inventory\ArmorInventory;

enum ItemSlot : string
{
	case ARMOR = "slot.armor";
	case ARMOR_CHEST = "slot.armor.chest";
	case ARMOR_FEET = "slot.armor.feet";
	case ARMOR_HEAD = "slot.armor.head";
	case ARMOR_LEGS = "slot.armor.legs";
	case CHEST = "slot.chest";
	case ENDERCHEST = "slot.enderchest";
	case EQUIPPABLE = "slot.equippable";
	case HOTBAR = "slot.hotbar";
	case INVENTORY = "slot.inventory";
	case NONE = "none";
	case SADDLE = "slot.saddle";
	case WEAPON_MAIN_HAND = "slot.weapon.mainhand";
	case WEAPON_OFF_HAND = "slot.weapon.offhand";

	/**
	 * Get the string name of the ItemSlot.
	 */
	public function getName() : string
	{
		return $this->name;
	}

	/**
	 * Get the string value of the ItemSlot.
	 */
	public function getValue() : string
	{
		return $this->value;
	}

	/**
	 * Get the ItemSlot from an Item instance.
	 */
	public static function fromArmorTypeInfo(int $slotArmor) : ItemSlot
	{
		return match ($slotArmor) {
			ArmorInventory::SLOT_CHEST => self::ARMOR_CHEST,
			ArmorInventory::SLOT_HEAD => self::ARMOR_HEAD,
			ArmorInventory::SLOT_LEGS => self::ARMOR_LEGS,
			ArmorInventory::SLOT_FEET => self::ARMOR_FEET,
			default => self::ARMOR
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\item\Item;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\TypeConverter;

class ItemAmmunition
{
	public function __construct(
		protected string $item,
		protected bool $searchInventory = true,
		protected bool $useInCreative = true,
		protected bool $useOffHand = true
	) {
	}

	/**
	 * Create an ItemAmmunition component from an Item instance.
	 */
	public static function from(
		Item $item,
		bool $searchInventory = true,
		bool $useInCreative = true,
		bool $useOffHand = true
	) : self {
		[$rid] = ($converter = TypeConverter::getInstance())->getItemTranslator()->toNetworkId($item);
		return new self(
			$converter->getItemTypeDictionary()->fromIntId($rid),
			$searchInventory,
			$useInCreative,
			$useOffHand
		);
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("item", new StringTag($this->item))
			->setTag("search_inventory", new ByteTag($this->searchInventory))
			->setTag("use_in_creative", new ByteTag($this->useInCreative))
			->setTag("use_offhand", new ByteTag($this->useOffHand));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

enum ItemCooldownType : string
{
	case NONE = "none";
	case CHORUS = "chorusfruit";

	/**
	 * Get the string value of the cooldown type.
	 */
	public function getValue() : string
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

/**
 * Bedrock built-in sound event values, documentation snapshot 2026-09-23.
 * Availability depends on the client version.
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_durability_sensor
 */
class SoundEvent
{
	public const ABSORB_BLOCK = "absorb_block";
	public const ACTIVATE = "activate";
	public const ADD_CHEST = "add.chest";
	public const ADMIRE = "admire";
	public const AGITATED = "agitated";
	public const AMBIENT = "ambient";
	public const AMBIENT_AGGRESSIVE = "ambient.aggressive";
	public const AMBIENT_BABY = "ambient.baby";
	public const AMBIENT_BASALT_DELTAS_ADDITIONS = "ambient.basalt_deltas.additions";
	public const AMBIENT_BASALT_DELTAS_LOOP = "ambient.basalt_deltas.loop";
	public const AMBIENT_BASALT_DELTAS_MOOD = "ambient.basalt_deltas.mood";
	public const AMBIENT_CANDLE = "ambient.candle";
	public const AMBIENT_CAVE = "ambient.cave";
	public const AMBIENT_CRIMSON_FOREST_ADDITIONS = "ambient.crimson_forest.additions";
	public const AMBIENT_CRIMSON_FOREST_LOOP = "ambient.crimson_forest.loop";
	public const AMBIENT_CRIMSON_FOREST_MOOD = "ambient.crimson_forest.mood";
	public const AMBIENT_IN_AIR = "ambient.in.air";
	public const AMBIENT_IN_RAID = "ambient.in.raid";
	public const AMBIENT_IN_WATER = "ambient.in.water";
	public const AMBIENT_NETHER_WASTES_ADDITIONS = "ambient.nether_wastes.additions";
	public const AMBIENT_NETHER_WASTES_LOOP = "ambient.nether_wastes.loop";
	public const AMBIENT_NETHER_WASTES_MOOD = "ambient.nether_wastes.mood";
	public const AMBIENT_POLLINATE = "ambient.pollinate";
	public const AMBIENT_SCREAMER = "ambient.screamer";
	public const AMBIENT_SOULSAND_VALLEY_ADDITIONS = "ambient.soulsand_valley.additions";
	public const AMBIENT_SOULSAND_VALLEY_LOOP = "ambient.soulsand_valley.loop";
	public const AMBIENT_SOULSAND_VALLEY_MOOD = "ambient.soulsand_valley.mood";
	public const AMBIENT_TAME = "ambient.tame";
	public const AMBIENT_UNDERWATER_ENTER = "ambient.underwater.enter";
	public const AMBIENT_UNDERWATER_EXIT = "ambient.underwater.exit";
	public const AMBIENT_WARPED_FOREST_ADDITIONS = "ambient.warped_forest.additions";
	public const AMBIENT_WARPED_FOREST_LOOP = "ambient.warped_forest.loop";
	public const AMBIENT_WARPED_FOREST_MOOD = "ambient.warped_forest.mood";
	public const AMBIENT_WEATHER_THE_END_LIGHT_FLASH = "ambient.weather.the_end_light_flash";
	public const AMBIENT_WORRIED = "ambient.worried";
	public const ANGRY = "angry";
	public const APPLY_EFFECT_BAD_OMEN = "apply_effect.bad_omen";
	public const APPLY_EFFECT_RAID_OMEN = "apply_effect.raid_omen";
	public const APPLY_EFFECT_TRIAL_OMEN = "apply_effect.trial_omen";
	public const ARMOR = "armor";
	public const ARMOR_BREAK_WOLF = "armor.break_wolf";
	public const ARMOR_CRACK_WOLF = "armor.crack_wolf";
	public const ARMOR_EQUIP_CHAIN = "armor.equip_chain";
	public const ARMOR_EQUIP_COPPER = "armor.equip_copper";
	public const ARMOR_EQUIP_DIAMOND = "armor.equip_diamond";
	public const ARMOR_EQUIP_ELYTRA = "armor.equip_elytra";
	public const ARMOR_EQUIP_GENERIC = "armor.equip_generic";
	public const ARMOR_EQUIP_GOLD = "armor.equip_gold";
	public const ARMOR_EQUIP_IRON = "armor.equip_iron";
	public const ARMOR_EQUIP_LEATHER = "armor.equip_leather";
	public const ARMOR_EQUIP_NETHERITE = "armor.equip_netherite";
	public const ARMOR_EQUIP_WOLF = "armor.equip_wolf";
	public const ARMOR_REPAIR_WOLF = "armor.repair_wolf";
	public const ARMOR_UNEQUIP_GENERIC = "armor.unequip_generic";
	public const ARMOR_UNEQUIP_WOLF = "armor.unequip_wolf";
	public const ATTACH = "attach";
	public const ATTACK = "attack";
	public const ATTACK_CRITICAL = "attack.critical";
	public const ATTACK_NODAMAGE = "attack.nodamage";
	public const ATTACK_STRONG = "attack.strong";
	public const BALLOONPOP = "balloonpop";
	public const BEACON_ACTIVATE = "beacon.activate";
	public const BEACON_AMBIENT = "beacon.ambient";
	public const BEACON_DEACTIVATE = "beacon.deactivate";
	public const BEACON_POWER = "beacon.power";
	public const BLAST = "blast";
	public const BLOCK_BAMBOO_SAPLING_PLACE = "block.bamboo_sapling.place";
	public const BLOCK_BARREL_CLOSE = "block.barrel.close";
	public const BLOCK_BARREL_OPEN = "block.barrel.open";
	public const BLOCK_BEEHIVE_DRIP = "block.beehive.drip";
	public const BLOCK_BEEHIVE_ENTER = "block.beehive.enter";
	public const BLOCK_BEEHIVE_EXIT = "block.beehive.exit";
	public const BLOCK_BEEHIVE_SHEAR = "block.beehive.shear";
	public const BLOCK_BEEHIVE_WORK = "block.beehive.work";
	public const BLOCK_BELL_HIT = "block.bell.hit";
	public const BLOCK_BLASTFURNACE_FIRE_CRACKLE = "block.blastfurnace.fire_crackle";
	public const BLOCK_CAMPFIRE_CRACKLE = "block.campfire.crackle";
	public const BLOCK_CARTOGRAPHY_TABLE_USE = "block.cartography_table.use";
	public const BLOCK_CLICK = "block.click";
	public const BLOCK_CLICK_FAIL = "block.click.fail";
	public const BLOCK_COMPOSTER_EMPTY = "block.composter.empty";
	public const BLOCK_COMPOSTER_FILL = "block.composter.fill";
	public const BLOCK_COMPOSTER_FILL_SUCCESS = "block.composter.fill_success";
	public const BLOCK_COMPOSTER_READY = "block.composter.ready";
	public const BLOCK_COPPER_BULB_TURN_OFF = "block.copper_bulb.turn_off";
	public const BLOCK_COPPER_BULB_TURN_ON = "block.copper_bulb.turn_on";
	public const BLOCK_CREAKING_HEART_TRAIL = "block.creaking_heart.trail";
	public const BLOCK_DECORATED_POT_INSERT = "block.decorated_pot.insert";
	public const BLOCK_DECORATED_POT_INSERT_FAIL = "block.decorated_pot.insert_fail";
	public const BLOCK_ENCHANTING_TABLE_USE = "block.enchanting_table.use";
	public const BLOCK_END_PORTAL_SPAWN = "block.end_portal.spawn";
	public const BLOCK_END_PORTAL_FRAME_FILL = "block.end_portal_frame.fill";
	public const BLOCK_FLETCHING_TABLE_USE = "block.fletching_table.use";
	public const BLOCK_FROG_SPAWN_BREAK = "block.frog_spawn.break";
	public const BLOCK_FROG_SPAWN_HATCH = "block.frog_spawn.hatch";
	public const BLOCK_FURNACE_LIT = "block.furnace.lit";
	public const BLOCK_GRINDSTONE_USE = "block.grindstone.use";
	public const BLOCK_LOOM_USE = "block.loom.use";
	public const BLOCK_SCAFFOLDING_CLIMB = "block.scaffolding.climb";
	public const BLOCK_SCULK_SPREAD = "block.sculk.spread";
	public const BLOCK_SCULK_CATALYST_BLOOM = "block.sculk_catalyst.bloom";
	public const BLOCK_SCULK_SENSOR_PLACE = "block.sculk_sensor.place";
	public const BLOCK_SCULK_SHRIEKER_PLACE = "block.sculk_shrieker.place";
	public const BLOCK_SCULK_SHRIEKER_SHRIEK = "block.sculk_shrieker.shriek";
	public const BLOCK_SIGN_WAXED_INTERACT_FAIL = "block.sign.waxed_interact_fail";
	public const BLOCK_SMITHING_TABLE_USE = "block.smithing_table.use";
	public const BLOCK_SMOKER_SMOKE = "block.smoker.smoke";
	public const BLOCK_SNIFFER_EGG_CRACK = "block.sniffer_egg.crack";
	public const BLOCK_SNIFFER_EGG_HATCH = "block.sniffer_egg.hatch";
	public const BLOCK_STONECUTTER_USE = "block.stonecutter.use";
	public const BLOCK_SWEET_BERRY_BUSH_HURT = "block.sweet_berry_bush.hurt";
	public const BLOCK_SWEET_BERRY_BUSH_PICK = "block.sweet_berry_bush.pick";
	public const BLOCK_TURTLE_EGG_ATTACK = "block.turtle_egg.attack";
	public const BLOCK_TURTLE_EGG_BREAK = "block.turtle_egg.break";
	public const BLOCK_TURTLE_EGG_CRACK = "block.turtle_egg.crack";
	public const BLOCK_TURTLE_EGG_HATCH = "block.turtle_egg.hatch";
	public const BOOST = "boost";
	public const BORN = "born";
	public const BOTTLE_DRAGONBREATH = "bottle.dragonbreath";
	public const BOTTLE_EMPTY = "bottle.empty";
	public const BOTTLE_FILL = "bottle.fill";
	public const BOUNCE = "bounce";
	public const BOW = "bow";
	public const BOW_HIT = "bow.hit";
	public const BREAK = "break";
	public const BREAK_BLOCK = "break.block";
	public const BREAK_POT = "break_pot";
	public const BREATHE = "breathe";
	public const BREEZE_WIND_CHARGE_BURST = "breeze_wind_charge.burst";
	public const BRUSH = "brush";
	public const BRUSH_COMPLETED = "brush_completed";
	public const BUBBLE_DOWN = "bubble.down";
	public const BUBBLE_DOWNINSIDE = "bubble.downinside";
	public const BUBBLE_POP = "bubble.pop";
	public const BUBBLE_UP = "bubble.up";
	public const BUBBLE_UPINSIDE = "bubble.upinside";
	public const BUCKET_EMPTY_FISH = "bucket.empty.fish";
	public const BUCKET_EMPTY_LAND_ANIMAL = "bucket.empty.land_animal";
	public const BUCKET_EMPTY_LAVA = "bucket.empty.lava";
	public const BUCKET_EMPTY_POWDER_SNOW = "bucket.empty.powder_snow";
	public const BUCKET_EMPTY_WATER = "bucket.empty.water";
	public const BUCKET_FILL_FISH = "bucket.fill.fish";
	public const BUCKET_FILL_LAND_ANIMAL = "bucket.fill.land_animal";
	public const BUCKET_FILL_LAVA = "bucket.fill.lava";
	public const BUCKET_FILL_POWDER_SNOW = "bucket.fill.powder_snow";
	public const BUCKET_FILL_WATER = "bucket.fill.water";
	public const BULLET_HIT = "bullet.hit";
	public const BUNDLE_DROP_CONTENTS = "bundle.drop_contents";
	public const BUNDLE_INSERT = "bundle.insert";
	public const BUNDLE_INSERT_FAIL = "bundle.insert_fail";
	public const BUNDLE_REMOVE_ONE = "bundle.remove_one";
	public const BURP = "burp";
	public const BUTTON_CLICK_OFF = "button.click_off";
	public const BUTTON_CLICK_ON = "button.click_on";
	public const CAKE_ADD_CANDLE = "cake.add_candle";
	public const CAMERA_TAKE_PICTURE = "camera.take_picture";
	public const CANT_BREED = "cant_breed";
	public const CAST_SPELL = "cast.spell";
	public const CAULDRON_DRIP_LAVA_POINTED_DRIPSTONE = "cauldron_drip.lava.pointed_dripstone";
	public const CAULDRON_DRIP_WATER_POINTED_DRIPSTONE = "cauldron_drip.water.pointed_dripstone";
	public const CELEBRATE = "celebrate";
	public const CHARGE = "charge";
	public const CHARGE_SCULK = "charge.sculk";
	public const CHEST_CLOSED = "chest.closed";
	public const CHEST_OPEN = "chest.open";
	public const CHIME_AMETHYST_BLOCK = "chime.amethyst_block";
	public const CHORUSDEATH = "chorusdeath";
	public const CHORUSGROW = "chorusgrow";
	public const CLOSE = "close";
	public const CLOSE_LONG = "close_long";
	public const CONDUIT_ACTIVATE = "conduit.activate";
	public const CONDUIT_AMBIENT = "conduit.ambient";
	public const CONDUIT_ATTACK = "conduit.attack";
	public const CONDUIT_DEACTIVATE = "conduit.deactivate";
	public const CONDUIT_SHORT = "conduit.short";
	public const CONVERT_MOOSHROOM = "convert_mooshroom";
	public const CONVERT_TO_DROWNED = "convert_to_drowned";
	public const CONVERT_TO_FROG = "convert_to_frog";
	public const CONVERT_TO_STRAY = "convert_to_stray";
	public const CONVERTED_TO_ZOMBIFIED = "converted_to_zombified";
	public const COPPER_WAX_OFF = "copper.wax.off";
	public const COPPER_WAX_ON = "copper.wax.on";
	public const CRAFTER_CRAFT = "crafter.craft";
	public const CRAFTER_DISABLE_SLOT = "crafter.disable_slot";
	public const CRAFTER_FAIL = "crafter.fail";
	public const CREAKING_HEART_SPAWN = "creaking_heart_spawn";
	public const CROSSBOW_LOADING_END = "crossbow.loading.end";
	public const CROSSBOW_LOADING_MIDDLE = "crossbow.loading.middle";
	public const CROSSBOW_LOADING_START = "crossbow.loading.start";
	public const CROSSBOW_QUICK_CHARGE_END = "crossbow.quick_charge.end";
	public const CROSSBOW_QUICK_CHARGE_MIDDLE = "crossbow.quick_charge.middle";
	public const CROSSBOW_QUICK_CHARGE_START = "crossbow.quick_charge.start";
	public const CROSSBOW_SHOOT = "crossbow.shoot";
	public const DASH_READY = "dash_ready";
	public const DEACTIVATE = "deactivate";
	public const DEATH = "death";
	public const DEATH_BABY = "death.baby";
	public const DEATH_IN_WATER = "death.in.water";
	public const DEATH_MID_VOLUME = "death.mid.volume";
	public const DEATH_MIN_VOLUME = "death.min.volume";
	public const DEATH_SCREAMER = "death.screamer";
	public const DEATH_TO_ZOMBIE = "death.to.zombie";
	public const DEFAULT = "default";
	public const DENY = "deny";
	public const DETACH = "detach";
	public const DISAPPEARED = "disappeared";
	public const DISMOUNT = "dismount";
	public const DOOR_CLOSE = "door.close";
	public const DOOR_OPEN = "door.open";
	public const DRINK = "drink";
	public const DRINK_HONEY = "drink.honey";
	public const DRINK_MILK = "drink.milk";
	public const DRIP_LAVA_POINTED_DRIPSTONE = "drip.lava.pointed_dripstone";
	public const DRIP_WATER_POINTED_DRIPSTONE = "drip.water.pointed_dripstone";
	public const DROP_SLOT = "drop.slot";
	public const EAT = "eat";
	public const EJECT_BLOCK = "eject_block";
	public const ELDERGUARDIAN_CURSE = "elderguardian.curse";
	public const ELEMCONSTRUCT_OPEN = "elemconstruct.open";
	public const ENDERCHEST_CLOSED = "enderchest.closed";
	public const ENDERCHEST_OPEN = "enderchest.open";
	public const EXPLODE = "explode";
	public const EXTINGUISH_CANDLE = "extinguish.candle";
	public const EXTINGUISH_FIRE = "extinguish.fire";
	public const FALL = "fall";
	public const FALL_BIG = "fall.big";
	public const FALL_SMALL = "fall.small";
	public const FANG = "fang";
	public const FENCE_GATE_CLOSE = "fence_gate.close";
	public const FENCE_GATE_OPEN = "fence_gate.open";
	public const FIRE = "fire";
	public const FIZZ = "fizz";
	public const FLAP = "flap";
	public const FLOP = "flop";
	public const FLY = "fly";
	public const FREEZE = "freeze";
	public const FUSE = "fuse";
	public const GALLOP = "gallop";
	public const GEYSER_CONTINUOUS_ERUPTION_ACTIVE = "geyser_continuous_eruption_active";
	public const GEYSER_CONTINUOUS_ERUPTION_START = "geyser_continuous_eruption_start";
	public const GEYSER_ERUPTION_ACTIVE = "geyser_eruption_active";
	public const GEYSER_ERUPTION_START = "geyser_eruption_start";
	public const GLASS = "glass";
	public const GLOW_SQUID_INK_SQUIRT = "glow_squid.ink_squirt";
	public const GLOWSTICK_USE = "glowstick.use";
	public const GROWL = "growl";
	public const HAGGLE = "haggle";
	public const HAGGLE_IDLE = "haggle.idle";
	public const HAGGLE_NO = "haggle.no";
	public const HAGGLE_YES = "haggle.yes";
	public const HEARTBEAT = "heartbeat";
	public const HEAVY_STEP = "heavy.step";
	public const HIT = "hit";
	public const HORN_BREAK = "horn_break";
	public const HORN_CALL0 = "horn_call0";
	public const HORN_CALL1 = "horn_call1";
	public const HORN_CALL2 = "horn_call2";
	public const HORN_CALL3 = "horn_call3";
	public const HORN_CALL4 = "horn_call4";
	public const HORN_CALL5 = "horn_call5";
	public const HORN_CALL6 = "horn_call6";
	public const HORN_CALL7 = "horn_call7";
	public const HURT = "hurt";
	public const HURT_BABY = "hurt.baby";
	public const HURT_IN_WATER = "hurt.in.water";
	public const HURT_REDUCED = "hurt.reduced";
	public const HURT_SCREAMER = "hurt.screamer";
	public const ICEBOMB_HIT = "icebomb.hit";
	public const IGNITE = "ignite";
	public const IMITATE_BLAZE = "imitate.blaze";
	public const IMITATE_BOGGED = "imitate.bogged";
	public const IMITATE_BREEZE = "imitate.breeze";
	public const IMITATE_CAMEL_HUSK = "imitate.camel_husk";
	public const IMITATE_CAVE_SPIDER = "imitate.cave_spider";
	public const IMITATE_CREAKING = "imitate.creaking";
	public const IMITATE_CREEPER = "imitate.creeper";
	public const IMITATE_DROWNED = "imitate.drowned";
	public const IMITATE_ELDER_GUARDIAN = "imitate.elder_guardian";
	public const IMITATE_ENDER_DRAGON = "imitate.ender_dragon";
	public const IMITATE_ENDERMAN = "imitate.enderman";
	public const IMITATE_ENDERMITE = "imitate.endermite";
	public const IMITATE_EVOCATION_ILLAGER = "imitate.evocation_illager";
	public const IMITATE_GHAST = "imitate.ghast";
	public const IMITATE_GUARDIAN = "imitate.guardian";
	public const IMITATE_HAPPY_GHAST = "imitate.happy_ghast";
	public const IMITATE_HUSK = "imitate.husk";
	public const IMITATE_MAGMA_CUBE = "imitate.magma_cube";
	public const IMITATE_PARCHED = "imitate.parched";
	public const IMITATE_PHANTOM = "imitate.phantom";
	public const IMITATE_PILLAGER = "imitate.pillager";
	public const IMITATE_POLAR_BEAR = "imitate.polar_bear";
	public const IMITATE_RAVAGER = "imitate.ravager";
	public const IMITATE_SHULKER = "imitate.shulker";
	public const IMITATE_SILVERFISH = "imitate.silverfish";
	public const IMITATE_SKELETON = "imitate.skeleton";
	public const IMITATE_SLIME = "imitate.slime";
	public const IMITATE_SPIDER = "imitate.spider";
	public const IMITATE_STRAY = "imitate.stray";
	public const IMITATE_VEX = "imitate.vex";
	public const IMITATE_VINDICATION_ILLAGER = "imitate.vindication_illager";
	public const IMITATE_WARDEN = "imitate.warden";
	public const IMITATE_WITCH = "imitate.witch";
	public const IMITATE_WITHER = "imitate.wither";
	public const IMITATE_WITHER_SKELETON = "imitate.wither_skeleton";
	public const IMITATE_WOLF = "imitate.wolf";
	public const IMITATE_ZOGLIN = "imitate.zoglin";
	public const IMITATE_ZOMBIE = "imitate.zombie";
	public const IMITATE_ZOMBIE_PIGMAN = "imitate.zombie_pigman";
	public const IMITATE_ZOMBIE_VILLAGER = "imitate.zombie_villager";
	public const INSERT = "insert";
	public const INSERT_ENCHANTED = "insert_enchanted";
	public const IRONGOLEM_CRACK = "irongolem.crack";
	public const IRONGOLEM_REPAIR = "irongolem.repair";
	public const ITEM_BOOK_PUT = "item.book.put";
	public const ITEM_COPPER_SPEAR_ATTACK_HIT = "item.copper_spear.attack_hit";
	public const ITEM_COPPER_SPEAR_ATTACK_MISS = "item.copper_spear.attack_miss";
	public const ITEM_COPPER_SPEAR_USE = "item.copper_spear.use";
	public const ITEM_DIAMOND_SPEAR_ATTACK_HIT = "item.diamond_spear.attack_hit";
	public const ITEM_DIAMOND_SPEAR_ATTACK_MISS = "item.diamond_spear.attack_miss";
	public const ITEM_DIAMOND_SPEAR_USE = "item.diamond_spear.use";
	public const ITEM_ENCHANT_LUNGE1 = "item.enchant.lunge1";
	public const ITEM_ENCHANT_LUNGE2 = "item.enchant.lunge2";
	public const ITEM_ENCHANT_LUNGE3 = "item.enchant.lunge3";
	public const ITEM_FIZZ = "item.fizz";
	public const ITEM_GOLDEN_SPEAR_ATTACK_HIT = "item.golden_spear.attack_hit";
	public const ITEM_GOLDEN_SPEAR_ATTACK_MISS = "item.golden_spear.attack_miss";
	public const ITEM_GOLDEN_SPEAR_USE = "item.golden_spear.use";
	public const ITEM_IRON_SPEAR_ATTACK_HIT = "item.iron_spear.attack_hit";
	public const ITEM_IRON_SPEAR_ATTACK_MISS = "item.iron_spear.attack_miss";
	public const ITEM_IRON_SPEAR_USE = "item.iron_spear.use";
	public const ITEM_NETHERITE_SPEAR_ATTACK_HIT = "item.netherite_spear.attack_hit";
	public const ITEM_NETHERITE_SPEAR_ATTACK_MISS = "item.netherite_spear.attack_miss";
	public const ITEM_NETHERITE_SPEAR_USE = "item.netherite_spear.use";
	public const ITEM_SHIELD_BLOCK = "item.shield.block";
	public const ITEM_SPEAR_ATTACK_HIT = "item.spear.attack_hit";
	public const ITEM_SPEAR_ATTACK_MISS = "item.spear.attack_miss";
	public const ITEM_SPEAR_USE = "item.spear.use";
	public const ITEM_SPYGLASS_STOP_USING = "item.spyglass.stop_using";
	public const ITEM_SPYGLASS_USE = "item.spyglass.use";
	public const ITEM_STONE_SPEAR_ATTACK_HIT = "item.stone_spear.attack_hit";
	public const ITEM_STONE_SPEAR_ATTACK_MISS = "item.stone_spear.attack_miss";
	public const ITEM_STONE_SPEAR_USE = "item.stone_spear.use";
	public const ITEM_TRIDENT_HIT = "item.trident.hit";
	public const ITEM_TRIDENT_HIT_GROUND = "item.trident.hit_ground";
	public const ITEM_TRIDENT_RETURN = "item.trident.return";
	public const ITEM_TRIDENT_RIPTIDE_1 = "item.trident.riptide_1";
	public const ITEM_TRIDENT_RIPTIDE_2 = "item.trident.riptide_2";
	public const ITEM_TRIDENT_RIPTIDE_3 = "item.trident.riptide_3";
	public const ITEM_TRIDENT_THROW = "item.trident.throw";
	public const ITEM_TRIDENT_THUNDER = "item.trident.thunder";
	public const ITEM_USE_ON = "item.use.on";
	public const ITEM_WOODEN_SPEAR_ATTACK_HIT = "item.wooden_spear.attack_hit";
	public const ITEM_WOODEN_SPEAR_ATTACK_MISS = "item.wooden_spear.attack_miss";
	public const ITEM_WOODEN_SPEAR_USE = "item.wooden_spear.use";
	public const ITEM_GIVEN = "item_given";
	public const ITEM_TAKEN = "item_taken";
	public const ITEM_THROWN = "item_thrown";
	public const JUMP = "jump";
	public const JUMP_PREVENT = "jump.prevent";
	public const JUMP_TO_BLOCK = "jump_to_block";
	public const LAND = "land";
	public const LARGE_BLAST = "large.blast";
	public const LAUNCH = "launch";
	public const LAVA = "lava";
	public const LAVA_POP = "lava.pop";
	public const LAY_EGG = "lay_egg";
	public const LAY_SPAWN = "lay_spawn";
	public const LEAD_BREAK = "lead.break";
	public const LEAD_LEASH = "lead.leash";
	public const LEAD_UNLEASH = "lead.unleash";
	public const LEASHKNOT_BREAK = "leashknot.break";
	public const LEASHKNOT_PLACE = "leashknot.place";
	public const LEVELUP = "levelup";
	public const LISTENING = "listening";
	public const LISTENING_ANGRY = "listening_angry";
	public const LODESTONE_COMPASS_LINK_COMPASS_TO_LODESTONE = "lodestone_compass.link_compass_to_lodestone";
	public const LT_REACTION_BLEACH = "lt.reaction.bleach";
	public const LT_REACTION_EPASTE = "lt.reaction.epaste";
	public const LT_REACTION_EPASTE2 = "lt.reaction.epaste2";
	public const LT_REACTION_FERTILIZER = "lt.reaction.fertilizer";
	public const LT_REACTION_FIRE = "lt.reaction.fire";
	public const LT_REACTION_FIREBALL = "lt.reaction.fireball";
	public const LT_REACTION_ICEBOMB = "lt.reaction.icebomb";
	public const LT_REACTION_MGSALT = "lt.reaction.mgsalt";
	public const LT_REACTION_MISCEXPLOSION = "lt.reaction.miscexplosion";
	public const LT_REACTION_MISCFIRE = "lt.reaction.miscfire";
	public const LT_REACTION_MISCMYSTICAL = "lt.reaction.miscmystical";
	public const LT_REACTION_MISCMYSTICAL2 = "lt.reaction.miscmystical2";
	public const LT_REACTION_PRODUCT = "lt.reaction.product";
	public const MACE_HEAVY_SMASH_GROUND = "mace.heavy_smash_ground";
	public const MACE_SMASH_AIR = "mace.smash_air";
	public const MACE_SMASH_GROUND = "mace.smash_ground";
	public const MAD = "mad";
	public const MILK = "milk";
	public const MILK_SCREAMER = "milk.screamer";
	public const MILK_SUSPICIOUSLY = "milk_suspiciously";
	public const MOB_ARMADILLO_BRUSH = "mob.armadillo.brush";
	public const MOB_ARMADILLO_SCUTE_DROP = "mob.armadillo.scute_drop";
	public const MOB_ARMOR_STAND_PLACE = "mob.armor_stand.place";
	public const MOB_HOGLIN_CONVERTED_TO_ZOMBIFIED = "mob.hoglin.converted_to_zombified";
	public const MOB_HUSK_CONVERT_TO_ZOMBIE = "mob.husk.convert_to_zombie";
	public const MOB_PIG_DEATH = "mob.pig.death";
	public const MOB_PLAYER_HURT_DROWN = "mob.player.hurt_drown";
	public const MOB_PLAYER_HURT_FREEZE = "mob.player.hurt_freeze";
	public const MOB_PLAYER_HURT_ON_FIRE = "mob.player.hurt_on_fire";
	public const MOB_WARNING = "mob.warning";
	public const MOB_WARNING_BABY = "mob.warning.baby";
	public const MOUNT = "mount";
	public const MULTI_SWAP = "multi_swap";
	public const NEARBY_CLOSE = "nearby_close";
	public const NEARBY_CLOSER = "nearby_closer";
	public const NEARBY_CLOSEST = "nearby_closest";
	public const NOTE = "note";
	public const NOTE_BASS = "note.bass";
	public const OMINOUS_BOTTLE_END_USE = "ominous_bottle.end_use";
	public const OMINOUS_ITEM_SPAWNER_ABOUT_TO_SPAWN_ITEM = "ominous_item_spawner.about_to_spawn_item";
	public const OMINOUS_ITEM_SPAWNER_SPAWN_ITEM = "ominous_item_spawner.spawn_item";
	public const OMINOUS_ITEM_SPAWNER_SPAWN_ITEM_BEGIN = "ominous_item_spawner.spawn_item_begin";
	public const OPEN = "open";
	public const OPEN_LONG = "open_long";
	public const PANIC = "panic";
	public const PANT = "pant";
	public const PARTICLE_SOUL_ESCAPE_LOUD = "particle.soul_escape.loud";
	public const PARTICLE_SOUL_ESCAPE_QUIET = "particle.soul_escape.quiet";
	public const PAUSE_GROWTH = "pause_growth";
	public const PICK_BERRIES_CAVE_VINES = "pick_berries.cave_vines";
	public const PICKUP = "pickup";
	public const PICKUP_ENCHANTED = "pickup_enchanted";
	public const PISTON_IN = "piston.in";
	public const PISTON_OUT = "piston.out";
	public const PLACE = "place";
	public const PLACE_IN_WATER = "place_in_water";
	public const PLACE_ITEM = "place_item";
	public const PLOP = "plop";
	public const POP = "pop";
	public const PORTAL = "portal";
	public const PORTAL_TRAVEL = "portal.travel";
	public const POTION_BREWED = "potion.brewed";
	public const POWER_OFF = "power.off";
	public const POWER_OFF_SCULK_SENSOR = "power.off.sculk_sensor";
	public const POWER_ON = "power.on";
	public const POWER_ON_SCULK_SENSOR = "power.on.sculk_sensor";
	public const PRE_RAM = "pre_ram";
	public const PRE_RAM_SCREAMER = "pre_ram.screamer";
	public const PREPARE_ATTACK = "prepare.attack";
	public const PREPARE_SUMMON = "prepare.summon";
	public const PREPARE_WOLOLO = "prepare.wololo";
	public const PRESNEEZE = "presneeze";
	public const PRESSURE_PLATE_CLICK_OFF = "pressure_plate.click_off";
	public const PRESSURE_PLATE_CLICK_ON = "pressure_plate.click_on";
	public const PUMPKIN_CARVE = "pumpkin.carve";
	public const PURR = "purr";
	public const PURREOW = "purreow";
	public const PUSHED_BY_PLAYER = "pushed_by_player";
	public const RAID_HORN = "raid.horn";
	public const RAM_IMPACT = "ram_impact";
	public const RAM_IMPACT_SCREAMER = "ram_impact.screamer";
	public const RANDOM_ANVIL_USE = "random.anvil_use";
	public const REAPPEARED = "reappeared";
	public const RECORD_11 = "record.11";
	public const RECORD_13 = "record.13";
	public const RECORD_5 = "record.5";
	public const RECORD_BLOCKS = "record.blocks";
	public const RECORD_BOUNCE = "record.bounce";
	public const RECORD_CAT = "record.cat";
	public const RECORD_CHIRP = "record.chirp";
	public const RECORD_CREATOR = "record.creator";
	public const RECORD_CREATOR_MUSIC_BOX = "record.creator_music_box";
	public const RECORD_FAR = "record.far";
	public const RECORD_LAVA_CHICKEN = "record.lava_chicken";
	public const RECORD_MALL = "record.mall";
	public const RECORD_MELLOHI = "record.mellohi";
	public const RECORD_NULL = "record.null";
	public const RECORD_OTHERSIDE = "record.otherside";
	public const RECORD_PIGSTEP = "record.pigstep";
	public const RECORD_PRECIPICE = "record.precipice";
	public const RECORD_RELIC = "record.relic";
	public const RECORD_STAL = "record.stal";
	public const RECORD_STRAD = "record.strad";
	public const RECORD_TEARS = "record.tears";
	public const RECORD_WAIT = "record.wait";
	public const RECORD_WARD = "record.ward";
	public const REFLECT = "reflect";
	public const REMEDY = "remedy";
	public const RESET_GROWTH = "reset_growth";
	public const RESPAWN_ANCHOR_AMBIENT = "respawn_anchor.ambient";
	public const RESPAWN_ANCHOR_CHARGE = "respawn_anchor.charge";
	public const RESPAWN_ANCHOR_DEPLETE = "respawn_anchor.deplete";
	public const RESPAWN_ANCHOR_SET_SPAWN = "respawn_anchor.set_spawn";
	public const RETREAT = "retreat";
	public const ROAR = "roar";
	public const SADDLE = "saddle";
	public const SADDLE_IN_WATER = "saddle_in_water";
	public const SCARED = "scared";
	public const SCRAPE = "scrape";
	public const SCREECH = "screech";
	public const SHAKE = "shake";
	public const SHATTER_POT = "shatter_pot";
	public const SHEAR = "shear";
	public const SHOOT = "shoot";
	public const SHULKER_CLOSE = "shulker.close";
	public const SHULKER_OPEN = "shulker.open";
	public const SHULKERBOX_CLOSED = "shulkerbox.closed";
	public const SHULKERBOX_OPEN = "shulkerbox.open";
	public const SINGLE_SWAP = "single_swap";
	public const SLEEP = "sleep";
	public const SLIME_LANDING = "slime_landing";
	public const SMITHING_TABLE_USE = "smithing_table.use";
	public const SNEEZE = "sneeze";
	public const SONIC_BOOM = "sonic_boom";
	public const SONIC_CHARGE = "sonic_charge";
	public const SPARKLER_ACTIVE = "sparkler.active";
	public const SPARKLER_USE = "sparkler.use";
	public const SPAWN = "spawn";
	public const SPLASH = "splash";
	public const SPONGE_ABSORB = "sponge.absorb";
	public const SQUID_INK_SQUIRT = "squid.ink_squirt";
	public const SQUISH_BIG = "squish.big";
	public const SQUISH_SMALL = "squish.small";
	public const STARE = "stare";
	public const STATE_CHANGE = "state_change";
	public const STEP = "step";
	public const STEP_BABY = "step.baby";
	public const STEP_LAVA = "step_lava";
	public const STEP_SAND = "step_sand";
	public const STRAW_BED_BREAK_LEAVE = "straw_bed.break_leave";
	public const STUN = "stun";
	public const SWIM = "swim";
	public const SWOOP = "swoop";
	public const TAKEOFF = "takeoff";
	public const TELEPORT = "teleport";
	public const TEMPT = "tempt";
	public const THORNS = "thorns";
	public const THROW = "throw";
	public const THUNDER = "thunder";
	public const TILT_DOWN_BIG_DRIPLEAF = "tilt_down.big_dripleaf";
	public const TILT_UP_BIG_DRIPLEAF = "tilt_up.big_dripleaf";
	public const TONGUE = "tongue";
	public const TRAPDOOR_CLOSE = "trapdoor.close";
	public const TRAPDOOR_OPEN = "trapdoor.open";
	public const TRIAL_SPAWNER_AMBIENT = "trial_spawner.ambient";
	public const TRIAL_SPAWNER_AMBIENT_OMINOUS = "trial_spawner.ambient_ominous";
	public const TRIAL_SPAWNER_CHARGE_ACTIVATE = "trial_spawner.charge_activate";
	public const TRIAL_SPAWNER_CLOSE_SHUTTER = "trial_spawner.close_shutter";
	public const TRIAL_SPAWNER_DETECT_PLAYER = "trial_spawner.detect_player";
	public const TRIAL_SPAWNER_EJECT_ITEM = "trial_spawner.eject_item";
	public const TRIAL_SPAWNER_OPEN_SHUTTER = "trial_spawner.open_shutter";
	public const TRIAL_SPAWNER_SPAWN_MOB = "trial_spawner.spawn_mob";
	public const TRIPOD = "tripod";
	public const TWINKLE = "twinkle";
	public const UI_CARTOGRAPHY_TABLE_TAKE_RESULT = "ui.cartography_table.take_result";
	public const UI_LOOM_TAKE_RESULT = "ui.loom.take_result";
	public const UI_STONECUTTER_TAKE_RESULT = "ui.stonecutter.take_result";
	public const UNDEFINED = "undefined";
	public const UNFECT = "unfect";
	public const UNFREEZE = "unfreeze";
	public const UNSADDLE = "unsaddle";
	public const VAULT_ACTIVATE = "vault.activate";
	public const VAULT_AMBIENT = "vault.ambient";
	public const VAULT_CLOSE_SHUTTER = "vault.close_shutter";
	public const VAULT_DEACTIVATE = "vault.deactivate";
	public const VAULT_EJECT_ITEM = "vault.eject_item";
	public const VAULT_INSERT_ITEM = "vault.insert_item";
	public const VAULT_INSERT_ITEM_FAIL = "vault.insert_item_fail";
	public const VAULT_OPEN_SHUTTER = "vault.open_shutter";
	public const VAULT_REJECT_REWARDED_PLAYER = "vault.reject_rewarded_player";
	public const WARN = "warn";
	public const WATER = "water";
	public const WHINE = "whine";
	public const WIND_CHARGE_BURST = "wind_charge.burst";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use function strtolower;

enum ItemRarity : string
{
	case COMMON = "common";
	case UNCOMMON = "uncommon";
	case RARE = "rare";
	case EPIC = "epic";

	/**
	 * The name of the rarity.
	 */
	public function getName() : string
	{
		return $this->name;
	}

	/**
	 * The value of the rarity.
	 */
	public function getValue() : string
	{
		return $this->value;
	}

	/**
	 * Create an ItemRarity from a string name.
	 */
	public static function fromName(string $name) : self
	{
		return match (strtolower($name)) {
			'common' => self::COMMON,
			'uncommon' => self::UNCOMMON,
			'rare' => self::RARE,
			'epic' => self::EPIC,
			default => throw new \InvalidArgumentException("Invalid item rarity name: $name"),
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

class DurabilityThreshold
{
	public function __construct(
		protected int $durability,
		protected ?string $sound = null,
		protected ?string $particle = null,
	) {
	}

	public function getDurability() : int
	{
		return $this->durability;
	}

	public function getSound() : ?string
	{
		return $this->sound;
	}

	public function getParticle() : ?string
	{
		return $this->particle;
	}

	public function toNBT() : CompoundTag
	{
		$nbt = CompoundTag::create()
			->setTag("durability", new IntTag($this->durability));

		if ($this->sound !== null) {
			$nbt->setTag("sound", new StringTag($this->sound));
		}

		if ($this->particle !== null) {
			$nbt->setTag("particle", new StringTag($this->particle));
		}

		return $nbt;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

enum ItemUseActionType : int
{
	case CHORUS_TELEPORT = 0;
	case SUSPICIOUS_STEW = 1;
	case NONE = 2;

	/**
	 * Get the integer value of the use action type.
	 */
	public function getValue() : int
	{
		return $this->value;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

/**
 * Bedrock built-in particle type values, documentation snapshot 2026-09-23.
 * Availability depends on the client version.
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_durability_sensor
 */
class ParticleType
{
	public const BALLOONGAS = "balloongas";
	public const BLEACH = "bleach";
	public const BLOCKFORCEFIELD = "blockforcefield";
	public const BLUEFLAME = "blueflame";
	public const BREEZEWINDEXPLOSION = "breezewindexplosion";
	public const BUBBLE = "bubble";
	public const BUBBLECOLUMNDOWN = "bubblecolumndown";
	public const BUBBLECOLUMNUP = "bubblecolumnup";
	public const BUBBLEMANUAL = "bubblemanual";
	public const CAMPFIRESMOKE = "campfiresmoke";
	public const CAMPFIRESMOKETALL = "campfiresmoketall";
	public const CANDLEFLAME = "candleflame";
	public const CARROTBOOST = "carrotboost";
	public const COLOREDFLAME = "coloredflame";
	public const CONDUIT = "conduit";
	public const CREAKINGCRUMBLE = "creakingcrumble";
	public const CRIT = "crit";
	public const DRAGONBREATH = "dragonbreath";
	public const DRAGONBREATHFIRE = "dragonbreathfire";
	public const DRAGONBREATHTRAIL = "dragonbreathtrail";
	public const DRAGONDESTROYBLOCK = "dragondestroyblock";
	public const DRIPHONEY = "driphoney";
	public const DRIPLAVA = "driplava";
	public const DRIPWATER = "dripwater";
	public const DUSTPLUME = "dustplume";
	public const ELECTRICSPARK = "electricspark";
	public const ENCHANTINGTABLE = "enchantingtable";
	public const ENDROD = "endrod";
	public const EVAPORATION = "evaporation";
	public const EXPLODE = "explode";
	public const EYEBLOSSOMCLOSE = "eyeblossomclose";
	public const EYEBLOSSOMOPEN = "eyeblossomopen";
	public const FALLINGBORDERDUST = "fallingborderdust";
	public const FALLINGDUST = "fallingdust";
	public const FIREWORKS = "fireworks";
	public const FIREWORKSOVERLAY = "fireworksoverlay";
	public const FIREWORKSSTARTER = "fireworksstarter";
	public const FLAME = "flame";
	public const FOOD = "food";
	public const GREENFLAME = "greenflame";
	public const HEART = "heart";
	public const HUGEEXPLOSION = "hugeexplosion";
	public const ICONCRACK = "iconcrack";
	public const INK = "ink";
	public const LARGEEXPLODE = "largeexplode";
	public const LARGESMOKE = "largesmoke";
	public const LAVA = "lava";
	public const MOBAPPEARANCE = "mobappearance";
	public const MOBFLAME = "mobflame";
	public const MOBSPELL = "mobspell";
	public const MOBSPELLAMBIENT = "mobspellambient";
	public const MOBSPELLINSTANTANEOUS = "mobspellinstantaneous";
	public const MYCELIUMDUST = "myceliumdust";
	public const NONE = "none";
	public const NOTE = "note";
	public const OBSIDIANTEAR = "obsidiantear";
	public const ORANGEPOPLARLEAVES = "orangepoplarleaves";
	public const PALEOAKLEAVES = "paleoakleaves";
	public const PAUSEMOBGROWTH = "pausemobgrowth";
	public const PORTAL = "portal";
	public const PORTALREVERSE = "portalreverse";
	public const RAINSPLASH = "rainsplash";
	public const REDDUST = "reddust";
	public const REDPOPLARLEAVES = "redpoplarleaves";
	public const RESETMOBGROWTH = "resetmobgrowth";
	public const RISINGBORDERDUST = "risingborderdust";
	public const SCULKSOUL = "sculksoul";
	public const SHRIEK = "shriek";
	public const SHULKERBULLET = "shulkerbullet";
	public const SLIME = "slime";
	public const SMOKE = "smoke";
	public const SNEEZE = "sneeze";
	public const SNOWBALLPOOF = "snowballpoof";
	public const SNOWFLAKE = "snowflake";
	public const SONICEXPLOSION = "sonicexplosion";
	public const SOUL = "soul";
	public const SPARKLER = "sparkler";
	public const SPIT = "spit";
	public const STALACTITEDRIPLAVA = "stalactitedriplava";
	public const STALACTITEDRIPWATER = "stalactitedripwater";
	public const SULFURCUBE = "sulfurcube";
	public const TERRAIN = "terrain";
	public const TOTEM = "totem";
	public const TOWNAURA = "townaura";
	public const TRACKINGEMITTER = "trackingemitter";
	public const VAULTCONNECTION = "vaultconnection";
	public const VILLAGERANGRY = "villagerangry";
	public const VILLAGERHAPPY = "villagerhappy";
	public const WATERSPLASH = "watersplash";
	public const WATERSPLASHMANUAL = "watersplashmanual";
	public const WATERWAKE = "waterwake";
	public const WAX = "wax";
	public const WHITESMOKE = "whitesmoke";
	public const WINDEXPLOSION = "windexplosion";
	public const WITCHSPELL = "witchspell";
	public const WOLFARMORCRACK = "wolfarmorcrack";
	public const YELLOWPOPLARLEAVES = "yellowpoplarleaves";
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\item\Item;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use function array_map;
use function is_double;
use function strval;

class ItemRepair
{
	/**
	 * @param string[] $items
	 */
	public function __construct(
		protected float|string   $amount,
		protected array $items,
	) {
		if (is_double($this->amount)) {
			$this->amount = strval($this->amount);
		}
	}

	/**
	 * Create an ItemRepair component from an amount and a list of items.
	 */
	public static function from(float|string $amount, Item ...$items) : ItemRepair
	{
		return new self(
			$amount,
			array_map(function (Item $item) : string {
				[$rid] = ($converter = TypeConverter::getInstance())->getItemTranslator()->toNetworkId($item);
				return $converter->getItemTypeDictionary()->fromIntId($rid);
			}, $items)
		);
	}

	public function getAmount() : float|string
	{
		return $this->amount;
	}

	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag(
				"repair_amount",
				CompoundTag::create()
				->setTag("expression", new StringTag($this->amount))
				->setTag("version", new IntTag(0))
			)
			->setTag("items", new ListTag(array_map(fn (string $stringId) => new StringTag($stringId), $this->items), NBT::TAG_String));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\item\Armor;
use pocketmine\item\Item;
use pocketmine\item\VanillaArmorMaterials;

enum OreTextureType : string
{
	case NONE = "none";
	case CHAIN = "chain";
	case COPPER = "copper";
	case DIAMOND = "diamond";
	case ELYTRA = "elytra";
	case GOLD = "gold";
	case IRON = "iron";
	case LEATHER = "leather";
	case NETHERITE = "netherite";
	case TURTLE = "turtle";

	/**
	 * Get the string value of the OreTextureType.
	 */
	public function getValue() : string
	{
		return $this->value;
	}

	/**
	 * Get the OreTextureType from an Item instance.
	 */
	public static function fromItem(Item $item) : OreTextureType
	{
		if ($item instanceof Armor) {
			return match (true) {
				$item->getMaterial() === VanillaArmorMaterials::LEATHER() => self::LEATHER,
				$item->getMaterial() === VanillaArmorMaterials::CHAINMAIL() => self::CHAIN,
				$item->getMaterial() === VanillaArmorMaterials::COPPER() => self::COPPER,
				$item->getMaterial() === VanillaArmorMaterials::IRON() => self::IRON,
				$item->getMaterial() === VanillaArmorMaterials::GOLD() => self::GOLD,
				$item->getMaterial() === VanillaArmorMaterials::DIAMOND() => self::DIAMOND,
				$item->getMaterial() === VanillaArmorMaterials::NETHERITE() => self::NETHERITE,
				$item->getMaterial() === VanillaArmorMaterials::TURTLE() => self::TURTLE,
				default => self::NONE,
			};
		}
		return self::NONE;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven\Types;

use pocketmine\inventory\ArmorInventory;
use pocketmine\item\Armor;
use pocketmine\item\Axe;
use pocketmine\item\Bow;
use pocketmine\item\FishingRod;
use pocketmine\item\Hoe;
use pocketmine\item\Item;
use pocketmine\item\Pickaxe;
use pocketmine\item\Shears;
use pocketmine\item\Shovel;
use pocketmine\item\Sword;

enum ItemEnchantSlot : string
{
	case SWORD = "sword";
	case HOE = "hoe";
	case SHOVEL = "shovel";
	case PICKAXE = "pickaxe";
	case AXE = "axe";
	case BOW = "bow";
	case CROSSBOW = "crossbow";
	case ELYTRA = "elytra";
	case FISHING_ROD = "fishing_rod";
	case SHEARS = "shears";
	case SHIELD = "shield";
	case FLINTSTEEL = "flintsteel";

	case ARMOR_HEAD = "armor_head";
	case ARMOR_TORSO = "armor_torso";
	case ARMOR_LEGS = "armor_legs";
	case ARMOR_FEET = "armor_feet";

	case ALL = "all";

	/**
	 * The name of the enchantment slot.
	 */
	public function getName() : string
	{
		return $this->name;
	}

	/**
	 * The value of the enchantment slot.
	 */
	public function getValue() : string
	{
		return $this->value;
	}

	/**
	 * Get the enchantment slot from armor type info.
	 */
	public static function fromArmorTypeInfo(int $slotArmor) : ItemEnchantSlot
	{
		return match ($slotArmor) {
			ArmorInventory::SLOT_HEAD => self::ARMOR_HEAD,
			ArmorInventory::SLOT_CHEST => self::ARMOR_TORSO,
			ArmorInventory::SLOT_LEGS => self::ARMOR_LEGS,
			ArmorInventory::SLOT_FEET => self::ARMOR_FEET,
			default => self::ALL
		};
	}

	/**
	 * Get the enchantment slot from an item instance.
	 */
	public static function fromItem(Item $item) : ItemEnchantSlot
	{
		return match (true) {
			$item instanceof Sword => ItemEnchantSlot::SWORD,
			$item instanceof Pickaxe => ItemEnchantSlot::PICKAXE,
			$item instanceof Axe => ItemEnchantSlot::AXE,
			$item instanceof Hoe => ItemEnchantSlot::HOE,
			$item instanceof Shovel => ItemEnchantSlot::SHOVEL,
			$item instanceof Bow => ItemEnchantSlot::BOW,
			//$item instanceof Crossbow => ItemEnchantSlot::CROSSBOW, TODO: Add CrossBow class
			//$item instanceof Elytra => ItemEnchantSlot::ELYTRA, TODO: Add Elytra class
			$item instanceof FishingRod => ItemEnchantSlot::FISHING_ROD,
			$item instanceof Shears => ItemEnchantSlot::SHEARS,
			//$item instanceof Shield => ItemEnchantSlot::SHIELD, TODO: Add Shield class
			$item instanceof Armor => self::fromArmorTypeInfo($item->getArmorSlot()),
			default => ItemEnchantSlot::ALL
		};
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class CooldownItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $duration,
		protected string $category = ""
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::COOLDOWN->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("duration", new FloatTag($this->duration))
			->setTag("category", new StringTag($this->category));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Items\Components\DataDriven\Types\ItemEnchantSlot;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;

#[Attribute(Attribute::TARGET_CLASS)]
class EnchantableItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected ItemEnchantSlot $slot,
		protected int $value,
	) {
		if ($value < 0) {
			throw new \InvalidArgumentException("Value must be non-negative");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::ENCHANTABLE->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("slot", new StringTag($this->slot->getName()))
			->setTag("value", new IntTag($this->value));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class DataDriven
{
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class BundleInteractionItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $capacity,
	) {
		if ($this->capacity < 1 || $this->capacity > 64) {
			throw new \InvalidArgumentException("Capacity must be between 1 and 64");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::BUNDLE_INTERACTION->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("num_viewable_slots", new IntTag($this->capacity));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Blocks\Components\Types\NbtDescriptor;
use pocketmine\block\Block;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use function array_map;
use function is_string;

#[Attribute(Attribute::TARGET_CLASS)]
class BlockPlacerItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected string $block,
		protected array $useOn = [],
		protected bool $canUseBlockAsIcon = false,
		protected bool $replaceBlockItem = false,
		protected bool $alignedPlacement = false
	) {
		foreach ($this->useOn as $k => $useOn) {
			if (is_string($useOn)) {
				$this->useOn[$k] = new NbtDescriptor($useOn);
			}
		}
	}

	public static function make(Block $block, array $useOn = [], bool $canUseBlockAsIcon = false, bool $replaceBlockItem = false, bool $alignedPlacement = false) : self
	{
		return new self(
			GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(),
			array_map(fn (Block $b) => GlobalBlockStateHandlers::getSerializer()->serialize($b->getStateId())->getName(), $useOn),
			$canUseBlockAsIcon,
			$replaceBlockItem,
			$alignedPlacement
		);
	}

	/**
	 * Create a BlockPlacerItemComponent from a Block instance.
	 *
	 * @return $this
	 */
	public static function from(Block $block, Block ...$useOn) : self
	{
		return new self(
			GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(),
			array_map(fn (Block $b) => GlobalBlockStateHandlers::getSerializer()->serialize($b->getStateId())->getName(), $useOn)
		);
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::BLOCK_PLACER->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("block", new StringTag($this->block))
			->setTag("use_on", new ListTag(array_map(fn (NbtDescriptor $block) : CompoundTag => $block->toNBT(), $this->useOn), NBT::TAG_Compound))
			->setTag("canUseBlockAsIcon", new ByteTag($this->canUseBlockAsIcon))
			->setTag("replace_block_item", new ByteTag($this->replaceBlockItem))
			->setTag("aligned_placement", new ByteTag($this->alignedPlacement));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

/**
 * @see https://learn.microsoft.com/en-us/minecraft/creator/reference/content/itemreference/examples/itemcomponents/minecraft_piercing_weapon?view=minecraft-bedrock-stable
 * @since 1.21.120
 * @internal
 */
#[Attribute(Attribute::TARGET_CLASS)]
class PiercingWeaponItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected float $hitboxMargin = 0,
		protected int   $min = 0,
		protected int   $max = 3,
	) {
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::PIERCING_WEAPON->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("hitbox_margin", new IntTag($this->hitboxMargin))
			->setTag(
				"reach",
				CompoundTag::create()
				->setTag("min", new IntTag($this->min))
				->setTag("max", new IntTag($this->max))
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use Nexly\Blocks\Components\Types\NbtDescriptor;
use pocketmine\block\Block;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use function array_map;

#[Attribute(Attribute::TARGET_CLASS)]
class EntityPlacerItemComponent extends DataDrivenItemComponent
{
	/**
	 * @param NbtDescriptor[] $dispenseOn
	 * @param NbtDescriptor[] $useOn
	 */
	public function __construct(
		protected string $entity = "none",
		protected array $dispenseOn = [],
		protected array $useOn = [],
	) {
	}

	/**
	 * Create an EntityPlacerItemComponent from the given parameters.
	 *
	 * @param Block[] $dispenseOn
	 * @param Block[] $useOn
	 */
	public static function from(string $entity, array $dispenseOn = [], array $useOn = []) : self
	{
		return new self(
			$entity,
			array_map(fn (Block $block) => GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(), $dispenseOn),
			array_map(fn (Block $block) => GlobalBlockStateHandlers::getSerializer()->serialize($block->getStateId())->getName(), $useOn)
		);
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::ENTITY_PLACER->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("entity", new StringTag($this->entity))
			->setTag("dispense_on", new ListTag(array_map(fn (NbtDescriptor $descriptor) => $descriptor->toNBT(), $this->dispenseOn), NBT::TAG_Compound))
			->setTag("use_on", new ListTag(array_map(fn (NbtDescriptor $descriptor) => $descriptor->toNBT(), $this->useOn), NBT::TAG_Compound));
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items\Components\DataDriven;

use Attribute;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;

#[Attribute(Attribute::TARGET_CLASS)]
class DurabilityItemComponent extends DataDrivenItemComponent
{
	public function __construct(
		protected int $durability,
		protected int $min = 0,
		protected int $max = 0,
	) {
		if ($durability < 1) {
			throw new \InvalidArgumentException("Durability must be at least 1");
		}

		if ($min < 0 || $max < 0) {
			throw new \InvalidArgumentException("Min and Max damage chance must be at least 0");
		}

		if ($min > $max) {
			throw new \InvalidArgumentException("Min damage chance cannot be greater than Max damage chance");
		}

		if ($max > 100) {
			throw new \InvalidArgumentException("Max damage chance cannot be greater than 100");
		}
	}

	/**
	 * The name of the component.
	 */
	public static function getName() : string
	{
		return DataDrivenComponentIds::DURABILITY->getValue();
	}

	/**
	 * Build the NBT tag for this component.
	 */
	public function toNBT() : CompoundTag
	{
		return CompoundTag::create()
			->setTag("max_durability", new IntTag($this->durability))
			->setTag(
				"damage_chance",
				CompoundTag::create()
				->setTag("min", new IntTag($this->min))
				->setTag("max", new IntTag($this->max))
			);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Items;

use Nexly\Items\Components\DataDriven\DataDriven;
use pocketmine\item\Armor;
use pocketmine\item\Dye;
use pocketmine\item\Item;
use pocketmine\item\ProjectileItem;
use pocketmine\item\Record;
use pocketmine\item\SpawnEgg;
use ReflectionClass;
use ReflectionException;
use function count;

enum ItemVersion : int
{
	case LEGACY = 0;
	case DATA_DRIVEN = 1;
	case NONE = 2;

	/**
	 * Get the integer value of the ItemVersion.
	 */
	public function getValue() : int
	{
		return $this->value;
	}

	/**
	 * Check if two ItemVersion instances are equal.
	 */
	public function equals(self $other) : bool
	{
		return $this === $other;
	}

	/**
	 * Get the ItemVersion from an Item instance.
	 */
	public static function fromItem(Item $item) : self
	{
		try {
			$reflection = new ReflectionClass($item::class);
		} catch (ReflectionException $e) {
			return self::NONE; // In case of reflection failure, return NONE
		}

		$attributes = $reflection->getAttributes(DataDriven::class);
		if (
			count($attributes) > 0 ||
			$item instanceof Armor ||
			$item instanceof SpawnEgg ||
			$item instanceof ProjectileItem ||
			$item instanceof Record ||
			$item instanceof Dye
		) {
			return self::DATA_DRIVEN;
		}

		return self::LEGACY;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use function array_filter;
use function array_map;
use function count;
use function implode;
use function is_bool;
use function is_float;
use function is_string;
use function reset;
use function sprintf;
use function str_replace;

class MolangHelper
{
	private function __construct()
	{
	}

	public static function value(mixed $value) : string
	{
		if (is_bool($value)) {
			return $value ? "1" : "0";
		}
		if (is_string($value)) {
			$escaped = str_replace("'", "\'", $value);
			return "'{$escaped}'";
		}
		if (is_float($value)) {
			return sprintf('%F', $value);
		}
		return (string) $value;
	}

	public static function query(string $queryName, mixed ...$args) : string
	{
		if (count($args) === 0) {
			return "q.{$queryName}";
		}

		$formattedArgs = array_map(fn ($arg) => self::value($arg), $args);
		return "q.{$queryName}(" . implode(", ", $formattedArgs) . ")";
	}

	public static function variable(string $variableName) : string
	{
		return "v.{$variableName}";
	}

	public static function state(string $stateName) : string
	{
		return self::query('block_state', $stateName);
	}

	public static function stateEquals(string $stateName, mixed $value) : string
	{
		return self::state($stateName) . " == " . self::value($value);
	}

	public static function stateNotEquals(string $stateName, mixed $value) : string
	{
		return self::state($stateName) . " != " . self::value($value);
	}

	public static function blockProperty(string $propertyName) : string
	{
		return self::query('block_property', $propertyName);
	}

	public static function anyTag(string ...$tags) : string
	{
		return self::query('any_tag', ...$tags);
	}

	public static function allTags(string ...$tags) : string
	{
		return self::query('all_tags', ...$tags);
	}

	public static function isItemNameAny(string ...$names) : string
	{
		return self::query('is_item_name_any', ...$names);
	}

	public static function gt(string $expression, mixed $value) : string
	{
		return "{$expression} > " . self::value($value);
	}

	public static function gte(string $expression, mixed $value) : string
	{
		return "{$expression} >= " . self::value($value);
	}

	public static function lt(string $expression, mixed $value) : string
	{
		return "{$expression} < " . self::value($value);
	}

	public static function lte(string $expression, mixed $value) : string
	{
		return "{$expression} <= " . self::value($value);
	}

	public static function and(string ...$expressions) : string
	{
		$filtered = array_filter($expressions, fn (string $expr) => $expr !== "");
		if (count($filtered) === 0) {
			return "";
		}
		if (count($filtered) === 1) {
			return reset($filtered);
		}

		return "(" . implode(") && (", $filtered) . ")";
	}

	public static function or(string ...$expressions) : string
	{
		$filtered = array_filter($expressions, fn (string $expr) => $expr !== "");
		if (count($filtered) === 0) {
			return "";
		}
		if (count($filtered) === 1) {
			return reset($filtered);
		}

		return "(" . implode(") || (", $filtered) . ")";
	}

	public static function not(string $expression) : string
	{
		if ($expression === "") {
			return "";
		}
		return "!(" . $expression . ")";
	}

	public static function ternary(string $condition, string|int|float $ifTrue, string|int|float $ifFalse) : string
	{
		return "({$condition}) ? ({$ifTrue}) : ({$ifFalse})";
	}

	public static function math(string $mathFunc, mixed ...$args) : string
	{
		$formattedArgs = array_map(fn ($arg) => self::value($arg), $args);
		return "math.{$mathFunc}(" . implode(", ", $formattedArgs) . ")";
	}

	public static function abs(mixed $value) : string
	{
		return self::math('abs', $value);
	}

	public static function clamp(mixed $value, mixed $min, mixed $max) : string
	{
		return self::math('clamp', $value, $min, $max);
	}

	public static function min(mixed ...$values) : string
	{
		return self::math('min', ...$values);
	}

	public static function max(mixed ...$values) : string
	{
		return self::math('max', ...$values);
	}

	public static function round(mixed $value) : string
	{
		return self::math('round', $value);
	}

	public static function floor(mixed $value) : string
	{
		return self::math('floor', $value);
	}

	public static function ceil(mixed $value) : string
	{
		return self::math('ceil', $value);
	}

	public static function mod(mixed $value, mixed $denominator) : string
	{
		return self::math('mod', $value, $denominator);
	}

	public static function pow(mixed $base, mixed $exponent) : string
	{
		return self::math('pow', $base, $exponent);
	}

	public static function random(mixed $min, mixed $max) : string
	{
		return self::math('random', $min, $max);
	}

	public static function randomInteger(mixed $min, mixed $max) : string
	{
		return self::math('random_integer', $min, $max);
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\data\bedrock\block\convert\property\ValueMappings;
use function array_keys;

class PropertyHelper
{
	public const BOOL = [false, true];
	public const MC_CORNER = [
		StateValues::MC_CORNER_NONE,
		StateValues::MC_CORNER_INNER_LEFT,
		StateValues::MC_CORNER_INNER_RIGHT,
		StateValues::MC_CORNER_OUTER_LEFT,
		StateValues::MC_CORNER_OUTER_RIGHT
	];
	public const MC_VERTICAL_HALF = [
		StateValues::MC_VERTICAL_HALF_BOTTOM,
		StateValues::MC_VERTICAL_HALF_TOP,
		"double"
	];
	public const MC_CARDINAL_DIRECTION = [
		StateValues::MC_CARDINAL_DIRECTION_NORTH,
		StateValues::MC_CARDINAL_DIRECTION_SOUTH,
		StateValues::MC_CARDINAL_DIRECTION_WEST,
		StateValues::MC_CARDINAL_DIRECTION_EAST
	];
	public const WALL_CONNECTION_TYPE = [
		StateValues::WALL_CONNECTION_TYPE_NORTH_NONE,
		StateValues::WALL_CONNECTION_TYPE_NORTH_SHORT,
		StateValues::WALL_CONNECTION_TYPE_NORTH_TALL,
	];
	public const LEVER_DIRECTION = [
		StateValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH,
		StateValues::LEVER_DIRECTION_DOWN_EAST_WEST,
		StateValues::LEVER_DIRECTION_UP_NORTH_SOUTH,
		StateValues::LEVER_DIRECTION_UP_EAST_WEST,
		StateValues::LEVER_DIRECTION_NORTH,
		StateValues::LEVER_DIRECTION_SOUTH,
		StateValues::LEVER_DIRECTION_WEST,
		StateValues::LEVER_DIRECTION_EAST
	];

	public static function get(string $name) : array
	{
		return array_keys(ValueMappings::getInstance()->$name->getRawToValueMap());
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use Nexly\Blocks\Vanilla\HeadBlock;
use pocketmine\block\Block;
use pocketmine\block\Farmland;
use pocketmine\block\Fence;
use pocketmine\block\Furnace;
use pocketmine\block\GlassPane;
use pocketmine\block\Hopper;
use pocketmine\block\Ladder;
use pocketmine\block\Lever;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\utils\Ageable;
use pocketmine\block\utils\LeverFacing;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\StairShape;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\data\bedrock\block\convert\BlockStateReader;
use pocketmine\math\Facing;
use ReflectionClass;
use RuntimeException;

class DeserializerHelper
{
	public static function decodeAgeable(Ageable $block, BlockStateReader $in) : Block
	{
		if (!$block instanceof Block) {
			throw new RuntimeException("Deserializer requires Block object.");
		}

		return $block->setAge($in->readBoundedInt(StateNames::AGE, 0, $block::MAX_AGE));
	}

	public static function decodeStair(Stair $block, BlockStateReader $in) : Stair
	{
		return $block
			->setUpsideDown($in->readBool(StateNames::UPSIDE_DOWN_BIT))
			->setFacing($in->read5MinusHorizontalFacing())
			->setShape(match ($in->readString(StateNames::MC_CORNER)) {
				StateValues::MC_CORNER_INNER_LEFT => StairShape::INNER_LEFT(),
				StateValues::MC_CORNER_INNER_RIGHT => StairShape::INNER_RIGHT(),
				StateValues::MC_CORNER_OUTER_LEFT => StairShape::OUTER_LEFT(),
				StateValues::MC_CORNER_OUTER_RIGHT => StairShape::OUTER_RIGHT(),
				default => StairShape::STRAIGHT,
			});
	}

	public static function decodeSlab(Slab $block, BlockStateReader $in) : Slab
	{
		return $block
			->setSlabType(match ($in->readString(StateNames::MC_VERTICAL_HALF)) {
				StateValues::MC_VERTICAL_HALF_BOTTOM => SlabType::BOTTOM,
				StateValues::MC_VERTICAL_HALF_TOP => SlabType::TOP,
				"double" => SlabType::DOUBLE,
				default => throw new RuntimeException("Invalid slab type"),
			});
	}

	public static function decodeFence(Fence $block, BlockStateReader $in) : Fence
	{
		$co = [];

		if ($in->readBool("minecraft:connection_north")) {
			$co[Facing::NORTH] = Facing::NORTH;
		}
		if ($in->readBool("minecraft:connection_south")) {
			$co[Facing::SOUTH] = Facing::SOUTH;
		}
		if ($in->readBool("minecraft:connection_west")) {
			$co[Facing::WEST] = Facing::WEST;
		}
		if ($in->readBool("minecraft:connection_east")) {
			$co[Facing::EAST] = Facing::EAST;
		}

		$reflection = new ReflectionClass(Fence::class);
		$connections = $reflection->getProperty("connections");
		$connections->setValue($block, $co);
		return $block;
	}

	public static function decodeHopper(Hopper $block, BlockStateReader $in) : Hopper
	{
		return $block
			->setPowered($in->readBool(StateNames::TOGGLE_BIT))
			->setFacing($in->readFacingWithoutUp());
	}

	public static function decodeFurnace(Furnace $block, BlockStateReader $in) : Furnace
	{
		return $block->setFacing($in->readCardinalHorizontalFacing());
	}

	public static function decodeHeadBlock(HeadBlock $block, BlockStateReader $in) : HeadBlock
	{
		return $block
			->setRotation($in->readInt(StateNames::ROTATION))
			->setFacing($in->readFacingWithoutDown());
	}

	public static function decodeLadder(Ladder $block, BlockStateReader $in) : Ladder
	{
		return $block
			->setFacing($in->readHorizontalFacing());
	}

	public static function decodeFarmland(Farmland $block, BlockStateReader $in) : Farmland
	{
		return $block
			->setWetness($in->readInt(StateNames::MOISTURIZED_AMOUNT));
	}

	public static function decodeGlassPane(GlassPane $block, BlockStateReader $in) : GlassPane
	{
		$co = [];

		if ($in->readInt(StateNames::MC_CONNECTION_NORTH)) {
			$co[Facing::NORTH] = Facing::NORTH;
		}
		if ($in->readInt(StateNames::MC_CONNECTION_SOUTH)) {
			$co[Facing::SOUTH] = Facing::SOUTH;
		}
		if ($in->readInt(StateNames::MC_CONNECTION_WEST)) {
			$co[Facing::WEST] = Facing::WEST;
		}
		if ($in->readInt(StateNames::MC_CONNECTION_EAST)) {
			$co[Facing::EAST] = Facing::EAST;
		}

		$reflection = new ReflectionClass(GlassPane::class);
		$connections = $reflection->getProperty("connections");
		$connections->setValue($block, $co);

		return $block;
	}

	public static function decodeLever(Lever $block, BlockStateReader $in) : Lever
	{
		return (clone $block)
			->setActivated($in->readBool(StateNames::OPEN_BIT))
			->setFacing(match($in->readString(StateNames::LEVER_DIRECTION)) {
				StateValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH => LeverFacing::DOWN_AXIS_Z,
				StateValues::LEVER_DIRECTION_DOWN_EAST_WEST => LeverFacing::DOWN_AXIS_X,
				StateValues::LEVER_DIRECTION_UP_NORTH_SOUTH => LeverFacing::UP_AXIS_Z,
				StateValues::LEVER_DIRECTION_UP_EAST_WEST => LeverFacing::UP_AXIS_X,
				StateValues::LEVER_DIRECTION_NORTH => LeverFacing::NORTH,
				StateValues::LEVER_DIRECTION_SOUTH => LeverFacing::SOUTH,
				StateValues::LEVER_DIRECTION_WEST => LeverFacing::WEST,
				StateValues::LEVER_DIRECTION_EAST => LeverFacing::EAST,
				default => throw new RuntimeException("Invalid lever direction"),
			});
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use pocketmine\block\Block;
use pocketmine\block\tile\Container;
use pocketmine\block\tile\Tile;
use pocketmine\block\tile\TileFactory;
use pocketmine\item\VanillaItems as Items;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\world\World;
use ReflectionClass;
use function get_class;

final class ComponentHelper
{
	/** @var array<string, array{hasTile: bool, slotCount: int|null}> */
	private static array $tileCache = [];

	/** @var array<string, bool> */
	private static array $interactCache = [];

	public static function hasTile(Block $block, ?int &$containerSlotCount = null) : bool
	{
		$class = get_class($block);

		if (isset(self::$tileCache[$class])) {
			$containerSlotCount = self::$tileCache[$class]['slotCount'];
			return self::$tileCache[$class]['hasTile'];
		}

		$containerSlotCount = null;
		$hasTile = false;
		$tileClass = $block->getIdInfo()->getTileClass();

		if ($tileClass !== null) {
			if (!TileFactory::getInstance()->isRegistered($tileClass)) {
				throw new \InvalidArgumentException("Tile class '{$tileClass}' is not registered");
			}

			try {
				$world = (new ReflectionClass(World::class))->newInstanceWithoutConstructor();
				/** @var Tile $tileInstance */
				$tileInstance = new $tileClass($world, Vector3::zero());

				if ($tileInstance instanceof Container) {
					$containerSlotCount = $tileInstance->getInventory()->getSize();
				}
				$hasTile = true;
			} catch (\Throwable $e) {
				// ignore it
			}
		}

		self::$tileCache[$class] = [
			'hasTile' => $hasTile,
			'slotCount' => $containerSlotCount
		];

		return $hasTile;
	}

	public static function hasInteract(Block $block) : bool
	{
		$class = get_class($block);

		if (isset(self::$interactCache[$class])) {
			return self::$interactCache[$class];
		}

		$hasInteract = false;
		$returnedItems = [];

		try {
			if ($block->onInteract(Items::AIR(), Facing::UP, Vector3::zero(), null, $returnedItems)) {
				$hasInteract = true;
			}
		} catch (\Throwable $e) {
			$hasInteract = true;
		}

		self::$interactCache[$class] = $hasInteract;
		return $hasInteract;
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use Nexly\Blocks\Vanilla\HeadBlock;
use pocketmine\block\Farmland;
use pocketmine\block\Fence;
use pocketmine\block\GlassPane;
use pocketmine\block\Hopper;
use pocketmine\block\Ladder;
use pocketmine\block\Lever;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\utils\Ageable;
use pocketmine\block\utils\LeverFacing;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\StairShape;
use pocketmine\data\bedrock\block\BlockStateNames as StateNames;
use pocketmine\data\bedrock\block\BlockStateStringValues as StateValues;
use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer;
use pocketmine\math\Facing;

class SerializerHelper
{
	public static function encodeAgeable(Ageable $block, Writer $out) : Writer
	{
		return $out->writeInt(StateNames::AGE, $block->getAge());
	}

	public static function encodeStair(Stair $block, Writer $out) : Writer
	{
		return $out
			->writeBool(StateNames::UPSIDE_DOWN_BIT, $block->isUpsideDown())
			->write5MinusHorizontalFacing($block->getFacing())
			->writeString(StateNames::MC_CORNER, match($block->getShape()) {
				StairShape::INNER_LEFT => StateValues::MC_CORNER_INNER_LEFT,
				StairShape::INNER_RIGHT => StateValues::MC_CORNER_INNER_RIGHT,
				StairShape::OUTER_LEFT => StateValues::MC_CORNER_OUTER_LEFT,
				StairShape::OUTER_RIGHT => StateValues::MC_CORNER_OUTER_RIGHT,
				default => StateValues::MC_CORNER_NONE,
			});
	}

	public static function encodeSlab(Slab $block, Writer $out) : Writer
	{
		return $out
			->writeString(StateNames::MC_VERTICAL_HALF, match ($block->getSlabType()) {
				SlabType::BOTTOM => StateValues::MC_VERTICAL_HALF_BOTTOM,
				SlabType::TOP => StateValues::MC_VERTICAL_HALF_TOP,
				SlabType::DOUBLE => "double",
			});
	}

	public static function encodeFence(Fence $block, Writer $out) : Writer
	{
		$reflection = new \ReflectionClass(Fence::class);
		$connections = $reflection->getProperty("connections");
		$co = $connections->getValue($block);
		return $out
			->writeBool("minecraft:connection_north", isset($co[Facing::NORTH]))
			->writeBool("minecraft:connection_south", isset($co[Facing::SOUTH]))
			->writeBool("minecraft:connection_west", isset($co[Facing::WEST]))
			->writeBool("minecraft:connection_east", isset($co[Facing::EAST]));
	}

	public static function encodeHopper(Hopper $block, Writer $out) : Writer
	{
		return $out
			->writeBool(StateNames::TOGGLE_BIT, $block->isPowered())
			->writeFacingWithoutUp($block->getFacing());
	}

	public static function encodeHeadBlock(HeadBlock $block, Writer $out) : Writer
	{
		return $out
			->writeFacingWithoutDown($block->getFacing())
			->writeInt(StateNames::ROTATION, $block->getRotation());
	}

	public static function encodeLadder(Ladder $block, Writer $out) : Writer
	{
		return $out
			->writeHorizontalFacing($block->getFacing());
	}

	public static function encodeFarmland(Farmland $block, Writer $out) : Writer
	{
		return $out
			->writeInt(StateNames::MOISTURIZED_AMOUNT, $block->getWetness());
	}

	public static function encodeGlassPane(GlassPane $block, Writer $out) : Writer
	{
		$reflection = new \ReflectionClass(GlassPane::class);
		$connections = $reflection->getProperty("connections");
		$co = $connections->getValue($block);

		return $out
			->writeBool(StateNames::MC_CONNECTION_NORTH, isset($co[Facing::NORTH]))
			->writeBool(StateNames::MC_CONNECTION_SOUTH, isset($co[Facing::SOUTH]))
			->writeBool(StateNames::MC_CONNECTION_WEST, isset($co[Facing::WEST]))
			->writeBool(StateNames::MC_CONNECTION_EAST, isset($co[Facing::EAST]));
	}

	public static function encodeLever(Lever $state, Writer $out) : Writer
	{
		return $out
			->writeBool(StateNames::OPEN_BIT, $state->isActivated())
			->writeString(StateNames::LEVER_DIRECTION, match($state->getFacing()) {
				LeverFacing::DOWN_AXIS_Z => StateValues::LEVER_DIRECTION_DOWN_NORTH_SOUTH,
				LeverFacing::DOWN_AXIS_X => StateValues::LEVER_DIRECTION_DOWN_EAST_WEST,
				LeverFacing::UP_AXIS_Z => StateValues::LEVER_DIRECTION_UP_NORTH_SOUTH,
				LeverFacing::UP_AXIS_X => StateValues::LEVER_DIRECTION_UP_EAST_WEST,
				LeverFacing::NORTH => StateValues::LEVER_DIRECTION_NORTH,
				LeverFacing::SOUTH => StateValues::LEVER_DIRECTION_SOUTH,
				LeverFacing::WEST => StateValues::LEVER_DIRECTION_WEST,
				LeverFacing::EAST => StateValues::LEVER_DIRECTION_EAST,
			});
	}
}
<?php

/*
 *  _   _           _
 * | \ | | _____  _| |_   _
 * |  \| |/ _ \ \/ / | | | |
 * | |\  |  __/>  <| | |_| |
 * |_| \_|\___/_/\_\_|\__, |
 *                    |___/
 *
 * Nexly - A modern PocketMine-MP framework for custom blocks and items.
 *
 * This project is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License, Version 2.0 as published by
 * the Apache Software Foundation.
 *
 * @author Zwuiix-cmd
 * @link   https://github.com/Zwuiix-cmd/Nexly
 *
 *
 */

namespace Nexly\Helpers;

use pocketmine\block\utils\DyeColor;
use function count;
use function explode;
use function str_contains;
use function str_replace;
use function strrpos;
use function strtolower;
use function substr_replace;

class StringsHelper
{
	public static function wood(string $stringId) : array
	{
		if (str_contains($stringId, '_stripped_')) {
			$strippedId = $stringId;
			$unstrippedId = str_replace('_stripped_', '_', $stringId);
			return [$unstrippedId, $strippedId];
		}

		$unstrippedId = $stringId;
		$lastUnderscorePos = strrpos($stringId, '_');
		if ($lastUnderscorePos !== false) {
			$strippedId = substr_replace($stringId, '_stripped_', $lastUnderscorePos, 1);
		} else {
			$strippedId = $stringId . "_stripped";
		}

		return [$unstrippedId, $strippedId];
	}

	public static function furnace(string $stringId) : array
	{
		if (str_contains($stringId, 'lit_')) {
			$litId = $stringId;
			$unlitId = str_replace('lit_', '', $stringId);
			return [$unlitId, $litId];
		}

		$unlitId = $stringId;

		$parts = explode(':', $stringId);
		$namespace = $parts[0];
		$name = $parts[1] ?? '';

		$nameParts = explode('_', $name, 2);

		if (count($nameParts) === 2) {
			$litId = $namespace . ':' . $nameParts[0] . '_lit_' . $nameParts[1];
		} else {
			$litId = $namespace . ':lit_' . $name;
		}

		return [$unlitId, $litId];
	}

	public static function recolor(string $stringId, ?DyeColor $from = null, ?DyeColor $to = null) : string
	{
		if ($from !== null && $to !== null) {
			return str_replace(strtolower($from->name), strtolower($to->name), $stringId);
		}

		return $stringId;
	}
}
name: Nexly
version: 1.0.4
main: Nexly\Nexly
src-namespace-prefix: Nexly
api: 5.0.0
author: Zwuiix
load: STARTUP
¤C£VFšó›º¿LÞÁ·aq7Ô ß¥Á   GBMB