44    private int $totalXp = 0;
 
   46    private bool $canAttractXpOrbs = 
true;
 
   48    private int $xpCooldown = 0;
 
   50    public function __construct(
 
   53        $this->levelAttr = self::fetchAttribute($entity, Attribute::EXPERIENCE_LEVEL);
 
   54        $this->progressAttr = self::fetchAttribute($entity, Attribute::EXPERIENCE);
 
   57    private static function fetchAttribute(
Entity $entity, 
string $attributeId) : 
Attribute{
 
   58        $attribute = AttributeFactory::getInstance()->mustGet($attributeId);
 
   59        $entity->getAttributeMap()->add($attribute);
 
   67        return (int) $this->levelAttr->getValue();
 
 
   74        return $this->setXpAndProgress($level, null);
 
 
   80    public function addXpLevels(
int $amount, 
bool $playSound = 
true) : bool{
 
   81        $oldLevel = $this->getXpLevel();
 
   82        if($this->setXpLevel($oldLevel + $amount)){
 
   84                $newLevel = $this->getXpLevel();
 
   85                if((
int) ($newLevel / 5) > (
int) ($oldLevel / 5)){
 
 
  100        return $this->addXpLevels(-$amount);
 
 
  107        return $this->progressAttr->getValue();
 
 
  114        return $this->setXpAndProgress(null, $progress);
 
 
  121        return (int) (
ExperienceUtils::getXpToCompleteLevel($this->getXpLevel()) * $this->getXpProgress());
 
 
  130        return 
ExperienceUtils::getXpToReachLevel($this->getXpLevel()) + $this->getRemainderXp();
 
 
  140        $xpLevel = (int) $newLevel;
 
  141        $xpProgress = $newLevel - (int) $newLevel;
 
  142        return $this->setXpAndProgress($xpLevel, $xpProgress);
 
 
  151    public function addXp(
int $amount, 
bool $playSound = 
true) : bool{
 
  152        $amount = min($amount, 
Limits::INT32_MAX - $this->totalXp);
 
  153        $oldLevel = $this->getXpLevel();
 
  154        $oldTotal = $this->getCurrentTotalXp();
 
  156        if($this->setCurrentTotalXp($oldTotal + $amount)){
 
  158                $this->totalXp += $amount;
 
  161                $newLevel = $this->getXpLevel();
 
  162                if((
int) ($newLevel / 5) > (
int) ($oldLevel / 5)){
 
  164                }elseif($this->getCurrentTotalXp() > $oldTotal){
 
  165                    $this->entity->broadcastSound(
new XpCollectSound());
 
 
  179        return $this->addXp(-$amount);
 
 
  182    public function setXpAndProgress(?
int $level, ?
float $progress) : bool{
 
  186        if($ev->isCancelled()){
 
  190        $level = $ev->getNewLevel();
 
  191        $progress = $ev->getNewProgress();
 
  194            $this->levelAttr->setValue($level);
 
  197        if($progress !== 
null){
 
  198            $this->progressAttr->setValue($progress);
 
  207    public function setXpAndProgressNoEvent(
int $level, 
float $progress) : void{
 
  208        $this->levelAttr->setValue($level);
 
  209        $this->progressAttr->setValue($progress);
 
  217        return $this->totalXp;
 
 
  225        if($amount < 0 || $amount > 
Limits::INT32_MAX){
 
  226            throw new \InvalidArgumentException(
"XP must be greater than 0 and less than " . Limits::INT32_MAX);
 
  229        $this->totalXp = $amount;
 
 
  236        return $this->xpCooldown === 0;
 
 
  239    public function onPickupXp(
int $xpValue) : void{
 
  246        if(($item = $this->entity->getInventory()->getItemInHand()) instanceof 
Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
 
  247            $equipment[$mainHandIndex] = $item;
 
  249        if(($item = $this->entity->getOffHandInventory()->getItem(0)) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
 
  250            $equipment[$offHandIndex] = $item;
 
  252        foreach($this->entity->getArmorInventory()->getContents() as $k => $armorItem){
 
  253            if($armorItem instanceof Durable && $armorItem->hasEnchantment(VanillaEnchantments::MENDING())){
 
  254                $equipment[$k] = $armorItem;
 
  258        if(count($equipment) > 0){
 
  259            $repairItem = $equipment[$k = array_rand($equipment)];
 
  260            if($repairItem->getDamage() > 0){
 
  261                $repairAmount = min($repairItem->getDamage(), $xpValue * 2);
 
  262                $repairItem->setDamage($repairItem->getDamage() - $repairAmount);
 
  263                $xpValue -= (int) ceil($repairAmount / 2);
 
  265                if($k === $mainHandIndex){
 
  266                    $this->entity->getInventory()->setItemInHand($repairItem);
 
  267                }elseif($k === $offHandIndex){
 
  268                    $this->entity->getOffHandInventory()->setItem(0, $repairItem);
 
  270                    $this->entity->getArmorInventory()->setItem($k, $repairItem);
 
  275        $this->addXp($xpValue); 
 
  276        $this->resetXpCooldown();
 
  283        $this->xpCooldown = $value;
 
 
  286    public function tick(
int $tickDiff = 1) : void{
 
  287        if($this->xpCooldown > 0){
 
  288            $this->xpCooldown = max(0, $this->xpCooldown - $tickDiff);
 
  292    public function canAttractXpOrbs() : bool{
 
  293        return $this->canAttractXpOrbs;
 
  296    public function setCanAttractXpOrbs(
bool $v = 
true) : void{
 
  297        $this->canAttractXpOrbs = $v;