I have the following code, and it runs without error, but when the entity is "spawned" the client cannot see it. The bossbar appears for a few seconds though. Am I missing something? PHP: public function startFight(int $bossHealth = 0) { $this->getConfig()->reload(); $level = $this->getServer()->getLevelByName((string) $this->getConfig()->get("World Name", "world")) ?? $this->getServer()->getDefaultLevel(); $x = (float)$this->getConfig()->get("X", 0); $y = (float)$this->getConfig()->get("Y", 100); $z = (float)$this->getConfig()->get("Z", 0); $drops = []; /** @var string[] $dropStrings */ $dropStrings = $this->getConfig()->getNested("Drops.Items", []); foreach($dropStrings as $string) { $args = explode(" ", $string); try{ $item = ItemFactory::fromString($args[0]); }catch(\InvalidArgumentException $e) { continue; } if(!isset($args[1])) { $item->setCount($item->getMaxStackSize()); }else { $item->setCount((int) $args[1]); } if(isset($args[2])) { $tags = $exception = null; $data = implode(" ", array_slice($args, 2)); try{ $tags = JsonNbtParser::parseJson($data); }catch(\Exception $ex) { $exception = $ex; } if(!($tags instanceof CompoundTag) or $exception !== null) { continue; } $item->setNamedTag($tags); } $drops[] = clone $item; } /** @var Boss $boss */ $boss = Boss::createEntity("Boss", $level, Boss::createBaseNBT(new Vector3($x, $y, $z)), $drops); if($bossHealth > 0) { $boss->setHealth($bossHealth); $boss->setMaxHealth($bossHealth); } $boss->spawnToAll(); $level->registerChunkLoader($boss, $boss->x >> 4, $boss->z >> 4); $this->boss = $boss; $this->showBossBar($boss); } /** * @param Entity $entity */ public function showBossBar(Entity $entity) { $bossBar = new BossEventPacket(); $bossBar->eventType = BossEventPacket::TYPE_SHOW; $bossBar->bossEid = $entity->getId(); $bossBar->title = Main::getInstance()->getConfig()->get("Boss Name", ""); // will be overwritten $bossBar->healthPercent = $entity->getHealth() / $entity->getMaxHealth(); $bossBar->unknownShort = 1; $bossBar->color = 1; $bossBar->overlay = 1; $entity->getLevel()->addGlobalPacket($bossBar); }
As long as the boss's entity is "spawned" (AddEntityPacket is sent) to the client, the client should get the boss bar updated. That only lasts until RemoveEntityPacket gets sent to the player, which happens when the boss despawns from the client in any way including being out of client's view-distance.
The boss is a chunk loader, so it shouldn't be able to despawn right? Following that logic, the bossbar stays on the screen for enough time to go to the coordinates of the entity but when I look around, the boss is not there.
Entities aren't ChunkLoaders, only Player entities are. https://github.com/pmmp/PocketMine-MP/blob/master/src/pocketmine/Player.php#L164 As soon as the boss despawns from the player, I think that stops the boss bar updates from happening. Even if the Boss entity was a ChunkLoader (you can make it a ChunkLoader by implementing the ChunkLoader class), if the Boss is out of range of player's view-distance, the server will send the player the despawn packet.
This is also a major advantage a "packet entity" has in this case — you won't need to worry about entity despawns. You can manually send/broadcast SetEntityDataPacket with $metadata[Entity::DATA_NAMETAG] = [Entity::DATA_TYPE_STRING, "BossBar title"] or an UpdateAttributesPacket for filling the boss bar.
I would rather not use packets in this case because the boss is intended to be able to attack players and receive damage. I know the client doesn't despawn the entity because the bossbar remains at the top of the screen when the entity is nowhere to be seen. The issue is that the boss is not appearing on the client at all. Nothing is wrong with the bossbar.
What's the NETWORK_ID of the Boss and did you by any chance override Entity::spawnTo() or Entity::sendSpawnPacket()?
The Network Id is 34 because the boss is a giant skeleton. I do have an override, but it's to give the skeleton a bow. The code should work because I borrowed it straight from PureEntitiesX PHP: const NETWORK_ID = 34; public function spawnTo(Player $player) : void { parent::spawnTo($player); $pk = new MobEquipmentPacket(); $pk->entityRuntimeId = $this->getId(); $pk->item = ItemFactory::get(Item::BOW); $pk->inventorySlot = 10; $pk->hotbarSlot = 10; $player->dataPacket($pk); }
Did you declare the entity's $width and $height? Spoiler: Off-Topic Consider moving the packet into sendSpawnPacket() instead. spawnTo can be called more than twice for the same entity so you're spamming the client with already-sent equipment packets. Add a var_dump() in spawnTo and you'll know why.
Width and Height are declared. It errors otherwise. After following your recommendation to move the packets into sendSpawnPacket() the boss entity appeared to the client. Thank you for helping! PHP: <?phpdeclare(strict_types=1);namespace jasonwynn10\ServerBossFight\entity;use jasonwynn10\ServerBossFight\Main;use pocketmine\entity\Living;use pocketmine\entity\projectile\Arrow;use pocketmine\entity\projectile\Projectile;use pocketmine\event\entity\EntityShootBowEvent;use pocketmine\event\entity\ProjectileLaunchEvent;use pocketmine\item\Item;use pocketmine\item\ItemFactory;use pocketmine\level\ChunkLoader;use pocketmine\level\format\Chunk;use pocketmine\level\Level;use pocketmine\level\sound\LaunchSound;use pocketmine\math\Vector3;use pocketmine\nbt\tag\CompoundTag;use pocketmine\network\mcpe\protocol\AddEntityPacket;use pocketmine\network\mcpe\protocol\MobEquipmentPacket;use pocketmine\Player;class Boss extends Living implements ChunkLoader { const NETWORK_ID = 34; // skeleton id /** @var Item[] $drops */ private $drops; /** @var int $attackDelay */ private $attackDelay; /** @var int|null $loaderId */ private $loaderId = 0; /** @var int|null $order */ private $order = null; /** * Boss constructor. * * @param Level $level * @param CompoundTag $nbt * @param Item[] $drops */ public function __construct(Level $level, CompoundTag $nbt, array $drops = []) { $this->width = 0.875; $this->height = 2.0; parent::__construct($level, $nbt); /** @var float $scale */ $scale = Main::getInstance()->getConfig()->get("Scale Size", 4.3); /** @var int $health */ $health = Main::getInstance()->getConfig()->get("Boss Health", 600); $this->setScale($scale); $this->setMaxHealth($health); $this->setHealth($health); $this->setImmobile(true); $this->setCanSaveWithChunk(false); $this->drops = $drops; $this->loaderId = Level::generateChunkLoaderId($this); $this->setNameTag((string) Main::getInstance()->getConfig()->get("Boss Name", "Boss")); } public function initEntity() : void { parent::initEntity(); } public function getName() : string { return "Boss"; } public function isFireProof() : bool { return true; } public function onUpdate(int $currentTick) : bool { /** @var Player|null $target */ $target = null; $possibleTargets = $this->getLevel()->getNearbyEntities($this->getBoundingBox(), $this); //$possibleTargets = $this->getViewers(); /** @var Player[] $possibleTargets */ $possibleTargets = array_filter($possibleTargets, function($entity) { return $entity instanceof Player and !$entity->isCreative(); }); if(empty($possibleTargets)) { return parent::onUpdate($currentTick); } $target = $possibleTargets[array_rand($possibleTargets)]; //TODO look at target if($this->attackDelay > 30 && mt_rand(1, 32) < 4 && $this->distanceSquared($target) <= 55) { $this->attackDelay = 0; $f = 1.2; $yaw = $this->yaw + mt_rand(-220, 220) / 10; $pitch = $this->pitch + mt_rand(-120, 120) / 10; $pos = new Vector3($this->x + (-sin($yaw / 180 * M_PI) * cos($pitch / 180 * M_PI) * 0.5), $this->y + 1.62, $this->z + (cos($yaw / 180 * M_PI) * cos($pitch / 180 * M_PI) * 0.5)); $motion = new Vector3(-sin($yaw / 180 * M_PI) * cos($pitch / 180 * M_PI) * $f, -sin($pitch / 180 * M_PI) * $f, cos($yaw / 180 * M_PI) * cos($pitch / 180 * M_PI) * $f); /** @var BossArrow $arrow */ $arrow = self::createEntity("BossArrow", $this->getLevel(), Arrow::createBaseNBT($pos, $motion, $yaw, $pitch), $this); $ev = new EntityShootBowEvent($this, Item::get(Item::ARROW, 0, 1), $arrow, $f); $this->server->getPluginManager()->callEvent($ev); $projectile = $ev->getProjectile(); if($ev->isCancelled()) { $projectile->kill(); }elseif($projectile instanceof Projectile) { $this->server->getPluginManager()->callEvent($launch = new ProjectileLaunchEvent($projectile)); if($launch->isCancelled()) { $projectile->kill(); }else { $projectile->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } } } return parent::onUpdate($currentTick); } protected function sendSpawnPacket(Player $player) : void{ $pk = new AddEntityPacket(); $pk->entityRuntimeId = $this->getId(); $pk->type = static::NETWORK_ID; $pk->position = $this->asVector3(); $pk->motion = $this->getMotion(); $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->attributes = $this->attributeMap->getAll(); $pk->metadata = $this->propertyManager->getAll(); $player->dataPacket($pk); $pk = new MobEquipmentPacket(); $pk->entityRuntimeId = $this->getId(); $pk->item = ItemFactory::get(Item::BOW); $pk->inventorySlot = 10; $pk->hotbarSlot = 10; $player->dataPacket($pk); } public function entityBaseTick(int $tickDiff = 1) : bool { $this->attackDelay += $tickDiff; return parent::entityBaseTick($tickDiff); } public function onDeath() : void { //TODO dragon death sounds parent::onDeath(); } public function getDrops() : array { return $this->drops; } public function getXpDropAmount() : int { /** @var int $exp */ $exp = Main::getInstance()->getConfig()->getNested("Drops.Experience", 300); return $exp; } public function onChunkChanged(Chunk $chunk) { //TODO } public function onChunkLoaded(Chunk $chunk) { //TODO } public function onChunkPopulated(Chunk $chunk) { //TODO } public function onChunkUnloaded(Chunk $chunk) { //TODO } public function onBlockChanged(Vector3 $block) { //TODO } public function getLoaderId() : int { return $this->loaderId; } public function isLoaderActive() : bool { return !$this->isClosed(); } /** * @param int $number */ public function setOrder(int $number) { $this->order = $number; } public function close() : void { $this->level->unregisterChunkLoader($this, $this->x >> 4, $this->z >> 4); parent::close(); Main::getInstance()->closeBoss($this->order); }}
You can call parent::sendSpawnPacket(player) in place of this PHP: $pk = new AddEntityPacket(); $pk->entityRuntimeId = $this->getId(); $pk->type = static::NETWORK_ID; $pk->position = $this->asVector3(); $pk->motion = $this->getMotion(); $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->attributes = $this->attributeMap->getAll(); $pk->metadata = $this->propertyManager->getAll(); $player->dataPacket($pk);