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

Human Entitys are despawning after around one minute

Discussion in 'Development' started by EnteMomo, Dec 20, 2020.

  1. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    Hey!
    So I've got a server where I spawn Human Entitys and I have autosave disabled.
    The problem is that (probably) because of the disabled autosave that the Humans despawn after around 1 minute if no player is on the server. Is this a bug or is there any way to solve that. The same thing happened to me when I was spawning an ItemEntity on a server with autosave disabled.

    Maybe someone has experienced the same.
    Thanks!
     
  2. Primus

    Primus Zombie Pigman

    Messages:
    749
    It makes sense for the entity to disappear. Once you're far enough, server will unload the chunks. Since they are not being saved, your entities aren't either. Therefore when chunks are loaded back in, no entities are there.

    Solution number one, would be to enable autosave. And the second, is to run the save manually /save-all (check /help). Last, but not least, spawn your entities when needed when chunks are loaded.

    It would be extremely helpful if we knew your intentions behind this and reasoning on why autosave is disabled in the first place.
     
    EnteMomo likes this.
  3. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    Yea you're right. So the Human Entities are for my hub server where the minigame NPCs for example are located. Since they don't need to be saved in the world, I have autosave off and don't save anything in the entity NBT. I have now found a solution with NBT tags, but I would actually like it better if the garbage collector was disabled for entities on the lobby server.
     
  4. Primus

    Primus Zombie Pigman

    Messages:
    749
    Best solution for this would be to spawn them on demand. Garbage collector won't dump them if you keep a reference to them in your plugin.
     
  5. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    By
    you mean to store them in a variable? Because I do so. The only thing now missing are the values which are not stored in NBT(Like my own callback functions for the interaction with the entity) and I now store these values in a seperate array and I am giving each Entity an ID which is also the key of the array. A bit difficult to explain but it works fine now. But I'm still a bit sad that I couldn't find a nicer solution.
     
  6. Primus

    Primus Zombie Pigman

    Messages:
    749
    I'm interested to see how your code looks. Especially the spawning and handling the interactions.

    I am no longer active developer for a long time, so I am unable to provide you a good enough solution from top of my head. But I am sure we can come to suitable solution if we slowly work through this.
     
  7. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    So I spawn them with this function. You can see I am storing the data which doesn't get saved after a chunk unloading in the $npcData variable which I store in an array with the key value being the entity id.
    PHP:
    public function addNPC(string $skinNameLocation $locationstring $nametag ""bool $isGeometry falsebool $turnToPlayer falseClosure $onPunch nullClosure $onClick nullClosure $onCollide nullfloat $scale 1.0bool $danceToPlayer false, array $emoteIds = [], string $skinPath ""): NPC{
        
    $nbt Human::createBaseNBT($location->asVector3(), null$location->getYaw(), $location->getPitch());
        
    $nbt->setTag(new CompoundTag("Skin", [
            
    "Name" => new StringTag("Name"$skinName),
            
    "Data" => new ByteArrayTag("Data"ManagerSystem::getAPI()->readImage(($skinPath === "" "/home/Datenbank/".($isGeometry "Geometries" "NPCSkins")."/" $skinPath).$skinName.".png")),
            
    "GeometryName" => new StringTag("GeometryName"$isGeometry "geometry.".$skinName "geometry.humanoid.custom"),
            
    "GeometryData" => new ByteArrayTag("GeometryData"str_replace("\n"""$isGeometry file_get_contents(($skinPath === "" "/home/Datenbank/Geometries/" $skinPath).$skinName.".json") : ""))
        ]));
        
    $npc = new NPC($location->getLevel(), $nbt);
        
    ManagerSystem::$npcData[$npc->getId()] = $npcData = [$npc->getId(), $nametag$turnToPlayer$onPunch$onClick$onCollide$scale$danceToPlayer$emoteIds];
        
    $npc->initNPC(...$npcData);
        
    ManagerSystem::$npcs[$npc->getId()] = $npc;
        return 
    $npc;
    }
    My NPC Object looks like this. I now store the entity id in the NBT Tag and after that load the data again when the server initializes the entity.

    PHP:
    <?php

    namespace ManagerSystem\utils;

    use 
    Closure;
    use 
    ManagerSystem\ManagerSystem;
    use 
    pocketmine\entity\Human;
    use 
    pocketmine\event\entity\EntityDamageByEntityEvent;
    use 
    pocketmine\event\entity\EntityDamageEvent;
    use 
    pocketmine\item\Item;
    use 
    pocketmine\math\Vector2;
    use 
    pocketmine\math\Vector3;
    use 
    pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket;
    use 
    pocketmine\Player;
    use function 
    array_keys;
    use function 
    atan2;
    use function 
    time;
    use const 
    M_PI;

    class 
    NPC extends Human {

        
    /** @var Closure|null */
        
    public $onPunch null$onClick null$onCollide null;

        
    /** @var bool */
        
    public $turnToPlayer false$danceToPlayer false;

        
    /** @var string[] */
        
    private $emoteIds = [];

        
    /** @var int */
        
    private $lastDanceTime 0$lastDanceIndex 0$npcId;

        public 
    $width 0$scale 1;

        protected function 
    initEntity(): void{
            
    $nbt $this->namedtag;
            
    parent::initEntity();
            
    $this->setImmobile(true);
            if(
    $nbt->hasTag("NPC_ID")){
                if(isset(
    ManagerSystem::$npcData[$nbt->getInt("NPC_ID")]))
                    
    $this->initNPC(...ManagerSystem::$npcData[$nbt->getInt("NPC_ID")]);
                else
                    
    $this->flagForDespawn();
            }
        }

        public function 
    saveNBT(): void{
            
    parent::saveNBT();
            
    $this->namedtag->setInt("NPC_ID"$this->npcId);
        }

        public function 
    initNPC(int $npcIdstring $nametag ""bool $turnToPlayer falseClosure $onPunch nullClosure $onClick nullClosure $onCollide nullfloat $scale 1.0bool $danceToPlayer false$emoteIds = []): void{
            
    $this->npcId $npcId;
            
    $this->onPunch $onPunch;
            
    $this->onClick $onClick;
            
    $this->onCollide $onCollide;
            
    $this->turnToPlayer $turnToPlayer;
            
    $this->danceToPlayer $danceToPlayer;
            
    $this->emoteIds $emoteIds;
            
    $this->setNameTag($nametag);
            
    $this->scale $scale;
            
    $this->setScale($scale);
        }

        public function 
    onCollideWithPlayer(Player $player): void{
            if(
    $this->onCollide !== null)
                (
    $this->onCollide)($player$this);
        }

        public function 
    attack(EntityDamageEvent $source): void{
            if(
    $source instanceof EntityDamageByEntityEvent){
                
    $damager $source->getDamager();
                if(
    $damager instanceof Player && $this->onPunch !== null){
                    (
    $this->onPunch)($damager$this);
                }
            }
            
    $source->setCancelled();
        }

        public function 
    onInteract(Player $playerItem $itemVector3 $clickPos): bool{
            if(
    $this->onClick !== null)
                (
    $this->onClick)($player$this);
            return 
    true;
        }

        public function 
    getWidth(): int{
            return 
    0;
        }

        public function 
    onUpdate(int $currentTick): bool{
            
    $danceViewers = [];
            foreach(
    $this->getLevel()->getNearbyEntities($this->getBoundingBox()->expandedCopy(757), $this) as $viewer){
                if(
    $viewer instanceof Player){
                    
    $danceViewers[] = $viewer;
                    if(
    $this->turnToPlayer){
                        
    $xdiff $viewer->$this->x;
                        
    $zdiff $viewer->$this->z;
                        
    $angle atan2($zdiff$xdiff);
                        
    $yaw = (($angle 180) / M_PI) - 90;
                        
    $ydiff $viewer->$this->y;
                        
    $v = new Vector2($this->x$this->z);
                        
    $dist $v->distance($viewer->x$viewer->z);
                        
    $angle atan2($dist$ydiff);
                        
    $pitch = (($angle 180) / M_PI) - 90;
                        
    $pk = new MoveActorAbsolutePacket();
                        
    $pk->position $this->getOffsetPosition($this);
                        
    $pk->xRot $pitch;
                        
    $pk->yRot $yaw;
                        
    $pk->zRot $yaw;
                        
    $pk->entityRuntimeId $this->getId();
                        
    $viewer->sendDataPacket($pk);
                    }
                }
            }
            if(
    $this->danceToPlayer){
                
    $now time();
                if(
    $this->lastDanceTime $now){
                    if(
    $this->lastDanceIndex === count($this->emoteIds))
                        
    $this->lastDanceIndex 0;
                    
    ManagerSystem::getAPI()->sendEmote($this, empty($this->emoteIds) ? array_keys(ManagerSystem::getAPI()::EMOTES)[$this->lastDanceIndex] : $this->emoteIds[$this->lastDanceIndex], $danceViewers);
                    
    $this->lastDanceIndex++;
                    
    $this->lastDanceTime $now 8;
                }
            }
            return 
    parent::onUpdate($currentTick);
        }

        public function 
    hasMovementUpdate(): bool{
            return 
    false;
        }
    }
     
  8. Primus

    Primus Zombie Pigman

    Messages:
    749
    This looks fine and would work flawlessly if it did save entities inside the chunk. However if you're determined to not use autosave, let's look for ways to spawn the entity back into level.

    This is unnecessary overhead for the server, but something like this might do the job.
    PHP:
    public function onChunkLoad(ChunkLoadEvent $event) {
       if(
    $event->isNewChunk()) return; // Let's assume we're spawning entities in existing chunks
      
       
    $chunk $event->getChunk(); 
     
       foreach(
    ManagerSystem::$npcs as $npc) {
          if((
    $npc->>> 4) === $chunk->&& ($npc->>> 4) === $chunk->z) {
             
    // 1) Check if this particular entity is not present
             // 2) Spawn Chunk::addEntity($npc) NPC::spawnToAll() ?!? Not exactly sure, but you'll figure it out
          
    }
       }
    }
    So this possibly is one of the approaches. I am really confused on why won't you leave this mechanic in hands of pocketmine api.
     
  9. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    I would leave it in the hands of the pocketmine api but as I said earlier I already have autosave enabled(after it didn't work at all with autsave disabled)

    Not sure if I undestand right what you meant by your code snippet, but I made a new Listener.
    PHP:
    public function onChunkLoad(ChunkLoadEvent $event) {
            if(
    $event->isNewChunk()) return;

            
    $chunk $event->getChunk();

            foreach(
    ManagerSystem::$npcs as $npc) {
                if((
    $npc->getX() >> 4) === $chunk->getX() && ($npc->getZ() >> 4) === $chunk->getZ()) {
                    if((
    $entity $event->getLevel()->getEntity($npc->getId())) === null)
                        
    $chunk->addEntity($npc);
                }
            }
        }
    After that I get this exception which kinda makes sense. But maybe I just didn't understand it right.
    Code:
    08:22:05 Server|CRITICAL > InvalidArgumentException: "Attempted to add a garbage closed Entity to a chunk" (EXCEPTION) in "src/pocketmine/level/format/Chunk" at line 601
    08:22:05 Server|CRITICAL > #0 plugins/ManagerSystem/src/ManagerSystem/listeners/ChunkLoadListener(19): pocketmine\level\format\Chunk->addEntity(object ManagerSystem\utils\NPC)
    08:22:05 Server|CRITICAL > #1 src/pocketmine/plugin/MethodEventExecutor(42): ManagerSystem\listeners\ChunkLoadListener->onChunkLoad(object pocketmine\event\level\ChunkLoadEvent)
    08:22:05 Server|CRITICAL > #2 src/pocketmine/plugin/RegisteredListener(80): pocketmine\plugin\MethodEventExecutor->execute(object ManagerSystem\listeners\ChunkLoadListener, object pocketmine\event\level\ChunkLoadEvent)
    08:22:05 Server|CRITICAL > #3 src/pocketmine/event/Event(88): pocketmine\plugin\RegisteredListener->callEvent(object pocketmine\event\level\ChunkLoadEvent)
     
  10. Primus

    Primus Zombie Pigman

    Messages:
    749
    Okay, so apparently you can't use the same entity object which I suspected, hence the comment I left. But basically with that snippet you now have control over loading the npcs on demand, you just need to add that part in, instead of the Chunk::addEntity option I gave :D
     
  11. EnteMomo

    EnteMomo Silverfish

    Messages:
    22
    GitHub:
    Joshiiiii
    Isn't it also loading on demand now? Because I load it when the initEntity() method is executed.

    I don't quite understand what I should do now in the listener so that the entities don't lose their properties.
    Do you mean that at that moment the entity is "deleted" and that's why I put the data from the old Entity into a newly created human entity?
     
  12. Primus

    Primus Zombie Pigman

    Messages:
    749
    What I currently understand is, that chunks are being unloaded, and entity data is not being saved, and your npc entities are being closed.

    Therefore spawning NPCs must be done persistently. My proposal was to do it whenever chunk loads, right as you put it
    Or modify NPC::isClosed() to something like this, and try re-adding the entity.
    PHP:
    /** @var NPC $this **/
    return isset(ManagerSystem::$npcs[$this->getId()]);
    As I mentioned, I am long retired developer, and this API might have changed significantly.
     
  13. ethaniccc

    ethaniccc Baby Zombie

    Messages:
    189
    GitHub:
    ethaniccc
    Try using Entity->setCanSaveWithChunk(), which should make it so that the entity will be saved when the chunk it's in is unloaded
     
  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.