1. The forums will be archived and moved to a read only mode in about 2 weeks (mid march).

Solved Boss Entity Invisible to Client

Discussion in 'Development' started by jasonwynn10, Jun 28, 2018.

  1. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    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($args2));
                    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"$levelBoss::createBaseNBT(new Vector3($x$y$z)), $drops);
            if(
    $bossHealth 0) {
                
    $boss->setHealth($bossHealth);
                
    $boss->setMaxHealth($bossHealth);
            }
            
    $boss->spawnToAll();
            
    $level->registerChunkLoader($boss$boss->>> 4$boss->>> 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);
        }
     
    Last edited: Jun 28, 2018
  2. Levi

    Levi Skeleton

    Messages:
    955
    GitHub:
    captainleviftw
    u need addentitypacket i think
     
  3. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    PocketMine's API already sends it
     
  4. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    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.
     
  5. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    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.
     
    Muqsit likes this.
  6. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    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.
     
  7. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    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.
     
  8. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    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.
     
  9. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    What's the NETWORK_ID of the Boss and did you by any chance override Entity::spawnTo() or Entity::sendSpawnPacket()?
     
  10. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    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);
        }
     
  11. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    Did you declare the entity's $width and $height?
    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.
     
  12. jasonwynn10

    jasonwynn10 Moderator Poggit Reviewer

    Messages:
    1,489
    GitHub:
    jasonwynn10
    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:
    <?php
    declare(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 $levelCompoundTag $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(132) < && $this->distanceSquared($target) <= 55) {
                
    $this->attackDelay 0;
                
    $f 1.2;
                
    $yaw $this->yaw mt_rand(-220220) / 10;
                
    $pitch $this->pitch mt_rand(-120120) / 10;
                
    $pos = new Vector3($this->+ (-sin($yaw 180 M_PI) * cos($pitch 180 M_PI) * 0.5), $this->1.62$this->+ (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) * $fcos($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($thisItem::get(Item::ARROW01), $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->>> 4$this->>> 4);
            
    parent::close();
            
    Main::getInstance()->closeBoss($this->order);
        }
    }
     
    Last edited: Jun 29, 2018
    Muqsit likes this.
  13. Muqsit

    Muqsit Chicken

    Messages:
    1,548
    GitHub:
    muqsit
    You can call parent::sendSpawnPacket(player) in place of this :p
    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);
     
    Last edited: Jun 29, 2018
    jasonwynn10 likes this.
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.