88    protected const DEFAULT_BREATH_TICKS = 300;
 
  101    private const TAG_LEGACY_HEALTH = 
"HealF"; 
 
  102    private const TAG_HEALTH = 
"Health"; 
 
  103    private const TAG_BREATH_TICKS = 
"Air"; 
 
  104    private const TAG_ACTIVE_EFFECTS = 
"ActiveEffects"; 
 
  105    private const TAG_EFFECT_ID = 
"Id"; 
 
  106    private const TAG_EFFECT_DURATION = 
"Duration"; 
 
  107    private const TAG_EFFECT_AMPLIFIER = 
"Amplifier"; 
 
  108    private const TAG_EFFECT_SHOW_PARTICLES = 
"ShowParticles"; 
 
  109    private const TAG_EFFECT_AMBIENT = 
"Ambient"; 
 
  111    protected int $attackTime = 0;
 
  113    public int $deadTicks = 0;
 
  114    protected int $maxDeadTicks = 25;
 
  116    protected float $jumpVelocity = 0.42;
 
  122    protected bool $breathing = 
true;
 
  123    protected int $breathTicks = self::DEFAULT_BREATH_TICKS;
 
  124    protected int $maxBreathTicks = self::DEFAULT_BREATH_TICKS;
 
  128    protected Attribute $knockbackResistanceAttr;
 
  131    protected bool $sprinting = 
false;
 
  132    protected bool $sneaking = 
false;
 
  133    protected bool $gliding = 
false;
 
  134    protected bool $swimming = 
false;
 
  136    private ?
