diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index ff9120364..bfc346e83 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -2260,6 +2260,8 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this->hasSpawned); } + + $this->exhaust(0.025); } break; } @@ -2294,7 +2296,6 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade if( $target instanceof Player and $this->server->getConfigBoolean("pvp", true) === false - ){ $cancelled = true; } @@ -2392,12 +2393,16 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade break; } - if($item->isTool() and $this->isSurvival()){ - if($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()){ - $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); - }else{ - $this->inventory->setItemInHand($item); + if($this->isSurvival()){ + if($item->isTool()){ + if($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()){ + $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); + }else{ + $this->inventory->setItemInHand($item); + } } + + $this->exhaust(0.3); } } @@ -3259,6 +3264,10 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $pk->eid = 0; $pk->event = EntityEventPacket::HURT_ANIMATION; $this->dataPacket($pk); + + if($this->isSurvival()){ + $this->exhaust(0.3); + } } } diff --git a/src/pocketmine/entity/Effect.php b/src/pocketmine/entity/Effect.php index 0f3d9e066..8ed114a98 100644 --- a/src/pocketmine/entity/Effect.php +++ b/src/pocketmine/entity/Effect.php @@ -201,6 +201,14 @@ class Effect{ return ($this->duration % $interval) === 0; } return true; + case Effect::HUNGER: + if($this->amplifier < 0){ // prevents hacking with amplifier -1 + return false; + } + if(($interval = 20) > 0){ + return ($this->duration % $interval) === 0; + } + return true; } return false; } @@ -225,6 +233,11 @@ class Effect{ $entity->heal($ev->getAmount(), $ev); } break; + + case Effect::HUNGER: + if($entity instanceof Human){ + $entity->exhaust(0.5 * $this->amplifier); + } } } diff --git a/src/pocketmine/entity/Human.php b/src/pocketmine/entity/Human.php index 7265c3f51..c8e91909c 100644 --- a/src/pocketmine/entity/Human.php +++ b/src/pocketmine/entity/Human.php @@ -21,6 +21,8 @@ namespace pocketmine\entity; +use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\PlayerInventory; use pocketmine\item\Item as ItemItem; @@ -58,6 +60,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ protected $skinName; protected $skin; + protected $foodTickTimer = 0; + public function getSkinData(){ return $this->skin; } @@ -93,18 +97,47 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ return (float) $this->attributeMap->getAttribute(Attribute::HUNGER)->getValue(); } - public function setFood(float $food){ - $this->attributeMap->getAttribute(Attribute::HUNGER)->setValue($food); + /** + * 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 + */ + public function setFood(float $new){ + $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)){ + $reset = true; + } + } + if(isset($reset)){ + $this->foodTickTimer = 0; + } } public function addFood(float $amount){ - $this->attributeMap->getAttribute(Attribute::HUNGER)->setValue($amount, true); + $attr = $this->attributeMap->getAttribute(Attribute::HUNGER); + $amount = max(min($amount, $attr->getMaxValue()), $attr->getMinValue()); + $this->setFood($amount); } public function getSaturation() : float{ return $this->attributeMap->getAttribute(Attribute::HUNGER)->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){ $this->attributeMap->getAttribute(Attribute::HUNGER)->setValue($saturation); } @@ -117,15 +150,32 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ 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){ $this->attributeMap->getAttribute(Attribute::EXHAUSTION)->setValue($exhaustion); } + /** + * Increases a human's exhaustion level. + * TODO walk per meter: 0.01 + * TODO sneak per meter: 0.005 + * TODO swim per meter: 0.015 + * TODO sprint per meter: 0.1 + * TODO jump: 0.2 + * TODO regen per halfheart | food >= 18: 4.0 + * + * @param float $amount + */ public function exhaust(float $amount){ $exhaustion = $this->getExhaustion(); $exhaustion += $amount; - if($exhaustion >= 4.0){ + while($exhaustion >= 4.0){ $exhaustion -= 4.0; $this->setExhaustion($exhaustion); @@ -181,14 +231,52 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ } } + parent::initEntity(); + $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::ATTACK_DAMAGE)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE_LEVEL)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE)); + } - parent::initEntity(); + public function entityBaseTick($tickDiff = 1){ + $hasUpdate = parent::entityBaseTick($tickDiff); + + $food = $this->getFood(); + $health = $this->getHealth(); + if($food >= 18){ + $this->foodTickTimer++; + if($this->foodTickTimer >= 80){ + $this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); + $this->exhaust(3.0); + $this->foodTickTimer = 0; + } + }elseif($food === 0){ + $this->foodTickTimer++; + if($this->foodTickTimer >= 80){ + $diff = $this->server->getDifficulty(); + $can = false; + if($diff === 1){ + $can = $health > 10; + }elseif($diff === 2){ + $can = $health > 1; + }elseif($diff === 3){ + $can = true; + } + if($can){ + $this->attack(1, new EntityDamageEvent($this, EntityDamageEvent::CAUSE_STARVATION, 1)); + } + } + } + if($food <= 6){ + if($this->isSprinting()){ + $this->setSprinting(false); + } + } + + return $hasUpdate; } public function getName(){ diff --git a/src/pocketmine/event/entity/EntityDamageEvent.php b/src/pocketmine/event/entity/EntityDamageEvent.php index f792f6bda..0b681a7c2 100644 --- a/src/pocketmine/event/entity/EntityDamageEvent.php +++ b/src/pocketmine/event/entity/EntityDamageEvent.php @@ -49,6 +49,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{ const CAUSE_SUICIDE = 12; const CAUSE_MAGIC = 13; const CAUSE_CUSTOM = 14; + const CAUSE_STARVATION = 15; private $cause; diff --git a/src/pocketmine/event/entity/EntityRegainHealthEvent.php b/src/pocketmine/event/entity/EntityRegainHealthEvent.php index 4be1d27da..14bbd80ac 100644 --- a/src/pocketmine/event/entity/EntityRegainHealthEvent.php +++ b/src/pocketmine/event/entity/EntityRegainHealthEvent.php @@ -31,6 +31,7 @@ class EntityRegainHealthEvent extends EntityEvent implements Cancellable{ const CAUSE_EATING = 1; const CAUSE_MAGIC = 2; const CAUSE_CUSTOM = 3; + const CAUSE_SATURATION = 4; private $amount; private $reason;