• 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!

TFS 1.x Ingame Account Manager [Searching Testers] UPDATED

Evil Hero

Legacy Member
TFS Developer
Joined
Dec 12, 2007
Messages
1,262
Solutions
29
Reaction score
739
Location
Germany
Hello folks~

Today I'm here to talk about a crucial feature which TFS 1.x currently doesn't have, the "Ingame Account Manager"

I've recently been starting to work towards an Ingame Account Manager which works similiar to the old one.
This Account Manager is mostly written in lua, requires just a few source edits which aren't hard to be done.

This Account Manager doesn't work 100% like the old one, you are able to login straight with no account name and password and you'll login to the Account Manager (multiple Account Managers can be logged in, regardless of "allowClones" and "onePlayerOnlinePerAccount" in config.lua)

looks like this (note I've just opened 2 accounts to show they don't interfere with eachother, there can be lots more account managers online at the same time)
1.png
2.png

As you can see everything you write into that channel cannot be seen by yourself or by other people, it's like a local channel just between you and the server.

This is how a full dialog of an Account + Character Creation looks like:
3.png

We can see that the second client doesn't pick any messages up (they don't interfere with eachother)

Let us login with our created Account
4.png


The Account Manager cannot move / pickup / talk / etc. just like the old one.
However the Account Manager is just used to create a new account + a character, or to recover a lost account.
If you want to create / delete or change the password you can do that directly on every character who you are logged in with from your account.

Looks like this:
5.png

This is however currently in development

Development list:
  1. Create Account with Character [DONE]
  2. Recover lost Account [DONE]
  3. Create Character with already logged in Character [DONE]
  4. Change Password with already logged in Character [DONE]
  5. Change E-mail with already logged in Character [DONE]

I would really like to hear about suggestions / ideas / concepts on how to make this better and maybe easier to use.
 
Last edited:
modalwindow would be awesome
I had that idea in mind aswell, however I thought it would be better to not do it modalwindow like, so servers which use tfs 1.x as base but use an old protocol (pre 9.71) then they couldn't use it aswell.
I could probably make it optional but that would have low priority for now until all the other stuff is finished :)
 
Last edited:
Looks great!

I did something similar, but since I use OTClient i'm going to re-do it and make an in-client account manager, that you can use before you log in at all.
(I kind of hate websites, I think all information should be in the client)

Oo! You could do the PIN thing. Where you can link your account with a PIN number if you want. And if someone from a new IP Address logs in they have to enter the PIN to play on the account (helps prevent hacking in some cases) I've seen this done on some of Xagulz servers.
 
Looks great!

I did something similar, but since I use OTClient i'm going to re-do it and make an in-client account manager, that you can use before you log in at all.
(I kind of hate websites, I think all information should be in the client)

Oo! You could do the PIN thing. Where you can link your account with a PIN number if you want. And if someone from a new IP Address logs in they have to enter the PIN to play on the account (helps prevent hacking in some cases) I've seen this done on some of Xagulz servers.
I have a similiar thing at Heroland (where I use OTC aswell) I don't like the website account thing either :p
The pin thing is actually a good idea, for now I wanted to do it like if you are ingame and want to change the pw / create character / delete character that you have to provide your pw first in oder to confirm that it's you and not just someone who passes by your pc and does some shit ^^

Keep up the good work! I did something similar, but with modal windows.
Here's the thread: https://otland.net/threads/opinions-in-game-account-manager.225537/

9orY98x.gif


Make sure you add some security. There are still ways to crash servers, especially in 0.3/0.4 8.6 when it comes to in-game account managers.
Yea I've saw your thread it's pretty amazing but that was also certainly the case why I avoided modalwindow because I knew you've been working on that already, it's always better to develope in different directions then everyone just in the same :)

If you provide me the information on how they where crashable etc. then I'll take extra care of it so it'll be stable and not abuseable in any way :D
 
