• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Solved [TFS 1.3] Create playerdata table of an offline player

Sir Bugg

Veteran • Software Engineer
Joined
Sep 22, 2008
Messages
9
Reaction score
1
Using TFS 1.3 - This may be a tricky one to explain, but let's see how it goes.

I've searched the forums up and down but haven't quite found what I'm looking for. Creating a variable for playerdata returns nil unless that player is actually online.
For example, it's the playerdata table that's created when you declare a variable like this:
Lua:
local player = Player(player_id)

The only drawback is that you can only create this when the player with a matching player_id is online. Otherwise you can't use the variable becuase it's returned with nil.

Question:

Is it possible to create the same Player() data without the player being online?
If so, how is this done (without source edits) or could someone please direct me to where I might find the direction to do so?
If this is not possible, how else can this be achieved? All I could find were some posts mentioning source edits but wasn't sure if that would work or potentially cause other unknown errors. I'd like to do this within a lua script.

I'm fairly certain that what I'm wanting to do will require a database query, but that shouldn't be anything more than retrieving the player's id or name, etc.
Something like this:
Lua:
-- get offline player's Id
local dbQuery_1 = db.storeQuery("SELECT `player_id` FROM `players` WHERE `name` = "..playerName.." LIMIT 1;")
local q_Id = result.getDataInt(dbQuery_1, "player_id")
result.free(dbQuery_1)
However, I'm unable to create an instance of the player using the queried data like this: local player = Player(q_Id) unless that player is actually online.
So, how can I accomplish this if the player is not online? Is it possible to create a tempPlayer to store this player data somehow?

Does anyone have any suggestions?
Thank you.
 
Last edited:
Solution
Would you be willing/able to provide the code used to create this function? The server console throws an error and returns a nil value when attempting to call this field.
If it already exists and I'm simply using it incorrectly somehow, please provide the location where this function can be found.
Thank you.


I'm beginning to believe you're right. Although I'm able to retrieve specific data from a player through the database, I don't see any simple ways of creating a table for playerdata without that player being online.
I have not yet tried recompiling the server after making source edits, though. I was worried I may mess something up or players wouldn't be able to access the server properly or some other error might occur.
It...
This function should create a metatable of a player
Lua:
Game.getOfflinePlayer(id)

EDIT:
Nekiro is right, actually I was looking at a function used on otservbr for the hirelings system.
 
Last edited:
It's not possible in tfs 1.3 without source edits. You can only execute queries to achieve similar effect.
 
Game.getOfflinePlayer(id)
Would you be willing/able to provide the code used to create this function? The server console throws an error and returns a nil value when attempting to call this field.
If it already exists and I'm simply using it incorrectly somehow, please provide the location where this function can be found.
Thank you.

It's not possible in tfs 1.3 without source edits. You can only execute queries to achieve similar effect.
I'm beginning to believe you're right. Although I'm able to retrieve specific data from a player through the database, I don't see any simple ways of creating a table for playerdata without that player being online.
I have not yet tried recompiling the server after making source edits, though. I was worried I may mess something up or players wouldn't be able to access the server properly or some other error might occur.
It seems for now at least, I'll search the forums for a guide on how to compile tfs 1.3. You wouldn't happen to know of any user-friendly guides to suggest, would you?
If not, no worries.


Thank you for your help. :)
 
Would you be willing/able to provide the code used to create this function? The server console throws an error and returns a nil value when attempting to call this field.
If it already exists and I'm simply using it incorrectly somehow, please provide the location where this function can be found.
Thank you.


I'm beginning to believe you're right. Although I'm able to retrieve specific data from a player through the database, I don't see any simple ways of creating a table for playerdata without that player being online.
I have not yet tried recompiling the server after making source edits, though. I was worried I may mess something up or players wouldn't be able to access the server properly or some other error might occur.
It seems for now at least, I'll search the forums for a guide on how to compile tfs 1.3. You wouldn't happen to know of any user-friendly guides to suggest, would you?
If not, no worries.


Thank you for your help. :)

Here's the edit:

However for simplier purposes:

Below
C++:
registerMethod("Game", "reload", LuaScriptInterface::luaGameReload);
Add
C++:
registerMethod("Game", "getOfflinePlayer", LuaScriptInterface::luaGameGetOfflinePlayer);

Below this function
C++:
int LuaScriptInterface::luaGameReload(lua_State* L)
Add this one
C++:
int LuaScriptInterface::luaGameGetOfflinePlayer(lua_State* L)
{
    uint32_t playerId = getNumber<uint32_t>(L,1);

    Player* offlinePlayer = new Player(nullptr);
    if (!IOLoginData::loadPlayerById(offlinePlayer, playerId)) {
        delete offlinePlayer;
        lua_pushnil(L);
    } else {
        pushUserdata<Player>(L, offlinePlayer);
        setMetatable(L, -1, "Player");
    }

    return 1;
}

Now, inside the function
C++:
int LuaScriptInterface::luaPlayerSave(lua_State* L)
Below
C++:
pushBoolean(L, IOLoginData::savePlayer(player));
Add
C++:
        if (player->isOffline()) {
            delete player; //avoiding memory leak
        }
 
Solution
Here's the edit:

However for simplier purposes:

Below
C++:
registerMethod("Game", "reload", LuaScriptInterface::luaGameReload);
Add
C++:
registerMethod("Game", "getOfflinePlayer", LuaScriptInterface::luaGameGetOfflinePlayer);

