namedtag->HealF)){ $this->namedtag->Health = new ShortTag("Health", (int) $this->namedtag["HealF"]); unset($this->namedtag->HealF); }elseif(!isset($this->namedtag->Health) or !($this->namedtag->Health instanceof ShortTag)){ $this->namedtag->Health = new ShortTag("Health", $this->getMaxHealth()); } $this->setHealth($this->namedtag["Health"]); } protected function addAttributes(){ $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::HEALTH)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::FOLLOW_RANGE)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::KNOCKBACK_RESISTANCE)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::MOVEMENT_SPEED)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::ATTACK_DAMAGE)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::ABSORPTION)); } public function setHealth($amount){ $wasAlive = $this->isAlive(); parent::setHealth($amount); $this->attributeMap->getAttribute(Attribute::HEALTH)->setValue($this->getHealth(), true); if($this->isAlive() and !$wasAlive){ $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = EntityEventPacket::RESPAWN; $this->server->broadcastPacket($this->hasSpawned, $pk); } } public function getMaxHealth(){ return $this->attributeMap->getAttribute(Attribute::HEALTH)->getMaxValue(); } public function setMaxHealth($amount){ $this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount); } public function getAbsorption() : int{ return (int) $this->attributeMap->getAttribute(Attribute::ABSORPTION)->getValue(); } public function setAbsorption(int $absorption){ $this->attributeMap->getAttribute(Attribute::ABSORPTION)->setValue($absorption); } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Health = new ShortTag("Health", $this->getHealth()); } abstract public function getName(); public function hasLineOfSight(Entity $entity){ //TODO: head height return true; //return $this->getLevel()->rayTraceBlocks(Vector3::createVector($this->x, $this->y + $this->height, $this->z), Vector3::createVector($entity->x, $entity->y + $entity->height, $entity->z)) === null; } public function heal($amount, EntityRegainHealthEvent $source){ parent::heal($amount, $source); if($source->isCancelled()){ return; } $this->attackTime = 0; } /** * Returns the initial upwards velocity of a jumping entity in blocks/tick, including additional velocity due to effects. * @return float */ public function getJumpVelocity() : float{ return $this->jumpVelocity + ($this->hasEffect(Effect::JUMP) ? ($this->getEffect(Effect::JUMP)->getEffectLevel() / 10) : 0); } /** * Called when the entity jumps from the ground. This method adds upwards velocity to the entity. */ public function jump(){ if($this->onGround){ $this->motionY = $this->getJumpVelocity(); //Y motion should already be 0 if we're jumping from the ground. } } public function attack($damage, EntityDamageEvent $source){ if($this->attackTime > 0 or $this->noDamageTicks > 0){ $lastCause = $this->getLastDamageCause(); if($lastCause !== null and $lastCause->getDamage() >= $damage){ $source->setCancelled(); } } parent::attack($damage, $source); if($source->isCancelled()){ return; } if($source instanceof EntityDamageByEntityEvent){ $e = $source->getDamager(); if($source instanceof EntityDamageByChildEntityEvent){ $e = $source->getChild(); } if($e !== null){ if($e->isOnFire() > 0){ $this->setOnFire(2 * $this->server->getDifficulty()); } $deltaX = $this->x - $e->x; $deltaZ = $this->z - $e->z; $this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack()); } } $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = $this->getHealth() <= 0 ? EntityEventPacket::DEATH_ANIMATION : EntityEventPacket::HURT_ANIMATION; //Ouch! $this->server->broadcastPacket($this->hasSpawned, $pk); $this->attackTime = 10; //0.5 seconds cooldown } public function knockBack(Entity $attacker, $damage, $x, $z, $base = 0.4){ $f = sqrt($x * $x + $z * $z); if($f <= 0){ return; } $f = 1 / $f; $motion = new Vector3($this->motionX, $this->motionY, $this->motionZ); $motion->x /= 2; $motion->y /= 2; $motion->z /= 2; $motion->x += $x * $f * $base; $motion->y += $base; $motion->z += $z * $f * $base; if($motion->y > $base){ $motion->y = $base; } $this->setMotion($motion); } public function kill(){ if(!$this->isAlive()){ return; } parent::kill(); $this->callDeathEvent(); } protected function callDeathEvent(){ $this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops())); foreach($ev->getDrops() as $item){ $this->getLevel()->dropItem($this, $item); } } public function entityBaseTick($tickDiff = 1){ Timings::$timerLivingEntityBaseTick->startTiming(); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BREATHING, !$this->isInsideOfWater()); $hasUpdate = parent::entityBaseTick($tickDiff); if($this->isAlive()){ if($this->isInsideOfSolid()){ $hasUpdate = true; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1); $this->attack($ev->getFinalDamage(), $ev); } if(!$this->hasEffect(Effect::WATER_BREATHING) and $this->isInsideOfWater()){ if($this instanceof WaterAnimal){ $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 400); }else{ $hasUpdate = true; $airTicks = $this->getDataProperty(self::DATA_AIR) - $tickDiff; if($airTicks <= -20){ $airTicks = 0; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_DROWNING, 2); $this->attack($ev->getFinalDamage(), $ev); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $airTicks); } }else{ if($this instanceof WaterAnimal){ $hasUpdate = true; $airTicks = $this->getDataProperty(self::DATA_AIR) - $tickDiff; if($airTicks <= -20){ $airTicks = 0; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 2); $this->attack($ev->getFinalDamage(), $ev); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $airTicks); }else{ $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 400); } } } if($this->attackTime > 0){ $this->attackTime -= $tickDiff; } Timings::$timerLivingEntityBaseTick->stopTiming(); return $hasUpdate; } /** * @return ItemItem[] */ public function getDrops(){ return []; } /** * @param int $maxDistance * @param int $maxLength * @param array $transparent * * @return Block[] */ public function getLineOfSight($maxDistance, $maxLength = 0, array $transparent = []){ if($maxDistance > 120){ $maxDistance = 120; } if(count($transparent) === 0){ $transparent = null; } $blocks = []; $nextIndex = 0; $itr = new BlockIterator($this->level, $this->getPosition(), $this->getDirectionVector(), $this->getEyeHeight(), $maxDistance); while($itr->valid()){ $itr->next(); $block = $itr->current(); $blocks[$nextIndex++] = $block; if($maxLength !== 0 and count($blocks) > $maxLength){ array_shift($blocks); --$nextIndex; } $id = $block->getId(); if($transparent === null){ if($id !== 0){ break; } }else{ if(!isset($transparent[$id])){ break; } } } return $blocks; } /** * @param int $maxDistance * @param array $transparent * * @return Block */ public function getTargetBlock($maxDistance, array $transparent = []){ try{ $block = $this->getLineOfSight($maxDistance, 1, $transparent)[0]; if($block instanceof Block){ return $block; } }catch(\ArrayOutOfBoundsException $e){ } return null; } }