83    private const TAG_INVENTORY = 
"Inventory"; 
 
   84    private const TAG_OFF_HAND_ITEM = 
"OffHandItem"; 
 
   85    private const TAG_ENDER_CHEST_INVENTORY = 
"EnderChestInventory"; 
 
   86    private const TAG_SELECTED_INVENTORY_SLOT = 
"SelectedInventorySlot"; 
 
   87    private const TAG_FOOD_LEVEL = 
"foodLevel"; 
 
   88    private const TAG_FOOD_EXHAUSTION_LEVEL = 
"foodExhaustionLevel"; 
 
   89    private const TAG_FOOD_SATURATION_LEVEL = 
"foodSaturationLevel"; 
 
   90    private const TAG_FOOD_TICK_TIMER = 
"foodTickTimer"; 
 
   91    private const TAG_XP_LEVEL = 
"XpLevel"; 
 
   92    private const TAG_XP_PROGRESS = 
"XpP"; 
 
   93    private const TAG_LIFETIME_XP_TOTAL = 
"XpTotal"; 
 
   94    private const TAG_XP_SEED = 
"XpSeed"; 
 
   95    private const TAG_SKIN = 
"Skin"; 
 
   96    private const TAG_SKIN_NAME = 
"Name"; 
 
   97    private const TAG_SKIN_DATA = 
"Data"; 
 
   98    private const TAG_SKIN_CAPE_DATA = 
"CapeData"; 
 
   99    private const TAG_SKIN_GEOMETRY_NAME = 
"GeometryName"; 
 
  100    private const TAG_SKIN_GEOMETRY_DATA = 
"GeometryData"; 
 
  102    public static function getNetworkTypeId() : 