Below this function
C++:
int LuaScriptInterface::luaGameReload(lua_State* L)
Add this one
C++:
int LuaScriptInterface::luaGameGetOfflinePlayer(lua_State* L)
{
    uint32_t playerId = getNumber<uint32_t>(L,1);

    Player* offlinePlayer = new Player(nullptr);
    if (!IOLoginData::loadPlayerById(offlinePlayer, playerId)) {
        delete offlinePlayer;
        lua_pushnil(L);
    } else {
        pushUserdata<Player>(L, offlinePlayer);
        setMetatable(L, -1, "Player");
    }

    return 1;
}

Now, inside the function
C++:
int LuaScriptInterface::luaPlayerSave(lua_State* L)
Below
C++:
pushBoolean(L, IOLoginData::savePlayer(player));
Add
C++:
        if (player->isOffline()) {
            delete player; //avoiding memory leak
        }

To make changes to these files would still require source edits and a recompile of the server to take effect, right?
I'll have to give this a try. Thanks, Kooda.
 
To make changes to these files would still require source edits and a recompile of the server to take effect, right?
I'll have to give this a try. Thanks, Kooda.
Yes, that's a source edit, thats the only way to get a Player() offline.
If by some reason you can't recompile your server, I would suggest using a metatable for something like OfflinePlayer, and creating individually the functions so you can call something like player:getPosition() that way
 
Yes, that's a source edit, thats the only way to get a Player() offline.
If by some reason you can't recompile your server, I would suggest using a metatable for something like OfflinePlayer, and creating individually the functions so you can call something like player:getPosition() that way
I'm not sure how to do something like that. Maybe using Metatable.methodName(param)?
Can you explain what you mean?
 
Lua:
OfflinePlayer = {
    id = -1,
    guid = -1,
    name = "",
    pos = Position(0,0,0),
}

function OfflinePlayer:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function OfflinePlayer:load(id)
    local player = db.storeQuery("SELECT * FROM players WHERE id = " .. id)
    if player then
        self.guid = result.getNumber(player, "id")
        self.id = 0x10000000 + self.guid
        self.name = result.getString(player, "name")
        self.pos = Position(result.getNumber(player, "pos_x"), result.getNumber(player, "pos_y"), result.getNumber(player, "pos_z"))
    end
    result.free(player)
    return player ~= nil
end

function OfflinePlayer:getPosition()
    return self.pos
end

function OfflinePlayer:getName()
    return self.name
end

function OfflinePlayer:getGuid()
    return self.guid
end

function OfflinePlayer:getId()
    return self.id
end

function OfflinePlayer:save()
   ...
end

With something like that, you could call something like

Lua:
function ...
    local player = OfflinePlayer:new()
    player:load(1)
    if player:getId() ~= -1 and player:getPosition() == Position(32369, 32241, 7) then
        ...
        player:save()
    end
end

However you will have to create each individual function for this metatable "OfflinePlayer", I would suggest just using the source edit instead.
 
Lua:
OfflinePlayer = {
    id = -1,
    guid = -1,
    name = "",
    pos = Position(0,0,0),
}

function OfflinePlayer:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function OfflinePlayer:load(id)
    local player = db.storeQuery("SELECT * FROM players WHERE id = " .. id)
    if player then
        self.guid = result.getNumber(player, "id")
        self.id = 0x10000000 + self.guid
        self.name = result.getString(player, "name")
        self.pos = Position(result.getNumber(player, "pos_x"), result.getNumber(player, "pos_y"), result.getNumber(player, "pos_z"))
    end
    result.free(player)
    return player ~= nil
end

function OfflinePlayer:getPosition()
    return self.pos
end

function OfflinePlayer:getName()
    return self.name
end

function OfflinePlayer:getGuid()
    return self.guid
end

function OfflinePlayer:getId()
    return self.id
end

function OfflinePlayer:save()
   ...
end

With something like that, you could call something like

Lua:
function ...
    local player = OfflinePlayer:new()
    player:load(1)
    if player:getId() ~= -1 and player:getPosition() == Position(32369, 32241, 7) then
        ...
        player:save()
    end
end

However you will have to create each individual function for this metatable "OfflinePlayer", I would suggest just using the source edit instead.
I can see a few applications for using something like this, but there's a bit more leg work involved than I expected. I wonder how you might push items to an offline player's inventory or depot or something similar using this method? Hmm...
Anyway, as you mentioned, doing the source edit would probably make things simpler in the long run.
This is actually very helpful though, so thank you for taking the time to explain.
 
I wonder how you might push items to an offline player's inventory or depot or something similar using this method?
Save guid and send to inbox/depot.

Make function like
function OfflinePlayer:sendItemToDepot(params)
get guid then send query
end

SQL:
INSERT INTO `player_depotitems`(`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES ([value-1],[value-2],[value-3],[value-4],[value-5],[value-6])
SQL:
INSERT INTO `player_inboxitems`(`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES ([value-1],[value-2],[value-3],[value-4],[value-5],[value-6])
 
Last edited:
Save guid and send to inbox/depot.

Make function like
function OfflinePlayer:sendItemToDepot(params)
get guid then send query
end

SQL:
INSERT INTO `player_depotitems`(`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES ([value-1],[value-2],[value-3],[value-4],[value-5],[value-6])
SQL:
INSERT INTO `player_inboxitems`(`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES ([value-1],[value-2],[value-3],[value-4],[value-5],[value-6])
Thanks for that, Kamilcioo.

Do you know if all all of those fields are required, or how that would affect the pushed items if any of those fields were missing values?
Also, aren't the attributes saved as a BLOB? That part would definitely be confusing to figure out, for me. Would it matter if they're custom attributes?

On a side note, the pid and sid was a little confusing to figure out. I'm pretty sure they indicate town_id and then slot_id within the container, and subsequent slot_id positions within containers inside containers, etc. But does it require you to know the pid/sid you want the item to go into or can this be automated somehow?
Sorry if that was confusing, I can try to explain better if that didn't make any sense.
 
Back
Top