setDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT, new FloatTag("Scale", 1.2)); parent::__construct($level, $nbt); $it = Item::get(Item::GOLDEN_HOE, 0); $it->addEnchantment(Enchantment::getEnchantment(Enchantment::SHARPNESS)); $this->getInventory()->setItemInHand($it); } /** * Gets the targeted player * * @return void */ public function getPlayer(){ return $this->associatedPlayer; } /** * Returns the speed of the entity * * @return float */ public function getSpeed(): float { return $this->fightType * 2; } // Phases /** * Starts the fight. * * @param Player $player */ public function startFight(Player $p) { $p->getLevel()->addSound(new TheReturnMusicPlay($p->asVector3()), [$p->getViewers()]); // Only after the ghost appeared, we can remove it. Effect::getEffectById(Effect::BLINDNESS)->setDuration(66*20)->setVisible(false)->add($p); Effect::getEffectById(Effect::INVISIBILITY)->setDuration(63*20)->setVisible(false)->add($this); $this->spawnTo($p); $this->associatedPlayer = $p; TickTask::registerGhost($this); } /** * Starts a sequence where the ghost is in an intense fight. * * @return void */ public function intenseFight(){ if(!$this->checkIfConnected()) return; // TODO: Custom intense fight $this->fightType = 1; } /** * Starts a sequence where the ghost is in an calm fight. * * @return void */ public function calmFight(){ if(!$this->checkIfConnected()) return; $this->fightType = 1; } /** * Starts a sequence where the ghost blinds the player, * slows him down (zooming effect w/ fov) and he appears invuulnerable * in front of the player. * @return void */ public function blackOutEnterPhase(){ if(!$this->checkIfConnected()) return; $this->fightType = 0; $spawnBlock = $this->getPlayer()->getLineOfSight(2); $spawnBlock = $spawnBlock[count($spawnBlock) -1]; $this->getPlayer()->addEffect( Effect::getEffectById(Effect::BLINDNESS)->setDuration(30*20)->setAmplifier(4)->setVisible(false) ); $this->getPlayer()->addEffect( Effect::getEffectById(Effect::SLOWNESS)->setDuration(30*20)->setAmplifier(99)->setVisible(false) ); $this->getPlayer()->addEffect( Effect::getEffectById(Effect::NAUSEA)->setDuration(30*20)->setAmplifier(99)->setVisible(false) ); $this->teleport(new Vector3($spawnBlock->x, $spawnBlock->y, $spawnBlock->z), abs($this->getPlayer()->getYaw() - 180)); $this->removeEffect(Effect::INVISIBILITY); } /** * Ends a sequence where the ghost blinds the player, * slows him down (zooming effect w/ fov) and he appears invuulnerable * in front of the player. * @return void */ public function blackOutExitPhase(){ if(!$this->checkIfConnected()) return; $this->getPlayer()->removeEffect(Effect::BLINDNESS); $this->getPlayer()->removeEffect(Effect::NAUSEA); $this->getPlayer()->removeEffect(Effect::SLOWNESS); } /** * Sequence where the ghost starts * to move the player randomly. * * @return void */ public function movePlayerRandomly(){ if(!$this->checkIfConnected()) return; $this->fightType = 0; for($i = 0; $i < 10; $i++){ $this->associatedPlayer->setMotion(new Vector3(rand() - rand(), 0, rand() - rand())); } } /** * Sequence where the ghost starts * to destroy blocks around the player randomly. * * @return void */ public function destroyBlocksRandomly(){ if(!$this->checkIfConnected()) return; $this->fightType = 0; $this->teleport(new Vector3($this->getPlayer()->x, $this->getPlayer()->y + 7, $this->getPlayer()->z), $this->yaw == 0 ? 359 : $this->yaw - 1, 130); srand(hash("sha512", time())); foreach($this->getLevel()->getEntities() as $et){ if($et instanceof \pocketmine\entity\FallingSand && $et->namedtag->getInt("SpawnTime") !== $null){ $diffTime = time() - $et->namedtag->getInt("SpawnTime"); if($diffTime < 4) $et->close(); } } for($i = 0; $i < 3; $i++) { $rx = rand(0, 12) - 6; $rz = rand(0, 12) - 6; for($ry = 128; $this->getLevel()->getBlock($rx, $ry, $rz)->getId() !== 0 || $ry == 0; $ry--){} // Determining y from the higthest workable block $b = $this->getLevel()->getBlock($rx, $ry, $rz); // Creating falling sand block $nbt = Entity::createBaseNBT($b->asVector3()->add(0.5, 0, 0.5)); $nbt->setInt("TileID", $b->getId()); $nbt->setInt("SpawnTime", time()); $nbt->setByte("Data", $b->getDamage()); $nbt->getListTag("Motion")[1] = 3; $fall = Entity::createEntity("FallingSand", $this->getLevel(), $nbt); if($fall !== null){ $fall->spawnToAll(); } } } /** * Check if the player is still connected to the server * * @return void */ public function checkIfConnected(){ if(!$this->associatedPlayer->isOnline()){ TickTask::unregisterGhost($this); $this->close(); return false; } return !($this->isClosed() || $this->getLevel() == null); } /** * Spawns the ghost to the player * * @param Player $player * @return void */ public function spawnTo(Player $player) { if(!isset($this->hasSpawned[$player->getLoaderId()]) && isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])) { $this->hasSpawned[$player->getLoaderId()] = $player; $uuid = $this->getUniqueId(); $entityId = $this->getId(); $pk = new AddPlayerPacket(); $pk->uuid = $uuid; $pk->username = ""; $pk->entityRuntimeId = $entityId; $pk->position = $this->asVector3(); $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->item = $this->getInventory()->getItemInHand(); $pk->metadata = $this->dataProperties; $pk->metadata[self::DATA_NAMETAG] = [self::DATA_TYPE_STRING, $this->getDisplayName($player)]; $player->dataPacket($pk); $this->inventory->sendArmorContents($player); $player->getServer()->updatePlayerListData($uuid, $entityId, "The Ghost", $this->skinId, $this->skin, [$player]); } } // Event listeners /** * Check the damage to reduce it by 25% * * @param EntityDamageEvent $event */ public function attack(EntityDamageEvent $event) { if($event instanceof EntityDamageByEntityEvent) { if($event->getDamager() instanceof Player && $event->getDamager()){ $event->setDamage($event->getDamage() * 0.75); } else { $event->setCancelled(true); $event->getDamager()->motionY = 12; $event->getDamager()->addEffect( Effect::getEffectById(Effect::NAUSEA)->setDuration(30*20)->setAmplifier(99)->setVisible(false) ); } } } /** * When the entity gets an update, recalibrate entity * * @param int $currentTick * @return bool */ public function onUpdate(int $currentTick): bool { if(!$this->checkIfConnected()) return false; if($this->fightType == 0) return false; // Teleportation if(rand(0, 200) == 0) { // Do we do teleportation? if(rand(0,1) == 0){ // Which kind of tp? Random around or forward? $los = $this->getLineOfSight(10); $b = $los[count($los) - 1]; $this->teleport($b); } else { $x = rand($this->x + 8, $this->x - 8); $z = rand($this->z + 8, $this->z - 8); for($y = $this->y + 16; $y > $this->y - 16; $this->y--){ if($this->getLevel()->getBlock($x, $y -1, $z)->getId() !== 0) break; } $this->teleport(new Vector3($x, $y, $z)); } } // Setting specific motion $diffV3 = $this->associatedPlayer->asVector3()->subtract($this->asVector3()); $distDiff = $diffV3->asVector3()->abs(); $distDiff = $distDiff->x + $distDiff->z; // Check if we can attack the player if($this->distanceSquared($this->associatedPlayer) <= 1){ $this->attackEntity($this->associatedPlayer); } // If not, try moving torowards him if ($diff > 0) { $this->motionX = $this->getSpeed() * 0.15 * ($diffV3->x / $distDiff); $this->motionZ = $this->getSpeed() * 0.15 * ($diffV3->z / $distDiff); $this->yaw = rad2deg(-atan2($diffV3->x / $distDiff, $diffV3->z / $distDiff)); } if($y == 0){ $this->pitch = 0; } else { $this->pitch = rad2deg(-atan2($diffV3->y, sqrt($diffV3->x ** 2 + $distDiff->z ** 2)));; } $currentB = $this->getLevel()->getBlock($this->asVector3()); if($currentB instanceof \pocketmine\block\Liquid){ // in water, we need to get it floating $this->motionY = $this->gravity * 2; } else { // Check if the ghost is in air and not stuck in the ground. Then, we'll get the target block to check if it's possible to jump. if($currentB->canPassThought()){ $targetB = $this->getTargetBlock(2); } else { $targetB = $currentB; } $canJump = true; // Check 3 blocks up that position (to see if the entity can go up) for($i = 1; $i <= 3; $i++){ $blockUp = $targetB->asVector3(); $blockUp->y += $i; if(!$this->getLevel()->getBlock($blockUp)->canPassThought()) $canJump = false; } // FInally, jump if possible if($canJump && $this->gravity * 3.2 > $this->motionY) $this->motionY = $this->gravity * 3.2; } } /** * Attacks an entity when the ghost is close enought from it. * * @param Entity $et * @return void */ public function attackEntity(Entity $et){ if($et instanceof Player){ $damage = [ EntityDamageEvent::MODIFIER_BASE => 10 // Two hit a player which has no armor ]; $points = 0; foreach($et->getInventory()->getArmorContents() as $armorItem){ $points += $armorItem->getDefensePoints(); } $damage[EntityDamageEvent::MODIFIER_ARMOR] = -($damage[EntityDamageEvent::MODIFIER_BASE] * $points * 0.04); $ev = new EntityDamageByEntityEvent($this, $this->associatedPlayer, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage); $et->attack($ev); } } /** * Gives some drops when the ghost die * * @return array */ public function getDrops() : array{ $it = Item::get(Item::GOLDEN_HOE, 0); $it->setCustomName("§r§cSoul Stealer"); $it->setNamedTagEntry(new StringTag("customDamage", 10)); $e = Enchantment::getEnchantment(Enchantment::SHARPNESS); $e->setLevel(10); $it->addEnchantment($e); return [$it]; } }