string{ 
return EntityIds::PLAYER; }
 
  108    protected UuidInterface $uuid;
 
  110    protected Skin $skin;
 
  115    protected int $xpSeed;
 
  119        parent::__construct($location, $nbt);
 
  129        $skinTag = $nbt->getCompoundTag(self::TAG_SKIN);
 
  130        if($skinTag === 
null){
 
  134            $skinTag->getString(self::TAG_SKIN_NAME),
 
  135            ($skinDataTag = $skinTag->getTag(self::TAG_SKIN_DATA)) instanceof 
StringTag ? $skinDataTag->
getValue() : $skinTag->getByteArray(self::TAG_SKIN_DATA), 
 
  136            $skinTag->getByteArray(self::TAG_SKIN_CAPE_DATA, 
""),
 
  137            $skinTag->getString(self::TAG_SKIN_GEOMETRY_NAME, 
""),
 
  138            $skinTag->getByteArray(self::TAG_SKIN_GEOMETRY_DATA, 
"")
 
 
  142    public function getUniqueId() : UuidInterface{
 
  167    public function sendSkin(?array $targets = 
null) : void{
 
 
  175        if($this->isSprinting()){
 
  176            $this->hungerManager->exhaust(0.2, PlayerExhaustEvent::CAUSE_SPRINT_JUMPING);
 
  178            $this->hungerManager->exhaust(0.05, PlayerExhaustEvent::CAUSE_JUMPING);
 
 
  182    public function emote(
string $emoteId) : void{
 
  183        NetworkBroadcastUtils::broadcastEntityEvent(
 
  185            fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEmote($recipients, $this, $emoteId)
 
  189    public function getHungerManager() : HungerManager{
 
  190        return $this->hungerManager;
 
  198        return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === 
World::DIFFICULTY_PEACEFUL;
 
 
  202        if($consumable instanceof 
FoodSource && $consumable->requiresHunger() && !$this->canEat()){
 
  206        return parent::consumeObject($consumable);
 
 
  211            $this->hungerManager->addFood($consumable->getFoodRestore());
 
  212            $this->hungerManager->addSaturation($consumable->getSaturationRestore());
 
  215        parent::applyConsumptionResults($consumable);
 
 
  218    public function getXpManager() : ExperienceManager{
 
  219        return $this->xpManager;
 
  222    public function getEnchantmentSeed() : int{
 
  223        return $this->xpSeed;
 
  226    public function setEnchantmentSeed(
int $seed) : void{
 
  227        $this->xpSeed = $seed;
 
  230    public function regenerateEnchantmentSeed() : void{
 
  231        $this->xpSeed = EnchantingHelper::generateSeed();
 
  237        return min(100, 7 * $this->xpManager->getXpLevel());
 
 
  241        return $this->inventory;
 
  244    public function getOffHandInventory() : PlayerOffHandInventory{ return $this->offHandInventory; }
 
  246    public function getEnderInventory() : PlayerEnderInventory{
 
  247        return $this->enderInventory;
 
  250    public function getSneakOffset() : float{
 
  259        $this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
 
 
  266    private static function populateInventoryFromListTag(
Inventory $inventory, array $items) : void{
 
  267        $listeners = $inventory->getListeners()->toArray();
 
  275    protected function initEntity(
CompoundTag $nbt) : void{
 
  276        parent::initEntity($nbt);
 
  278        $this->hungerManager = 
new HungerManager($this);
 
  279        $this->xpManager = 
new ExperienceManager($this);
 
  281        $this->inventory = 
new PlayerInventory($this);
 
  282        $syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent(
 
  286        $this->inventory->getListeners()->add(
new CallbackInventoryListener(
 
  287            function(Inventory $unused, 
int $slot, Item $unused2) use ($syncHeldItem) : 
void{
 
  288                if($slot === $this->inventory->getHeldItemIndex()){
 
  292            function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{
 
  293                if(array_key_exists($this->inventory->getHeldItemIndex(), $oldItems)){
 
  298        $this->offHandInventory = 
new PlayerOffHandInventory($this);
 
  299        $this->enderInventory = 
new PlayerEnderInventory($this);
 
  300        $this->initHumanData($nbt);
 
  302        $inventoryTag = $nbt->
getListTag(self::TAG_INVENTORY, CompoundTag::class);
 
  303        if($inventoryTag !== 
null){
 
  304            $inventoryItems = [];
 
  305            $armorInventoryItems = [];
 
  307            foreach($inventoryTag as $i => $item){
 
  308                $slot = $item->getByte(SavedItemStackData::TAG_SLOT);
 
  309                if($slot >= 0 && $slot < 9){ 
 
  311                }elseif($slot >= 100 && $slot < 104){ 
 
  312                    $armorSlot = $slot - 100;
 
  313                    $armorInventoryItems[$armorSlot] = Item::safeNbtDeserialize($item, 
"Human armor slot $armorSlot");
 
  314                }elseif($slot >= 9 && $slot < $this->inventory->getSize() + 9){
 
  315                    $inventorySlot = $slot - 9;
 
  316                    $inventoryItems[$inventorySlot] = Item::safeNbtDeserialize($item, 
"Human inventory slot $inventorySlot");
 
  320            self::populateInventoryFromListTag($this->inventory, $inventoryItems);
 
  321            self::populateInventoryFromListTag($this->armorInventory, $armorInventoryItems);
 
  324        if($offHand !== 
null){
 
  325            $this->offHandInventory->setItem(0, Item::safeNbtDeserialize($offHand, 
"Human off-hand item"));
 
  327        $this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
 
  329            fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobOffHandItemChange($recipients, $this)
 
  332        $enderChestInventoryTag = $nbt->
getListTag(self::TAG_ENDER_CHEST_INVENTORY, CompoundTag::class);
 
  333        if($enderChestInventoryTag !== 
null){
 
  334            $enderChestInventoryItems = [];
 
  336            foreach($enderChestInventoryTag as $item){
 
  337                $slot = $item->getByte(SavedItemStackData::TAG_SLOT);
 
  338                $enderChestInventoryItems[$slot] = Item::safeNbtDeserialize($item, 
"Human ender chest slot $slot");
 
  340            self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
 
  343        $this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0));
 
  344        $this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
 
  346            fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this)
 
  349        $this->hungerManager->setFood((
float) $nbt->getInt(self::TAG_FOOD_LEVEL, (
int) $this->hungerManager->getFood()));
 
  350        $this->hungerManager->setExhaustion($nbt->getFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion()));
 
  351        $this->hungerManager->setSaturation($nbt->getFloat(self::TAG_FOOD_SATURATION_LEVEL, $this->hungerManager->getSaturation()));
 
  352        $this->hungerManager->setFoodTickTimer($nbt->getInt(self::TAG_FOOD_TICK_TIMER, $this->hungerManager->getFoodTickTimer()));
 
  354        $this->xpManager->setXpAndProgressNoEvent(
 
  355            $nbt->getInt(self::TAG_XP_LEVEL, 0),
 
  356            $nbt->getFloat(self::TAG_XP_PROGRESS, 0.0));
 
  357        $this->xpManager->setLifetimeTotalXp($nbt->getInt(self::TAG_LIFETIME_XP_TOTAL, 0));
 
  359        if(($xpSeedTag = $nbt->
getTag(self::TAG_XP_SEED)) instanceof IntTag){
 
  360            $this->xpSeed = $xpSeedTag->getValue();
 
  362            $this->xpSeed = EnchantingHelper::generateSeed();
 
  366    protected function entityBaseTick(
int $tickDiff = 1) : bool{
 
  367        $hasUpdate = parent::entityBaseTick($tickDiff);
 
  369        $this->hungerManager->tick($tickDiff);
 
  370        $this->xpManager->tick($tickDiff);
 
  375    public function getName() : string{
 
  376        return $this->getNameTag();
 
  380        parent::applyDamageModifiers($source);
 
  382        $type = $source->getCause();
 
  383        if($type !== EntityDamageEvent::CAUSE_SUICIDE && $type !== EntityDamageEvent::CAUSE_VOID
 
  384            && ($this->inventory->getItemInHand() instanceof 
Totem || $this->offHandInventory->getItem(0) instanceof 
Totem)){
 
  386            $compensation = $this->getHealth() - $source->getFinalDamage() - 1;
 
  387            if($compensation <= -1){
 
  388                $source->setModifier($compensation, EntityDamageEvent::MODIFIER_TOTEM);
 
 
  394        parent::applyPostDamageEffects($source);
 
  395        $totemModifier = $source->getModifier(EntityDamageEvent::MODIFIER_TOTEM);
 
  396        if($totemModifier < 0){ 
 
  397            $this->effectManager->clear();
 
  399            $this->effectManager->add(
new EffectInstance(VanillaEffects::REGENERATION(), 40 * 20, 1));
 
  400            $this->effectManager->add(
new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 40 * 20, 1));
 
  401            $this->effectManager->add(
new EffectInstance(VanillaEffects::ABSORPTION(), 5 * 20, 1));
 
  406            $hand = $this->inventory->getItemInHand();
 
  407            if($hand instanceof 
Totem){
 
  409                $this->inventory->setItemInHand($hand);
 
  410            }elseif(($offHand = $this->offHandInventory->getItem(0)) instanceof 
Totem){
 
  412                $this->offHandInventory->setItem(0, $offHand);
 
 
  418        return array_filter(array_merge(
 
  419            array_values($this->inventory->getContents()),
 
  420            array_values($this->armorInventory->getContents()),
 
  421            array_values($this->offHandInventory->getContents()),
 
  422        ), function(
Item $item) : bool{ return !$item->hasEnchantment(
VanillaEnchantments::VANISHING()) && !$item->keepOnDeath(); });
 
 
  426        $nbt = parent::saveNBT();
 
  428        $nbt->
setInt(self::TAG_FOOD_LEVEL, (
int) $this->hungerManager->getFood());
 
  429        $nbt->
setFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion());
 
  430        $nbt->
setFloat(self::TAG_FOOD_SATURATION_LEVEL, $this->hungerManager->getSaturation());
 
  431        $nbt->
setInt(self::TAG_FOOD_TICK_TIMER, $this->hungerManager->getFoodTickTimer());
 
  433        $nbt->
setInt(self::TAG_XP_LEVEL, $this->xpManager->getXpLevel());
 
  434        $nbt->
setFloat(self::TAG_XP_PROGRESS, $this->xpManager->getXpProgress());
 
  435        $nbt->
setInt(self::TAG_LIFETIME_XP_TOTAL, $this->xpManager->getLifetimeTotalXp());
 
  436        $nbt->
setInt(self::TAG_XP_SEED, $this->xpSeed);
 
  438        $inventoryTag = 
new ListTag([], NBT::TAG_Compound);
 
  439        $nbt->
setTag(self::TAG_INVENTORY, $inventoryTag);
 
  442        $slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
 
  443        for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
 
  444            $item = $this->inventory->getItem($slot - 9);
 
  445            if(!$item->isNull()){
 
  446                $inventoryTag->push($item->nbtSerialize($slot));
 
  451        for($slot = 100; $slot < 104; ++$slot){
 
  452            $item = $this->armorInventory->getItem($slot - 100);
 
  453            if(!$item->isNull()){
 
  454                $inventoryTag->push($item->nbtSerialize($slot));
 
  458        $nbt->
setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->inventory->getHeldItemIndex());
 
  460        $offHandItem = $this->offHandInventory->getItem(0);
 
  461        if(!$offHandItem->isNull()){
 
  462            $nbt->setTag(self::TAG_OFF_HAND_ITEM, $offHandItem->nbtSerialize());
 
  468        $slotCount = $this->enderInventory->getSize();
 
  469        for($slot = 0; $slot < $slotCount; ++$slot){
 
  470            $item = $this->enderInventory->getItem($slot);
 
  471            if(!$item->isNull()){
 
  472                $items[] = $item->nbtSerialize($slot);
 
  476        $nbt->
setTag(self::TAG_ENDER_CHEST_INVENTORY, 
new ListTag($items, NBT::TAG_Compound));
 
  478        $nbt->
setTag(self::TAG_SKIN, CompoundTag::create()
 
  479            ->setString(self::TAG_SKIN_NAME, $this->skin->getSkinId())
 
  480            ->setByteArray(self::TAG_SKIN_DATA, $this->skin->getSkinData())
 
  481            ->setByteArray(self::TAG_SKIN_CAPE_DATA, $this->skin->getCapeData())
 
  482            ->setString(self::TAG_SKIN_GEOMETRY_NAME, $this->skin->getGeometryName())
 
  483            ->setByteArray(self::TAG_SKIN_GEOMETRY_DATA, $this->skin->getGeometryData())
 
  489    public function spawnTo(Player $player) : void{
 
  490        if($player !== $this){
 
  491            parent::spawnTo($player);
 
  496        $networkSession = $player->getNetworkSession();
 
  497        $typeConverter = $networkSession->getTypeConverter();
 
  498        if(!($this instanceof 
Player)){
 
  499            $networkSession->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->
id, $this->getName(), $typeConverter->getSkinAdapter()->toSkinData($this->skin))]));
 
  502        $networkSession->sendDataPacket(AddPlayerPacket::create(
 
  503            $this->getUniqueId(),
 
  507            $this->location->asVector3(),
 
  509            $this->location->pitch,
 
  510            $this->location->yaw,
 
  511            $this->location->yaw, 
 
  512            ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->getInventory()->getItemInHand())),
 
  514            $this->getAllNetworkData(),
 
  516            UpdateAbilitiesPacket::create(
new AbilitiesData(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() , [
 
  518                    AbilitiesLayer::LAYER_BASE,
 
  519                    array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, 
false),
 
  531        $this->sendData([$player], [EntityMetadataProperties::NAMETAG => 
new StringMetadataProperty($this->getNameTag())]);
 
  533        $entityEventBroadcaster = $networkSession->getEntityEventBroadcaster();
 
  534        $entityEventBroadcaster->onMobArmorChange([$networkSession], $this);
 
  535        $entityEventBroadcaster->onMobOffHandItemChange([$networkSession], $this);
 
  537        if(!($this instanceof 
Player)){
 
  538            $networkSession->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($this->uuid)]));
 
 
  542    public function getOffsetPosition(Vector3 $vector3) : Vector3{
 
  543        return $vector3->add(0, 1.621, 0); 
 
  547        $this->inventory->removeAllViewers();
 
  548        $this->inventory->getHeldItemIndexChangeListeners()->clear();
 
  549        $this->offHandInventory->removeAllViewers();
 
  550        $this->enderInventory->removeAllViewers();
 
 
  557            $this->offHandInventory,
 
  558            $this->enderInventory,
 
  559            $this->hungerManager,
 
  562        parent::destroyCycles();