int $frostWalkerLevel = 
null;
 
  142    abstract public function getName() : string;
 
  148    protected function initEntity(
CompoundTag $nbt) : void{
 
  149        parent::initEntity($nbt);
 
  152        $this->effectManager->getEffectAddHooks()->add(
function() : 
void{ $this->networkPropertiesDirty = 
true; });
 
  153        $this->effectManager->getEffectRemoveHooks()->add(
function() : 
void{ $this->networkPropertiesDirty = 
true; });
 
  155        $this->armorInventory = 
new ArmorInventory($this);
 
  157        $this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
 
  159            fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
 
  161        $this->armorInventory->getListeners()->add(
new CallbackInventoryListener(
 
  162            onSlotChange: 
function(Inventory $inventory, 
int $slot) : 
void{
 
  163                if($slot === ArmorInventory::SLOT_FEET){
 
  164                    $this->frostWalkerLevel = 
null;
 
  167            onContentChange: 
function() : 
void{ $this->frostWalkerLevel = 
null; }
 
  170        $health = $this->getMaxHealth();
 
  172        if(($healFTag = $nbt->
getTag(self::TAG_LEGACY_HEALTH)) instanceof FloatTag){
 
  173            $health = $healFTag->getValue();
 
  174        }elseif(($healthTag = $nbt->
getTag(self::TAG_HEALTH)) instanceof ShortTag){
 
  175            $health = $healthTag->getValue(); 
 
  176        }elseif($healthTag instanceof FloatTag){
 
  177            $health = $healthTag->getValue();
 
  180        $this->setHealth($health);
 
  182        $this->setAirSupplyTicks($nbt->getShort(self::TAG_BREATH_TICKS, self::DEFAULT_BREATH_TICKS));
 
  184        $activeEffectsTag = $nbt->
getListTag(self::TAG_ACTIVE_EFFECTS, CompoundTag::class);
 
  185        if($activeEffectsTag !== 
null){
 
  186            foreach($activeEffectsTag as $e){
 
  187                $effect = EffectIdMap::getInstance()->fromId($e->getByte(self::TAG_EFFECT_ID));
 
  188                if($effect === 
null){
 
  192                $this->effectManager->add(
new EffectInstance(
 
  194                    $e->getInt(self::TAG_EFFECT_DURATION),
 
  195                    Binary::unsignByte($e->getByte(self::TAG_EFFECT_AMPLIFIER)),
 
  196                    $e->getByte(self::TAG_EFFECT_SHOW_PARTICLES, 1) !== 0,
 
  197                    $e->getByte(self::TAG_EFFECT_AMBIENT, 0) !== 0
 
  203    protected function addAttributes() : void{
 
  204        $this->attributeMap->add($this->healthAttr = AttributeFactory::getInstance()->mustGet(Attribute::HEALTH));
 
  205        $this->attributeMap->add(AttributeFactory::getInstance()->mustGet(Attribute::FOLLOW_RANGE));
 
  206        $this->attributeMap->add($this->knockbackResistanceAttr = AttributeFactory::getInstance()->mustGet(Attribute::KNOCKBACK_RESISTANCE));
 
  207        $this->attributeMap->add($this->moveSpeedAttr = AttributeFactory::getInstance()->mustGet(Attribute::MOVEMENT_SPEED));
 
  208        $this->attributeMap->add(AttributeFactory::getInstance()->mustGet(Attribute::ATTACK_DAMAGE));
 
  209        $this->attributeMap->add($this->absorptionAttr = AttributeFactory::getInstance()->mustGet(Attribute::ABSORPTION));
 
  216        return $this->nameTag !== 
"" ? $this->nameTag : $this->getName();
 
 
  220        $wasAlive = $this->isAlive();
 
  221        parent::setHealth($amount);
 
  222        $this->healthAttr->setValue(ceil($this->getHealth()), 
true);
 
  223        if($this->isAlive() && !$wasAlive){
 
 
  228    public function getMaxHealth() : int{
 
  229        return (int) $this->healthAttr->getMaxValue();
 
  232    public function setMaxHealth(
int $amount) : void{
 
  233        $this->healthAttr->setMaxValue($amount)->setDefaultValue($amount);
 
  236    public function getAbsorption() : float{
 
  237        return $this->absorptionAttr->getValue();
 
  240    public function setAbsorption(
float $absorption) : void{
 
  241        $this->absorptionAttr->setValue($absorption);
 
  244    public function getSneakOffset() : float{
 
  248    public function isSneaking() : bool{
 
  249        return $this->sneaking;
 
  252    public function setSneaking(
bool $value = 
true) : void{
 
  253        $this->sneaking = $value;
 
  254        $this->networkPropertiesDirty = 
true;
 
  255        $this->recalculateSize();
 
  258    public function isSprinting() : bool{
 
  259        return $this->sprinting;
 
  262    public function setSprinting(
bool $value = 
true) : void{
 
  263        if($value !== $this->isSprinting()){
 
  264            $this->sprinting = $value;
 
  265            $this->networkPropertiesDirty = 
true;
 
  266            $moveSpeed = $this->getMovementSpeed();
 
  267            $this->setMovementSpeed($value ? ($moveSpeed * 1.3) : ($moveSpeed / 1.3));
 
  268            $this->moveSpeedAttr->markSynchronized(
false); 
 
  272    public function isGliding() : bool{
 
  273        return $this->gliding;
 
  276    public function setGliding(
bool $value = 
true) : void{
 
  277        $this->gliding = $value;
 
  278        $this->networkPropertiesDirty = 
true;
 
  279        $this->recalculateSize();
 
  282    public function isSwimming() : bool{
 
  283        return $this->swimming;
 
  286    public function setSwimming(
bool $value = 
true) : void{
 
  287        $this->swimming = $value;
 
  288        $this->networkPropertiesDirty = 
true;
 
  289        $this->recalculateSize();
 
  292    private function recalculateSize() : void{
 
  293        $size = $this->getInitialSizeInfo();
 
  294        if($this->isSwimming() || $this->isGliding()){
 
  295            $width = $size->getWidth();
 
  296            $this->setSize((
new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale()));
 
  297        }elseif($this->isSneaking()){
 
  298            $this->setSize((
new EntitySizeInfo($size->getHeight() - $this->getSneakOffset(), $size->getWidth(), $size->getEyeHeight() - $this->getSneakOffset()))->scale($this->getScale()));
 
  300            $this->setSize($size->scale($this->getScale()));
 
  304    public function getMovementSpeed() : float{
 
  305        return $this->moveSpeedAttr->getValue();
 
  308    public function setMovementSpeed(
float $v, 
bool $fit = 
false) : void{
 
  309        $this->moveSpeedAttr->setValue($v, $fit);
 
  312    public function saveNBT() : CompoundTag{
 
  313        $nbt = parent::saveNBT();
 
  314        $nbt->
setFloat(self::TAG_HEALTH, $this->getHealth());
 
  316        $nbt->
setShort(self::TAG_BREATH_TICKS, $this->getAirSupplyTicks());
 
  318        if(count($this->effectManager->all()) > 0){
 
  320            foreach($this->effectManager->all() as $effect){
 
  321                $effects[] = CompoundTag::create()
 
  322                    ->setByte(self::TAG_EFFECT_ID, EffectIdMap::getInstance()->toId($effect->getType()))
 
  323                    ->setByte(self::TAG_EFFECT_AMPLIFIER, Binary::signByte($effect->getAmplifier()))
 
  324                    ->setInt(self::TAG_EFFECT_DURATION, $effect->getDuration())
 
  325                    ->setByte(self::TAG_EFFECT_AMBIENT, $effect->isAmbient() ? 1 : 0)
 
  326                    ->setByte(self::TAG_EFFECT_SHOW_PARTICLES, $effect->isVisible() ? 1 : 0);
 
  329            $nbt->
setTag(self::TAG_ACTIVE_EFFECTS, 
new ListTag($effects));
 
  336        return $this->effectManager;
 
  344        $this->applyConsumptionResults($consumable);
 
 
  353        foreach($consumable->getAdditionalEffects() as $effect){
 
  354            $this->effectManager->add($effect);
 
  360        $consumable->onConsume($this);
 
 
  367        return $this->jumpVelocity + ((($jumpBoost = $this->effectManager->get(
VanillaEffects::JUMP_BOOST())) !== null ? $jumpBoost->getEffectLevel() : 0) / 10);
 
 
  375            $this->motion = $this->motion->withComponents(
null, $this->getJumpVelocity(), 
null); 
 
 
  379    protected function calculateFallDamage(
float $fallDistance) : float{
 
  380        return ceil($fallDistance - 3 - (($jumpBoost = $this->effectManager->get(VanillaEffects::JUMP_BOOST())) !== null ? $jumpBoost->getEffectLevel() : 0));
 
  384        $fallBlockPos = $this->location->floor();
 
  385        $fallBlock = $this->getWorld()->getBlock($fallBlockPos);
 
  386        if(count($fallBlock->getCollisionBoxes()) === 0){
 
  387            $fallBlockPos = $fallBlockPos->down();
 
  388            $fallBlock = $this->getWorld()->getBlock($fallBlockPos);
 
  390        $newVerticalVelocity = $fallBlock->onEntityLand($this);
 
  392        $damage = $this->calculateFallDamage($this->fallDistance);
 
  397            $this->broadcastSound($damage > 4 ?
 
  401        }elseif($fallBlock->getTypeId() !== BlockTypeIds::AIR){
 
  402            $this->broadcastSound(
new EntityLandSound($this, $fallBlock));
 
  404        return $newVerticalVelocity;
 
 
  414        foreach($this->armorInventory->getContents() as $item){
 
  415            $total += $item->getDefensePoints();
 
 
  426        foreach($this->armorInventory->getContents() as $item){
 
  427            $result = max($result, $item->getEnchantmentLevel($enchantment));
 
 
  433    public function getArmorInventory() : ArmorInventory{
 
  434        return $this->armorInventory;
 
  437    public function setOnFire(
int $seconds) : void{
 
  438        parent::setOnFire($seconds - (int) min($seconds, $seconds * $this->getHighestArmorEnchantmentLevel(VanillaEnchantments::FIRE_PROTECTION()) * 0.15));
 
  446        if($this->lastDamageCause !== null && $this->attackTime > 0){
 
  447            if($this->lastDamageCause->getBaseDamage() >= $source->
getBaseDamage()){
 
  450            $source->setModifier(-$this->lastDamageCause->getBaseDamage(), EntityDamageEvent::MODIFIER_PREVIOUS_DAMAGE_COOLDOWN);
 
  452        if($source->canBeReducedByArmor()){
 
  454            $source->setModifier(-$source->getFinalDamage() * $this->getArmorPoints() * 0.04, EntityDamageEvent::MODIFIER_ARMOR);
 
  457        $cause = $source->getCause();
 
  458        if(($resistance = $this->effectManager->get(VanillaEffects::RESISTANCE())) !== 
null && $cause !== EntityDamageEvent::CAUSE_VOID && $cause !== EntityDamageEvent::CAUSE_SUICIDE){
 
  459            $source->setModifier(-$source->getFinalDamage() * min(1, 0.2 * $resistance->getEffectLevel()), EntityDamageEvent::MODIFIER_RESISTANCE);
 
  463        foreach($this->armorInventory->getContents() as $item){
 
  464            if($item instanceof Armor){
 
  465                $totalEpf += $item->getEnchantmentProtectionFactor($source);
 
  468        $source->setModifier(-$source->getFinalDamage() * min(ceil(min($totalEpf, 25) * (mt_rand(50, 100) / 100)), 20) * 0.04, EntityDamageEvent::MODIFIER_ARMOR_ENCHANTMENTS);
 
  470        $source->setModifier(-min($this->getAbsorption(), $source->getFinalDamage()), EntityDamageEvent::MODIFIER_ABSORPTION);
 
  472        if($cause === EntityDamageEvent::CAUSE_FALLING_BLOCK && $this->armorInventory->getHelmet() instanceof Armor){
 
  473            $source->setModifier(-($source->getFinalDamage() / 4), EntityDamageEvent::MODIFIER_ARMOR_HELMET);
 
 
  483        $this->setAbsorption(max(0, $this->getAbsorption() + $source->getModifier(
EntityDamageEvent::MODIFIER_ABSORPTION)));
 
  485            $this->damageArmor($source->getBaseDamage());
 
  490            foreach($this->armorInventory->getContents() as $k => $item){
 
  491                if($item instanceof Armor && ($thornsLevel = $item->getEnchantmentLevel(VanillaEnchantments::THORNS())) > 0){
 
  492                    if(mt_rand(0, 99) < $thornsLevel * 15){
 
  493                        $this->damageItem($item, 3);
 
  494                        $damage += ($thornsLevel > 10 ? $thornsLevel - 10 : 1 + mt_rand(0, 3));
 
  496                        $this->damageItem($item, 1); 
 
  499                    $this->armorInventory->setItem($k, $item);
 
  504                $attacker->attack(new EntityDamageByEntityEvent($this, $attacker, EntityDamageEvent::CAUSE_MAGIC, $damage));
 
  507            if($source->getModifier(EntityDamageEvent::MODIFIER_ARMOR_HELMET) < 0){
 
  508                $helmet = $this->armorInventory->getHelmet();
 
  509                if($helmet instanceof Armor){
 
  510                    $finalDamage = $source->getFinalDamage();
 
  511                    $this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
 
  512                    $this->armorInventory->setHelmet($helmet);
 
 
  523        $durabilityRemoved = (int) max(floor($damage / 4), 1);
 
  525        $armor = $this->armorInventory->getContents();
 
  526        foreach($armor as $slotId => $item){
 
  527            if($item instanceof 
Armor){
 
  528                $oldItem = clone $item;
 
  529                $this->damageItem($item, $durabilityRemoved);
 
  530                if(!$item->equalsExact($oldItem)){
 
  531                    $this->armorInventory->setItem($slotId, $item);
 
 
  537    private function damageItem(Durable $item, 
int $durabilityRemoved) : void{
 
  538        $item->applyDamage($durabilityRemoved);
 
  539        if($item->isBroken()){
 
  540            $this->broadcastSound(new ItemBreakSound());
 
  544    public function attack(EntityDamageEvent $source) : void{
 
  545        if($this->noDamageTicks > 0 && $source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE){
 
  549        if($this->effectManager->has(VanillaEffects::FIRE_RESISTANCE()) && (
 
  550                $source->getCause() === EntityDamageEvent::CAUSE_FIRE
 
  551                || $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK
 
  552                || $source->getCause() === EntityDamageEvent::CAUSE_LAVA
 
  558        if($source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE){
 
  559            $this->applyDamageModifiers($source);
 
  562        if($source instanceof EntityDamageByEntityEvent && (
 
  563            $source->getCause() === EntityDamageEvent::CAUSE_BLOCK_EXPLOSION ||
 
  564            $source->getCause() === EntityDamageEvent::CAUSE_ENTITY_EXPLOSION)
 
  568            $base = $source->getKnockBack();
 
  569            $source->setKnockBack($base - min($base, $base * $this->getHighestArmorEnchantmentLevel(VanillaEnchantments::BLAST_PROTECTION()) * 0.15));
 
  572        parent::attack($source);
 
  574        if($source->isCancelled()){
 
  578        if($this->attackTime <= 0){
 
  581            $this->attackTime = $source->getAttackCooldown();
 
  583            if($source instanceof EntityDamageByChildEntityEvent){
 
  584                $e = $source->getChild();
 
  586                    $motion = $e->getMotion();
 
  587                    $this->knockBack($motion->x, $motion->z, $source->getKnockBack(), $source->getVerticalKnockBackLimit());
 
  589            }elseif($source instanceof EntityDamageByEntityEvent){
 
  590                $e = $source->getDamager();
 
  592                    $deltaX = $this->location->x - $e->location->x;
 
  593                    $deltaZ = $this->location->z - $e->location->z;
 
  594                    $this->knockBack($deltaX, $deltaZ, $source->getKnockBack(), $source->getVerticalKnockBackLimit());
 
  598            if($this->isAlive()){
 
  599                $this->doHitAnimation();
 
  603        if($this->isAlive()){
 
  604            $this->applyPostDamageEffects($source);
 
  608    protected function doHitAnimation() : void{
 
  609        $this->broadcastAnimation(new HurtAnimation($this));
 
  612    public function knockBack(
float $x, 
float $z, 
float $force = self::DEFAULT_KNOCKBACK_FORCE, ?
float $verticalLimit = self::DEFAULT_KNOCKBACK_VERTICAL_LIMIT) : void{
 
  613        $f = sqrt($x * $x + $z * $z);
 
  617        if(mt_rand() / mt_getrandmax() > $this->knockbackResistanceAttr->getValue()){
 
  620            $motionX = $this->motion->x / 2;
 
  621            $motionY = $this->motion->y / 2;
 
  622            $motionZ = $this->motion->z / 2;
 
  623            $motionX += $x * $f * $force;
 
  625            $motionZ += $z * $f * $force;
 
  627            $verticalLimit ??= $force;
 
  628            if($motionY > $verticalLimit){
 
  629                $motionY = $verticalLimit;
 
  632            $this->setMotion(
new Vector3($motionX, $motionY, $motionZ));
 
  637        $ev = new 
EntityDeathEvent($this, $this->getDrops(), $this->getXpDropAmount());
 
  639        foreach($ev->getDrops() as $item){
 
  640            $this->getWorld()->dropItem($this->location, $item);
 
  644        $this->getWorld()->dropExperience($this->location, $ev->getXpDropAmount());
 
  646        $this->startDeathAnimation();
 
 
  650        if($this->deadTicks < $this->maxDeadTicks){
 
  651            $this->deadTicks += $tickDiff;
 
  652            if($this->deadTicks >= $this->maxDeadTicks){
 
  653                $this->endDeathAnimation();
 
  657        return $this->deadTicks >= $this->maxDeadTicks;
 
 
  660    protected function startDeathAnimation() : void{
 
  661        $this->broadcastAnimation(new DeathAnimation($this));
 
  664    protected function endDeathAnimation() : void{
 
  665        $this->despawnFromAll();
 
  668    protected function entityBaseTick(
int $tickDiff = 1) : bool{
 
  669        Timings::$livingEntityBaseTick->startTiming();
 
  671        $hasUpdate = parent::entityBaseTick($tickDiff);
 
  673        if($this->isAlive()){
 
  674            if($this->effectManager->tick($tickDiff)){
 
  678            if($this->isInsideOfSolid()){
 
  680                $ev = 
new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1);
 
  684            if($this->doAirSupplyTick($tickDiff)){
 
  688            foreach($this->armorInventory->getContents() as $index => $item){
 
  689                $oldItem = clone $item;
 
  690                if($item->onTickWorn($this)){
 
  692                    if(!$item->equalsExact($oldItem)){
 
  693                        $this->armorInventory->setItem($index, $item);
 
  699        if($this->attackTime > 0){
 
  700            $this->attackTime -= $tickDiff;
 
  703        Timings::$livingEntityBaseTick->stopTiming();
 
  708    protected function move(
float $dx, 
float $dy, 
float $dz) : void{
 
  709        $oldX = $this->location->x;
 
  710        $oldZ = $this->location->z;
 
  712        parent::move($dx, $dy, $dz);
 
  714        $frostWalkerLevel = $this->getFrostWalkerLevel();
 
  715        if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){
 
  716            $this->applyFrostWalker($frostWalkerLevel);
 
  720    protected function applyFrostWalker(
int $level) : void{
 
  721        $radius = $level + 2;
 
  722        $world = $this->getWorld();
 
  724        $baseX = $this->location->getFloorX();
 
  725        $y = $this->location->getFloorY() - 1;
 
  726        $baseZ = $this->location->getFloorZ();
 
  728        $liquid = VanillaBlocks::WATER();
 
  729        $targetBlock = VanillaBlocks::FROSTED_ICE();
 
  730        if(EntityFrostWalkerEvent::hasHandlers()){
 
  731            $ev = 
new EntityFrostWalkerEvent($this, $radius, $liquid, $targetBlock);
 
  733            if($ev->isCancelled()){
 
  736            $radius = $ev->getRadius();
 
  737            $liquid = $ev->getLiquid();
 
  738            $targetBlock = $ev->getTargetBlock();
 
  741        for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
 
  742            for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
 
  743                $block = $world->getBlockAt($x, $y, $z);
 
  745                    !$block->isSameState($liquid) ||
 
  746                    $world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
 
  747                    count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
 
  751                $world->setBlockAt($x, $y, $z, $targetBlock);
 
  756    public function getFrostWalkerLevel() : int{
 
  757        return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->
getEnchantmentLevel(VanillaEnchantments::FROST_WALKER());
 
  764        $ticks = $this->getAirSupplyTicks();
 
  766        if(!$this->canBreathe()){
 
  767            $this->setBreathing(
false);
 
  769            if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
 
  770                Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
 
  775                    $this->onAirExpired();
 
  778        }elseif(!$this->isBreathing()){
 
  779            if($ticks < ($max = $this->getMaxAirSupplyTicks())){
 
  780                $ticks += $tickDiff * 5;
 
  784                $this->setBreathing(
true);
 
  788        if($ticks !== $oldTicks){
 
  789            $this->setAirSupplyTicks($ticks);
 
  792        return $ticks !== $oldTicks;
 
 
  799        return $this->effectManager->has(
VanillaEffects::WATER_BREATHING()) || $this->effectManager->has(
VanillaEffects::CONDUIT_POWER()) || !$this->isUnderwater();
 
 
  806        return $this->breathing;
 
 
  814        $this->breathing = $value;
 
  815        $this->networkPropertiesDirty = 
true;
 
 
  823        return $this->breathTicks;
 
 
  830        $this->breathTicks = $ticks;
 
  831        $this->networkPropertiesDirty = 
true;
 
 
  838        return $this->maxBreathTicks;
 
 
  845        $this->maxBreathTicks = $ticks;
 
  846        $this->networkPropertiesDirty = 
true;
 
 
  878    public function getLineOfSight(
int $maxDistance, 
int $maxLength = 0, array $transparent = []) : array{
 
  879        if($maxDistance > 120){
 
  883        if(count($transparent) === 0){
 
  890        foreach(VoxelRayTrace::inDirection($this->location->add(0, $this->size->getEyeHeight(), 0), $this->getDirectionVector(), $maxDistance) as $vector3){
 
  891            $block = $this->getWorld()->getBlockAt($vector3->x, $vector3->y, $vector3->z);
 
  892            $blocks[$nextIndex++] = $block;
 
  894            if($maxLength !== 0 && count($blocks) > $maxLength){
 
  895                array_shift($blocks);
 
  899            $id = $block->getTypeId();
 
  901            if($transparent === 
null){
 
  902                if($id !== BlockTypeIds::AIR){
 
  906                if(!isset($transparent[$id])){
 
 
  920        $line = $this->getLineOfSight($maxDistance, 1, $transparent);
 
  921        if(count($line) > 0){
 
  922            return array_shift($line);
 
 
  933        $xDist = $target->x - $this->location->x;
 
  934        $zDist = $target->z - $this->location->z;
 
  936        $horizontal = sqrt($xDist ** 2 + $zDist ** 2);
 
  937        $vertical = $target->y - ($this->location->y + $this->getEyeHeight());
 
  938        $pitch = -atan2($vertical, $horizontal) / M_PI * 180; 
 
  940        $yaw = atan2($zDist, $xDist) / M_PI * 180 - 90;
 
  945        $this->setRotation($yaw, $pitch);
 
 
  949        parent::sendSpawnPacket($player);
 
  951        $networkSession = $player->getNetworkSession();
 
  952        $networkSession->getEntityEventBroadcaster()->onMobArmorChange([$networkSession], $this);
 
 
  956        parent::syncNetworkData($properties);
 
  958        $visibleEffects = [];
 
  959        foreach ($this->effectManager->all() as $effect) {
 
  960            if (!$effect->isVisible() || !$effect->getType()->hasBubbles()) {
 
  963            $visibleEffects[EffectIdMap::getInstance()->toId($effect->getType())] = $effect->isAmbient();
 
  967        ksort($visibleEffects, SORT_NUMERIC);
 
  970        $packedEffectsCount = 0;
 
  971        foreach ($visibleEffects as $effectId => $isAmbient) {
 
  972            $effectsData = ($effectsData << 7) |
 
  973                (($effectId & 0x3f) << 1) | 
 
  974                ($isAmbient ? 1 : 0);
 
  976            if (++$packedEffectsCount >= 8) {
 
  980        $properties->setLong(EntityMetadataProperties::VISIBLE_MOB_EFFECTS, $effectsData);
 
  982        $properties->setShort(EntityMetadataProperties::AIR, $this->breathTicks);
 
  983        $properties->setShort(EntityMetadataProperties::MAX_AIR, $this->maxBreathTicks);
 
  985        $properties->setGenericFlag(EntityMetadataFlags::BREATHING, $this->breathing);
 
  986        $properties->setGenericFlag(EntityMetadataFlags::SNEAKING, $this->sneaking);
 
  987        $properties->setGenericFlag(EntityMetadataFlags::SPRINTING, $this->sprinting);
 
  988        $properties->setGenericFlag(EntityMetadataFlags::GLIDING, $this->gliding);
 
  989        $properties->setGenericFlag(EntityMetadataFlags::SWIMMING, $this->swimming);
 
  993        $this->armorInventory->removeAllViewers();
 
  994        $this->effectManager->getEffectAddHooks()->clear();
 
  995        $this->effectManager->getEffectRemoveHooks()->clear();
 
 
 1001            $this->armorInventory,
 
 1002            $this->effectManager
 
 1004        parent::destroyCycles();