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: Spoiler: php 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.
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. Spoiler When I say EntityID, I'm making reference to the runtime ID, not the NetworkID.
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.
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.
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.
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?
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.
https://github.com/pmmp/PocketMine-MP/blob/master/src/pocketmine/Server.php#L1886 plugins are properly disabled on forceShutdown
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. Spoiler: onDisable 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.
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.
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.