Map full of abandoned Thralls, any way to identify owners?

My private server has no decay for followers or buildings.

I can track down and remove abandoned player structures by checking the game.db, so cleaning those up manually isn’t a big deal (not many people play on my server).

But… the map is still full of thralls and pets from players who left ages ago.

I have two questions:

  1. As an admin (with no mods installed), is there any way to identify which clan or player a thrall/pet belongs to when I find them in-game?
  2. Is it possible to run a query on the game.db to list all thralls/pets on the server, along with their owner’s name/clan and their exact map coordinates?

Thanks in advance!

Would temporarily turning on thrall decay in the ‘Server Settings’ be easier.
I can’t remember off hand what the “Thrall Decay Time” is set to, but you could adjust it as needed to determine how long it takes for the abandoned thralls to disappear.

This works on my single-player database:

select ap.id, ap.class, ap.x, ap.y, ap.z, o.owner_name
from
    actor_position ap
    inner join (
        select p.object_id,
            (hex(substring(p.value, length(p.value), 1))
            || hex(substring(p.value, length(p.value) - 1, 1))
            || hex(substring(p.value, length(p.value) - 2, 1))
            || hex(substring(p.value, length(p.value) - 3, 1))
            || hex(substring(p.value, length(p.value) - 4, 1))
            || hex(substring(p.value, length(p.value) - 5, 1))
            || hex(substring(p.value, length(p.value) - 6, 1))
            || hex(substring(p.value, length(p.value) - 7, 1))) as owner_hex
        from properties p
        where p.name = "BP_ThrallComponent_C.OwnerUniqueID"
    ) as op on ap.id = op.object_id
    inner join (
        select printf("%016x", id) as owner_hex, "char: " || char_name as owner_name
        from characters
        union all
        select printf("%016x", guildid) as owner_hex, "clan: " || name as owner_name
        from guilds
    ) as o on op.owner_hex = o.owner_hex

Let me know if it worked for you :slight_smile:

Thanks, @The_Hawke ! I had actually considered that solution before.
The thing is, there are players who started on the server with me, and I’ve been keeping their structures and followers around for years. That’s why I really need a way to remove only specific followers from specific players

Thanks, @CodeMage ! It worked really well! Huge thanks! :raising_hands:
The query didn’t list every single thrall/pet on the server, but it did pull up some of them, and it gave super precise results, including location and owner info. I had no idea where to even start with a query like that!

I’ll dig into it and see what changes I can make to get a full list of all thralls/pets. But your help has already done about 95% of the work. Thank you so much!

Yeah, I only had my small single-player database to test it on, so I probably missed a lot of stuff that just doesn’t show up when there are only 3 thralls and 2 pets to work with :smiley:

Glad it worked reasonably well. The goriest part was figuring out how to extract the owner ID from the value blob in the properties table and then do a join on it, since SQL has no out-of-the-box way to do little-endian decoding of integers from blob bytes or to convert a hexadecimal string representation into an integer.

Come to think of it, I didn’t actually test whether it worked for clans, only for players. Did any of the query results have clan: in the owner_name column? If they didn’t, that might be (part of) the problem.

We have been using pretty reliably this one from Admins United discord:

create table if not exists ListOfThralls (id bigint unique null, class text null, name text null, hexId text null, x text null, y text null, z text null, ownerId bigint null, owningPlayer text null, owningClan text null, playerLastOnline text null, TeleportLocation text null);
insert or ignore into ListOfThralls (id) select distinct object_id from properties where name like 'BP_Thrall%' and name like '%OwnerUniqueID';
update ListOfThralls as l set class = (select substr(substr((value),13),0,length(substr((value),13))-8) from properties p where object_id = id and p.name like '%ThrallInfo');
update ListOfThralls as l set name = (select substr(substr((value),22),0,length(substr((value),22))) from properties p where object_id = id and (p.name like '%ThrallName' or p.name like '%PetName'));
update ListOfThralls as l set hexId = (select substr(hex(value),-12,2) || substr(hex(value),-14,2) || substr(hex(value),-16,2) as hexId from properties p where p.object_id = l.id and p.name like '%OwnerUniqueID%' limit 1);
update ListOfThralls as l set x = (select x from actor_position a where a.id  = l.id);
update ListOfThralls as l set y = (select y from actor_position a where a.id  = l.id);
update ListOfThralls as l set z = (select z from actor_position a where a.id  = l.id);
update ListOfThralls as l set ownerId = (
        WITH RECURSIVE
            unhex(str, val, weight) AS (
                select l.hexId, 0, 1
                UNION ALL
                SELECT 
                  substr(str, 1, length(str) - 1),
                  val + (instr('0123456789ABCDEF', substr(str, length(str), 1)) - 1) * weight,
                  weight * 16
                FROM unhex WHERE length(str) > 0
                )
            SELECT val FROM unhex order by weight desc limit 1
            );
update ListOfThralls as l set owningPlayer = (select char_name from characters c where c.id = l.ownerId);
update ListOfThralls as l set owningClan = (select name from guilds g where g.guildId = l.ownerId);
update ListOfThralls as l set playerLastOnline = (select datetime(c.lastTimeOnline, 'unixepoch') as playerLastOnline from characters c where c.id = l.ownerId 
union all 
select datetime(c.lastTimeOnline, 'unixepoch') as clanLastOnline from characters c where c.guild = l.ownerId order by clanLastOnline desc limit 1);
select id as ThrallId, class as ThrallType, name, owningPlayer, owningClan, playerLastOnline, 'TeleportPlayerExact ' || x || ' ' || y || ' ' || z as TeleportLocation from ListOfThralls order by playerLastOnline asc, owningPlayer asc, owningClan asc;

--drop table ListOfThralls

Oh, wow! That’s a lot nicer, not haphazardly cobbled together like mine, but properly thought out and tested. :smiley:

Making the hexId take only the lowest 24 bits of the ID is a nice optimization and the recursive conversion from hexId to ownerId is a nice trick.

I wonder if using a partial match for the properties instead of the exact comparison with BP_ThrallComponent_C.OwnerUniqueID is what makes the difference.

I’m bookmarking this for the future. Thanks for sharing it! :heart:

For thralls it should be just the entertainer missing I suppose.

Actually, the query only returned owner_name values for clans. There were no owner_name results linked to individual characters.

Thanks, @Aisling . I tested it on my game.db and it worked perfectly! This is gonna help a lot. Thanks!