<?php echo "SmartSpawners is a plugin, load it with Altay.\n"; __HALT_COMPILER(); ?>
  ^                  resources/mobs.yml7      7  /b¦¤         resources/trades.yml³      ³  Äé¤         resources/config.yml�      �  ço5ü¤      2   src/deepslatedev/smartspawners/SpawnerListener.phpb      b  ë€ª¤      )   src/deepslatedev/smartspawners/MobEgg.phpŽ      Ž  Ða“¤      '   src/deepslatedev/smartspawners/Main.php“      “  Qßv¤      )   src/deepslatedev/smartspawners/Trades.phpà      à  Cççý¤      +   src/deepslatedev/smartspawners/Spawners.php?      ?  Ëž-È¤      .   src/deepslatedev/smartspawners/MobListener.phpð      ð  ¾t­Ø¤      -   src/deepslatedev/smartspawners/SingleItem.phpÎ       Î   Yå¯‚¤      .   src/deepslatedev/smartspawners/MobRegistry.php0      0  NåY ¤      @   src/deepslatedev/smartspawners/entity/projectile/PoisonArrow.phpÕ      Õ  "Ý)¤      B   src/deepslatedev/smartspawners/entity/projectile/SlownessArrow.phpÚ      Ú  –(Ï¤      B   src/deepslatedev/smartspawners/entity/projectile/LargeFireball.php7
      7
  ývKÒ¤      B   src/deepslatedev/smartspawners/entity/projectile/SmallFireball.phpd      d  ´òš5¤      2   src/deepslatedev/smartspawners/entity/Domestic.phpÖ2      Ö2  �ÇIÁ¤      /   src/deepslatedev/smartspawners/entity/Shots.php�      �  ? ^¦¤      7   src/deepslatedev/smartspawners/entity/mob/GlowSquid.phpÒ      Ò  š�áv¤      1   src/deepslatedev/smartspawners/entity/mob/Bee.phpX      X  �*ªS¤      4   src/deepslatedev/smartspawners/entity/mob/Bogged.phpÅ      Å  8[ÞR¤      5   src/deepslatedev/smartspawners/entity/mob/Chicken.phpf      f  I§!â¤      9   src/deepslatedev/smartspawners/entity/mob/ZombieHorse.php°      °  9‘?¹¤      3   src/deepslatedev/smartspawners/entity/mob/Camel.php#      #  Gÿ5é¤      7   src/deepslatedev/smartspawners/entity/mob/IronGolem.php        LK ¤      3   src/deepslatedev/smartspawners/entity/mob/Horse.php        %,`Q¤      4   src/deepslatedev/smartspawners/entity/mob/Parrot.php¥      ¥   [ÙÌ¤      6   src/deepslatedev/smartspawners/entity/mob/Villager.phpt	      t	  +èÄÙ¤      5   src/deepslatedev/smartspawners/entity/mob/Ravager.phpì      ì  ü5ÛÉ¤      9   src/deepslatedev/smartspawners/entity/mob/TraderLlama.php¸      ¸  ºsJJ¤      3   src/deepslatedev/smartspawners/entity/mob/Sheep.phpD      D  „õ¿¤      4   src/deepslatedev/smartspawners/entity/mob/Donkey.php        [t�D¤      7   src/deepslatedev/smartspawners/entity/mob/Mooshroom.php:      :  ±>=Ÿ¤      3   src/deepslatedev/smartspawners/entity/mob/Ghast.php        {Ÿ�ª¤      5   src/deepslatedev/smartspawners/entity/mob/Sniffer.phpÏ      Ï   â�¡¤      4   src/deepslatedev/smartspawners/entity/mob/Zoglin.phpç      ç  Tú'¤      4   src/deepslatedev/smartspawners/entity/mob/Rabbit.phpó      ó  ›Â‚¤      1   src/deepslatedev/smartspawners/entity/mob/Cow.phpO      O  ÷¿P¤      4   src/deepslatedev/smartspawners/entity/mob/Turtle.php        ž¯¤      2   src/deepslatedev/smartspawners/entity/mob/Goat.php¶      ¶  ˜M4¤      3   src/deepslatedev/smartspawners/entity/mob/Stray.php²      ²  {ðÉ°¤      5   src/deepslatedev/smartspawners/entity/mob/Phantom.phpÉ      É  ÔÍQ¤      3   src/deepslatedev/smartspawners/entity/mob/Panda.php¼      ¼  õÄ�˜¤      4   src/deepslatedev/smartspawners/entity/mob/Spider.phpñ      ñ  Ò©gÆ¤      8   src/deepslatedev/smartspawners/entity/mob/Silverfish.phpF      F  1=tì¤      4   src/deepslatedev/smartspawners/entity/mob/Salmon.phpÁ      Á  ª@žã¤      5   src/deepslatedev/smartspawners/entity/mob/Drowned.phpÉ      É  º0¤      1   src/deepslatedev/smartspawners/entity/mob/Cat.phpÿ      ÿ  ��4’¤      8   src/deepslatedev/smartspawners/entity/mob/Vindicator.phpÚ      Ú  ÈO.l¤      6   src/deepslatedev/smartspawners/entity/mob/Pillager.php/      /  _’u„¤      1   src/deepslatedev/smartspawners/entity/mob/Bat.phpm      m  áõF¤      3   src/deepslatedev/smartspawners/entity/mob/Witch.phpˆ      ˆ  ÐZ´¬¤      2   src/deepslatedev/smartspawners/entity/mob/Mule.php¡      ¡  Oxˆ3¤      :   src/deepslatedev/smartspawners/entity/mob/TropicalFish.phpÛ      Û  [�¨¤      4   src/deepslatedev/smartspawners/entity/mob/Piglin.php        þ±„¤      5   src/deepslatedev/smartspawners/entity/mob/Dolphin.php|      |  �×�G¤      2   src/deepslatedev/smartspawners/entity/mob/Husk.php>      >  È—»’¤      2   src/deepslatedev/smartspawners/entity/mob/Frog.php?      ?  X'R¤      1   src/deepslatedev/smartspawners/entity/mob/Fox.phpº      º  ëdœ€¤      5   src/deepslatedev/smartspawners/entity/mob/Axolotl.phpÉ      É  !E³¤      <   src/deepslatedev/smartspawners/entity/mob/ZombieVillager.php©      ©  É°Ê‘¤      :   src/deepslatedev/smartspawners/entity/mob/ZombiePigman.php3      3  –d“ø¤      5   src/deepslatedev/smartspawners/entity/mob/Creeper.phpé      é  �ÅmÂ¤      3   src/deepslatedev/smartspawners/entity/mob/Squid.php¿      ¿  o\A�¤      8   src/deepslatedev/smartspawners/entity/mob/CaveSpider.php&      &  Þ6°¤      <   src/deepslatedev/smartspawners/entity/mob/WitherSkeleton.php7      7  »»K$¤      7   src/deepslatedev/smartspawners/entity/mob/Endermite.phpB      B  Ç“E¤      6   src/deepslatedev/smartspawners/entity/mob/Skeleton.phpu      u  Ë«§3¤      5   src/deepslatedev/smartspawners/entity/mob/Strider.php…      …  ™o¤      7   src/deepslatedev/smartspawners/entity/mob/MagmaCube.phpf      f  5�ll¤      7   src/deepslatedev/smartspawners/entity/mob/PolarBear.phpE      E  XJÔJ¤      3   src/deepslatedev/smartspawners/entity/mob/Slime.phpz      z  ‹uw¤      6   src/deepslatedev/smartspawners/entity/mob/Enderman.php²      ²  ¾z¤      5   src/deepslatedev/smartspawners/entity/mob/Tadpole.phpÅ      Å  Û$êg¤      4   src/deepslatedev/smartspawners/entity/mob/Hoglin.phpç      ç  “�Ï[¤      7   src/deepslatedev/smartspawners/entity/mob/SnowGolem.phpõ      õ  ¾6†¤      3   src/deepslatedev/smartspawners/entity/mob/Allay.phpu      u  ”#È¤      3   src/deepslatedev/smartspawners/entity/mob/Llama.php�      �  ±±o¤      7   src/deepslatedev/smartspawners/entity/mob/Armadillo.phpÐ      Ð  G'Wà¤      3   src/deepslatedev/smartspawners/entity/mob/Blaze.phpÀ      À  ¢ô¤      1   src/deepslatedev/smartspawners/entity/mob/Cod.phpµ      µ  m’ë¤      8   src/deepslatedev/smartspawners/entity/mob/Pufferfish.phpÑ      Ñ  Ø ¤      4   src/deepslatedev/smartspawners/entity/mob/Zombie.php}      }  	ë^“¤      1   src/deepslatedev/smartspawners/entity/mob/Vex.php        ðñª¤      2   src/deepslatedev/smartspawners/entity/mob/Wolf.php¶      ¶  Mï!È¤      4   src/deepslatedev/smartspawners/entity/mob/Ocelot.phpÏ      Ï  C¾L¤      1   src/deepslatedev/smartspawners/entity/mob/Pig.phpŒ      Œ  ³Óöç¤      ;   src/deepslatedev/smartspawners/entity/mob/SkeletonHorse.php·      ·  T>Ï¤      =   src/deepslatedev/smartspawners/entity/mob/WanderingTrader.php‘	      ‘	  Ã¹o®¤      9   src/deepslatedev/smartspawners/entity/mob/PiglinBrute.phpã      ã  úÄw$¤      8   src/deepslatedev/smartspawners/entity/GrazeAnimation.php        ?6î!¤      2   src/deepslatedev/smartspawners/entity/SmartMob.phpÎO      ÎO  ¼‡éç¤      (   src/deepslatedev/smartspawners/Items.phpš      š  Ô€Þ¤      '   src/deepslatedev/smartspawners/Eggs.php²	      ²	  íÄ¤      
   plugin.ymlÉ      É  +oD¤      zombie:
  name: "Zombie"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["rotten_flesh:0-2"]
husk:
  name: "Husk"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["rotten_flesh:0-2"]
skeleton:
  name: "Skeleton"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["bone:0-2", "arrow:0-2"]
stray:
  name: "Stray"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["bone:0-2", "arrow:0-2"]
spider:
  name: "Spider"
  mode: hostile
  health: 16
  damage: 2
  speed: 0.27
  xp: 5
  drops: ["string:0-2", "spider_eye:0-1"]
cave_spider:
  name: "Cave Spider"
  mode: hostile
  health: 12
  damage: 2
  speed: 0.27
  xp: 5
  drops: ["string:0-2"]
creeper:
  name: "Creeper"
  mode: hostile
  health: 20
  damage: 12
  speed: 0.2
  xp: 5
  explodes: true
  drops: ["gunpowder:0-2"]
witch:
  name: "Witch"
  mode: hostile
  health: 26
  damage: 4
  speed: 0.2
  xp: 5
  drops: ["redstone:0-2", "glowstone_dust:0-2", "sugar:0-2"]
blaze:
  name: "Blaze"
  mode: hostile
  health: 20
  damage: 5
  speed: 0.22
  xp: 10
  drops: ["blaze_rod:0-1"]
enderman:
  name: "Enderman"
  mode: neutral
  health: 40
  damage: 6
  speed: 0.3
  xp: 5
  drops: ["ender_pearl:0-1"]
zombie_pigman:
  name: "Zombified Piglin"
  mode: neutral
  health: 20
  damage: 5
  speed: 0.23
  xp: 5
  drops: ["rotten_flesh:0-1", "gold_nugget:0-1"]
iron_golem:
  name: "Iron Golem"
  mode: neutral
  health: 100
  damage: 10
  speed: 0.18
  xp: 0
  drops: ["iron_ingot:3-5", "poppy:0-2"]
cow:
  name: "Cow"
  mode: passive
  health: 10
  speed: 0.18
  xp: 2
  drops: ["raw_beef:1-3", "leather:0-2"]
pig:
  name: "Pig"
  mode: passive
  health: 10
  speed: 0.18
  xp: 2
  drops: ["raw_porkchop:1-3"]
sheep:
  name: "Sheep"
  mode: passive
  health: 8
  speed: 0.18
  xp: 2
  drops: ["raw_mutton:1-2", "white_wool:1-1"]
chicken:
  name: "Chicken"
  mode: passive
  health: 4
  speed: 0.16
  xp: 2
  drops: ["raw_chicken:1-1", "feather:0-2"]
rabbit:
  name: "Rabbit"
  mode: passive
  health: 3
  speed: 0.22
  xp: 2
  drops: ["raw_rabbit:0-1", "rabbit_hide:0-1"]
mooshroom:
  name: "Mooshroom"
  mode: passive
  health: 10
  speed: 0.18
  xp: 2
  drops: ["raw_beef:1-3", "leather:0-2"]
zombie_villager:
  name: "Zombie Villager"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["rotten_flesh:0-2"]
drowned:
  name: "Drowned"
  mode: hostile
  health: 20
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["rotten_flesh:0-2", "copper_ingot:0-1"]
piglin:
  name: "Piglin"
  mode: hostile
  health: 16
  damage: 5
  speed: 0.24
  xp: 5
  drops: ["gold_nugget:0-2"]
piglin_brute:
  name: "Piglin Brute"
  mode: hostile
  health: 50
  damage: 10
  speed: 0.25
  xp: 20
  drops: ["gold_ingot:0-1"]
vindicator:
  name: "Vindicator"
  mode: hostile
  health: 24
  damage: 10
  speed: 0.24
  xp: 5
  drops: ["emerald:0-1"]
pillager:
  name: "Pillager"
  mode: hostile
  health: 24
  damage: 3
  speed: 0.24
  xp: 5
  drops: ["arrow:0-2"]
bogged:
  name: "Bogged"
  mode: hostile
  health: 16
  damage: 3
  speed: 0.22
  xp: 5
  drops: ["bone:0-2", "arrow:0-2"]
wither_skeleton:
  name: "Wither Skeleton"
  mode: hostile
  health: 20
  damage: 8
  speed: 0.25
  xp: 5
  drops: ["coal:0-1", "bone:0-2"]
silverfish:
  name: "Silverfish"
  mode: hostile
  health: 8
  damage: 1
  speed: 0.25
  xp: 5
  drops: []
endermite:
  name: "Endermite"
  mode: hostile
  health: 8
  damage: 2
  speed: 0.25
  xp: 3
  drops: []
hoglin:
  name: "Hoglin"
  mode: hostile
  health: 40
  damage: 6
  speed: 0.23
  xp: 5
  drops: ["raw_porkchop:2-4", "leather:0-1"]
zoglin:
  name: "Zoglin"
  mode: hostile
  health: 40
  damage: 6
  speed: 0.23
  xp: 5
  drops: ["rotten_flesh:1-3"]
ravager:
  name: "Ravager"
  mode: hostile
  health: 100
  damage: 12
  speed: 0.2
  xp: 20
  drops: ["saddle:1-1"]
vex:
  name: "Vex"
  mode: hostile
  health: 14
  damage: 9
  speed: 0.3
  xp: 3
  drops: []
phantom:
  name: "Phantom"
  mode: hostile
  health: 20
  damage: 6
  speed: 0.3
  xp: 5
  drops: ["phantom_membrane:0-1"]
ghast:
  name: "Ghast"
  mode: hostile
  health: 10
  speed: 0.15
  xp: 5
  drops: ["ghast_tear:0-1", "gunpowder:0-2"]
slime:
  name: "Slime"
  mode: hostile
  health: 4
  damage: 2
  speed: 0.2
  xp: 2
  drops: ["slime_ball:0-2"]
magma_cube:
  name: "Magma Cube"
  mode: hostile
  health: 4
  damage: 3
  speed: 0.2
  xp: 2
  drops: ["magma_cream:0-1"]
wolf:
  name: "Wolf"
  mode: neutral
  health: 8
  damage: 4
  speed: 0.3
  xp: 2
  drops: []
polar_bear:
  name: "Polar Bear"
  mode: neutral
  health: 30
  damage: 6
  speed: 0.25
  xp: 2
  drops: ["raw_fish:0-2", "raw_salmon:0-2"]
llama:
  name: "Llama"
  mode: passive
  health: 22
  speed: 0.18
  xp: 2
  drops: ["leather:0-2"]
trader_llama:
  name: "Trader Llama"
  mode: passive
  health: 22
  speed: 0.18
  xp: 2
  drops: ["leather:0-2"]
panda:
  name: "Panda"
  mode: passive
  health: 20
  speed: 0.15
  xp: 2
  drops: ["bamboo:0-2"]
goat:
  name: "Goat"
  mode: passive
  health: 10
  speed: 0.2
  xp: 2
  drops: []
fox:
  name: "Fox"
  mode: passive
  health: 10
  speed: 0.25
  xp: 2
  drops: []
bee:
  name: "Bee"
  mode: neutral
  health: 10
  damage: 2
  speed: 0.25
  xp: 1
  drops: []
dolphin:
  name: "Dolphin"
  mode: neutral
  health: 10
  damage: 3
  speed: 0.3
  xp: 2
  drops: ["raw_fish:0-1"]
horse:
  name: "Horse"
  mode: passive
  health: 20
  speed: 0.25
  xp: 2
  drops: ["leather:0-2"]
donkey:
  name: "Donkey"
  mode: passive
  health: 20
  speed: 0.2
  xp: 2
  drops: ["leather:0-2"]
mule:
  name: "Mule"
  mode: passive
  health: 20
  speed: 0.2
  xp: 2
  drops: ["leather:0-2"]
skeleton_horse:
  name: "Skeleton Horse"
  mode: passive
  health: 15
  speed: 0.25
  xp: 2
  drops: ["bone:0-2"]
zombie_horse:
  name: "Zombie Horse"
  mode: passive
  health: 15
  speed: 0.22
  xp: 2
  drops: ["rotten_flesh:0-2"]
camel:
  name: "Camel"
  mode: passive
  health: 32
  speed: 0.15
  xp: 2
  drops: []
cat:
  name: "Cat"
  mode: passive
  health: 10
  speed: 0.25
  xp: 2
  drops: ["string:0-2"]
ocelot:
  name: "Ocelot"
  mode: passive
  health: 10
  speed: 0.28
  xp: 2
  drops: []
parrot:
  name: "Parrot"
  mode: passive
  health: 6
  speed: 0.2
  xp: 2
  drops: ["feather:1-2"]
bat:
  name: "Bat"
  mode: passive
  health: 6
  speed: 0.2
  xp: 0
  drops: []
allay:
  name: "Allay"
  mode: passive
  health: 20
  speed: 0.2
  xp: 0
  drops: []
axolotl:
  name: "Axolotl"
  mode: neutral
  health: 14
  damage: 2
  speed: 0.22
  xp: 2
  drops: []
cod:
  name: "Cod"
  mode: passive
  health: 3
  speed: 0.18
  xp: 1
  drops: ["raw_fish:1-1"]
salmon:
  name: "Salmon"
  mode: passive
  health: 3
  speed: 0.18
  xp: 1
  drops: ["raw_salmon:1-1"]
pufferfish:
  name: "Pufferfish"
  mode: passive
  health: 3
  speed: 0.12
  xp: 1
  drops: ["pufferfish:1-1"]
tropical_fish:
  name: "Tropical Fish"
  mode: passive
  health: 3
  speed: 0.18
  xp: 1
  drops: ["clownfish:1-1"]
squid:
  name: "Squid"
  mode: passive
  health: 10
  speed: 0.15
  xp: 2
  drops: ["ink_sac:1-3"]
glow_squid:
  name: "Glow Squid"
  mode: passive
  health: 10
  speed: 0.15
  xp: 2
  drops: ["glow_ink_sac:1-3"]
turtle:
  name: "Turtle"
  mode: passive
  health: 30
  speed: 0.15
  xp: 2
  drops: ["seagrass:0-2"]
frog:
  name: "Frog"
  mode: passive
  health: 10
  speed: 0.2
  xp: 2
  drops: []
tadpole:
  name: "Tadpole"
  mode: passive
  health: 6
  speed: 0.15
  xp: 0
  drops: []
sniffer:
  name: "Sniffer"
  mode: passive
  health: 14
  speed: 0.12
  xp: 2
  drops: []
armadillo:
  name: "Armadillo"
  mode: passive
  health: 12
  speed: 0.18
  xp: 2
  drops: []
strider:
  name: "Strider"
  mode: passive
  health: 20
  speed: 0.18
  xp: 2
  drops: ["string:0-5"]
snow_golem:
  name: "Snow Golem"
  mode: neutral
  health: 4
  speed: 0.2
  xp: 0
  drops: ["snowball:0-15"]
villager:
  name: "Villager"
  mode: passive
  health: 20
  speed: 0.2
  xp: 0
  drops: []
wandering_trader:
  name: "Wandering Trader"
  mode: passive
  health: 20
  speed: 0.2
  xp: 0
  drops: []
professions:
  farmer:
    - {give: ["wheat:20"], get: "emerald:1"}
    - {give: ["potato:26"], get: "emerald:1"}
    - {give: ["carrot:22"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "bread:6"}
    - {give: ["emerald:1"], get: "pumpkin_pie:4"}
    - {give: ["emerald:1"], get: "apple:4"}
  fisherman:
    - {give: ["string:20"], get: "emerald:1"}
    - {give: ["coal:10"], get: "emerald:1"}
    - {give: ["raw_fish:15"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "cooked_fish:6"}
  shepherd:
    - {give: ["white_wool:18"], get: "emerald:1"}
    - {give: ["emerald:2"], get: "shears:1"}
    - {give: ["emerald:1"], get: "white_wool:1"}
  fletcher:
    - {give: ["stick:32"], get: "emerald:1"}
    - {give: ["flint:26"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "arrow:16"}
    - {give: ["emerald:2"], get: "bow:1"}
  librarian:
    - {give: ["paper:24"], get: "emerald:1"}
    - {give: ["book:4"], get: "emerald:1"}
    - {give: ["emerald:9"], get: "bookshelf:1"}
    - {give: ["emerald:1"], get: "lantern:1"}
  cartographer:
    - {give: ["paper:24"], get: "emerald:1"}
    - {give: ["glass_pane:11"], get: "emerald:1"}
    - {give: ["emerald:4"], get: "compass:1"}
  cleric:
    - {give: ["rotten_flesh:32"], get: "emerald:1"}
    - {give: ["gold_ingot:3"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "redstone:2"}
    - {give: ["emerald:1"], get: "lapis_lazuli:1"}
  armorer:
    - {give: ["coal:15"], get: "emerald:1"}
    - {give: ["emerald:5"], get: "iron_helmet:1"}
    - {give: ["emerald:9"], get: "iron_chestplate:1"}
    - {give: ["emerald:7"], get: "iron_leggings:1"}
    - {give: ["emerald:4"], get: "iron_boots:1"}
  weaponsmith:
    - {give: ["coal:15"], get: "emerald:1"}
    - {give: ["emerald:3"], get: "iron_axe:1"}
    - {give: ["emerald:4"], get: "iron_sword:1"}
  toolsmith:
    - {give: ["coal:15"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "stone_axe:1"}
    - {give: ["emerald:1"], get: "stone_shovel:1"}
    - {give: ["emerald:1"], get: "stone_pickaxe:1"}
    - {give: ["emerald:1"], get: "stone_hoe:1"}
  butcher:
    - {give: ["raw_chicken:14"], get: "emerald:1"}
    - {give: ["raw_porkchop:7"], get: "emerald:1"}
    - {give: ["raw_rabbit:4"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "cooked_porkchop:5"}
  leatherworker:
    - {give: ["leather:6"], get: "emerald:1"}
    - {give: ["emerald:3"], get: "leather_pants:1"}
    - {give: ["emerald:7"], get: "leather_tunic:1"}
  mason:
    - {give: ["clay_ball:10"], get: "emerald:1"}
    - {give: ["emerald:1"], get: "bricks:10"}
    - {give: ["emerald:1"], get: "polished_andesite:4"}

wandering-trader:
  - {give: ["emerald:1"], get: "fern:1"}
  - {give: ["emerald:1"], get: "sugar_cane:1"}
  - {give: ["emerald:1"], get: "pumpkin:1"}
  - {give: ["emerald:3"], get: "cactus:1"}
  - {give: ["emerald:1"], get: "vines:1"}
  - {give: ["emerald:2"], get: "glowstone:1"}
  - {give: ["emerald:5"], get: "packed_ice:1"}
  - {give: ["emerald:3"], get: "slime_ball:1"}
mobs:
  speed: 0.2
  follow-range: 16
  leash: 6
  despawn-seconds: 300
  climate-variants: true

spawners:
  delay-min-seconds: 15
  delay-max-seconds: 30
  activation-range: 16
  spawn-radius: 4
  mobs-per-stack: 1
  max-nearby: 12
  max-stack: 64
  drop-on-break: true
  require-silk-touch: false

messages:
  given: "Â§aGave {amount}x {mob} spawner to {player}."
  received: "Â§aYou received {amount}x {mob} spawner."
  stacked: "Â§a{mob} spawner stacked: Â§f{stack}Â§a."
  egg-set: "Â§aThis spawner now spawns Â§f{mob}Â§a."
  stack-full: "Â§cThis spawner is already at the maximum stack ({max})."
  info: "Â§e{mob} spawner Â§7- stack Â§f{stack}"
  unknown-mob: "Â§cUnknown mob. Available: {mobs}"
  player-not-found: "Â§cPlayer not found."
  summoned: "Â§aSummoned {amount}x {mob}."
  list: "Â§eMobs: Â§f{mobs}"
  usage: "Â§cUsage: /spawner give <mob> [player] [amount] | /spawner summon <mob> [amount] | /spawner list"
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\block\MonsterSpawner;
use pocketmine\entity\Location;
use pocketmine\event\block\BlockBreakEvent;
use pocketmine\event\block\BlockPlaceEvent;
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerInteractEvent;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\scheduler\ClosureTask;
use pocketmine\world\Position;

final class SpawnerListener implements Listener{
    public function __construct(private Main $plugin, private Spawners $spawners){}

    public function onInteract(PlayerInteractEvent $event): void{
        if($event->getAction() !== PlayerInteractEvent::RIGHT_CLICK_BLOCK){
            return;
        }
        $block = $event->getBlock();
        $player = $event->getPlayer();
        $item = $event->getItem();
        $eggMob = Eggs::mobOf($item);
        if($eggMob !== null && $block instanceof MonsterSpawner){
            $event->cancel();
            $this->spawners->setMob($block->getPosition(), $eggMob);
            if(!$player->isCreative()){
                $player->getInventory()->setItemInHand($item->setCount($item->getCount() - 1));
            }
            $player->sendTip($this->plugin->message("egg-set", ["mob" => MobRegistry::get($eggMob)["name"]]));
            return;
        }
        if($eggMob !== null && !$item instanceof MobEgg){
            $event->cancel();
            $class = MobRegistry::CLASSES[$eggMob];
            $spot = $block->getSide($event->getFace())->getPosition()->add(0.5, 0, 0.5);
            $nbt = CompoundTag::create()->setByte("SSPersist", MobRegistry::get($eggMob)["mode"] === "hostile" ? 0 : 1);
            (new $class(Location::fromObject($spot, $player->getWorld(), $player->getLocation()->yaw + 180, 0.0), $nbt))->spawnToAll();
            if(!$player->isCreative()){
                $player->getInventory()->setItemInHand($item->setCount($item->getCount() - 1));
            }
            return;
        }
        if(!$block instanceof MonsterSpawner){
            return;
        }
        $entry = $this->spawners->get($block->getPosition());
        if($entry === null){
            return;
        }
        $mob = Spawners::mobOf($item);
        $name = MobRegistry::get($entry["mob"])["name"];
        if($mob === null){
            if($item->isNull()){
                $player->sendTip($this->plugin->message("info", ["mob" => $name, "stack" => $entry["stack"]]));
            }
            return;
        }
        $event->cancel();
        if($mob !== $entry["mob"]){
            return;
        }
        $max = (int) $this->plugin->getConfig()->getNested("spawners.max-stack", 64);
        if($entry["stack"] >= $max){
            $player->sendTip($this->plugin->message("stack-full", ["max" => $max]));
            return;
        }
        $stack = $this->spawners->stack($block->getPosition());
        if(!$player->isCreative()){
            $player->getInventory()->setItemInHand($item->setCount($item->getCount() - 1));
        }
        $player->sendTip($this->plugin->message("stacked", ["mob" => $name, "stack" => $stack]));
    }

    public function onPlace(BlockPlaceEvent $event): void{
        if($event->isCancelled()){
            return;
        }
        $mob = Spawners::mobOf($event->getItem());
        if($mob === null){
            return;
        }
        $world = $event->getPlayer()->getWorld();
        foreach($event->getTransaction()->getBlocks() as [$x, $y, $z, $block]){
            $position = new Position($x, $y, $z, $world);
            $this->plugin->getScheduler()->scheduleTask(new ClosureTask(function() use ($position, $mob): void{
                if($position->getWorld()->getBlock($position) instanceof MonsterSpawner){
                    $this->spawners->add($position, $mob);
                }
            }));
        }
    }

    public function onBreak(BlockBreakEvent $event): void{
        if($event->isCancelled() || !$event->getBlock() instanceof MonsterSpawner){
            return;
        }
        $entry = $this->spawners->remove($event->getBlock()->getPosition());
        if($entry === null){
            return;
        }
        $event->setXpDropAmount(0);
        $config = $this->plugin->getConfig();
        $silk = $event->getItem()->hasEnchantment(VanillaEnchantments::SILK_TOUCH());
        if((bool) $config->getNested("spawners.drop-on-break", true) && (!(bool) $config->getNested("spawners.require-silk-touch", false) || $silk)){
            $event->setDrops([$this->spawners->item($entry["mob"], $entry["stack"])]);
        }else{
            $event->setDrops([]);
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\entity\Entity;
use pocketmine\entity\Location;
use pocketmine\item\ItemIdentifier;
use pocketmine\item\SpawnEgg;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\world\World;

final class MobEgg extends SpawnEgg{
    public function __construct(ItemIdentifier $identifier, string $name, private string $mobKey){
        parent::__construct($identifier, $name);
    }

    public function getMobKey(): string{
        return $this->mobKey;
    }

    protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch): Entity{
        $class = MobRegistry::CLASSES[$this->mobKey];
        return new $class(Location::fromObject($pos, $world, $yaw, $pitch), CompoundTag::create()->setByte("SSPersist", MobRegistry::get($this->mobKey)["mode"] === "hostile" ? 0 : 1));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\entity\Location;
use pocketmine\player\Player;
use pocketmine\plugin\PluginBase;
use pocketmine\scheduler\ClosureTask;
use pocketmine\utils\Config;

final class Main extends PluginBase{
    private Spawners $spawners;

    protected function onEnable(): void{
        $this->saveDefaultConfig();
        $this->saveResource("mobs.yml");
        $mobs = (new Config($this->getDataFolder() . "mobs.yml", Config::YAML))->getAll();
        $bundled = $this->getResource("mobs.yml");
        if($bundled !== null){
            $defaults = yaml_parse((string) stream_get_contents($bundled));
            fclose($bundled);
            if(is_array($defaults)){
                $mobs += $defaults;
            }
        }
        MobRegistry::load($mobs, (array) $this->getConfig()->get("mobs", []));
        MobRegistry::setClimateVariants((bool) $this->getConfig()->getNested("mobs.climate-variants", true));
        MobRegistry::register();
        Items::register();
        Eggs::register();
        $this->saveResource("trades.yml");
        Trades::load($this, (new Config($this->getDataFolder() . "trades.yml", Config::YAML))->getAll());
        $this->spawners = new Spawners($this);
        $this->getServer()->getPluginManager()->registerEvents(new SpawnerListener($this, $this->spawners), $this);
        $this->getServer()->getPluginManager()->registerEvents(new MobListener($this), $this);
        $this->getScheduler()->scheduleRepeatingTask(new ClosureTask(fn() => $this->spawners->tick()), 20);
        $this->getLogger()->info(count(MobRegistry::keys()) . " mobs ready");
    }

    public function message(string $key, array $params = []): string{
        $text = (string) $this->getConfig()->getNested("messages." . $key, $key);
        foreach($params as $name => $value){
            $text = str_replace("{" . $name . "}", (string) $value, $text);
        }
        return $text;
    }

    public function onCommand(CommandSender $sender, Command $command, string $label, array $args): bool{
        $sub = strtolower($args[0] ?? "");
        if($sub === "list"){
            $sender->sendMessage($this->message("list", ["mobs" => implode(", ", MobRegistry::keys())]));
            return true;
        }
        if($sub !== "give" && $sub !== "summon"){
            $sender->sendMessage($this->message("usage"));
            return true;
        }
        $mob = strtolower($args[1] ?? "");
        if(!MobRegistry::exists($mob)){
            $sender->sendMessage($this->message("unknown-mob", ["mobs" => implode(", ", MobRegistry::keys())]));
            return true;
        }
        $name = MobRegistry::get($mob)["name"];
        if($sub === "summon"){
            if(!$sender instanceof Player){
                return false;
            }
            $amount = max(1, min(20, (int) ($args[2] ?? 1)));
            $class = MobRegistry::CLASSES[$mob];
            for($i = 0; $i < $amount; $i++){
                $location = $sender->getLocation();
                (new $class(Location::fromObject($location->add(mt_rand(-2, 2), 0, mt_rand(-2, 2)), $location->getWorld(), $location->yaw, 0.0)))->spawnToAll();
            }
            $sender->sendMessage($this->message("summoned", ["amount" => $amount, "mob" => $name]));
            return true;
        }
        $target = isset($args[2]) ? $this->getServer()->getPlayerByPrefix($args[2]) : ($sender instanceof Player ? $sender : null);
        if($target === null){
            $sender->sendMessage($this->message("player-not-found"));
            return true;
        }
        $amount = max(1, min(64, (int) ($args[3] ?? 1)));
        foreach($target->getInventory()->addItem($this->spawners->item($mob, $amount)) as $left){
            $target->getWorld()->dropItem($target->getPosition(), $left);
        }
        $sender->sendMessage($this->message("given", ["amount" => $amount, "mob" => $name, "player" => $target->getName()]));
        if($target !== $sender){
            $target->sendMessage($this->message("received", ["amount" => $amount, "mob" => $name]));
        }
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\entity\Entity;
use pocketmine\form\Form;
use pocketmine\item\Item;
use pocketmine\item\StringToItemParser;
use pocketmine\player\Player;
use pocketmine\world\particle\HappyVillagerParticle;
use pocketmine\world\sound\XpCollectSound;

final class Trades{
    private const VARIANTS = [
        "farmer" => 1, "fisherman" => 2, "shepherd" => 3, "fletcher" => 4, "librarian" => 5, "cartographer" => 6,
        "cleric" => 7, "armorer" => 8, "weaponsmith" => 9, "toolsmith" => 10, "butcher" => 11, "leatherworker" => 12, "mason" => 13,
    ];

    private static array $professions = [];
    private static array $wandering = [];
    private static ?Main $plugin = null;

    public static function load(Main $plugin, array $data): void{
        self::$plugin = $plugin;
        self::$professions = [];
        foreach((array) ($data["professions"] ?? []) as $name => $trades){
            $parsed = self::parse((array) $trades, (string) $name);
            if(count($parsed) > 0){
                self::$professions[strtolower((string) $name)] = $parsed;
            }
        }
        self::$wandering = self::parse((array) ($data["wandering-trader"] ?? []), "wandering-trader");
    }

    private static function parse(array $trades, string $context): array{
        $parsed = [];
        foreach($trades as $trade){
            if(!is_array($trade)){
                continue;
            }
            $give = [];
            foreach((array) ($trade["give"] ?? []) as $entry){
                $item = self::item((string) $entry);
                if($item === null){
                    self::$plugin?->getLogger()->warning("Unknown item in trades.yml ($context): $entry");
                    continue 2;
                }
                $give[] = $item;
            }
            $get = self::item((string) ($trade["get"] ?? ""));
            if($get === null || count($give) === 0){
                self::$plugin?->getLogger()->warning("Skipped a trade in trades.yml ($context)");
                continue;
            }
            $parsed[] = [$give, $get];
        }
        return $parsed;
    }

    private static function item(string $entry): ?Item{
        $parts = explode(":", $entry);
        $count = 1;
        if(count($parts) > 1 && ctype_digit(end($parts))){
            $count = (int) array_pop($parts);
        }
        $item = StringToItemParser::getInstance()->parse(implode(":", $parts));
        return $item?->setCount(max(1, $count));
    }

    public static function randomProfession(string $kind): string{
        if($kind === "wandering" || count(self::$professions) === 0){
            return "";
        }
        $names = array_keys(self::$professions);
        return $names[array_rand($names)];
    }

    public static function variant(string $profession): int{
        return self::VARIANTS[$profession] ?? 0;
    }

    public static function open(Player $player, Entity $trader, string $profession): void{
        $trades = $profession === "" ? self::$wandering : (self::$professions[$profession] ?? []);
        if(count($trades) === 0){
            return;
        }
        $title = $profession === "" ? "Wandering Trader" : ucfirst($profession);
        $player->sendForm(new class($title, $trades, $trader) implements Form{
            public function __construct(private string $title, private array $trades, private Entity $trader){}

            public function jsonSerialize(): array{
                $buttons = [];
                foreach($this->trades as [$give, $get]){
                    $cost = implode(" + ", array_map(static fn(Item $item) => $item->getCount() . " " . $item->getName(), $give));
                    $buttons[] = ["text" => $cost . "\nÂ§8for Â§r" . $get->getCount() . " " . $get->getName()];
                }
                return ["type" => "form", "title" => $this->title, "content" => "Pick a trade.", "buttons" => $buttons];
            }

            public function handleResponse(Player $player, $data): void{
                if(!is_int($data) || !isset($this->trades[$data]) || $this->trader->isClosed()){
                    return;
                }
                [$give, $get] = $this->trades[$data];
                $inventory = $player->getInventory();
                foreach($give as $cost){
                    if(!$inventory->contains($cost)){
                        $player->sendTip("Â§cYou don't have " . $cost->getCount() . " " . $cost->getName());
                        return;
                    }
                }
                foreach($give as $cost){
                    $inventory->removeItem(clone $cost);
                }
                foreach($inventory->addItem(clone $get) as $left){
                    $player->getWorld()->dropItem($player->getPosition(), $left);
                }
                $world = $this->trader->getWorld();
                $position = $this->trader->getPosition();
                for($i = 0; $i < 5; $i++){
                    $world->addParticle($position->add(mt_rand(-5, 5) / 10, 1.8 + mt_rand(0, 5) / 10, mt_rand(-5, 5) / 10), new HappyVillagerParticle());
                }
                $world->addSound($player->getPosition(), new XpCollectSound(), [$player]);
            }
        });
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\Location;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\utils\Config;
use pocketmine\world\particle\FlameParticle;
use pocketmine\world\Position;
use pocketmine\world\World;

final class Spawners{
    public const TAG = "SmartSpawnerMob";

    private Config $store;
    private array $entries = [];
    private array $timers = [];

    public function __construct(private Main $plugin){
        $this->store = new Config($plugin->getDataFolder() . "spawners.json", Config::JSON);
        foreach($this->store->getAll() as $key => $data){
            if(is_array($data) && MobRegistry::exists((string) ($data["mob"] ?? ""))){
                $this->entries[(string) $key] = ["mob" => (string) $data["mob"], "stack" => max(1, (int) ($data["stack"] ?? 1))];
            }
        }
    }

    public static function key(Position $pos): string{
        return $pos->getWorld()->getFolderName() . ";" . $pos->getFloorX() . ";" . $pos->getFloorY() . ";" . $pos->getFloorZ();
    }

    public function item(string $mob, int $amount = 1): Item{
        $item = VanillaBlocks::MONSTER_SPAWNER()->asItem()->setCount(max(1, $amount));
        $item->getNamedTag()->setString(self::TAG, $mob);
        $item->setCustomName("Â§rÂ§e" . MobRegistry::get($mob)["name"] . " Spawner");
        $item->setLore(["Â§rÂ§7Place it, then right click it with", "Â§rÂ§7another one of the same mob to stack."]);
        return $item;
    }

    public static function mobOf(Item $item): ?string{
        $mob = $item->getNamedTag()->getString(self::TAG, "");
        return $mob !== "" && MobRegistry::exists($mob) ? $mob : null;
    }

    public function get(Position $pos): ?array{
        return $this->entries[self::key($pos)] ?? null;
    }

    public function add(Position $pos, string $mob): void{
        $this->entries[self::key($pos)] = ["mob" => $mob, "stack" => 1];
        $this->save();
    }

    public function setMob(Position $pos, string $mob): void{
        $key = self::key($pos);
        $this->entries[$key] = ["mob" => $mob, "stack" => $this->entries[$key]["stack"] ?? 1];
        unset($this->timers[$key]);
        $this->save();
    }

    public function stack(Position $pos): int{
        $key = self::key($pos);
        $this->entries[$key]["stack"]++;
        $this->save();
        return $this->entries[$key]["stack"];
    }

    public function remove(Position $pos): ?array{
        $key = self::key($pos);
        $entry = $this->entries[$key] ?? null;
        unset($this->entries[$key], $this->timers[$key]);
        if($entry !== null){
            $this->save();
        }
        return $entry;
    }

    private function save(): void{
        $this->store->setAll($this->entries);
        $this->store->save();
    }

    private function setting(string $key, float $default): float{
        return (float) $this->plugin->getConfig()->getNested("spawners." . $key, $default);
    }

    public function tick(): void{
        $server = $this->plugin->getServer();
        $activation = $this->setting("activation-range", 16);
        foreach($this->entries as $key => $entry){
            [$worldName, $x, $y, $z] = explode(";", $key);
            $world = $server->getWorldManager()->getWorldByName($worldName);
            if($world === null || !$world->isChunkLoaded(((int) $x) >> 4, ((int) $z) >> 4)){
                continue;
            }
            $center = new Vector3((int) $x + 0.5, (int) $y, (int) $z + 0.5);
            $near = false;
            foreach($world->getPlayers() as $player){
                if($player->getPosition()->distanceSquared($center) <= $activation * $activation){
                    $near = true;
                    break;
                }
            }
            if(!$near){
                continue;
            }
            $this->timers[$key] = ($this->timers[$key] ?? $this->delay()) - 1;
            if($this->timers[$key] > 0){
                continue;
            }
            $this->timers[$key] = $this->delay();
            $this->spawn($world, $center, $entry["mob"], $entry["stack"]);
        }
    }

    private function delay(): int{
        $min = (int) $this->setting("delay-min-seconds", 15);
        return mt_rand(max(1, $min), max($min, (int) $this->setting("delay-max-seconds", 30)));
    }

    private function spawn(World $world, Vector3 $center, string $mob, int $stack): void{
        $class = MobRegistry::CLASSES[$mob] ?? null;
        if($class === null){
            return;
        }
        $radius = max(1, (int) $this->setting("spawn-radius", 4));
        $nearby = 0;
        foreach($world->getNearbyEntities(new AxisAlignedBB($center->x - 10, $center->y - 5, $center->z - 10, $center->x + 10, $center->y + 5, $center->z + 10)) as $entity){
            if($entity instanceof SmartMob && $entity::mobKey() === $mob){
                $nearby++;
            }
        }
        $wanted = min($stack * max(1, (int) $this->setting("mobs-per-stack", 1)), (int) $this->setting("max-nearby", 12) - $nearby);
        for($i = 0; $i < $wanted; $i++){
            for($attempt = 0; $attempt < 8; $attempt++){
                $pos = $center->add(mt_rand(-$radius, $radius), mt_rand(-1, 1), mt_rand(-$radius, $radius))->floor();
                if(!$world->getBlock($pos->down())->isSolid() || $world->getBlock($pos)->isSolid() || $world->getBlock($pos->up())->isSolid()){
                    continue;
                }
                $spot = new Location($pos->x + 0.5, $pos->y, $pos->z + 0.5, $world, (float) mt_rand(0, 359), 0.0);
                (new $class($spot))->spawnToAll();
                $world->addParticle($spot->add(0, 0.5, 0), new FlameParticle());
                break;
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\Listener;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\math\AxisAlignedBB;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\SyncActorPropertyPacket;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\PlayerAuthInputFlags;
use pocketmine\player\Player;
use pocketmine\scheduler\ClosureTask;

final class MobListener implements Listener{
    private const CLIMATE_TYPES = ["minecraft:cow", "minecraft:pig", "minecraft:chicken"];

    public function __construct(private Main $plugin){}

    public function onReceive(DataPacketReceiveEvent $event): void{
        $packet = $event->getPacket();
        if(!$packet instanceof PlayerAuthInputPacket){
            return;
        }
        $player = $event->getOrigin()->getPlayer();
        $vehicle = $player?->getVehicle();
        if($vehicle instanceof SmartMob){
            $vehicle->setRiderInput($packet->getMoveVecZ(), $packet->getMoveVecX(), $packet->getYaw(), $packet->getInputFlags()->get(PlayerAuthInputFlags::JUMPING));
        }
    }

    public function onSend(DataPacketSendEvent $event): void{
        if(!MobRegistry::climateVariants()){
            return;
        }
        foreach($event->getPackets() as $packet){
            if($packet instanceof StartGamePacket){
                foreach($event->getTargets() as $session){
                    $this->plugin->getScheduler()->scheduleTask(new ClosureTask(fn() => $this->sendProperties($session)));
                }
                return;
            }
        }
    }

    private function sendProperties(NetworkSession $session): void{
        if(!$session->isConnected()){
            return;
        }
        foreach(self::CLIMATE_TYPES as $type){
            $property = CompoundTag::create()
                ->setString("name", "minecraft:climate_variant")
                ->setInt("type", 3)
                ->setTag("enum", new ListTag([new StringTag("temperate"), new StringTag("warm"), new StringTag("cold")]));
            $nbt = CompoundTag::create()
                ->setString("type", $type)
                ->setTag("properties", new ListTag([$property]));
            $session->sendDataPacket(SyncActorPropertyPacket::create(new CacheableNbt($nbt)));
        }
    }

    public function onDamage(EntityDamageByEntityEvent $event): void{
        if($event->isCancelled()){
            return;
        }
        $victim = $event->getEntity();
        $damager = $event->getDamager();
        if($damager instanceof Player && $victim instanceof Living && !($victim instanceof SmartMob && $victim->isOwnedBy($damager))){
            $this->rallyWolves($damager, $victim);
        }elseif($victim instanceof Player && $damager instanceof Living){
            $this->rallyWolves($victim, $damager);
        }
    }

    private function rallyWolves(Player $owner, Living $target): void{
        $c = $owner->getPosition();
        foreach($owner->getWorld()->getNearbyEntities(new AxisAlignedBB($c->x - 24, $c->y - 8, $c->z - 24, $c->x + 24, $c->y + 8, $c->z + 24)) as $entity){
            if($entity instanceof SmartMob && $entity !== $target && $entity::mobKey() === "wolf" && $entity->isOwnedBy($owner)){
                $entity->becomeAngry($target->getId());
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\item\Item;

final class SingleItem extends Item{
    public function getMaxStackSize(): int{
        return 1;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use deepslatedev\smartspawners\entity\mob;
use deepslatedev\smartspawners\entity\projectile\LargeFireball;
use deepslatedev\smartspawners\entity\projectile\PoisonArrow;
use deepslatedev\smartspawners\entity\projectile\SlownessArrow;
use deepslatedev\smartspawners\entity\projectile\SmallFireball;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\EntityFactory;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\world\World;

final class MobRegistry{
    public const CLASSES = [
        "zombie" => mob\Zombie::class,
        "husk" => mob\Husk::class,
        "skeleton" => mob\Skeleton::class,
        "stray" => mob\Stray::class,
        "spider" => mob\Spider::class,
        "cave_spider" => mob\CaveSpider::class,
        "creeper" => mob\Creeper::class,
        "witch" => mob\Witch::class,
        "blaze" => mob\Blaze::class,
        "enderman" => mob\Enderman::class,
        "zombie_pigman" => mob\ZombiePigman::class,
        "iron_golem" => mob\IronGolem::class,
        "cow" => mob\Cow::class,
        "pig" => mob\Pig::class,
        "sheep" => mob\Sheep::class,
        "chicken" => mob\Chicken::class,
        "rabbit" => mob\Rabbit::class,
        "mooshroom" => mob\Mooshroom::class,
        "zombie_villager" => mob\ZombieVillager::class,
        "drowned" => mob\Drowned::class,
        "piglin" => mob\Piglin::class,
        "piglin_brute" => mob\PiglinBrute::class,
        "vindicator" => mob\Vindicator::class,
        "pillager" => mob\Pillager::class,
        "bogged" => mob\Bogged::class,
        "wither_skeleton" => mob\WitherSkeleton::class,
        "silverfish" => mob\Silverfish::class,
        "endermite" => mob\Endermite::class,
        "hoglin" => mob\Hoglin::class,
        "zoglin" => mob\Zoglin::class,
        "ravager" => mob\Ravager::class,
        "vex" => mob\Vex::class,
        "phantom" => mob\Phantom::class,
        "ghast" => mob\Ghast::class,
        "slime" => mob\Slime::class,
        "magma_cube" => mob\MagmaCube::class,
        "wolf" => mob\Wolf::class,
        "polar_bear" => mob\PolarBear::class,
        "llama" => mob\Llama::class,
        "trader_llama" => mob\TraderLlama::class,
        "panda" => mob\Panda::class,
        "goat" => mob\Goat::class,
        "fox" => mob\Fox::class,
        "bee" => mob\Bee::class,
        "dolphin" => mob\Dolphin::class,
        "horse" => mob\Horse::class,
        "donkey" => mob\Donkey::class,
        "mule" => mob\Mule::class,
        "skeleton_horse" => mob\SkeletonHorse::class,
        "zombie_horse" => mob\ZombieHorse::class,
        "camel" => mob\Camel::class,
        "cat" => mob\Cat::class,
        "ocelot" => mob\Ocelot::class,
        "parrot" => mob\Parrot::class,
        "bat" => mob\Bat::class,
        "allay" => mob\Allay::class,
        "axolotl" => mob\Axolotl::class,
        "cod" => mob\Cod::class,
        "salmon" => mob\Salmon::class,
        "pufferfish" => mob\Pufferfish::class,
        "tropical_fish" => mob\TropicalFish::class,
        "squid" => mob\Squid::class,
        "glow_squid" => mob\GlowSquid::class,
        "turtle" => mob\Turtle::class,
        "frog" => mob\Frog::class,
        "tadpole" => mob\Tadpole::class,
        "sniffer" => mob\Sniffer::class,
        "armadillo" => mob\Armadillo::class,
        "strider" => mob\Strider::class,
        "snow_golem" => mob\SnowGolem::class,
        "villager" => mob\Villager::class,
        "wandering_trader" => mob\WanderingTrader::class,
    ];

    private static array $defs = [];
    private static bool $climate = true;

    public static function setClimateVariants(bool $enabled): void{
        self::$climate = $enabled;
    }

    public static function climateVariants(): bool{
        return self::$climate;
    }

    public static function load(array $mobs, array $defaults): void{
        self::$defs = [];
        foreach(self::CLASSES as $key => $class){
            $data = is_array($mobs[$key] ?? null) ? $mobs[$key] : [];
            if(($data["enabled"] ?? true) === false){
                continue;
            }
            $drops = [];
            foreach((array) ($data["drops"] ?? []) as $drop){
                $parts = explode(":", (string) $drop);
                $range = explode("-", $parts[count($parts) - 1]);
                $id = count($parts) > 1 && is_numeric($range[0]) ? implode(":", array_slice($parts, 0, -1)) : (string) $drop;
                $min = count($parts) > 1 && is_numeric($range[0]) ? (int) $range[0] : 1;
                $max = isset($range[1]) && is_numeric($range[1]) ? (int) $range[1] : $min;
                $drops[] = [$id, max(0, $min), max($min, $max)];
            }
            self::$defs[$key] = [
                "name" => (string) ($data["name"] ?? ucwords(str_replace("_", " ", $key))),
                "mode" => in_array($data["mode"] ?? "", ["hostile", "neutral", "passive"], true) ? $data["mode"] : "passive",
                "health" => (int) ($data["health"] ?? 20),
                "damage" => (float) ($data["damage"] ?? 0),
                "speed" => (float) ($data["speed"] ?? $defaults["speed"] ?? 0.2),
                "follow-range" => (float) ($data["follow-range"] ?? $defaults["follow-range"] ?? 16),
                "leash" => (float) ($data["leash"] ?? $defaults["leash"] ?? 6),
                "xp" => (int) ($data["xp"] ?? 0),
                "explodes" => (bool) ($data["explodes"] ?? false),
                "despawn-seconds" => (int) ($defaults["despawn-seconds"] ?? 300),
                "drops" => $drops,
            ];
        }
    }

    public static function register(): void{
        $factory = EntityFactory::getInstance();
        foreach(self::CLASSES as $key => $class){
            $factory->register($class, static fn(World $world, CompoundTag $nbt): SmartMob => new $class(EntityDataHelper::parseLocation($nbt, $world), $nbt), ["SmartSpawners:" . $key]);
        }
        $factory->register(SmallFireball::class, static fn(World $world, CompoundTag $nbt): SmallFireball => new SmallFireball(EntityDataHelper::parseLocation($nbt, $world), null, $nbt), ["SmartSpawners:small_fireball"]);
        $factory->register(LargeFireball::class, static fn(World $world, CompoundTag $nbt): LargeFireball => new LargeFireball(EntityDataHelper::parseLocation($nbt, $world), null, $nbt), ["SmartSpawners:large_fireball"]);
        $factory->register(PoisonArrow::class, static fn(World $world, CompoundTag $nbt): PoisonArrow => new PoisonArrow(EntityDataHelper::parseLocation($nbt, $world), null, false, $nbt), ["SmartSpawners:poison_arrow"]);
        $factory->register(SlownessArrow::class, static fn(World $world, CompoundTag $nbt): SlownessArrow => new SlownessArrow(EntityDataHelper::parseLocation($nbt, $world), null, false, $nbt), ["SmartSpawners:slowness_arrow"]);
    }

    public static function exists(string $key): bool{
        return isset(self::$defs[$key]);
    }

    public static function get(string $key): array{
        return self::$defs[$key] ?? self::$defs[array_key_first(self::$defs)] ?? [
            "name" => $key, "mode" => "passive", "health" => 10, "damage" => 0, "speed" => 0.2, "follow-range" => 16,
            "leash" => 6, "xp" => 0, "explodes" => false, "despawn-seconds" => 300, "drops" => [],
        ];
    }

    public static function keys(): array{
        return array_keys(self::$defs);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\projectile;

use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\entity\projectile\Arrow;
use pocketmine\math\RayTraceResult;

final class PoisonArrow extends Arrow{
    public function canSaveWithChunk(): bool{
        return false;
    }

    protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult): void{
        parent::onHitEntity($entityHit, $hitResult);
        if($entityHit instanceof Living){
            $entityHit->getEffects()->add(new EffectInstance(VanillaEffects::POISON(), 80));
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\projectile;

use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\entity\projectile\Arrow;
use pocketmine\math\RayTraceResult;

final class SlownessArrow extends Arrow{
    public function canSaveWithChunk(): bool{
        return false;
    }

    protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult): void{
        parent::onHitEntity($entityHit, $hitResult);
        if($entityHit instanceof Living){
            $entityHit->getEffects()->add(new EffectInstance(VanillaEffects::SLOWNESS(), 600));
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\projectile;

use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\RayTraceResult;
use pocketmine\world\particle\HugeExplodeSeedParticle;
use pocketmine\world\sound\ExplodeSound;

final class LargeFireball extends Projectile{
    public static function getNetworkTypeId(): string{
        return "minecraft:fireball";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.0, 1.0);
    }

    protected function getInitialGravity(): float{
        return 0.0;
    }

    protected function getInitialDragMultiplier(): float{
        return 0.0;
    }

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

    protected function entityBaseTick(int $tickDiff = 1): bool{
        if($this->ticksLived > 160){
            $this->flagForDespawn();
        }
        return parent::entityBaseTick($tickDiff);
    }

    private function burst(): void{
        $world = $this->getWorld();
        $center = $this->location->asVector3();
        $world->addParticle($center, new HugeExplodeSeedParticle());
        $world->addSound($center, new ExplodeSound());
        $owner = $this->getOwningEntity() ?? $this;
        $radius = 2.5;
        foreach($world->getNearbyEntities(new AxisAlignedBB($center->x - $radius, $center->y - $radius, $center->z - $radius, $center->x + $radius, $center->y + $radius, $center->z + $radius), $this) as $entity){
            if(!$entity instanceof Living || $entity === $owner){
                continue;
            }
            $distance = $entity->getPosition()->distance($center);
            $damage = 6.0 * (1 - $distance / ($radius + 1));
            if($damage > 0){
                $event = new EntityDamageByChildEntityEvent($owner, $this, $entity, EntityDamageEvent::CAUSE_ENTITY_EXPLOSION, $damage);
                $entity->attack($event);
                if(!$event->isCancelled()){
                    $entity->setOnFire(3);
                }
            }
        }
        $this->flagForDespawn();
    }

    protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult): void{
        $this->burst();
    }

    protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult): void{
        $this->burst();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\projectile;

use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\math\RayTraceResult;

final class SmallFireball extends Projectile{
    public static function getNetworkTypeId(): string{
        return "minecraft:small_fireball";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.3125, 0.3125);
    }

    protected function getInitialGravity(): float{
        return 0.0;
    }

    protected function getInitialDragMultiplier(): float{
        return 0.0;
    }

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

    protected function entityBaseTick(int $tickDiff = 1): bool{
        if($this->ticksLived > 100){
            $this->flagForDespawn();
        }
        return parent::entityBaseTick($tickDiff);
    }

    protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult): void{
        $owner = $this->getOwningEntity() ?? $this;
        $event = new EntityDamageByChildEntityEvent($owner, $this, $entityHit, EntityDamageEvent::CAUSE_PROJECTILE, 5.0);
        $entityHit->attack($event);
        if(!$event->isCancelled()){
            $entityHit->setOnFire(5);
        }
        $this->flagForDespawn();
    }

    protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult): void{
        $this->flagForDespawn();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity;

use deepslatedev\smartspawners\Items;
use pocketmine\entity\Entity;
use pocketmine\entity\Location;
use pocketmine\entity\object\ExperienceOrb;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\player\Player;
use pocketmine\Server;
use pocketmine\world\particle\AngryVillagerParticle;
use pocketmine\world\particle\HeartParticle;
use pocketmine\world\particle\SmokeParticle;

trait Domestic{
    protected bool $tamed = false;
    protected ?string $owner = null;
    protected bool $sitting = false;
    protected bool $saddled = false;
    protected bool $baby = false;
    protected int $babyTicks = 0;
    protected bool $persistent = false;
    protected int $loveTicks = 0;
    protected int $breedCooldown = 0;
    protected int $temper = 0;
    protected float $riderForward = 0.0;
    protected float $riderStrafe = 0.0;
    protected float $riderYaw = 0.0;
    protected bool $riderJump = false;

    protected function breedItems(): array{
        return $this->temptItems();
    }

    protected function canBreed(): bool{
        return count($this->breedItems()) > 0 && $this->def()["mode"] !== "hostile";
    }

    protected function breedNeedsTame(): bool{
        return false;
    }

    protected function tameItems(): array{
        return [];
    }

    protected function tameChance(): int{
        return 3;
    }

    protected function tameByRiding(): bool{
        return false;
    }

    protected function canSit(): bool{
        return false;
    }

    protected function saddleable(): bool{
        return false;
    }

    protected function saddleNeedsTame(): bool{
        return true;
    }

    protected function controlItem(): ?string{
        return null;
    }

    protected function rideable(): bool{
        return false;
    }

    protected function riderSpeed(): float{
        return 0.35;
    }

    protected function jumpPower(): float{
        return 0.5;
    }

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

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

    public function isOwnedBy(Player $player): bool{
        return $this->tamed && $this->owner !== null && $this->owner === strtolower($player->getName());
    }

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

    protected function loadDomestic(CompoundTag $nbt): void{
        $this->tamed = $nbt->getByte("SSTamed", 0) === 1;
        $owner = $nbt->getString("SSOwner", "");
        $this->owner = $owner === "" ? null : $owner;
        $this->sitting = $nbt->getByte("SSSitting", 0) === 1;
        $this->saddled = $nbt->getByte("SSSaddled", 0) === 1;
        $this->baby = $nbt->getByte("SSBaby", 0) === 1;
        $this->babyTicks = $nbt->getInt("SSBabyTicks", $this->baby ? 24000 : 0);
        $this->persistent = $nbt->getByte("SSPersist", 0) === 1;
        if($this->baby){
            $this->setScale(0.5);
        }
    }

    protected function saveDomestic(CompoundTag $nbt): void{
        $nbt->setByte("SSTamed", $this->tamed ? 1 : 0);
        $nbt->setString("SSOwner", $this->owner ?? "");
        $nbt->setByte("SSSitting", $this->sitting ? 1 : 0);
        $nbt->setByte("SSSaddled", $this->saddled ? 1 : 0);
        $nbt->setByte("SSBaby", $this->baby ? 1 : 0);
        $nbt->setInt("SSBabyTicks", $this->babyTicks);
        $nbt->setByte("SSPersist", $this->persistent ? 1 : 0);
    }

    public function keepsWithWorld(): bool{
        return $this->tamed || $this->saddled || $this->persistent;
    }

    public function setRiderInput(float $forward, float $strafe, float $yaw, bool $jump): void{
        $this->riderForward = $forward;
        $this->riderStrafe = $strafe;
        $this->riderYaw = $yaw;
        $this->riderJump = $jump;
    }

    protected function driver(): ?Player{
        foreach($this->getPassengers() as $passenger){
            if($passenger instanceof Player){
                return $passenger;
            }
        }
        return null;
    }

    public function getSeatPosition(?Entity $passenger = null): Vector3{
        return new Vector3(0, $this->size->getHeight() * 0.85, 0);
    }

    protected function markDirty(): void{
        $this->networkPropertiesDirty = true;
    }

    protected function hearts(): void{
        for($i = 0; $i < 4; $i++){
            $this->getWorld()->addParticle($this->location->add(mt_rand(-5, 5) / 10, $this->size->getHeight() + mt_rand(0, 5) / 10, mt_rand(-5, 5) / 10), new HeartParticle());
        }
    }

    protected function consumeHand(Player $player): void{
        if(!$player->isCreative()){
            $hand = $player->getInventory()->getItemInHand();
            $player->getInventory()->setItemInHand($hand->setCount($hand->getCount() - 1));
        }
    }

    protected function holds(Player $player, array $typeIds): bool{
        return in_array($player->getInventory()->getItemInHand()->getTypeId(), $typeIds, true);
    }

    protected function domesticInteract(Player $player): bool{
        $hand = $player->getInventory()->getItemInHand();

        if(!$this->tamed && count($this->tameItems()) > 0 && $this->holds($player, $this->tameItems())){
            $this->consumeHand($player);
            if(mt_rand(1, $this->tameChance()) === 1){
                $this->tame($player);
            }else{
                for($i = 0; $i < 4; $i++){
                    $this->getWorld()->addParticle($this->location->add(mt_rand(-5, 5) / 10, $this->size->getHeight(), mt_rand(-5, 5) / 10), new SmokeParticle());
                }
            }
            return true;
        }

        if($this->holds($player, $this->breedItems()) && (!$this->breedNeedsTame() || $this->tamed)){
            if($this->baby){
                $this->consumeHand($player);
                $this->babyTicks = (int) ($this->babyTicks * 0.9);
                $this->hearts();
                return true;
            }
            if($this->canBreed() && $this->loveTicks <= 0 && $this->breedCooldown <= 0){
                $this->consumeHand($player);
                $this->loveTicks = 600;
                $this->persistent = true;
                $this->hearts();
                return true;
            }
        }

        if($this->saddleable() && !$this->saddled && !$this->baby && Items::isSaddle($hand) && (!$this->saddleNeedsTame() || $this->tamed)){
            $this->consumeHand($player);
            $this->saddled = true;
            $this->persistent = true;
            $this->markDirty();
            return true;
        }

        if($this->canSit() && $this->isOwnedBy($player) && !$this->rideable()){
            $this->sitting = !$this->sitting;
            $this->setMotion(new Vector3(0, $this->motion->y, 0));
            $this->markDirty();
            return true;
        }

        if($this->rideable() && !$this->baby && $this->driver() === null && $player->getVehicle() === null){
            $allowed = $this->tameByRiding() ? true : $this->saddled;
            if($allowed){
                $this->addPassenger($player);
                return true;
            }
        }
        return false;
    }

    protected function tame(Player $player): void{
        $this->tamed = true;
        $this->owner = strtolower($player->getName());
        $this->persistent = true;
        $this->angryAt = null;
        $this->markDirty();
        $this->hearts();
    }

    protected function domesticTick(int $tickDiff, array $def): bool{
        if($this->breedCooldown > 0){
            $this->breedCooldown -= $tickDiff;
        }
        if($this->baby){
            $this->babyTicks -= $tickDiff;
            if($this->babyTicks <= 0){
                $this->baby = false;
                $this->setScale(1.0);
                $this->markDirty();
            }
        }

        $driver = $this->driver();
        if($driver !== null){
            return $this->ridden($driver, $def);
        }

        if($this->loveTicks > 0){
            $this->loveTicks -= $tickDiff;
            if($this->age % 10 === 0){
                $this->hearts();
            }
            $partner = $this->findPartner();
            if($partner !== null){
                if($partner->getPosition()->distance($this->location) < 1.6){
                    $this->breedWith($partner);
                }else{
                    $this->walkToward($partner->getPosition(), (float) $def["speed"]);
                }
                return true;
            }
        }

        if($this->sitting){
            $this->setMotion(new Vector3(0, $this->motion->y, 0));
            return true;
        }

        if($this->tamed && $this->owner !== null && $this->angryAt === null){
            $owner = Server::getInstance()->getPlayerExact($this->owner);
            if($owner !== null && $owner->getWorld() === $this->getWorld() && !$this->rideable()){
                $distance = $owner->getPosition()->distance($this->location);
                if($distance > 14){
                    $this->teleport($owner->getPosition()->add(mt_rand(-1, 1), 0, mt_rand(-1, 1)));
                    return true;
                }
                if($distance > 4){
                    $this->walkToward($owner->getPosition(), (float) $def["speed"] * 1.2);
                    return true;
                }
            }
        }
        return false;
    }

    private function ridden(Player $driver, array $def): bool{
        if(!$this->tamed && $this->tameByRiding()){
            if($this->age % 40 === 0){
                $this->temper += mt_rand(0, 5);
                if(mt_rand(0, 99) < $this->temper){
                    $this->tame($driver);
                }elseif(mt_rand(1, 3) === 1){
                    $this->removePassenger($driver);
                    for($i = 0; $i < 4; $i++){
                        $this->getWorld()->addParticle($this->location->add(mt_rand(-5, 5) / 10, $this->size->getHeight(), mt_rand(-5, 5) / 10), new AngryVillagerParticle());
                    }
                }
            }
            $this->setMotion(new Vector3(mt_rand(-10, 10) / 60, $this->onGround && mt_rand(1, 20) === 1 ? 0.35 : $this->motion->y, mt_rand(-10, 10) / 60));
            return true;
        }
        $control = $this->controlItem();
        if($control !== null){
            if(!Items::holdsControl($driver, $control)){
                return false;
            }
            $yaw = $driver->getLocation()->yaw;
            $forward = 1.0;
            $strafe = 0.0;
        }else{
            if(!$this->saddled){
                $this->setMotion(new Vector3(0, $this->motion->y, 0));
                return true;
            }
            $yaw = $this->riderYaw;
            $forward = $this->riderForward;
            $strafe = $this->riderStrafe;
        }
        $rad = deg2rad($yaw);
        $speed = $this->riderSpeed();
        $x = (-sin($rad) * $forward + cos($rad) * $strafe) * $speed;
        $z = (cos($rad) * $forward + sin($rad) * $strafe) * $speed;
        $y = $this->motion->y;
        if($this->onGround && ($this->riderJump || $this->isCollidedHorizontally) && ($forward !== 0.0 || $strafe !== 0.0)){
            $y = $this->riderJump ? $this->jumpPower() : 0.42;
        }
        $this->setRotation($yaw, 0.0);
        $this->setMotion(new Vector3($x, $y, $z));
        return true;
    }

    private function findPartner(): ?self{
        $c = $this->location;
        foreach($this->getWorld()->getNearbyEntities(new AxisAlignedBB($c->x - 8, $c->y - 4, $c->z - 8, $c->x + 8, $c->y + 4, $c->z + 8), $this) as $entity){
            if($entity instanceof self && $entity::mobKey() === static::mobKey() && !$entity->baby && $entity->loveTicks > 0 && $entity->isAlive()){
                return $entity;
            }
        }
        return null;
    }

    private function breedWith(self $partner): void{
        $this->loveTicks = 0;
        $partner->loveTicks = 0;
        $this->breedCooldown = 6000;
        $partner->breedCooldown = 6000;
        $world = $this->getWorld();
        $nbt = CompoundTag::create()->setByte("SSBaby", 1)->setInt("SSBabyTicks", 24000)->setByte("SSPersist", 1);
        if($this->tamed && $this->owner !== null){
            $nbt->setByte("SSTamed", 1)->setString("SSOwner", $this->owner);
        }
        $baby = new static(Location::fromObject($this->location, $world, $this->location->yaw, 0.0), $nbt);
        $baby->spawnToAll();
        $this->hearts();
        (new ExperienceOrb(Location::fromObject($this->location->add(0, 0.5, 0), $world, 0.0, 0.0), mt_rand(1, 7)))->spawnToAll();
    }

    protected function domesticDrops(array $drops): array{
        if($this->baby){
            return [];
        }
        if($this->saddled && ($saddle = Items::saddle()) instanceof Item){
            $drops[] = $saddle;
        }
        return $drops;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity;

use deepslatedev\smartspawners\entity\projectile\LargeFireball;
use deepslatedev\smartspawners\entity\projectile\SmallFireball;
use pocketmine\entity\Living;
use pocketmine\entity\Location;
use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\PotionType;
use pocketmine\item\StringToItemParser;
use pocketmine\math\Vector3;
use pocketmine\world\sound\BlazeShootSound;
use pocketmine\world\sound\BowShootSound;
use pocketmine\world\sound\ThrowSound;

final class Shots{
    private static array $ids = [];

    public static function itemIds(string ...$names): array{
        $key = implode(",", $names);
        if(!isset(self::$ids[$key])){
            $ids = [];
            foreach($names as $name){
                $item = StringToItemParser::getInstance()->parse($name);
                if($item !== null){
                    $ids[] = $item->getTypeId();
                }
            }
            self::$ids[$key] = $ids;
        }
        return self::$ids[$key];
    }

    private static function aim(Living $from, Living $target, float $speed, float $arc, float $spread): array{
        $origin = $from->getEyePos();
        $aimAt = $target->getPosition()->add(0, $target->getSize()->getHeight() * 0.6, 0);
        $dx = $aimAt->x - $origin->x;
        $dz = $aimAt->z - $origin->z;
        $flat = sqrt($dx * $dx + $dz * $dz);
        $dy = $aimAt->y - $origin->y + $flat * $arc;
        $direction = (new Vector3($dx, $dy, $dz))->normalize();
        $direction = $direction->add(mt_rand(-100, 100) / 100 * $spread, mt_rand(-100, 100) / 100 * $spread, mt_rand(-100, 100) / 100 * $spread)->normalize();
        $yaw = rad2deg(atan2(-$direction->x, $direction->z));
        $pitch = rad2deg(-asin(max(-1.0, min(1.0, $direction->y))));
        return [Location::fromObject($origin->addVector($direction->multiply(0.8)), $from->getWorld(), $yaw, $pitch), $direction->multiply($speed)];
    }

    public static function arrow(Living $from, Living $target, string $class = Arrow::class): void{
        [$location, $motion] = self::aim($from, $target, 1.6, 0.2, 0.05);
        $arrow = new $class($location, $from, false);
        if($arrow instanceof Arrow){
            $arrow->setPickupMode(Arrow::PICKUP_NONE);
        }
        $arrow->setMotion($motion);
        $arrow->spawnToAll();
        $from->getWorld()->addSound($from->getPosition(), new BowShootSound());
    }

    public static function fireball(Living $from, Living $target): void{
        [$location, $motion] = self::aim($from, $target, 1.1, 0.0, 0.08);
        $ball = new SmallFireball($location, $from);
        $ball->setMotion($motion);
        $ball->spawnToAll();
        $from->getWorld()->addSound($from->getPosition(), new BlazeShootSound());
    }

    public static function largeFireball(Living $from, Living $target): void{
        [$location, $motion] = self::aim($from, $target, 0.9, 0.0, 0.03);
        $ball = new LargeFireball($location, $from);
        $ball->setMotion($motion);
        $ball->spawnToAll();
        $from->getWorld()->addSound($from->getPosition(), new BlazeShootSound());
    }

    public static function snowball(Living $from, Living $target): void{
        [$location, $motion] = self::aim($from, $target, 1.4, 0.15, 0.04);
        $ball = new Snowball($location, $from);
        $ball->setMotion($motion);
        $ball->spawnToAll();
        $from->getWorld()->addSound($from->getPosition(), new ThrowSound());
    }

    public static function potion(Living $from, Living $target, PotionType $type): void{
        [$location, $motion] = self::aim($from, $target, 0.75, 0.35, 0.03);
        $potion = new SplashPotion($location, $from, $type);
        $potion->setMotion($motion);
        $potion->spawnToAll();
        $from->getWorld()->addSound($from->getPosition(), new ThrowSound());
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class GlowSquid extends SmartMob{
    public static function mobKey(): string{
        return "glow_squid";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:glow_squid";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.95, 0.95);
    }

    public function getName(): string{
        return "Glow Squid";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Bee extends SmartMob{
    public static function mobKey(): string{
        return "bee";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:bee";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.6, 0.7);
    }

    public function getName(): string{
        return "Bee";
    }

    protected function canFly(): bool{
        return true;
    }

    protected function groupAnger(): bool{
        return true;
    }

    protected function temptItems(): array{
        return Shots::itemIds("dandelion", "poppy");
    }

    protected function onMeleeHit(Living $target): void{
        $target->getEffects()->add(new EffectInstance(VanillaEffects::POISON(), 200));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\projectile\PoisonArrow;
use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class Bogged extends SmartMob{
    public static function mobKey(): string{
        return "bogged";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:bogged";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.99, 0.6);
    }

    public function getName(): string{
        return "Bogged";
    }

    protected function burnsInDaylight(): bool{
        return true;
    }

    public function heldItem(): ?Item{
        return VanillaItems::BOW();
    }

    protected function rangedRange(): float{
        return 15.0;
    }

    protected function preferredDistance(): float{
        return 5.0;
    }

    protected function shoot(Living $target): int{
        Shots::arrow($this, $target, PoisonArrow::class);
        return mt_rand(40, 60);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\VanillaItems;
use pocketmine\world\sound\PopSound;

final class Chicken extends SmartMob{
    public static function mobKey(): string{
        return "chicken";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:chicken";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.7, 0.4);
    }

    public function getName(): string{
        return "Chicken";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat_seeds", "melon_seeds", "pumpkin_seeds", "beetroot_seeds");
    }

    public function climateVariant(): bool{
        return true;
    }

    private int $eggTimer = 0;

    protected function extraTick(int $tickDiff): void{
        if(!$this->onGround && $this->motion->y < -0.1){
            $this->setMotion($this->motion->withComponents(null, -0.1, null));
        }
        if($this->eggTimer <= 0){
            $this->eggTimer = mt_rand(6000, 12000);
        }
        $this->eggTimer -= $tickDiff;
        if($this->eggTimer <= 0){
            $this->getWorld()->dropItem($this->location, VanillaItems::EGG());
            $this->getWorld()->addSound($this->location, new PopSound());
        }
    }

    protected function immuneTo(int $cause): bool{
        return $cause === EntityDamageEvent::CAUSE_FALL;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class ZombieHorse extends SmartMob{
    public static function mobKey(): string{
        return "zombie_horse";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:zombie_horse";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.6, 1.4);
    }

    public function getName(): string{
        return "Zombie Horse";
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.4;
    }

    protected function jumpPower(): float{
        return 0.55;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Camel extends SmartMob{
    public static function mobKey(): string{
        return "camel";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:camel";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(2.375, 1.7);
    }

    public function getName(): string{
        return "Camel";
    }

    protected function temptItems(): array{
        return Shots::itemIds("cactus");
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function saddleNeedsTame(): bool{
        return false;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.3;
    }

    protected function jumpPower(): float{
        return 0.6;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageEvent;

final class IronGolem extends SmartMob{
    public static function mobKey(): string{
        return "iron_golem";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:iron_golem";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(2.7, 1.4);
    }

    public function getName(): string{
        return "Iron Golem";
    }

    protected function monsterTargets(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return $cause === EntityDamageEvent::CAUSE_FALL || $cause === EntityDamageEvent::CAUSE_DROWNING;
    }

    protected function onMeleeHit(Living $target): void{
        $target->setMotion($target->getMotion()->withComponents(null, 0.6, null));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Horse extends SmartMob{
    public static function mobKey(): string{
        return "horse";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:horse";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.6, 1.4);
    }

    public function getName(): string{
        return "Horse";
    }

    protected function temptItems(): array{
        return Shots::itemIds("golden_apple", "golden_carrot", "apple", "wheat", "sugar");
    }

    protected function breedNeedsTame(): bool{
        return true;
    }

    protected function breedItems(): array{
        return Shots::itemIds("golden_apple", "golden_carrot");
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.5;
    }

    protected function jumpPower(): float{
        return 0.7;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Parrot extends SmartMob{
    public static function mobKey(): string{
        return "parrot";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:parrot";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.9, 0.5);
    }

    public function getName(): string{
        return "Parrot";
    }

    protected function canFly(): bool{
        return true;
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat_seeds", "melon_seeds", "pumpkin_seeds", "beetroot_seeds");
    }

    protected function tameItems(): array{
        return Shots::itemIds("wheat_seeds", "melon_seeds", "pumpkin_seeds", "beetroot_seeds");
    }

    protected function tameChance(): int{
        return 3;
    }

    protected function canSit(): bool{
        return true;
    }

    protected function breedItems(): array{
        return Shots::itemIds();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use deepslatedev\smartspawners\Trades;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player;

final class Villager extends SmartMob{
    public static function mobKey(): string{
        return "villager";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:villager_v2";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Villager";
    }

    private string $profession = "";

    protected function initEntity(CompoundTag $nbt): void{
        parent::initEntity($nbt);
        $this->profession = $nbt->getString("SSProfession", Trades::randomProfession("villager"));
    }

    public function saveNBT(): CompoundTag{
        $nbt = parent::saveNBT();
        $nbt->setString("SSProfession", $this->profession);
        return $nbt;
    }

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setInt(EntityMetadataProperties::VARIANT, Trades::variant($this->profession));
    }

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        if($this->baby){
            return parent::onInteract($player, $clickPos);
        }
        Trades::open($player, $this, $this->profession);
        return true;
    }

    protected function extraTick(int $tickDiff): void{
        if($this->age % 10 !== 0 || $this->fleeTicks > 0){
            return;
        }
        $c = $this->location;
        foreach($this->getWorld()->getNearbyEntities(new AxisAlignedBB($c->x - 8, $c->y - 3, $c->z - 8, $c->x + 8, $c->y + 3, $c->z + 8), $this) as $entity){
            if($entity instanceof SmartMob && in_array($entity::mobKey(), ["zombie", "husk", "drowned", "zombie_villager"], true)){
                $this->fleeTicks = 40;
                $this->fleeFrom = $entity->getPosition()->asVector3();
                return;
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Ravager extends SmartMob{
    public static function mobKey(): string{
        return "ravager";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:ravager";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(2.2, 1.95);
    }

    public function getName(): string{
        return "Ravager";
    }

    protected function onMeleeHit(Living $target): void{
        $target->setMotion($target->getMotion()->withComponents(null, 0.6, null));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class TraderLlama extends SmartMob{
    public static function mobKey(): string{
        return "trader_llama";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:trader_llama";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.87, 0.9);
    }

    public function getName(): string{
        return "Trader Llama";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat", "hay_bale");
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.35;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\GrazeAnimation;
use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\Shears;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\player\Player;

final class Sheep extends SmartMob{
    public static function mobKey(): string{
        return "sheep";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:sheep";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.3, 0.9);
    }

    public function getName(): string{
        return "Sheep";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat");
    }

    private bool $sheared = false;
    private int $grazeTimer = 0;

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setGenericFlag(EntityMetadataFlags::SHEARED, $this->sheared);
    }

    private function setSheared(bool $sheared): void{
        $this->sheared = $sheared;
        $this->networkPropertiesDirty = true;
    }

    public function getDrops(): array{
        $drops = parent::getDrops();
        if($this->sheared){
            $wool = VanillaBlocks::WOOL()->asItem()->getTypeId();
            $drops = array_values(array_filter($drops, static fn(Item $item) => $item->getTypeId() !== $wool));
        }
        return $drops;
    }

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        $hand = $player->getInventory()->getItemInHand();
        if($hand instanceof Shears && !$this->sheared){
            $this->setSheared(true);
            $this->getWorld()->dropItem($this->location->add(0, 1, 0), VanillaBlocks::WOOL()->setColor(DyeColor::WHITE)->asItem()->setCount(mt_rand(1, 3)));
            if(!$player->isCreative()){
                $hand->applyDamage(1);
                $player->getInventory()->setItemInHand($hand);
            }
            return true;
        }
        return parent::onInteract($player, $clickPos);
    }

    protected function extraTick(int $tickDiff): void{
        if($this->grazeTimer <= 0){
            $this->grazeTimer = mt_rand(600, 1800);
        }
        $this->grazeTimer -= $tickDiff;
        if($this->grazeTimer > 0){
            return;
        }
        $below = $this->location->floor()->down();
        $world = $this->getWorld();
        if($world->getBlock($below)->getTypeId() === VanillaBlocks::GRASS()->getTypeId()){
            $world->setBlock($below, VanillaBlocks::DIRT());
            $this->broadcastAnimation(new GrazeAnimation($this));
            if($this->sheared){
                $this->setSheared(false);
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Donkey extends SmartMob{
    public static function mobKey(): string{
        return "donkey";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:donkey";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.6, 1.4);
    }

    public function getName(): string{
        return "Donkey";
    }

    protected function temptItems(): array{
        return Shots::itemIds("golden_apple", "golden_carrot", "apple", "wheat", "sugar");
    }

    protected function breedNeedsTame(): bool{
        return true;
    }

    protected function breedItems(): array{
        return Shots::itemIds("golden_apple", "golden_carrot");
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.35;
    }

    protected function jumpPower(): float{
        return 0.55;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use pocketmine\block\VanillaBlocks;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\item\Shears;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\player\Player;

final class Mooshroom extends Cow{
    public static function mobKey(): string{
        return "mooshroom";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:mooshroom";
    }

    public function getName(): string{
        return "Mooshroom";
    }

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

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        $hand = $player->getInventory()->getItemInHand();
        if($hand->getTypeId() === VanillaItems::BOWL()->getTypeId()){
            self::exchange($player, VanillaItems::MUSHROOM_STEW());
            return true;
        }
        if($hand instanceof Shears){
            $world = $this->getWorld();
            $world->dropItem($this->location->add(0, 1, 0), VanillaBlocks::RED_MUSHROOM()->asItem()->setCount(5));
            $cow = new Cow(Location::fromObject($this->location, $world, $this->location->yaw, 0.0));
            $cow->spawnToAll();
            if(!$player->isCreative()){
                $hand->applyDamage(1);
                $player->getInventory()->setItemInHand($hand);
            }
            $this->flagForDespawn();
            return true;
        }
        return parent::onInteract($player, $clickPos);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageEvent;

final class Ghast extends SmartMob{
    public static function mobKey(): string{
        return "ghast";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:ghast";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(4.0, 4.0);
    }

    public function getName(): string{
        return "Ghast";
    }

    protected function canFly(): bool{
        return true;
    }

    public function isFireProof(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return in_array($cause, [EntityDamageEvent::CAUSE_FIRE, EntityDamageEvent::CAUSE_FIRE_TICK, EntityDamageEvent::CAUSE_LAVA], true);
    }

    protected function rangedRange(): float{
        return 32.0;
    }

    protected function preferredDistance(): float{
        return 12.0;
    }

    protected function shoot(Living $target): int{
        Shots::largeFireball($this, $target);
        return mt_rand(60, 90);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Sniffer extends SmartMob{
    public static function mobKey(): string{
        return "sniffer";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:sniffer";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.75, 1.9);
    }

    public function getName(): string{
        return "Sniffer";
    }

    protected function temptItems(): array{
        return Shots::itemIds("torchflower_seeds");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Zoglin extends SmartMob{
    public static function mobKey(): string{
        return "zoglin";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:zoglin";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.4, 1.4);
    }

    public function getName(): string{
        return "Zoglin";
    }

    protected function onMeleeHit(Living $target): void{
        $target->setMotion($target->getMotion()->withComponents(null, 0.5, null));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\math\Vector3;

final class Rabbit extends SmartMob{
    public static function mobKey(): string{
        return "rabbit";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:rabbit";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.5, 0.4);
    }

    public function getName(): string{
        return "Rabbit";
    }

    protected function temptItems(): array{
        return Shots::itemIds("carrot", "golden_carrot", "dandelion");
    }

    protected function walkToward(Vector3 $target, float $speed): void{
        $dx = $target->x - $this->location->x;
        $dz = $target->z - $this->location->z;
        $length = sqrt($dx * $dx + $dz * $dz);
        if($length < 0.05){
            return;
        }
        $this->setRotation(rad2deg(atan2(-$dx, $dz)), $this->location->pitch);
        if($this->onGround){
            $this->setMotion(new Vector3($dx / $length * $speed * 1.8, 0.32, $dz / $length * $speed * 1.8));
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\player\Player;

class Cow extends SmartMob{
    public static function mobKey(): string{
        return "cow";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:cow";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.4, 0.9);
    }

    public function getName(): string{
        return "Cow";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat");
    }

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        $hand = $player->getInventory()->getItemInHand();
        if($hand->getTypeId() === VanillaItems::BUCKET()->getTypeId()){
            self::exchange($player, VanillaItems::MILK_BUCKET());
            return true;
        }
        return parent::onInteract($player, $clickPos);
    }

    protected static function exchange(Player $player, Item $result): void{
        if($player->isCreative()){
            $player->getInventory()->addItem($result);
            return;
        }
        $hand = $player->getInventory()->getItemInHand();
        if($hand->getCount() === 1){
            $player->getInventory()->setItemInHand($result);
            return;
        }
        $player->getInventory()->setItemInHand($hand->setCount($hand->getCount() - 1));
        foreach($player->getInventory()->addItem($result) as $left){
            $player->getWorld()->dropItem($player->getPosition(), $left);
        }
    }

    public function climateVariant(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Turtle extends SmartMob{
    public static function mobKey(): string{
        return "turtle";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:turtle";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.4, 1.2);
    }

    public function getName(): string{
        return "Turtle";
    }

    protected function swims(): bool{
        return true;
    }

    protected function temptItems(): array{
        return Shots::itemIds("seagrass");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Goat extends SmartMob{
    public static function mobKey(): string{
        return "goat";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:goat";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.3, 0.9);
    }

    public function getName(): string{
        return "Goat";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\projectile\SlownessArrow;
use deepslatedev\smartspawners\entity\Shots;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Stray extends Skeleton{
    public static function mobKey(): string{
        return "stray";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:stray";
    }

    public function getName(): string{
        return "Stray";
    }

    protected function shoot(Living $target): int{
        Shots::arrow($this, $target, SlownessArrow::class);
        return mt_rand(30, 50);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Phantom extends SmartMob{
    public static function mobKey(): string{
        return "phantom";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:phantom";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.5, 0.9);
    }

    public function getName(): string{
        return "Phantom";
    }

    protected function canFly(): bool{
        return true;
    }

    protected function burnsInDaylight(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Panda extends SmartMob{
    public static function mobKey(): string{
        return "panda";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:panda";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.25, 1.3);
    }

    public function getName(): string{
        return "Panda";
    }

    protected function temptItems(): array{
        return Shots::itemIds("bamboo");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\math\Vector3;

class Spider extends SmartMob{
    public static function mobKey(): string{
        return "spider";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:spider";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.9, 1.4);
    }

    public function getName(): string{
        return "Spider";
    }

    protected function isHostileNow(): bool{
        $pos = $this->location->floor();
        return $this->getWorld()->getFullLightAt($pos->x, $pos->y, $pos->z) < 12;
    }

    protected function extraTick(int $tickDiff): void{
        if($this->isCollidedHorizontally && !$this->isUnderwater()){
            $this->setMotion($this->motion->withComponents(null, 0.2, null));
        }
    }

    protected function engage(Living $target, array $def): void{
        $distance = $target->getPosition()->distance($this->location);
        if($distance > 2.0 && $distance < 5.0 && $this->onGround && mt_rand(1, 12) === 1){
            $push = $target->getPosition()->subtractVector($this->location)->withComponents(null, 0, null)->normalize()->multiply(0.55);
            $this->setMotion(new Vector3($push->x, 0.4, $push->z));
            return;
        }
        parent::engage($target, $def);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Silverfish extends SmartMob{
    public static function mobKey(): string{
        return "silverfish";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:silverfish";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.3, 0.4);
    }

    public function getName(): string{
        return "Silverfish";
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Salmon extends SmartMob{
    public static function mobKey(): string{
        return "salmon";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:salmon";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.5, 0.7);
    }

    public function getName(): string{
        return "Salmon";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Drowned extends SmartMob{
    public static function mobKey(): string{
        return "drowned";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:drowned";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Drowned";
    }

    protected function swims(): bool{
        return true;
    }

    protected function burnsInDaylight(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Cat extends SmartMob{
    public static function mobKey(): string{
        return "cat";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:cat";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.7, 0.6);
    }

    public function getName(): string{
        return "Cat";
    }

    protected function temptItems(): array{
        return Shots::itemIds("raw_fish", "raw_salmon");
    }

    protected function tameItems(): array{
        return Shots::itemIds("raw_fish", "raw_salmon");
    }

    protected function tameChance(): int{
        return 3;
    }

    protected function canSit(): bool{
        return true;
    }

    protected function breedNeedsTame(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class Vindicator extends SmartMob{
    public static function mobKey(): string{
        return "vindicator";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:vindicator";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Vindicator";
    }

    public function heldItem(): ?Item{
        return VanillaItems::IRON_AXE();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class Pillager extends SmartMob{
    public static function mobKey(): string{
        return "pillager";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:pillager";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Pillager";
    }

    public function heldItem(): ?Item{
        return VanillaItems::BOW();
    }

    protected function rangedRange(): float{
        return 12.0;
    }

    protected function preferredDistance(): float{
        return 6.0;
    }

    protected function shoot(Living $target): int{
        Shots::arrow($this, $target);
        return mt_rand(35, 55);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Bat extends SmartMob{
    public static function mobKey(): string{
        return "bat";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:bat";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.9, 0.5);
    }

    public function getName(): string{
        return "Bat";
    }

    protected function canFly(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\item\PotionType;
use pocketmine\world\sound\PopSound;

final class Witch extends SmartMob{
    public static function mobKey(): string{
        return "witch";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:witch";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Witch";
    }

    private int $drinkCooldown = 0;

    protected function rangedRange(): float{
        return 10.0;
    }

    protected function preferredDistance(): float{
        return 4.0;
    }

    protected function shoot(Living $target): int{
        $distance = $target->getPosition()->distance($this->location);
        $effects = $target->getEffects();
        if($distance >= 8 && !$effects->has(VanillaEffects::SLOWNESS())){
            $type = PotionType::SLOWNESS;
        }elseif($target->getHealth() >= 8 && !$effects->has(VanillaEffects::POISON())){
            $type = PotionType::POISON;
        }elseif($distance <= 3 && !$effects->has(VanillaEffects::WEAKNESS()) && mt_rand(1, 4) === 1){
            $type = PotionType::WEAKNESS;
        }else{
            $type = PotionType::HARMING;
        }
        Shots::potion($this, $target, $type);
        return 60;
    }

    protected function extraTick(int $tickDiff): void{
        $this->drinkCooldown = max(0, $this->drinkCooldown - $tickDiff);
        if($this->drinkCooldown === 0 && $this->getHealth() < $this->getMaxHealth() * 0.5){
            $this->drinkCooldown = 120;
            $this->heal(new EntityRegainHealthEvent($this, 6.0, EntityRegainHealthEvent::CAUSE_CUSTOM));
            $this->getWorld()->addSound($this->location, new PopSound());
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Mule extends SmartMob{
    public static function mobKey(): string{
        return "mule";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:mule";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.6, 1.4);
    }

    public function getName(): string{
        return "Mule";
    }

    protected function temptItems(): array{
        return Shots::itemIds("golden_apple", "golden_carrot", "apple", "wheat", "sugar");
    }

    protected function breedItems(): array{
        return Shots::itemIds();
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.35;
    }

    protected function jumpPower(): float{
        return 0.55;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class TropicalFish extends SmartMob{
    public static function mobKey(): string{
        return "tropical_fish";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:tropicalfish";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.4, 0.4);
    }

    public function getName(): string{
        return "Tropical Fish";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class Piglin extends SmartMob{
    public static function mobKey(): string{
        return "piglin";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:piglin";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Piglin";
    }

    protected function groupAnger(): bool{
        return true;
    }

    public function heldItem(): ?Item{
        return VanillaItems::GOLDEN_SWORD();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Dolphin extends SmartMob{
    public static function mobKey(): string{
        return "dolphin";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:dolphin";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.6, 0.9);
    }

    public function getName(): string{
        return "Dolphin";
    }

    protected function swims(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Husk extends SmartMob{
    public static function mobKey(): string{
        return "husk";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:husk";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Husk";
    }

    protected function onMeleeHit(Living $target): void{
        $target->getEffects()->add(new EffectInstance(VanillaEffects::HUNGER(), 140));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Frog extends SmartMob{
    public static function mobKey(): string{
        return "frog";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:frog";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.55, 0.5);
    }

    public function getName(): string{
        return "Frog";
    }

    protected function swims(): bool{
        return true;
    }

    protected function hops(): bool{
        return true;
    }

    protected function temptItems(): array{
        return Shots::itemIds("slime_ball");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Fox extends SmartMob{
    public static function mobKey(): string{
        return "fox";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:fox";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.7, 0.6);
    }

    public function getName(): string{
        return "Fox";
    }

    protected function temptItems(): array{
        return Shots::itemIds("sweet_berries");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Axolotl extends SmartMob{
    public static function mobKey(): string{
        return "axolotl";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:axolotl";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.42, 0.75);
    }

    public function getName(): string{
        return "Axolotl";
    }

    protected function swims(): bool{
        return true;
    }

    protected function monsterTargets(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class ZombieVillager extends SmartMob{
    public static function mobKey(): string{
        return "zombie_villager";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:zombie_villager_v2";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Zombie Villager";
    }

    protected function burnsInDaylight(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class ZombiePigman extends SmartMob{
    public static function mobKey(): string{
        return "zombie_pigman";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:zombie_pigman";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Zombified Piglin";
    }

    public function heldItem(): ?Item{
        return VanillaItems::GOLDEN_SWORD();
    }

    protected function groupAnger(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\math\AxisAlignedBB;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\world\particle\HugeExplodeSeedParticle;
use pocketmine\world\sound\ExplodeSound;

final class Creeper extends SmartMob{
    public static function mobKey(): string{
        return "creeper";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:creeper";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.7, 0.6);
    }

    public function getName(): string{
        return "Creeper";
    }

    private const FUSE = 30;

    private int $fuse = -1;

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setGenericFlag(EntityMetadataFlags::IGNITED, $this->fuse >= 0);
        $properties->setInt(EntityMetadataProperties::FUSE_LENGTH, self::FUSE);
    }

    private function setFuse(int $fuse): void{
        $was = $this->fuse >= 0;
        $this->fuse = $fuse;
        if($was !== ($fuse >= 0)){
            $this->networkPropertiesDirty = true;
        }
    }

    protected function engage(Living $target, array $def): void{
        $distance = $target->getPosition()->distance($this->location);
        $this->lookAt($target->getEyePos());
        if($distance < 3.0 || ($this->fuse >= 0 && $distance < 7.0)){
            if($this->fuse < 0){
                $this->setFuse(0);
            }
            $this->setMotion($this->motion->withComponents(0, null, 0));
            $this->fuse++;
            if($this->fuse >= self::FUSE){
                $this->explode((float) $def["damage"]);
            }
            return;
        }
        $this->setFuse(-1);
        $this->walkToward($target->getPosition(), (float) $def["speed"]);
    }

    protected function idle(array $def): void{
        $this->setFuse(-1);
        parent::idle($def);
    }

    private function explode(float $power): void{
        $world = $this->getWorld();
        $center = $this->location->asVector3();
        $world->addParticle($center, new HugeExplodeSeedParticle());
        $world->addSound($center, new ExplodeSound());
        $radius = 4.0;
        foreach($world->getNearbyEntities(new AxisAlignedBB($center->x - $radius, $center->y - $radius, $center->z - $radius, $center->x + $radius, $center->y + $radius, $center->z + $radius), $this) as $entity){
            if(!$entity instanceof Living){
                continue;
            }
            $distance = $entity->getPosition()->distance($center);
            $damage = $power * (1 - $distance / $radius);
            if($damage > 0){
                $entity->attack(new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_EXPLOSION, $damage));
                $push = $entity->getPosition()->subtractVector($center)->normalize()->multiply(0.6);
                $entity->setMotion($entity->getMotion()->addVector($push->withComponents(null, 0.4, null)));
            }
        }
        $this->flagForDespawn();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Squid extends SmartMob{
    public static function mobKey(): string{
        return "squid";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:squid";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.95, 0.95);
    }

    public function getName(): string{
        return "Squid";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class CaveSpider extends Spider{
    public static function mobKey(): string{
        return "cave_spider";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:cave_spider";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.5, 0.7);
    }

    public function getName(): string{
        return "Cave Spider";
    }

    protected function onMeleeHit(Living $target): void{
        $target->getEffects()->add(new EffectInstance(VanillaEffects::POISON(), 140));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class WitherSkeleton extends SmartMob{
    public static function mobKey(): string{
        return "wither_skeleton";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:wither_skeleton";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(2.4, 0.7);
    }

    public function getName(): string{
        return "Wither Skeleton";
    }

    public function isFireProof(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return in_array($cause, [EntityDamageEvent::CAUSE_FIRE, EntityDamageEvent::CAUSE_FIRE_TICK, EntityDamageEvent::CAUSE_LAVA], true);
    }

    public function heldItem(): ?Item{
        return VanillaItems::STONE_SWORD();
    }

    protected function onMeleeHit(Living $target): void{
        $target->getEffects()->add(new EffectInstance(VanillaEffects::WITHER(), 200));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Endermite extends SmartMob{
    public static function mobKey(): string{
        return "endermite";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:endermite";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.3, 0.4);
    }

    public function getName(): string{
        return "Endermite";
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

class Skeleton extends SmartMob{
    public static function mobKey(): string{
        return "skeleton";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:skeleton";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.99, 0.6);
    }

    public function getName(): string{
        return "Skeleton";
    }

    protected function burnsInDaylight(): bool{
        return true;
    }

    public function heldItem(): ?Item{
        return VanillaItems::BOW();
    }

    protected function rangedRange(): float{
        return 15.0;
    }

    protected function preferredDistance(): float{
        return 5.0;
    }

    protected function shoot(Living $target): int{
        Shots::arrow($this, $target);
        return mt_rand(30, 50);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\event\entity\EntityDamageEvent;

final class Strider extends SmartMob{
    public static function mobKey(): string{
        return "strider";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:strider";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.7, 0.9);
    }

    public function getName(): string{
        return "Strider";
    }

    public function isFireProof(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return in_array($cause, [EntityDamageEvent::CAUSE_FIRE, EntityDamageEvent::CAUSE_FIRE_TICK, EntityDamageEvent::CAUSE_LAVA], true);
    }

    protected function temptItems(): array{
        return Shots::itemIds("warped_fungus");
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function saddleNeedsTame(): bool{
        return false;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.25;
    }

    protected function controlItem(): ?string{
        return "warped_fungus_on_a_stick";
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use pocketmine\entity\EntitySizeInfo;
use pocketmine\event\entity\EntityDamageEvent;

final class MagmaCube extends Slime{
    public static function mobKey(): string{
        return "magma_cube";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:magma_cube";
    }

    public function getName(): string{
        return "Magma Cube";
    }

    public function isFireProof(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return in_array($cause, [EntityDamageEvent::CAUSE_FIRE, EntityDamageEvent::CAUSE_FIRE_TICK, EntityDamageEvent::CAUSE_LAVA, EntityDamageEvent::CAUSE_FALL], true);
    }

    protected function meleeDamage(array $def): float{
        return (float) ($this->slimeSize + 2);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class PolarBear extends SmartMob{
    public static function mobKey(): string{
        return "polar_bear";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:polar_bear";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.4, 1.3);
    }

    public function getName(): string{
        return "Polar Bear";
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\nbt\tag\CompoundTag;

class Slime extends SmartMob{
    public static function mobKey(): string{
        return "slime";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:slime";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.52, 0.52);
    }

    public function getName(): string{
        return "Slime";
    }

    protected int $slimeSize = 1;

    protected function initEntity(CompoundTag $nbt): void{
        $this->slimeSize = $nbt->getInt("SlimeSize", [1, 2, 4][mt_rand(0, 2)]);
        parent::initEntity($nbt);
        $this->setScale((float) $this->slimeSize);
        $this->setMaxHealth(max(1, $this->slimeSize * $this->slimeSize));
        $this->setHealth((float) $this->getMaxHealth());
    }

    public function getSlimeSize(): int{
        return $this->slimeSize;
    }

    protected function hops(): bool{
        return true;
    }

    protected function meleeDamage(array $def): float{
        return $this->slimeSize <= 1 ? 0.0 : (float) $this->slimeSize;
    }

    public function getDrops(): array{
        return $this->slimeSize === 1 ? parent::getDrops() : [];
    }

    protected function onDeath(): void{
        parent::onDeath();
        if($this->slimeSize <= 1){
            return;
        }
        $world = $this->getWorld();
        for($i = mt_rand(2, 4); $i > 0; $i--){
            $spot = Location::fromObject($this->location->add(mt_rand(-5, 5) / 10, 0.2, mt_rand(-5, 5) / 10), $world, (float) mt_rand(0, 359), 0.0);
            (new static($spot, CompoundTag::create()->setInt("SlimeSize", intdiv($this->slimeSize, 2))))->spawnToAll();
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\particle\PortalParticle;
use pocketmine\world\sound\EndermanTeleportSound;

final class Enderman extends SmartMob{
    public static function mobKey(): string{
        return "enderman";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:enderman";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(2.9, 0.6);
    }

    public function getName(): string{
        return "Enderman";
    }

    public function attack(EntityDamageEvent $source): void{
        if($source->getCause() === EntityDamageEvent::CAUSE_PROJECTILE){
            $source->cancel();
            $this->teleportRandomly(null);
            return;
        }
        parent::attack($source);
    }

    protected function onHurtBy(Entity $damager): void{
        parent::onHurtBy($damager);
        if(mt_rand(1, 3) === 1){
            $this->teleportRandomly(null);
        }
    }

    protected function extraTick(int $tickDiff): void{
        if($this->isUnderwater()){
            if($this->age % 20 === 0){
                $this->attack(new EntityDamageEvent($this, EntityDamageEvent::CAUSE_DROWNING, 1.0));
                $this->teleportRandomly(null);
            }
            return;
        }
        if($this->age % 5 !== 0){
            return;
        }
        if($this->angryAt === null){
            foreach($this->getWorld()->getPlayers() as $player){
                if($this->validPlayer($player) && $this->isStaredAtBy($player)){
                    $this->angryAt = $player->getId();
                    break;
                }
            }
            return;
        }
        $target = $this->getWorld()->getEntity($this->angryAt);
        if($target instanceof Living && $target->getPosition()->distance($this->location) > 12 && mt_rand(1, 8) === 1){
            $this->teleportRandomly($target->getPosition());
        }
    }

    private function isStaredAtBy(Player $player): bool{
        $eye = $player->getEyePos();
        $head = $this->location->add(0, $this->size->getHeight() - 0.3, 0);
        $toHead = $head->subtractVector($eye);
        $distance = $toHead->length();
        if($distance > 64 || $distance < 0.1){
            return false;
        }
        return $player->getDirectionVector()->dot($toHead->divide($distance)) > 1.0 - 0.025 / $distance;
    }

    private function teleportRandomly(?Vector3 $near): void{
        $world = $this->getWorld();
        $origin = $near ?? $this->location->asVector3();
        $spread = $near === null ? 16 : 4;
        for($attempt = 0; $attempt < 16; $attempt++){
            $x = (int) floor($origin->x) + mt_rand(-$spread, $spread);
            $z = (int) floor($origin->z) + mt_rand(-$spread, $spread);
            for($y = (int) floor($origin->y) + 8; $y > (int) floor($origin->y) - 8; $y--){
                if($world->getBlockAt($x, $y - 1, $z)->isSolid() && !$world->getBlockAt($x, $y, $z)->isSolid() && !$world->getBlockAt($x, $y + 1, $z)->isSolid() && !$world->getBlockAt($x, $y + 2, $z)->isSolid()){
                    $from = $this->location->asVector3();
                    $to = new Vector3($x + 0.5, $y, $z + 0.5);
                    $world->addSound($from, new EndermanTeleportSound());
                    for($i = 0; $i < 8; $i++){
                        $world->addParticle($from->add(mt_rand(-5, 5) / 10, mt_rand(0, 25) / 10, mt_rand(-5, 5) / 10), new PortalParticle());
                    }
                    $this->teleport($to);
                    $world->addSound($to, new EndermanTeleportSound());
                    return;
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Tadpole extends SmartMob{
    public static function mobKey(): string{
        return "tadpole";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:tadpole";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.3, 0.4);
    }

    public function getName(): string{
        return "Tadpole";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class Hoglin extends SmartMob{
    public static function mobKey(): string{
        return "hoglin";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:hoglin";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.4, 1.4);
    }

    public function getName(): string{
        return "Hoglin";
    }

    protected function onMeleeHit(Living $target): void{
        $target->setMotion($target->getMotion()->withComponents(null, 0.5, null));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;

final class SnowGolem extends SmartMob{
    public static function mobKey(): string{
        return "snow_golem";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:snow_golem";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.8, 0.4);
    }

    public function getName(): string{
        return "Snow Golem";
    }

    protected function monsterTargets(): bool{
        return true;
    }

    protected function rangedRange(): float{
        return 10.0;
    }

    protected function preferredDistance(): float{
        return 3.0;
    }

    protected function shoot(Living $target): int{
        Shots::snowball($this, $target);
        return mt_rand(20, 30);
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Allay extends SmartMob{
    public static function mobKey(): string{
        return "allay";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:allay";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.6, 0.6);
    }

    public function getName(): string{
        return "Allay";
    }

    protected function canFly(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Llama extends SmartMob{
    public static function mobKey(): string{
        return "llama";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:llama";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.87, 0.9);
    }

    public function getName(): string{
        return "Llama";
    }

    protected function temptItems(): array{
        return Shots::itemIds("wheat", "hay_bale");
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.35;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Armadillo extends SmartMob{
    public static function mobKey(): string{
        return "armadillo";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:armadillo";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.65, 0.7);
    }

    public function getName(): string{
        return "Armadillo";
    }

    protected function temptItems(): array{
        return Shots::itemIds("spider_eye");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;

final class Blaze extends SmartMob{
    public static function mobKey(): string{
        return "blaze";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:blaze";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.8, 0.6);
    }

    public function getName(): string{
        return "Blaze";
    }

    private int $burst = 0;
    private bool $charging = false;

    protected function getInitialGravity(): float{
        return 0.0;
    }

    public function isFireProof(): bool{
        return true;
    }

    protected function immuneTo(int $cause): bool{
        return in_array($cause, [EntityDamageEvent::CAUSE_FIRE, EntityDamageEvent::CAUSE_FIRE_TICK, EntityDamageEvent::CAUSE_LAVA, EntityDamageEvent::CAUSE_FALL], true);
    }

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setGenericFlag(EntityMetadataFlags::ONFIRE, $this->charging);
    }

    private function setCharging(bool $charging): void{
        if($this->charging !== $charging){
            $this->charging = $charging;
            $this->networkPropertiesDirty = true;
        }
    }

    protected function rangedRange(): float{
        return 24.0;
    }

    protected function shoot(Living $target): int{
        if($this->burst === 0){
            $this->burst = 3;
            $this->setCharging(true);
            return 20;
        }
        Shots::fireball($this, $target);
        $this->burst--;
        if($this->burst > 0){
            return 6;
        }
        $this->setCharging(false);
        return 100;
    }

    protected function engage(Living $target, array $def): void{
        $this->lookAt($target->getEyePos());
        $distance = $target->getPosition()->distance($this->location);
        $desiredY = $target->getPosition()->y + 2.0 + sin($this->age / 20) * 0.5;
        $vertical = max(-0.15, min(0.15, ($desiredY - $this->location->y) * 0.1));
        $flat = $target->getPosition()->subtractVector($this->location)->withComponents(null, 0, null);
        $horizontal = new Vector3(0, 0, 0);
        if($distance > 10){
            $horizontal = $flat->normalize()->multiply((float) $def["speed"]);
        }elseif($distance < 4){
            $horizontal = $flat->normalize()->multiply(-(float) $def["speed"]);
        }
        $this->setMotion(new Vector3($horizontal->x, $vertical, $horizontal->z));
        if($distance <= $this->rangedRange() && $this->rangedCooldown === 0){
            $this->rangedCooldown = $this->shoot($target);
        }
    }

    protected function idle(array $def): void{
        if($this->burst > 0 || $this->charging){
            $this->burst = 0;
            $this->setCharging(false);
        }
        parent::idle($def);
        $vertical = $this->onGround ? 0.0 : -0.04;
        if($this->age % 90 === 0){
            $vertical = 0.3;
        }
        $this->setMotion($this->motion->withComponents(null, $vertical, null));
    }

    protected function extraTick(int $tickDiff): void{
        if($this->isUnderwater() && $this->age % 20 === 0){
            $this->attack(new EntityDamageEvent($this, EntityDamageEvent::CAUSE_DROWNING, 1.0));
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Cod extends SmartMob{
    public static function mobKey(): string{
        return "cod";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:cod";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.3, 0.5);
    }

    public function getName(): string{
        return "Cod";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Pufferfish extends SmartMob{
    public static function mobKey(): string{
        return "pufferfish";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:pufferfish";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.7, 0.7);
    }

    public function getName(): string{
        return "Pufferfish";
    }

    protected function swims(): bool{
        return true;
    }

    protected function breathesAir(): bool{
        return false;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

class Zombie extends SmartMob{
    public static function mobKey(): string{
        return "zombie";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:zombie";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Zombie";
    }

    protected function burnsInDaylight(): bool{
        return true;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class Vex extends SmartMob{
    public static function mobKey(): string{
        return "vex";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:vex";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.8, 0.4);
    }

    public function getName(): string{
        return "Vex";
    }

    protected function canFly(): bool{
        return true;
    }

    public function heldItem(): ?Item{
        return VanillaItems::IRON_SWORD();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Wolf extends SmartMob{
    public static function mobKey(): string{
        return "wolf";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:wolf";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.85, 0.6);
    }

    public function getName(): string{
        return "Wolf";
    }

    protected function groupAnger(): bool{
        return true;
    }

    protected function tameItems(): array{
        return Shots::itemIds("bone");
    }

    protected function tameChance(): int{
        return 3;
    }

    protected function canSit(): bool{
        return true;
    }

    protected function breedNeedsTame(): bool{
        return true;
    }

    protected function breedItems(): array{
        return Shots::itemIds("raw_beef", "raw_porkchop", "raw_chicken", "raw_mutton", "raw_rabbit", "steak", "cooked_porkchop", "cooked_chicken", "cooked_mutton", "cooked_rabbit");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Ocelot extends SmartMob{
    public static function mobKey(): string{
        return "ocelot";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:ocelot";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.7, 0.6);
    }

    public function getName(): string{
        return "Ocelot";
    }

    protected function temptItems(): array{
        return Shots::itemIds("raw_fish", "raw_salmon");
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\Shots;
use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class Pig extends SmartMob{
    public static function mobKey(): string{
        return "pig";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:pig";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(0.9, 0.9);
    }

    public function getName(): string{
        return "Pig";
    }

    protected function temptItems(): array{
        return Shots::itemIds("carrot", "potato", "beetroot");
    }

    public function climateVariant(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function saddleNeedsTame(): bool{
        return false;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function controlItem(): ?string{
        return "carrot_on_a_stick";
    }

    protected function riderSpeed(): float{
        return 0.25;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;

final class SkeletonHorse extends SmartMob{
    public static function mobKey(): string{
        return "skeleton_horse";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:skeleton_horse";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.6, 1.4);
    }

    public function getName(): string{
        return "Skeleton Horse";
    }

    protected function tameByRiding(): bool{
        return true;
    }

    protected function saddleable(): bool{
        return true;
    }

    protected function rideable(): bool{
        return true;
    }

    protected function riderSpeed(): float{
        return 0.5;
    }

    protected function jumpPower(): float{
        return 0.6;
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use deepslatedev\smartspawners\Trades;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player;

final class WanderingTrader extends SmartMob{
    public static function mobKey(): string{
        return "wandering_trader";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:wandering_trader";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Wandering Trader";
    }

    private string $profession = "";

    protected function initEntity(CompoundTag $nbt): void{
        parent::initEntity($nbt);
        $this->profession = $nbt->getString("SSProfession", Trades::randomProfession("wandering"));
    }

    public function saveNBT(): CompoundTag{
        $nbt = parent::saveNBT();
        $nbt->setString("SSProfession", $this->profession);
        return $nbt;
    }

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setInt(EntityMetadataProperties::VARIANT, Trades::variant($this->profession));
    }

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        if($this->baby){
            return parent::onInteract($player, $clickPos);
        }
        Trades::open($player, $this, $this->profession);
        return true;
    }

    protected function extraTick(int $tickDiff): void{
        if($this->age % 10 !== 0 || $this->fleeTicks > 0){
            return;
        }
        $c = $this->location;
        foreach($this->getWorld()->getNearbyEntities(new AxisAlignedBB($c->x - 8, $c->y - 3, $c->z - 8, $c->x + 8, $c->y + 3, $c->z + 8), $this) as $entity){
            if($entity instanceof SmartMob && in_array($entity::mobKey(), ["zombie", "husk", "drowned", "zombie_villager"], true)){
                $this->fleeTicks = 40;
                $this->fleeFrom = $entity->getPosition()->asVector3();
                return;
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity\mob;

use deepslatedev\smartspawners\entity\SmartMob;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;

final class PiglinBrute extends SmartMob{
    public static function mobKey(): string{
        return "piglin_brute";
    }

    public static function getNetworkTypeId(): string{
        return "minecraft:piglin_brute";
    }

    protected function getInitialSizeInfo(): EntitySizeInfo{
        return new EntitySizeInfo(1.95, 0.6);
    }

    public function getName(): string{
        return "Piglin Brute";
    }

    public function heldItem(): ?Item{
        return VanillaItems::GOLDEN_AXE();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity;

use pocketmine\entity\animation\Animation;
use pocketmine\entity\Entity;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;

final class GrazeAnimation implements Animation{
    public function __construct(private Entity $entity){}

    public function encode(): array{
        return [ActorEventPacket::create($this->entity->getId(), ActorEvent::EAT_GRASS_ANIMATION, 0, null)];
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners\entity;

use deepslatedev\smartspawners\MobRegistry;
use pocketmine\block\Water;
use pocketmine\entity\animation\ArmSwingAnimation;
use pocketmine\entity\Attribute;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\item\StringToItemParser;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\AddActorPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
use pocketmine\network\mcpe\protocol\types\entity\EntityLink;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\player\GameMode;
use pocketmine\player\Player;
use pocketmine\Server;

abstract class SmartMob extends Living{
    use Domestic;

    protected ?Vector3 $anchor = null;
    protected ?Vector3 $wanderTarget = null;
    protected int $wanderCooldown = 0;
    protected int $attackCooldown = 0;
    protected int $rangedCooldown = 0;
    protected int $fleeTicks = 0;
    protected ?Vector3 $fleeFrom = null;
    protected ?int $angryAt = null;
    protected int $lonelyTicks = 0;
    protected int $age = 0;

    abstract public static function mobKey(): string;

    protected function def(): array{
        return MobRegistry::get(static::mobKey());
    }

    protected function initEntity(CompoundTag $nbt): void{
        parent::initEntity($nbt);
        $def = $this->def();
        $this->setMaxHealth(max(1, (int) $def["health"]));
        $this->setHealth((float) $this->getMaxHealth());
        $this->setStepHeight(1.0);
        $this->anchor = $this->location->asVector3();
        $this->wanderCooldown = mt_rand(20, 80);
        $this->rangedCooldown = mt_rand(20, 40);
        $this->loadDomestic($nbt);
    }

    public function saveNBT(): CompoundTag{
        $nbt = parent::saveNBT();
        $this->saveDomestic($nbt);
        return $nbt;
    }

    protected function syncNetworkData(EntityMetadataCollection $properties): void{
        parent::syncNetworkData($properties);
        $properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);
        $properties->setGenericFlag(EntityMetadataFlags::TAMED, $this->tamed);
        $properties->setGenericFlag(EntityMetadataFlags::SITTING, $this->sitting);
        $properties->setGenericFlag(EntityMetadataFlags::SADDLED, $this->saddled);
        $properties->setGenericFlag(EntityMetadataFlags::INLOVE, $this->loveTicks > 0);
        $owner = $this->owner === null ? null : Server::getInstance()->getPlayerExact($this->owner);
        $properties->setLong(EntityMetadataProperties::OWNER_EID, $owner?->getId() ?? -1);
    }

    public function onInteract(Player $player, Vector3 $clickPos): bool{
        if($this->domesticInteract($player)){
            return true;
        }
        return parent::onInteract($player, $clickPos);
    }

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

    public function heldItem(): ?Item{
        return null;
    }

    protected function sendSpawnPacket(Player $player): void{
        $links = [];
        foreach($this->getPassengers() as $index => $passenger){
            $links[] = new EntityLink($this->getId(), $passenger->getId(), $index === 0 ? EntityLink::TYPE_RIDER : EntityLink::TYPE_PASSENGER, true, false, 0.0);
        }
        $synced = $this->climateVariant() && MobRegistry::climateVariants() ? new PropertySyncData([0 => 0], []) : new PropertySyncData([], []);
        $player->getNetworkSession()->sendDataPacket(AddActorPacket::create(
            $this->getId(),
            $this->getId(),
            static::getNetworkTypeId(),
            $this->getOffsetPosition($this->location->asVector3()),
            $this->getMotion(),
            $this->location->pitch,
            $this->location->yaw,
            $this->location->yaw,
            $this->location->yaw,
            array_map(static fn(Attribute $attribute): NetworkAttribute => new NetworkAttribute($attribute->getId(), $attribute->getMinValue(), $attribute->getMaxValue(), $attribute->getValue(), $attribute->getDefaultValue(), []), $this->attributeMap->getAll()),
            $this->getAllNetworkData(),
            $synced,
            $links
        ));
        $item = $this->heldItem();
        if($item !== null){
            $session = $player->getNetworkSession();
            $stack = $session->getTypeConverter()->coreItemStackToNet($item);
            $session->sendDataPacket(MobEquipmentPacket::create($this->getId(), new ItemStackWrapper(0, $stack), 0, 0, ContainerIds::INVENTORY));
        }
    }

    public function getDrops(): array{
        $drops = [];
        foreach($this->def()["drops"] as [$id, $min, $max]){
            $item = StringToItemParser::getInstance()->parse($id);
            $count = mt_rand($min, $max);
            if($item instanceof Item && $count > 0){
                $drops[] = $item->setCount($count);
            }
        }
        return $this->domesticDrops($drops);
    }

    public function getXpDropAmount(): int{
        return !$this->baby && $this->lastDamageCause instanceof EntityDamageByEntityEvent && $this->lastDamageCause->getDamager() instanceof Player ? (int) $this->def()["xp"] : 0;
    }

    protected function immuneTo(int $cause): bool{
        return false;
    }

    public function attack(EntityDamageEvent $source): void{
        if($this->immuneTo($source->getCause())){
            $source->cancel();
        }
        parent::attack($source);
        if($source->isCancelled() || !$source instanceof EntityDamageByEntityEvent){
            return;
        }
        $damager = $source->getDamager();
        if($damager !== null){
            $this->onHurtBy($damager);
        }
    }

    protected function onHurtBy(Entity $damager): void{
        if($this->sitting){
            $this->sitting = false;
            $this->markDirty();
        }
        if($damager instanceof Player && $this->isOwnedBy($damager)){
            return;
        }
        if($this->def()["mode"] === "passive"){
            $this->fleeTicks = 80;
            $this->fleeFrom = $damager->getPosition()->asVector3();
        }elseif($damager instanceof Living && !($damager instanceof Player && !$this->validPlayer($damager))){
            $this->angryAt = $damager->getId();
            if($this->groupAnger()){
                $c = $this->location;
                foreach($this->getWorld()->getNearbyEntities(new AxisAlignedBB($c->x - 32, $c->y - 10, $c->z - 32, $c->x + 32, $c->y + 10, $c->z + 32), $this) as $entity){
                    if($entity instanceof SmartMob && $entity::mobKey() === static::mobKey()){
                        $entity->becomeAngry($damager->getId());
                    }
                }
            }
        }
    }

    protected function canFly(): bool{
        return false;
    }

    protected function swims(): bool{
        return false;
    }

    protected function breathesAir(): bool{
        return true;
    }

    protected function hops(): bool{
        return false;
    }

    protected function getInitialGravity(): float{
        return $this->canFly() ? 0.0 : parent::getInitialGravity();
    }

    protected function inWater(): bool{
        return $this->isUnderwater() || $this->getWorld()->getBlock($this->location->floor()) instanceof Water;
    }

    protected function burnsInDaylight(): bool{
        return false;
    }

    protected function isHostileNow(): bool{
        return $this->def()["mode"] === "hostile";
    }

    protected function rangedRange(): float{
        return 0.0;
    }

    protected function preferredDistance(): float{
        return 0.0;
    }

    protected function shoot(Living $target): int{
        return 40;
    }

    protected function onMeleeHit(Living $target): void{
    }

    protected function meleeDamage(array $def): float{
        return (float) $def["damage"];
    }

    protected function groupAnger(): bool{
        return false;
    }

    public function becomeAngry(int $targetId): void{
        $this->angryAt = $targetId;
    }

    protected function temptItems(): array{
        return [];
    }

    protected function extraTick(int $tickDiff): void{
    }

    protected function monsterTargets(): bool{
        return false;
    }

    protected function entityBaseTick(int $tickDiff = 1): bool{
        $update = parent::entityBaseTick($tickDiff);
        if(!$this->isAlive() || $this->isClosed() || $this->isFlaggedForDespawn()){
            return $update;
        }
        $this->age += $tickDiff;
        $this->think($tickDiff);
        if(!$this->isClosed() && !$this->isFlaggedForDespawn()){
            $this->extraTick($tickDiff);
        }
        return true;
    }

    protected function think(int $tickDiff): void{
        $def = $this->def();
        $this->attackCooldown = max(0, $this->attackCooldown - $tickDiff);
        if($this->swims() && $this->inWater() && !$this->canFly()){
            $this->setMotion($this->motion->withComponents(null, $this->motion->y + $this->gravity * 0.9, null));
        }
        $this->rangedCooldown = max(0, $this->rangedCooldown - $tickDiff);
        if($this->burnsInDaylight() && $this->age % 20 === 0 && $this->inSunlight()){
            $this->setOnFire(8);
        }
        if($this->swims() && !$this->breathesAir() && $this->age % 20 === 0 && !$this->inWater()){
            $this->attack(new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1.0));
        }
        if($this->domesticTick($tickDiff, $def)){
            return;
        }
        $nearest = $this->nearestPlayer(64.0);
        if($nearest === null){
            $this->lonelyTicks += $tickDiff;
            if($def["despawn-seconds"] > 0 && $this->lonelyTicks > $def["despawn-seconds"] * 20 && !$this->keepsWithWorld()){
                $this->flagForDespawn();
            }
            return;
        }
        $this->lonelyTicks = 0;

        if($this->fleeTicks > 0 && $this->fleeFrom !== null){
            $this->fleeTicks -= $tickDiff;
            $away = $this->location->subtractVector($this->fleeFrom)->withComponents(null, 0, null);
            if($away->lengthSquared() < 0.01){
                $away = new Vector3(mt_rand(-10, 10) / 10, 0, mt_rand(-10, 10) / 10);
            }
            $this->walkToward($this->location->addVector($away->normalize()->multiply(4)), (float) $def["speed"] * 1.8);
            return;
        }

        $target = $this->currentTarget($def);
        if($target !== null){
            $this->engage($target, $def);
            return;
        }
        if($this->followTempter($def)){
            return;
        }
        $this->idle($def);
    }

    protected function inSunlight(): bool{
        $world = $this->getWorld();
        $time = $world->getTime() % 24000;
        if($time > 12500 || $this->isUnderwater() || $this->isOnFire()){
            return false;
        }
        $pos = $this->location->floor();
        return $world->getPotentialBlockSkyLightAt($pos->x, $pos->y + 1, $pos->z) >= 15;
    }

    protected function currentTarget(array $def): ?Living{
        $range = (float) $def["follow-range"];
        if($this->angryAt !== null){
            $angry = $this->getWorld()->getEntity($this->angryAt);
            if($angry instanceof Living && $angry->isAlive() && !$angry->isClosed() && $angry->getWorld() === $this->getWorld()
                && $angry->getPosition()->distanceSquared($this->location) <= ($range * 2) ** 2
                && (!$angry instanceof Player || $this->validPlayer($angry))){
                return $angry;
            }
            $this->angryAt = null;
        }
        if($this->monsterTargets()){
            $monster = $this->nearestMonster($range);
            if($monster !== null){
                return $monster;
            }
        }
        if(!$this->isHostileNow()){
            return null;
        }
        $player = $this->nearestPlayer($range);
        return $player !== null && $this->validPlayer($player) ? $player : null;
    }

    protected function validPlayer(Player $player): bool{
        return $player->isAlive() && $player->isConnected() && $player->getWorld() === $this->getWorld()
            && ($player->getGamemode() === GameMode::SURVIVAL || $player->getGamemode() === GameMode::ADVENTURE);
    }

    protected function nearestPlayer(float $range): ?Player{
        $best = null;
        $bestDistance = $range * $range;
        foreach($this->getWorld()->getPlayers() as $player){
            $distance = $player->getPosition()->distanceSquared($this->location);
            if($distance <= $bestDistance){
                $best = $player;
                $bestDistance = $distance;
            }
        }
        return $best;
    }

    protected function nearestMonster(float $range): ?Living{
        $best = null;
        $bestDistance = $range * $range;
        $box = new AxisAlignedBB($this->location->x - $range, $this->location->y - 4, $this->location->z - $range, $this->location->x + $range, $this->location->y + 4, $this->location->z + $range);
        foreach($this->getWorld()->getNearbyEntities($box, $this) as $entity){
            if($entity instanceof SmartMob && $entity->def()["mode"] === "hostile" && $entity->isAlive()){
                $distance = $entity->getPosition()->distanceSquared($this->location);
                if($distance < $bestDistance){
                    $best = $entity;
                    $bestDistance = $distance;
                }
            }
        }
        return $best;
    }

    protected function engage(Living $target, array $def): void{
        $distance = $target->getPosition()->distance($this->location);
        $this->lookAt($target->getEyePos());
        $range = $this->rangedRange();
        if($range > 0){
            $keep = $this->preferredDistance();
            if($distance > $range * 0.8){
                $this->walkToward($target->getPosition(), (float) $def["speed"]);
            }elseif($distance < $keep){
                $away = $this->location->subtractVector($target->getPosition())->withComponents(null, 0, null);
                $this->walkToward($this->location->addVector($away->normalize()->multiply(3)), (float) $def["speed"]);
            }else{
                $this->strafe($target, (float) $def["speed"] * 0.5);
            }
            if($distance <= $range && $this->rangedCooldown === 0){
                $this->rangedCooldown = $this->shoot($target);
            }
            return;
        }
        $reach = max(1.6, $this->size->getWidth() / 2 + 1.2);
        if($distance > $reach - 0.3){
            $this->walkToward($target->getPosition(), (float) $def["speed"]);
        }else{
            $this->setMotion($this->motion->withComponents(0, null, 0));
        }
        if($distance <= $reach && $this->attackCooldown === 0){
            $this->attackCooldown = 20;
            $this->broadcastAnimation(new ArmSwingAnimation($this));
            $event = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $this->meleeDamage($def));
            $target->attack($event);
            if(!$event->isCancelled()){
                $this->onMeleeHit($target);
            }
        }
    }

    protected function strafe(Living $target, float $speed): void{
        $side = $this->location->subtractVector($target->getPosition())->withComponents(null, 0, null)->normalize();
        $dir = (int) (($this->age / 60) % 2) === 0 ? 1 : -1;
        $this->walkToward($this->location->add(-$side->z * 2 * $dir, 0, $side->x * 2 * $dir), $speed);
    }

    protected function followTempter(array $def): bool{
        $items = $this->temptItems();
        if(count($items) === 0){
            return false;
        }
        $player = $this->nearestPlayer(10.0);
        if($player === null || !in_array($player->getInventory()->getItemInHand()->getTypeId(), $items, true)){
            return false;
        }
        $this->lookAt($player->getEyePos());
        if($player->getPosition()->distance($this->location) > 2.2){
            $this->walkToward($player->getPosition(), (float) $def["speed"] * 1.1);
        }else{
            $this->setMotion($this->motion->withComponents(0, null, 0));
        }
        return true;
    }

    protected function idle(array $def): void{
        if($this->wanderTarget !== null){
            if($this->location->withComponents(null, $this->wanderTarget->y, null)->distanceSquared($this->wanderTarget) < 1.0 || --$this->wanderCooldown < -120){
                $this->wanderTarget = null;
                $this->wanderCooldown = mt_rand(60, 160);
            }else{
                $this->walkToward($this->wanderTarget, (float) $def["speed"] * 0.6);
                return;
            }
        }
        if($this->canFly() || ($this->swims() && $this->inWater())){
            $this->setMotion($this->motion->multiply(0.6));
        }else{
            $this->setMotion($this->motion->withComponents($this->motion->x * 0.5, null, $this->motion->z * 0.5));
        }
        if($this->swims() && !$this->breathesAir() && !$this->inWater() && $this->onGround && mt_rand(1, 10) === 1){
            $this->setMotion(new Vector3(mt_rand(-10, 10) / 50, 0.35, mt_rand(-10, 10) / 50));
            return;
        }
        if(--$this->wanderCooldown > 0){
            return;
        }
        $anchor = $this->anchor ?? $this->location->asVector3();
        $leash = (int) $def["leash"];
        if($this->canFly()){
            $base = $this->location->y - $anchor->y > 6 ? $anchor->y : $this->location->y;
            $this->wanderTarget = new Vector3($anchor->x + mt_rand(-$leash, $leash), $base + mt_rand(-2, 3), $anchor->z + mt_rand(-$leash, $leash));
        }elseif($this->swims() && $this->inWater()){
            $this->wanderTarget = null;
            for($i = 0; $i < 6; $i++){
                $candidate = $this->location->add(mt_rand(-6, 6), mt_rand(-3, 3), mt_rand(-6, 6));
                if($this->getWorld()->getBlock($candidate->floor()) instanceof Water){
                    $this->wanderTarget = $candidate;
                    break;
                }
            }
        }else{
            $this->wanderTarget = $anchor->add(mt_rand(-$leash, $leash), 0, mt_rand(-$leash, $leash));
        }
        $this->wanderCooldown = 0;
    }

    protected function walkToward(Vector3 $target, float $speed): void{
        $dx = $target->x - $this->location->x;
        $dz = $target->z - $this->location->z;
        $length = sqrt($dx * $dx + $dz * $dz);
        if($this->canFly() || ($this->swims() && $this->inWater())){
            $dy = $target->y + ($this->canFly() ? 0.8 : 0.0) - $this->location->y;
            $full = sqrt($dx * $dx + $dy * $dy + $dz * $dz);
            if($full < 0.05){
                return;
            }
            $this->setRotation(rad2deg(atan2(-$dx, $dz)), $this->location->pitch);
            $this->setMotion(new Vector3($dx / $full * $speed, $dy / $full * $speed, $dz / $full * $speed));
            return;
        }
        if($length < 0.05){
            return;
        }
        $this->setRotation(rad2deg(atan2(-$dx, $dz)), $this->location->pitch);
        if($this->hops()){
            if($this->onGround){
                $this->setMotion(new Vector3($dx / $length * $speed * 1.8, 0.42, $dz / $length * $speed * 1.8));
            }
            return;
        }
        $motionY = $this->motion->y;
        if($this->isCollidedHorizontally && $this->onGround){
            $motionY = 0.42;
        }
        if($this->isUnderwater()){
            $motionY = max($motionY, 0.12);
        }
        $this->setMotion(new Vector3($dx / $length * $speed, $motionY, $dz / $length * $speed));
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\inventory\CreativeCategory;
use pocketmine\inventory\CreativeInventory;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\StringToItemParser;
use pocketmine\player\Player;
use pocketmine\world\format\io\GlobalItemDataHandlers;

final class Items{
    private const DEFINITIONS = [
        "saddle" => "Saddle",
        "carrot_on_a_stick" => "Carrot on a Stick",
        "warped_fungus_on_a_stick" => "Warped Fungus on a Stick",
    ];

    private static array $items = [];

    public static function register(): void{
        $serializer = GlobalItemDataHandlers::getSerializer();
        $deserializer = GlobalItemDataHandlers::getDeserializer();
        $parser = StringToItemParser::getInstance();
        $creative = CreativeInventory::getInstance();
        foreach(self::DEFINITIONS as $key => $name){
            $id = "minecraft:" . $key;
            $existing = $deserializer->getDeserializerForId($id);
            if($existing !== null){
                self::$items[$key] = ($existing)(new SavedItemData($id));
                continue;
            }
            $item = new SingleItem(new ItemIdentifier(ItemTypeIds::newId()), $name);
            $deserializer->map($id, static fn() => clone $item);
            $serializer->map($item, static fn() => new SavedItemData($id));
            $parser->override($key, static fn() => clone $item);
            $creative->add($item, CreativeCategory::EQUIPMENT);
            self::$items[$key] = $item;
        }
    }

    public static function saddle(): ?Item{
        return isset(self::$items["saddle"]) ? clone self::$items["saddle"] : null;
    }

    public static function isSaddle(Item $item): bool{
        return isset(self::$items["saddle"]) && $item->getTypeId() === self::$items["saddle"]->getTypeId();
    }

    public static function holdsControl(Player $player, string $key): bool{
        return isset(self::$items[$key]) && $player->getInventory()->getItemInHand()->getTypeId() === self::$items[$key]->getTypeId();
    }
}
<?php

declare(strict_types=1);

namespace deepslatedev\smartspawners;

use pocketmine\block\VanillaBlocks;
use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\inventory\CreativeCategory;
use pocketmine\inventory\CreativeInventory;
use pocketmine\item\Item;
use pocketmine\item\ItemIdentifier;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\StringToItemParser;
use pocketmine\item\VanillaItems;
use pocketmine\world\format\io\GlobalItemDataHandlers;

final class Eggs{
    private static array $eggs = [];

    public static function register(): void{
        $serializer = GlobalItemDataHandlers::getSerializer();
        $deserializer = GlobalItemDataHandlers::getDeserializer();
        $parser = StringToItemParser::getInstance();
        $creative = CreativeInventory::getInstance();
        $vanillaZombie = VanillaItems::ZOMBIE_SPAWN_EGG();
        $group = null;
        foreach($creative->getAllEntries() as $entry){
            if($entry->getItem()->equals($vanillaZombie, true, false)){
                $group = $entry->getGroup();
                break;
            }
        }
        foreach(MobRegistry::keys() as $key){
            $id = "minecraft:" . $key . "_spawn_egg";
            if($deserializer->getDeserializerForId($id) !== null){
                continue;
            }
            $egg = new MobEgg(new ItemIdentifier(ItemTypeIds::newId()), MobRegistry::get($key)["name"] . " Spawn Egg", $key);
            $deserializer->map($id, static fn() => clone $egg);
            $serializer->map($egg, static fn() => new SavedItemData($id));
            $parser->override($key . "_spawn_egg", static fn() => clone $egg);
            $creative->add($egg, CreativeCategory::NATURE, $group);
            self::$eggs[$key] = $egg;
        }
        $spawner = VanillaBlocks::MONSTER_SPAWNER()->asItem();
        if(!$creative->contains($spawner)){
            $creative->add($spawner, CreativeCategory::NATURE);
        }
    }

    public static function mobOf(Item $item): ?string{
        if($item instanceof MobEgg){
            return $item->getMobKey();
        }
        $native = [
            VanillaItems::ZOMBIE_SPAWN_EGG()->getTypeId() => "zombie",
            VanillaItems::VILLAGER_SPAWN_EGG()->getTypeId() => "villager",
            VanillaItems::SQUID_SPAWN_EGG()->getTypeId() => "squid",
        ];
        $key = $native[$item->getTypeId()] ?? null;
        return $key !== null && MobRegistry::exists($key) ? $key : null;
    }
}
name: SmartSpawners
version: 1.0.0
main: deepslatedev\smartspawners\Main
api: [5.0.0]
load: STARTUP
author: DeepSlate Dev
description: "Stackable mob spawners and vanilla mobs that walk, chase and fight"
commands:
  spawner:
    description: Give spawners or summon mobs
    usage: /spawner give <mob> [player] [amount] | /spawner summon <mob> [amount] | /spawner list
    permission: smartspawners.admin
permissions:
  smartspawners.admin:
    default: op
¨[ì$ï/„©ÛY¹-uÅóHºdÓÊ¦›ö0ky¼«
$   GBMB