what happened to your project? :( that post was tl;dr xD and its locked

@this post
great! looking forward to it!

Haha, it's still on my computer somewhere, I never really got around finishing it (can't remember what made me stop, hence the thread closure).
There's still a bug when it comes to allowing clones, but it shouldn't prevent the manager from working successfully.

Yea I've saw your thread it's pretty amazing but that was also certainly the case why I avoided modalwindow because I knew you've been working on that already, it's always better to develope in different directions then everyone just in the same :)

If you provide me the information on how they where crashable etc. then I'll take extra care of it so it'll be stable and not abuseable in any way :D

I ain't working on it much anymore, feel free to do whatever you want. I had serious considerations using the message window, but what the hell, why not try modal windows? Then it started to get a little confusing with a little dashboard for everything.

Crash-wise, just know your communication between the player and the database. There's a way to put stress on the database on 0.3/0.4 servers, eventually bringing it down and disconnecting everyone. So, some kind of delay between certain tasks may be needed.
 
Last edited:
Haha, it's still on my computer somewhere, I never really got around finishing it (can't remember what made me stop, hence the thread closure).
There's still a bug when it comes to allowing clones, but it shouldn't prevent the manager from working successfully.
Yea I've stumbled upon that issue aswell but solved it pretty easily this way:
Code:
bool IOLoginData::updateOnlineStatus(uint32_t guid, bool login)
{
   std::ostringstream accmanager;
   Database* db = Database::getInstance();
   accmanager << "SELECT `id` FROM `players` WHERE `name` = 'Account Manager'";
   DBResult_ptr result = db->storeQuery(accmanager.str());
   if (!result) {
     return false;
   }
  
   if ((uint32_t)result->getDataInt("id") == guid) {
     return true;
   }

   std::ostringstream query;
   if (login) {
     query << "INSERT INTO `players_online` VALUES (" << guid << ')';
   } else {
     query << "DELETE FROM `players_online` WHERE `player_id` = " << guid;
   }
   return Database::getInstance()->executeQuery(query.str());
}

The good thing with this is, the account manager can be added after characters have been created already it doesn't matter, as it'll just pick the right guid of the database and work with it :)
 
Crash-wise, just know your communication between the player and the database. There's a way to put stress on the database on 0.3/0.4 servers, eventually bringing it down and disconnecting everyone. So, some kind of delay between certain tasks may be needed.
I only use database connection once the entire account player is confirmed so just right before the end, saving the entire account in a lua table mask before establishing database connection.
I also use async queries so the database connection cannot be flooded and therefore lose connection.
Code:
function createAccount(accName, accPassword, email, charName, charVoc, level, gender)
   table.insert(existingAccountNames, accName)
   table.insert(existingCharacterNames, charName)
   db.query("INSERT INTO `accounts`(`name`, `password`, `type`, `premdays`, `lastday`, `email`, `creation`) VALUES ('".. accName .."', '".. accPassword .."', 0, 0, 0, '".. email .."', ".. os.time() ..")")
   local pid = db.storeQuery("SELECT `id` FROM `accounts` WHERE `name` = '".. accName .."'")
   local res = 0
   local exp = level == 1 and 0 or 4200
   local lookType = gender == 0 and 136 or 128
   if pid then
     res = result.getDataInt(pid, "id")
   end
   result.free(pid)
   db.query("INSERT INTO `players`(`name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `maglevel`, `mana`, `manamax`, `manaspent`, `soul`, `town_id`, `posx`, `posy`, `posz`, `conditions`, `cap`, `sex`, `lastlogin`, `lastip`, `save`, `skull`, `skulltime`, `lastlogout`, `blessings`, `onlinetime`, `deletion`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`) VALUES ('".. charName .."',1,".. res ..",".. level ..",".. charVoc ..",185,185,".. exp ..",68,76,78,58,".. lookType.. ",0,0,40,40,0,100,1,".. startDest.x ..",".. startDest.y ..",".. startDest.z ..",0,435,".. gender ..",0,0,1,0,0,0,0,0,0,0,43200,-1,2520,10,0,10,0,10,0,10,0,10,0,10,0,10,0)")
