From da0358529a46f7fbad03a02143e44a73e6e9e241 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 6 Jul 2019 18:50:34 +0100 Subject: [PATCH] Extract a HungerManager unit from Human --- changelogs/4.0-snapshot.md | 13 + src/pocketmine/entity/Human.php | 197 ++------------ src/pocketmine/entity/HungerManager.php | 254 ++++++++++++++++++ src/pocketmine/entity/effect/HungerEffect.php | 2 +- .../entity/effect/SaturationEffect.php | 5 +- src/pocketmine/player/Player.php | 30 +-- 6 files changed, 300 insertions(+), 201 deletions(-) create mode 100644 src/pocketmine/entity/HungerManager.php diff --git a/changelogs/4.0-snapshot.md b/changelogs/4.0-snapshot.md index 25732e783..44282db67 100644 --- a/changelogs/4.0-snapshot.md +++ b/changelogs/4.0-snapshot.md @@ -150,6 +150,7 @@ This version features substantial changes to the network system, improving coher - The following API methods have been added: - `ItemEntity->getDespawnDelay()` - `ItemEntity->setDespawnDelay()` + - `Human->getHungerManager()` - The following methods have signature changes: - `Entity->entityBaseTick()` is now `protected`. - `Entity->move()` is now `protected`. @@ -157,6 +158,7 @@ This version features substantial changes to the network system, improving coher - `Living->getEffects()` now returns `EffectManager` instead of `Effect[]`. - The following classes have been added: - `effect\EffectManager`: contains effect-management functionality extracted from `Living` + - `HungerManager`: contains hunger-management functionality extracted from `Human` - The following API methods have been moved / renamed: - `Living->removeAllEffects()` -> `EffectManager->clear()` - `Living->removeEffect()` -> `EffectManager->remove()` @@ -165,6 +167,17 @@ This version features substantial changes to the network system, improving coher - `Living->hasEffect()` -> `EffectManager->has()` - `Living->hasEffects()` -> `EffectManager->hasEffects()` - `Living->getEffects()` -> `EffectManager->all()` + - `Human->getFood()` -> `HungerManager->getFood()` + - `Human->setFood()` -> `HungerManager->setFood()` + - `Human->getMaxFood()` -> `HungerManager->getMaxFood()` + - `Human->addFood()` -> `HungerManager->addFood()` + - `Human->isHungry()` -> `HungerManager->isHungry()` + - `Human->getSaturation()` -> `HungerManager->getSaturation()` + - `Human->setSaturation()` -> `HungerManager->setSaturation()` + - `Human->addSaturation()` -> `HungerManager->addSaturation()` + - `Human->getExhaustion()` -> `HungerManager->getExhaustion()` + - `Human->setExhaustion()` -> `HungerManager->setExhaustion()` + - `Human->exhaust()` -> `HungerManager->exhaust()` - The following API methods have been removed: - `Human->getRawUniqueId()`: use `Human->getUniqueId()->toBinary()` instead - The following classes have been removed: diff --git a/src/pocketmine/entity/Human.php b/src/pocketmine/entity/Human.php index 1dd906d99..fadef01ab 100644 --- a/src/pocketmine/entity/Human.php +++ b/src/pocketmine/entity/Human.php @@ -28,7 +28,6 @@ use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\projectile\ProjectileSource; use pocketmine\entity\utils\ExperienceUtils; use pocketmine\event\entity\EntityDamageEvent; -use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\event\player\PlayerExhaustEvent; use pocketmine\event\player\PlayerExperienceChangeEvent; use pocketmine\inventory\EnderChestInventory; @@ -65,7 +64,6 @@ use function array_rand; use function array_values; use function ceil; use function in_array; -use function max; use function min; use function random_int; use function strlen; @@ -90,7 +88,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ /** @var Skin */ protected $skin; - protected $foodTickTimer = 0; + /** @var HungerManager */ + protected $hungerManager; protected $totalXp = 0; protected $xpSeed; @@ -170,139 +169,27 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function jump() : void{ parent::jump(); if($this->isSprinting()){ - $this->exhaust(0.8, PlayerExhaustEvent::CAUSE_SPRINT_JUMPING); + $this->hungerManager->exhaust(0.8, PlayerExhaustEvent::CAUSE_SPRINT_JUMPING); }else{ - $this->exhaust(0.2, PlayerExhaustEvent::CAUSE_JUMPING); + $this->hungerManager->exhaust(0.2, PlayerExhaustEvent::CAUSE_JUMPING); } } - public function getFood() : float{ - return $this->attributeMap->getAttribute(Attribute::HUNGER)->getValue(); - } - /** - * WARNING: This method does not check if full and may throw an exception if out of bounds. - * Use {@link Human::addFood()} for this purpose - * - * @param float $new - * - * @throws \InvalidArgumentException + * @return HungerManager */ - public function setFood(float $new) : void{ - $attr = $this->attributeMap->getAttribute(Attribute::HUNGER); - $old = $attr->getValue(); - $attr->setValue($new); - - // ranges: 18-20 (regen), 7-17 (none), 1-6 (no sprint), 0 (health depletion) - foreach([17, 6, 0] as $bound){ - if(($old > $bound) !== ($new > $bound)){ - $this->foodTickTimer = 0; - break; - } - } - } - - public function getMaxFood() : float{ - return $this->attributeMap->getAttribute(Attribute::HUNGER)->getMaxValue(); - } - - public function addFood(float $amount) : void{ - $attr = $this->attributeMap->getAttribute(Attribute::HUNGER); - $amount += $attr->getValue(); - $amount = max(min($amount, $attr->getMaxValue()), $attr->getMinValue()); - $this->setFood($amount); - } - - /** - * Returns whether this Human may consume objects requiring hunger. - * - * @return bool - */ - public function isHungry() : bool{ - return $this->getFood() < $this->getMaxFood(); - } - - public function getSaturation() : float{ - return $this->attributeMap->getAttribute(Attribute::SATURATION)->getValue(); - } - - /** - * WARNING: This method does not check if saturated and may throw an exception if out of bounds. - * Use {@link Human::addSaturation()} for this purpose - * - * @param float $saturation - * - * @throws \InvalidArgumentException - */ - public function setSaturation(float $saturation) : void{ - $this->attributeMap->getAttribute(Attribute::SATURATION)->setValue($saturation); - } - - public function addSaturation(float $amount) : void{ - $attr = $this->attributeMap->getAttribute(Attribute::SATURATION); - $attr->setValue($attr->getValue() + $amount, true); - } - - public function getExhaustion() : float{ - return $this->attributeMap->getAttribute(Attribute::EXHAUSTION)->getValue(); - } - - /** - * WARNING: This method does not check if exhausted and does not consume saturation/food. - * Use {@link Human::exhaust()} for this purpose. - * - * @param float $exhaustion - */ - public function setExhaustion(float $exhaustion) : void{ - $this->attributeMap->getAttribute(Attribute::EXHAUSTION)->setValue($exhaustion); - } - - /** - * Increases a human's exhaustion level. - * - * @param float $amount - * @param int $cause - * - * @return float the amount of exhaustion level increased - */ - public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{ - $ev = new PlayerExhaustEvent($this, $amount, $cause); - $ev->call(); - if($ev->isCancelled()){ - return 0.0; - } - - $exhaustion = $this->getExhaustion(); - $exhaustion += $ev->getAmount(); - - while($exhaustion >= 4.0){ - $exhaustion -= 4.0; - - $saturation = $this->getSaturation(); - if($saturation > 0){ - $saturation = max(0, $saturation - 1.0); - $this->setSaturation($saturation); - }else{ - $food = $this->getFood(); - if($food > 0){ - $food--; - $this->setFood(max($food, 0)); - } - } - } - $this->setExhaustion($exhaustion); - - return $ev->getAmount(); + public function getHungerManager() : HungerManager{ + return $this->hungerManager; } public function consumeObject(Consumable $consumable) : bool{ if($consumable instanceof FoodSource){ - if($consumable->requiresHunger() and !$this->isHungry()){ + if($consumable->requiresHunger() and !$this->hungerManager->isHungry()){ return false; } - $this->addFood($consumable->getFoodRestore()); - $this->addSaturation($consumable->getSaturationRestore()); + $this->hungerManager->addFood($consumable->getFoodRestore()); + $this->hungerManager->addSaturation($consumable->getSaturationRestore()); } return parent::consumeObject($consumable); @@ -587,6 +474,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ protected function initEntity(CompoundTag $nbt) : void{ parent::initEntity($nbt); + $this->hungerManager = new HungerManager($this); + $this->setPlayerFlag(PlayerMetadataFlags::SLEEP, false); $this->propertyManager->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, null); @@ -624,10 +513,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ $this->inventory->setHeldItemIndex($nbt->getInt("SelectedInventorySlot", 0), false); - $this->setFood((float) $nbt->getInt("foodLevel", (int) $this->getFood(), true)); - $this->setExhaustion($nbt->getFloat("foodExhaustionLevel", $this->getExhaustion(), true)); - $this->setSaturation($nbt->getFloat("foodSaturationLevel", $this->getSaturation(), true)); - $this->foodTickTimer = $nbt->getInt("foodTickTimer", $this->foodTickTimer, true); + $this->hungerManager->setFood((float) $nbt->getInt("foodLevel", (int) $this->hungerManager->getFood(), true)); + $this->hungerManager->setExhaustion($nbt->getFloat("foodExhaustionLevel", $this->hungerManager->getExhaustion(), true)); + $this->hungerManager->setSaturation($nbt->getFloat("foodSaturationLevel", $this->hungerManager->getSaturation(), true)); + $this->hungerManager->setFoodTickTimer($nbt->getInt("foodTickTimer", $this->hungerManager->getFoodTickTimer(), true)); $this->setXpLevel($nbt->getInt("XpLevel", $this->getXpLevel(), true)); $this->setXpProgress($nbt->getFloat("XpP", $this->getXpProgress(), true)); @@ -643,9 +532,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ protected function addAttributes() : void{ parent::addAttributes(); - $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::SATURATION)); - $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXHAUSTION)); - $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::HUNGER)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE_LEVEL)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE)); } @@ -653,7 +539,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ protected function entityBaseTick(int $tickDiff = 1) : bool{ $hasUpdate = parent::entityBaseTick($tickDiff); - $this->doFoodTick($tickDiff); + $this->hungerManager->tick($tickDiff); if($this->xpCooldown > 0){ $this->xpCooldown--; @@ -662,46 +548,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ return $hasUpdate; } - protected function doFoodTick(int $tickDiff = 1) : void{ - if($this->isAlive()){ - $food = $this->getFood(); - $health = $this->getHealth(); - $difficulty = $this->world->getDifficulty(); - - $this->foodTickTimer += $tickDiff; - if($this->foodTickTimer >= 80){ - $this->foodTickTimer = 0; - } - - if($difficulty === World::DIFFICULTY_PEACEFUL and $this->foodTickTimer % 10 === 0){ - if($food < $this->getMaxFood()){ - $this->addFood(1.0); - $food = $this->getFood(); - } - if($this->foodTickTimer % 20 === 0 and $health < $this->getMaxHealth()){ - $this->heal(new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); - } - } - - if($this->foodTickTimer === 0){ - if($food >= 18){ - if($health < $this->getMaxHealth()){ - $this->heal(new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); - $this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN); - } - }elseif($food <= 0){ - if(($difficulty === World::DIFFICULTY_EASY and $health > 10) or ($difficulty === World::DIFFICULTY_NORMAL and $health > 1) or $difficulty === World::DIFFICULTY_HARD){ - $this->attack(new EntityDamageEvent($this, EntityDamageEvent::CAUSE_STARVATION, 1)); - } - } - } - - if($food <= 6){ - $this->setSprinting(false); - } - } - } - public function getName() : string{ return $this->getNameTag(); } @@ -751,10 +597,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); - $nbt->setInt("foodLevel", (int) $this->getFood()); - $nbt->setFloat("foodExhaustionLevel", $this->getExhaustion()); - $nbt->setFloat("foodSaturationLevel", $this->getSaturation()); - $nbt->setInt("foodTickTimer", $this->foodTickTimer); + $nbt->setInt("foodLevel", (int) $this->hungerManager->getFood()); + $nbt->setFloat("foodExhaustionLevel", $this->hungerManager->getExhaustion()); + $nbt->setFloat("foodSaturationLevel", $this->hungerManager->getSaturation()); + $nbt->setInt("foodTickTimer", $this->hungerManager->getFoodTickTimer()); $nbt->setInt("XpLevel", $this->getXpLevel()); $nbt->setFloat("XpP", $this->getXpProgress()); @@ -855,6 +701,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ protected function destroyCycles() : void{ $this->inventory = null; $this->enderChestInventory = null; + $this->hungerManager = null; parent::destroyCycles(); } diff --git a/src/pocketmine/entity/HungerManager.php b/src/pocketmine/entity/HungerManager.php new file mode 100644 index 000000000..f13e55afc --- /dev/null +++ b/src/pocketmine/entity/HungerManager.php @@ -0,0 +1,254 @@ +entity = $entity; + + $this->hungerAttr = self::fetchAttribute($entity, Attribute::HUNGER); + $this->saturationAttr = self::fetchAttribute($entity, Attribute::SATURATION); + $this->exhaustionAttr = self::fetchAttribute($entity, Attribute::EXHAUSTION); + } + + private static function fetchAttribute(Entity $entity, string $attributeId) : Attribute{ + $entity->getAttributeMap()->addAttribute(Attribute::getAttribute($attributeId)); + return $entity->getAttributeMap()->getAttribute($attributeId); + } + + public function getFood() : float{ + return $this->hungerAttr->getValue(); + } + + /** + * WARNING: This method does not check if full and may throw an exception if out of bounds. + * @see HungerManager::addFood() + * + * @param float $new + * + * @throws \InvalidArgumentException + */ + public function setFood(float $new) : void{ + $old = $this->hungerAttr->getValue(); + $this->hungerAttr->setValue($new); + + // ranges: 18-20 (regen), 7-17 (none), 1-6 (no sprint), 0 (health depletion) + foreach([17, 6, 0] as $bound){ + if(($old > $bound) !== ($new > $bound)){ + $this->foodTickTimer = 0; + break; + } + } + } + + public function getMaxFood() : float{ + return $this->hungerAttr->getMaxValue(); + } + + public function addFood(float $amount) : void{ + $amount += $this->hungerAttr->getValue(); + $amount = max(min($amount, $this->hungerAttr->getMaxValue()), $this->hungerAttr->getMinValue()); + $this->setFood($amount); + } + + /** + * Returns whether this Human may consume objects requiring hunger. + * + * @return bool + */ + public function isHungry() : bool{ + return $this->getFood() < $this->getMaxFood(); + } + + public function getSaturation() : float{ + return $this->saturationAttr->getValue(); + } + + /** + * WARNING: This method does not check if saturated and may throw an exception if out of bounds. + * @see HungerManager::addSaturation() + * + * @param float $saturation + * + * @throws \InvalidArgumentException + */ + public function setSaturation(float $saturation) : void{ + $this->saturationAttr->setValue($saturation); + } + + public function addSaturation(float $amount) : void{ + $this->saturationAttr->setValue($this->saturationAttr->getValue() + $amount, true); + } + + public function getExhaustion() : float{ + return $this->exhaustionAttr->getValue(); + } + + /** + * WARNING: This method does not check if exhausted and does not consume saturation/food. + * @see HungerManager::exhaust() + * + * @param float $exhaustion + */ + public function setExhaustion(float $exhaustion) : void{ + $this->exhaustionAttr->setValue($exhaustion); + } + + /** + * Increases exhaustion level. + * + * @param float $amount + * @param int $cause + * + * @return float the amount of exhaustion level increased + */ + public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{ + if(!$this->enabled){ + return 0; + } + $ev = new PlayerExhaustEvent($this->entity, $amount, $cause); + $ev->call(); + if($ev->isCancelled()){ + return 0.0; + } + + $exhaustion = $this->getExhaustion(); + $exhaustion += $ev->getAmount(); + + while($exhaustion >= 4.0){ + $exhaustion -= 4.0; + + $saturation = $this->getSaturation(); + if($saturation > 0){ + $saturation = max(0, $saturation - 1.0); + $this->setSaturation($saturation); + }else{ + $food = $this->getFood(); + if($food > 0){ + $food--; + $this->setFood(max($food, 0)); + } + } + } + $this->setExhaustion($exhaustion); + + return $ev->getAmount(); + } + + /** + * @return int + */ + public function getFoodTickTimer() : int{ + return $this->foodTickTimer; + } + + /** + * @param int $foodTickTimer + */ + public function setFoodTickTimer(int $foodTickTimer) : void{ + if($foodTickTimer < 0){ + throw new \InvalidArgumentException("Expected a non-negative value"); + } + $this->foodTickTimer = $foodTickTimer; + } + + public function tick(int $tickDiff = 1) : void{ + if(!$this->entity->isAlive() or !$this->enabled){ + return; + } + $food = $this->getFood(); + $health = $this->entity->getHealth(); + $difficulty = $this->entity->getWorld()->getDifficulty(); + + $this->foodTickTimer += $tickDiff; + if($this->foodTickTimer >= 80){ + $this->foodTickTimer = 0; + } + + if($difficulty === World::DIFFICULTY_PEACEFUL and $this->foodTickTimer % 10 === 0){ + if($food < $this->getMaxFood()){ + $this->addFood(1.0); + $food = $this->getFood(); + } + if($this->foodTickTimer % 20 === 0 and $health < $this->entity->getMaxHealth()){ + $this->entity->heal(new EntityRegainHealthEvent($this->entity, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); + } + } + + if($this->foodTickTimer === 0){ + if($food >= 18){ + if($health < $this->entity->getMaxHealth()){ + $this->entity->heal(new EntityRegainHealthEvent($this->entity, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); + $this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN); + } + }elseif($food <= 0){ + if(($difficulty === World::DIFFICULTY_EASY and $health > 10) or ($difficulty === World::DIFFICULTY_NORMAL and $health > 1) or $difficulty === World::DIFFICULTY_HARD){ + $this->entity->attack(new EntityDamageEvent($this->entity, EntityDamageEvent::CAUSE_STARVATION, 1)); + } + } + } + + if($food <= 6){ + $this->entity->setSprinting(false); + } + } + + /** + * @return bool + */ + public function isEnabled() : bool{ + return $this->enabled; + } + + /** + * @param bool $enabled + */ + public function setEnabled(bool $enabled) : void{ + $this->enabled = $enabled; + } +} diff --git a/src/pocketmine/entity/effect/HungerEffect.php b/src/pocketmine/entity/effect/HungerEffect.php index 25599c6b9..022241be1 100644 --- a/src/pocketmine/entity/effect/HungerEffect.php +++ b/src/pocketmine/entity/effect/HungerEffect.php @@ -36,7 +36,7 @@ class HungerEffect extends Effect{ public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null) : void{ if($entity instanceof Human){ - $entity->exhaust(0.025 * $instance->getEffectLevel(), PlayerExhaustEvent::CAUSE_POTION); + $entity->getHungerManager()->exhaust(0.025 * $instance->getEffectLevel(), PlayerExhaustEvent::CAUSE_POTION); } } } diff --git a/src/pocketmine/entity/effect/SaturationEffect.php b/src/pocketmine/entity/effect/SaturationEffect.php index e20eeec83..638c78130 100644 --- a/src/pocketmine/entity/effect/SaturationEffect.php +++ b/src/pocketmine/entity/effect/SaturationEffect.php @@ -31,8 +31,9 @@ class SaturationEffect extends InstantEffect{ public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null) : void{ if($entity instanceof Human){ - $entity->addFood($instance->getEffectLevel()); - $entity->addSaturation($instance->getEffectLevel() * 2); + $manager = $entity->getHungerManager(); + $manager->addFood($instance->getEffectLevel()); + $manager->addSaturation($instance->getEffectLevel() * 2); } } } diff --git a/src/pocketmine/player/Player.php b/src/pocketmine/player/Player.php index e837ed421..c85624ae2 100644 --- a/src/pocketmine/player/Player.php +++ b/src/pocketmine/player/Player.php @@ -1198,6 +1198,8 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, $this->gamemode = $gm; $this->allowFlight = $this->isCreative(); + $this->hungerManager->setEnabled($this->isSurvival()); + if($this->isSpectator()){ $this->setFlying(true); $this->despawnFromAll(); @@ -1414,9 +1416,9 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, $distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2)); //TODO: check swimming (adds 0.015 exhaustion in MCPE) if($this->isSprinting()){ - $this->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING); + $this->hungerManager->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING); }else{ - $this->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING); + $this->hungerManager->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING); } } } @@ -1510,24 +1512,6 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, return true; } - protected function doFoodTick(int $tickDiff = 1) : void{ - if($this->isSurvival()){ - parent::doFoodTick($tickDiff); - } - } - - public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{ - if($this->isSurvival()){ - return parent::exhaust($amount, $cause); - } - - return 0.0; - } - - public function isHungry() : bool{ - return $this->isSurvival() and parent::isHungry(); - } - public function canBreathe() : bool{ return $this->isCreative() or parent::canBreathe(); } @@ -1828,7 +1812,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, if($this->hasFiniteResources() and !$item->equalsExact($oldItem)){ $this->inventory->setItemInHand($item); } - $this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING); + $this->hungerManager->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING); return true; } } @@ -1928,7 +1912,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, $this->inventory->setItemInHand($heldItem); } - $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK); + $this->hungerManager->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK); } return true; @@ -2431,7 +2415,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, protected function applyPostDamageEffects(EntityDamageEvent $source) : void{ parent::applyPostDamageEffects($source); - $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_DAMAGE); + $this->hungerManager->exhaust(0.3, PlayerExhaustEvent::CAUSE_DAMAGE); } public function attack(EntityDamageEvent $source) : void{