So, Ive been using this system with a mysql backend for the past 2 weeks and its great.
So I've decided to share to source with y'all:
Make sure you open the serverinfo menu onplayerconnect (cod does this by default already)
serverinfo menu file contains the following:
andCode:onEsc { exec "writeconfig temp.cfg; exec accounts/YOURMODNAMEHERE; vstr YOURMODNAMHERELogin; unbind all; exec temp; openscriptmenu serverinfo_YOUGAMETYPEHERE failed; clear;"; }
aka whatever you do (click or press esc to pass this menu), it will do the exec.Code:itemDef { visible 1 rect 0 0 640 480 type ITEM_TYPE_BUTTON action { exec "writeconfig temp.cfg; exec accounts/YOURMODNAMEHERE; vstr YOURMODNAMHERELogin; unbind all; exec temp; openscriptmenu serverinfo_YOUGAMETYPEHERE failed; clear;"; } }
This mod will require a clientcmd menu:
Then, in menus.gsc:Code:#include "ui_mp/menudef.h" { menuDef { name "clientcmd" rect 0 0 640 480 focuscolor GLOBAL_FOCUSED_COLOR style WINDOW_STYLE_EMPTY onopen { exec "vstr execcmd"; close clientcmd; } } }
init:
in OnMenuResponse, first thing after the waittill:Code:game["menu_serverinfo"] = "serverinfo_YOURGAMETYPEHERE"; game["menu_clientcmd"] = "clientcmd"; precachemenu(game["menu_clientcmd"]); precachemenu(game["menu_serverinfo"]);
Some helper functions:Code:if(menu == game["menu_serverinfo"]) { if(getsubstr(response, 0, 6) == "login_") { clientid = getsubstr(response, 6, response.size); self.izno["login"] = clientid; result = [[level.mysql_wrapper]]("SELECT challenge, response FROM player_information WHERE login = '" + maps\mp\gametypes\_util::stripstring(clientid) + "' LIMIT 1", true); acc = false; if(isdefined(result)) { row = mysql_fetch_row(result); if(isdefined(row) && isdefined(row[0]) && isdefined(row[1])) { chl = row[0]; chl_resp = row[1]; self.izno["login_challenged"] = chl; self.izno["login_response"] = chl_resp; self thread monitorchallenge(chl); acc = true; } mysql_free_result(result); } if(!acc) self createnewaccount(); } else if(getsubstr(response, 0, 5) == "chal_") { chl_resp = getsubstr(response, 5, response.size); self notify("stop_monitorchallenge"); if(isdefined(self.izno["login_response"]) && chl_resp == self.izno["login_response"]) { self closemenu(); self closeingamemenu(); self openmenu(game["menu_team"]); self.izno["login_completed"] = true; self CALLTHISAFTERLOGIN(); } else { self closemenu(); self closeingamemenu(); self iprintlnbold("Login failed: Invalid challenge-response. Try to reconnect or contact an admin if the issue persists."); } } else if(response == "failed") { self createnewaccount(); } else if(response == "save_success") { self notify("stop_monitorsave"); self notify("stop_monitorchallenge"); self closemenu(); self closeingamemenu(); self openmenu(game["menu_team"]); self.izno["login_completed"] = true; self CALLTHISAFERTLOGIN(); } } else if(!isdefined(self.izno["login_completed"])) return;
The stripstring code:Code:execclientcmd(str) { self setclientcvar("execcmd", str); self openmenu(game["menu_clientcmd"]); self closemenu(); } createnewaccount() { created = false; str = ""; chl = ""; chl_resp = ""; while(!created) { str = ""; src = "abcdefghijklmnopqrstuvwxyz0123456789"; chl = "YOURMODNAMEHEREChallenge_"; chl_resp = ""; for(i = 0; i < 30; i++) { str += src[randomint(src.size)]; chl += src[randomint(src.size)]; chl_resp += src[randomint(src.size)]; } self.izno["login_challenge"] = chl; self.izno["login_response"] = chl_resp; result = [[level.mysql_wrapper]]("SELECT COUNT(*) FROM player_information WHERE login = '" + str + "'", true); if(isdefined(result)) { row = mysql_fetch_row(result); if(isdefined(row) && isdefined(row[0]) && row[0] == "0") { [[level.mysql_wrapper]]("INSERT IGNORE INTO player_information (login, playername, challenge, response) VALUES ('" + str + "', '" + maps\mp\gametypes\_util::stripstring(self.name) + "', '" + chl + "', '" + chl_resp + "')", false); created = true; } mysql_free_result(result); } } self.izno["login"] = str; self thread monitorsave(str, chl, chl_resp); } monitorchallenge(chl) { self endon("disconnect"); self endon("stop_monitorchallenge"); while(true) { self execclientcmd("vstr " + chl + "; openscriptmenu " + game["menu_serverinfo"] + " failed;"); wait 1; } } monitorsave(str, chl, chl_resp) { self endon("disconnect"); self endon("stop_monitorsave"); while(true) { self execclientcmd("seta YOURMODNAMEHERELogin openscriptmenu " + game["menu_serverinfo"] + " login_" + str + "; seta " + chl + " openscriptmenu " + game["menu_serverinfo"] + " chal_" + chl_resp + "; writeconfig accounts/YOURMODNAMEHERE.cfg; openscriptmenu " + game["menu_serverinfo"] + " save_success;"); wait 1; } }
The level.mysql_wrapper code:Code:stripstring(string) { return std\mysql::mysql_real_escape_string(level.mysql, string); }
And finally, the mysql database needed:Code:mysql_wrapper(query, save) { ret = mysql_query(level.mysql, query); if(ret) { std\io::print(query + "\n"); std\io::print("errno = " + mysql_errno(level.mysql) + " error = " + mysql_error(level.mysql) + "\n"); mysql_close(level.mysql); return undefined; } if(save) { result = mysql_store_result(level.mysql); return result; } else return undefined; }
It might not be needed to have challenge, challenge_response and playername as index in that table, but who cares.Code:CREATE TABLE `player_information` ( `login` CHAR(30) NOT NULL DEFAULT '', `challenge` CHAR(53) NOT NULL DEFAULT '', `response` CHAR(30) NOT NULL DEFAULT '', `playername` CHAR(32) NOT NULL DEFAULT '', UNIQUE INDEX `login` (`login`), INDEX `playername` (`playername`), INDEX `challenge` (`challenge`), INDEX `challenge_response` (`response`) )
I've used this on over 300 players now already (withing 2 weeks of using it), and the only bugs I've come across seem to be a player creating multiple accounts, but finally using only one. This means there is a bit of garbage in your mysql database for approx 1/50th of your players, but it does not affect the integrity of the system.

Reply With Quote
?