end
I'm however not fond of my mysql skills, so if the query could be done better feel free to let me know :)

For everyone who want to watch live development on Twitch feel free to join my channel and give me live feedback! :D
 
Last edited:
I only use database connection once the entire account player is confirmed so just right before the end, saving the entire account in a lua table mask before establishing database connection.
I also use async queries so the database connection cannot be flooded and therefore lose connection.
Code:
function createAccount(accName, accPassword, email, charName, charVoc, level, gender)
   db.asyncQuery("INSERT INTO `accounts`(`name`, `password`, `type`, `premdays`, `lastday`, `email`, `creation`) VALUES ('".. accName .."', '".. accPassword .."', 0, 0, 0, '".. email .."', ".. os.time() ..")")
   local pid = db.asyncStoreQuery("SELECT `id` FROM `accounts` WHERE `name` = '".. accName .."'")
   local res = 0
   local exp = level == 1 and 0 or 4200
   local lookType = gender == 0 and 136 or 128
   if pid then
     res = result.getDataInt(pid, "id")
     result.free(pid)
   end
   db.asyncQuery("INSERT INTO `players`(`name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `maglevel`, `mana`, `manamax`, `manaspent`, `soul`, `town_id`, `posx`, `posy`, `posz`, `conditions`, `cap`, `sex`, `lastlogin`, `lastip`, `save`, `skull`, `skulltime`, `lastlogout`, `blessings`, `onlinetime`, `deletion`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`) VALUES ('".. charName .."',1,".. res ..",".. level ..",".. charVoc ..",185,185,".. exp ..",68,76,78,58,".. lookType.. ",0,0,40,40,0,100,1,".. startDest.x ..",".. startDest.y ..",".. startDest.z ..",0,435,".. gender ..",0,0,1,0,0,0,0,0,0,0,43200,-1,2520,10,0,10,0,10,0,10,0,10,0,10,0,10,0)")
end
I'm however not fond of my mysql skills, so if the query could be done better feel free to let me know :)

For everyone who want to watch live development on Twitch feel free to join my channel and give me live feedback! :D

Okay this is good, what about checking to see if an account/player already exists? Still need to do database queries there.
 
Okay this is good, what about checking to see if an account/player already exists? Still need to do database queries there.
Yea I have to use a query to check that ofc.
looks like this:
Code:
local accName = db.asyncStoreQuery("SELECT `name` FROM `accounts` WHERE `name`= '".. message .."'")
         if not accName then
           accounts[pId].accName = message
           player:sendTextToClient("Account Name accepted, please chose your Password.", "Account Manager", 9, TALKTYPE_CHANNEL_R1)
           state[pId] = 3
           result.free(accName)
           return false
         else
           player:sendTextToClient("This Account Name already exists, please chose another.", "Account Manager", 9, TALKTYPE_CHANNEL_R1)
         end
         result.free(accName)

EDIT!

I could probably do it a different way and load all existing account names / character names at startup into a table and then just fetch the names from there, to avoid using everytime a query, instead just do this once at startup and then just use the table.
 
Last edited:
I've decided to fetch all account / character names into a table once server starts, to avoid that we have to do queries everytime we request to look if a certain name already exists.
looks like this now:

onStartup:
Code:
-- Fetch all already existing Account Names from the database for the Account Manager
   local resultId = db.storeQuery("SELECT `name` FROM `accounts`")
   if resultId then
     repeat
       local name = result.getDataString(resultId, "name")
       table.insert(existingAccountNames, name)
     until not result.next(resultId)
     result.free(resultId)
   end
   
   -- Fetch all already existing Character Names from the database for the Account Manager
   local resultId = db.storeQuery("SELECT `name` FROM `players`")
   if resultId then
     repeat
       local name = result.getDataString(resultId, "name")
       table.insert(existingCharacterNames, name)
     until not result.next(resultId)
     result.free(resultId)
   end
