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

[SOLVED] Tracking specific entities after server restarts

Discussion in 'Development' started by Redux, Jan 25, 2017.

  1. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    For the past few day's I've been working on a private remake of the Citizens Spigot plugin (just for practice) but for PMMP but have run into a bit of a snag.

    So far I've been keeping track of the entities by their entity IDs in a json file, which then gets turned into an array when the plugin is enabled. Whenever an NPC is removed or created the array is updated and then saved back to the json file, using the entity IDs. However when the server is turned off and then back on, the entity IDs seem to change. I create an NPC and everything worked as expected. I had an entity ID of 1, and it had an ID of 2. But upon a server restart the NPC now has an ID of 1 and when I join I now have an ID of 2. This creates a conflict with how I've been keeping track of the entities and then breaks the rest of the code.

    I had the idea of tracking the entities UUID instead of their entity ID but I'd have to loop through each entity and check it's type and UUID, which can put strain on the server.

    This is my current code for spawning an NPC/adding it to the json file:
    Code:
    public function spawnNPC($player, $name) {
    
        $pos = new Location($player->x,$player->y,$player->z,$player->yaw,$player->pitch);
    
        $nbt = new CompoundTag("", [
                    new ListTag("Pos", [
                        new DoubleTag(0, $pos->x),
                        new DoubleTag(1, $pos->y),
                        new DoubleTag(2, $pos->z)
                    ]),
                    new ListTag("Motion", [
                        new DoubleTag(0, 0.0),
                        new DoubleTag(1, 0.0),
                        new DoubleTag(2, 0.0)
                    ]),
                    new ListTag("Rotation", [
                        new FloatTag(0, $pos->yaw),
                        new FloatTag(1, $pos->pitch)
                    ]),
                    new StringTag("NameTag", $name),
                ]);
        $nbt->Pos->setTagType(NBT::TAG_Double);
        $nbt->Motion->setTagType(NBT::TAG_Double);
        $nbt->Rotation->setTagType(NBT::TAG_Float);
    
        $path = './plugins/Citizens/skins/default.png'; //Steve skin
        if (!file_exists($path) && !is_dir($path)) {
            return false;
        }
        $img = imagecreatefrompng($path);
        $bytes = '';
        $l = (int)getimagesize($path)[1];
    
        for ($y = 0; $y < $l; $y++) {
            for ($x = 0; $x < 64; $x++) {
                $rgba = imagecolorat($img, $x, $y);
                $a = ((~((int)($rgba >> 24))) << 1) & 0xff;
                $r = ($rgba >> 16) & 0xff;
                $g = ($rgba >> 8) & 0xff;
                $b = $rgba & 0xff;
                $bytes .= chr($r).chr($g).chr($b).chr($a);
            }
        }
    
        imagedestroy($img);
    
        $human = Entity::createEntity("Human", $player->chunk, $nbt);
        $human->setSkin($bytes, 'Standard_Custom');
        $human->setNameTagVisible(true);
        $human->setNameTagAlwaysVisible(true);
    
        $entity_id = $human->getId();
        $entity_uuid = (string)$player->getLevel()->getEntity($entity_id)->getUniqueId();
    
        $this->npcs[$entity_id] = array("entityId"=>$entity_id,
                                        "uuid"=>$entity_uuid,
                                        "name"=>$name,
                                        "pos"=>$pos,
                                        "nbt"=>$nbt
                                        );
    
        $json = fopen("./plugins/Citizens/npcs/_all.json", "wb");
        fwrite($json, json_encode($this->npcs));
        fclose($json);
    
        $human->spawnToAll();
    }
    

    I am still fairly new to PMMP plugin development and have not fully explored all the corners of the source, so I'm sure there's a much better way to handle entity tracking in the API. If someone could point me in the right direction that would be great.
     
  2. Jack Noordhuis

    Jack Noordhuis Zombie Pigman Poggit Reviewer

    Messages:
    618
    GitHub:
    JackNoordhuis
    The EntityID is a unique number assigned to every entity that is tracked by the server when it is spawned. This means it assigns a new ID to every entity after a restart (and reload? not sure lol).

    The EntityID is assigned to an entity so the server can send packets with reference to the correct entity. Movement is a good example of this, if there was no way to track an individual entity then multiplayer just wouldn't work.

    You need to assign a unique ID or use an existing one that is saved with the entity (like the UUID on Human entities) to be able to track it between restarts/chunk unloads. When I work with entities I usually extend an existing entity class from PocketMine and add my own data there or save my data into the entities existing NBT data.

    When I say EntityID, I'm making reference to the runtime ID, not the NetworkID.
     
    Last edited: Jan 25, 2017
  3. robske_110 (Tim)

    robske_110 (Tim) Wither Skeleton Poggit Reviewer

    Messages:
    1,342
    GitHub:
    robske110
    Small addition:
    https://github.com/pmmp/PocketMine-MP/blob/master/src/pocketmine/Server.php#L1838-L1840
    On reload they shouldn't change. The levels are just saved.
     
  4. Jack Noordhuis

    Jack Noordhuis Zombie Pigman Poggit Reviewer

    Messages:
    618
    GitHub:
    JackNoordhuis
  5. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    I would love to use the entity UUID since I am using the Human entity, but from what I can tell I can only "get" the entity (via $level->getEntity(); ) by using it's EntityID. So I don't see how I'd be able to grab the entity by using anything other than its EntityID.
     
  6. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    Any solution for this? Been digging through the source but it seems to always come back to the entity ID as the only way to "get" the specific entity (via getEntity(); ) and it's already been said multiple times that the EntityID changes between restarts, so that's not really an option.

    I need to be able to track and work with the entity even after restarts to do things like move it or change it's skin after it's spawned. The only thing I can think of would be to store the entity UUID, which is made when an NPC is generated, and store it somewhere. Then loop through all the entities when the plugin loads and compare each entity UUID to the stored ones. But that's also not much of an option if the server ends up having thousands of entities.
     
  7. robske_110 (Tim)

    robske_110 (Tim) Wither Skeleton Poggit Reviewer

    Messages:
    1,342
    GitHub:
    robske110
    Turns out this does't seem to be possible without wasting a lot of time iterating through entities
    How about saving the attributes on stop, despawning the entity and respawning the entity on start?
     
  8. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    Yeah that's probably the best solution, I'll give this a whirl. Now I just need to figure out how to handle all the ways the server can stop from within the plugin. onDisable() works fine for normal shutdowns and restarts, but it doesn't seem to fire when you force-close the server or the server crashes.
     
  9. robske_110 (Tim)

    robske_110 (Tim) Wither Skeleton Poggit Reviewer

    Messages:
    1,342
    GitHub:
    robske110
  10. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    I ran a quick test around 10 minutes ago and it doesn't seem to fire correctly. In my `onDisable` function I had it create a blank json file (so that I could physically see if the code ran) and it only worked when using /stop. It did not work correctly when the server crashes or I force close the server.
    Code:
    public function onDisable() {
            $json = fopen("./test.json", "wb");
            fwrite($json, json_encode(array()));
            fclose($json);
    }
    
    This snippet did not run when the server was force-closed.
     
  11. robske_110 (Tim)

    robske_110 (Tim) Wither Skeleton Poggit Reviewer

    Messages:
    1,342
    GitHub:
    robske110
    makes non sense. If the server was properly force closed it will have called that. Why? because even a normal shutdown just calls that function with one difference: it waits for the ongoing tick to finish. many do not know that.
     
  12. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    I agree it doesn't make sense as I fully expected it to work. However, it did not.
     
  13. Redux

    Redux Spider Jockey

    Messages:
    49
    GitHub:
    reduxredstone
    Going to mark this as "solved" since my original question has been answered. Thanks for the help guys! Got it all working as it should be.
     
  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.