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;