Code:
if not isInArray(existingAccountNames, message) then
    accounts[pId].accName = message
    player:sendTextToClient("Account Name accepted, please chose your Password.", "Account Manager", 9, TALKTYPE_CHANNEL_R1)
    state[pId] = 3
    return false
else
    player:sendTextToClient("This Account Name already exists, please chose another.", "Account Manager", 9, TALKTYPE_CHANNEL_R1)
end
Code:
function createAccount(accName, accPassword, email, charName, charVoc, level, gender)
   table.insert(existingAccountNames, accName)
   table.insert(existingCharacterNames, charName)
   db.query("INSERT INTO `accounts`(`name`, `password`, `type`, `premdays`, `lastday`, `email`, `creation`) VALUES ('".. accName .."', '".. accPassword .."', 0, 0, 0, '".. email .."', ".. os.time() ..")")
   local pid = db.storeQuery("SELECT `id` FROM `accounts` WHERE `name` = '".. accName .."'")
   local res = 0
   local exp = level == 1 and 0 or 4200
   local lookType = gender == 0 and 136 or 128
   if pid then
     res = result.getDataInt(pid, "id")
   end
   result.free(pid)
   db.query("INSERT INTO `players`(`name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `maglevel`, `mana`, `manamax`, `manaspent`, `soul`, `town_id`, `posx`, `posy`, `posz`, `conditions`, `cap`, `sex`, `lastlogin`, `lastip`, `save`, `skull`, `skulltime`, `lastlogout`, `blessings`, `onlinetime`, `deletion`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`) VALUES ('".. charName .."',1,".. res ..",".. level ..",".. charVoc ..",185,185,".. exp ..",68,76,78,58,".. lookType.. ",0,0,40,40,0,100,1,".. startDest.x ..",".. startDest.y ..",".. startDest.z ..",0,435,".. gender ..",0,0,1,0,0,0,0,0,0,0,43200,-1,2520,10,0,10,0,10,0,10,0,10,0,10,0,10,0)")
end
I think this method is a lot better as we don't have to stress out the database connection if we need info, as we pre fetched it at the startup already :)
 
that is getting great! the only thing that I really miss is the modal window thing, as I don't think that anyone (except a few, very few users) are using tfs 1.x downgraded to a version lower than 9.7x
 
Looks great as always Evil Hero.
Looking forward to test it out, I will sleep on it and see if I can come up with some improvements, glad to hear from you again!

Kind Regards,
Eldin.
 
that is getting great! the only thing that I really miss is the modal window thing, as I don't think that anyone (except a few, very few users) are using tfs 1.x downgraded to a version lower than 9.7x
I'll do modalwindow once the entire core is done, so I can make it optional for those who want to use it :)
Looks great as always Evil Hero.
Looking forward to test it out, I will sleep on it and see if I can come up with some improvements, glad to hear from you again!

Kind Regards,
Eldin.
Feel free to do that, I'm awaiting your suggestions :D

I've finished the "recover lost account" part
6.png

If you however insert a wrong account name or email then you'll be kicked after 5 seconds to avoid bots flooding queries or even generate account names or emails by time.
7.png

I basicly compare the requested account name with the already existing ones in the table, if the account name appears to be true, then I do the query to get the email, so I only execute the query if the actual account name is valid :)
 
only account name and e-mail is a weak combination, what about adding recovery key as well?
 
only account name and e-mail is a weak combination, what about adding recovery key as well?
Had that in mind aswell but that would also require database edits then, as the current database doesn't support that aslong as we don't add a table therefore :p
 
I've added the Recovery Key part

looks like this now (tell me if it's odd and could be done better)
8.png

9.png

I'm checking the recovery key before I check the email because every recovery key has a certain valid sequence in it, so if that sequence is not valid then the key will not be valid aswell.
Only if the sequence of the key is valid it'll retrieve the email and recovery key from the database to make the final check :)
 
This is how it looks like if you want to create a character of already logged in character for that account.
10.png

It'll do the regular checks and look if the name is already taken and stuff but we only need to do the query for the character creation itself and not more :)
11.png
 
Back
Top