PDA

View Full Version : Saving a variable client-side (persistently)



IzNoGoD
30th December 2013, 02:02
Hey all
Just made a proof-of-concept clientside storage thingy that stores stuff even when cod2 is /quit.

Ill release it next week or so

RobsoN
30th December 2013, 02:15
Amazing ! Well done. Can't wait for release.

P.s does it require libcod?

IzNoGoD
30th December 2013, 02:17
Nope, so far its all just stock cod2 functions :)

php
30th December 2013, 05:38
Confirmed, nice find izzo

serthy
30th December 2013, 10:33
is its done with the client vars/writeconfig/autoexec/openscriptmenu combination? also looking forward to it

IzNoGoD
30th December 2013, 12:13
Currently resolving security concerns when a rogue server admin of another server copies the fs_game of the target, hosts a serv and lets a high-ranking player of that other serv join his server.

I think I blocked that pretty nicely with a theoretical 310 bit security :)

IzNoGoD
31st December 2013, 12:26
Read below. Still not secure.

Ok, release time

It basically works like this:



savestuff()
{
self execclientcmd("seta izno_msg IzNoGoD is super awesome!; writeconfig test.cfg; clear;");
}

loadstuff()
{
self execclientcmd("writeconfig temp.cfg; exec test; unbind all; exec temp; clear;");
}

Using this technique allows you to remotely save a username and password.

The problem is, once someone knows these cvars (and they are not exactly a secret), they can set up a dummy server and steal these usernames/passwords from users, then abuse this.

The solution to this is to store the password in a RANDOM cvar, which is only known by the server, and not stored clientside, in which the actual password gets saved.

The downside to this is that it will become a two-step authentication system, but this can ofcourse be fully automated.

An implementation of said method:



init()
{
game["menu_clientcmd"] = "clientcmd";
precacheMenu(game["menu_clientcmd"]);
init_stattracking();
init_ranks();
thread waitforconnect();
}

waitforconnect()
{
while(true)
{
level waittill("connecting", player);
player thread onconnect();
}
}

onconnect()
{
self.stats = [];
for(i = 0; i < level.stats.size; i++)
self.stats[level.stats[i]] = 0;
}

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";
for(i = 0; i < 30; i++)
str += src[randomint(src.size)];
chl = "izno_challenge_";
for(i = 0; i < 30; i++)
chl += src[randomint(src.size)];
chl_resp = "";
for(i = 0; i < 30; i++)
chl_resp += src[randomint(src.size)];
self.pers["izno_login_challenge"] = chl;
self.pers["izno_login_response"] = chl_resp;
fid = openfile("player_" + str + ".txt", "read");
if(fid == -1)
{
fid = openfile("player_" + str + ".txt", "write");
closefile(fid);
fid = openfile("player_" + str + ".txt", "append");
fprintln(fid, chl + "," + chl_resp);
created = true;
closefile(fid);
}
}
self.pers["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_clientcmd"] + " failed;");
wait 1;
}
}

getstat(stat)
{
if(!isdefined(stat))
return;
if(!isdefined(self) || !isplayer(self))
return;
if(!isdefined(self.stats[stat]))
return 0;
else
return self.stats[stat];
}

addstat(stat, amount)
{
if(!isdefined(stat) || !isdefined(amount))
return;
if(!isdefined(self) || !isplayer(self))
return;
if(!isdefined(self.stats[stat]))
self.stats[stat] = 0;
self.stats[stat] += amount;
switch(stat)
{
case "xp":
self checkrank();
break;
}
}

updatestats()
{
file = self.pers["izno_login"];
if(!isdefined(file) || !isdefined(self.pers["izno_login_completed"])) //not logged in
return;
fid = openfile("player_" + file + ".txt", "write");
closefile(fid);
fid = openfile("player_" + file + ".txt", "append");
fprintln(fid, self.pers["izno_login_challenge"] + "," + self.pers["izno_login_response"]);
for(i = 0; i < level.stats.size; i++)
{
if(!isdefined(self.stats[level.stats[i]]))
self.stats[level.stats[i]] = 0;
fprintln(fid, "\n" + level.stats[i] + "," + self.stats[level.stats[i]]);
}
closefile(fid);
}

loadstats()
{
file = self.pers["izno_login"];
if(isdefined(file) && isdefined(self.pers["izno_login_completed"])) //logged in
{
fid = openfile("player_" + file + ".txt", "read");
if(fid == -1)
{
self updatestats();
fid = openfile("player_" + file + ".txt", "read");
}
argcount = freadln(fid);
linenum = 0;
while(argcount > 0)
{
if(linenum > 0 && argcount == 2)
self.stats[fgetarg(fid, 0)] = int(fgetarg(fid, 1));
argcount = freadln(fid);
linenum++;
}
closefile(fid);
}
self checkrank();
self thread counter();
}

counter()
{
self endon("disconnect");
counter = 0;
while(true)
{

while(counter < 60)
{
if(isdefined(self.sessionstate) && self.sessionstate == "playing")
counter++;
wait 1;
}
counter = 0;
self addstat("time_played", 1);
self updatestats();
}
}

init_ranks()
{
level.ranks = [];
fid = openfile("config_ranks.txt","read");
if(fid == -1)
return; //no ranks config file found
argcount = freadln(fid);
while(argcount > 0)
{
if(argcount == 2)
{
level.ranks[level.ranks.size] = spawnstruct();
level.ranks[level.ranks.size-1].startxp = int(fgetarg(fid,0));
level.ranks[level.ranks.size-1].name = fgetarg(fid,1);
}
argcount = freadln(fid);
}
closefile(fid);
}

init_stattracking()
{
level.stats = [];
level.stats[level.stats.size] = "kills";
level.stats[level.stats.size] = "assists";
level.stats[level.stats.size] = "deaths";
level.stats[level.stats.size] = "headshots";
level.stats[level.stats.size] = "money";
level.stats[level.stats.size] = "time_played";
level.stats[level.stats.size] = "rank";
level.stats[level.stats.size] = "xp";
level.stats[level.stats.size] = "meleekills";
level.stats[level.stats.size] = "longest_killstreak";
level.stats[level.stats.size] = "teamkills";
}

checkrank()
{
oldrank = self.stats["rank"];
newrank = 0;
for(i = 0; i < level.ranks.size; i++)
{
if(self.stats["xp"] >= level.ranks[i].startxp)
newrank = i;
}
self.stats["rank"] = newrank;
if(oldrank < newrank)
self promoted();
}

promoted()
{
self iprintlnbold("You have been promoted to " + level.ranks[self.stats["rank"]].name);
self updatestats();
}

monitorsave(str, chl, chl_resp)
{
self endon("disconnect");
self endon("stop_monitorsave");
while(true)
{
self execclientcmd("seta izno_loginsystem openscriptmenu " + game["menu_clientcmd"] + " login_" + str + "; seta " + chl + " openscriptmenu " + game["menu_clientcmd"] + " chal_" + chl_resp + "; writeconfig izno_login.cfg; openscriptmenu " + game["menu_clientcmd"] + " save_success;");
wait 1;
}
}

onmenuresponse(response)
{
if(getsubstr(response, 0, 6) == "login_")
{
clientid = getsubstr(response, 6, response.size);
self.pers["izno_login"] = clientid;
fid = openfile("player_" + self.pers["izno_login"] + ".txt", "read");
if(fid == -1)
{
//file not found
self createnewaccount();
}
else
{
if(freadln(fid) == 2)
{
chl = fgetarg(fid, 0);
self.pers["izno_login_response"] = fgetarg(fid, 1);
self.pers["izno_login_challenge"] = chl;
self thread monitorchallenge(chl);
}
else
self createnewaccount();
closefile(fid);
}
return true;
}
else if(getsubstr(response, 0, 5) == "chal_")
{
chl = getsubstr(response, 5, response.size);
self notify("stop_monitorchallenge");
if(isdefined(self.pers["izno_login_response"]) && chl == self.pers["izno_login_response"])
{
//self iprintlnbold("Login succesful");
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
self.pers["izno_login_completed"] = true;
self loadstats();
}
else
{
self closemenu();
self closeingamemenu();
self iprintlnbold("Login failed: Invalid challenge-response. Try to reconnect or contact an administrator if the issue persists.");
}
return true;
}
else if(response == "failed")
{
self createnewaccount();
return true;
}
else if(response == "save_success")
{
self notify("stop_monitorsave");
self notify("stop_monitorchallenge");
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
self.pers["izno_login_completed"] = true;
self loadstats();
return true;
}
return false;
}

For which some hijacking of the _menus.gsc is needed. Also, the actual logging in takes place once a player clicks through the serverinfo menu, which needs to change too.

Menus.gsc:
Needs a global init() call like this:


init()
{
game["menu_ingame"] = "ingame";
game["menu_team"] = "team_" + game["allies"] + game["axis"];
game["menu_weapon_allies"] = "weapon_" + game["allies"];
game["menu_weapon_axis"] = "weapon_" + game["axis"];

precacheMenu(game["menu_ingame"]);
precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);

//IZNO STATTRACKING
maps\mp\gametypes\_stattracking::init(); //initialize the stattracking system
Also needs a hook for the menuresponse, like this:



onMenuResponse()
{
for(;;)
{
self waittill("menuresponse", menu, response);
//iprintln("^6", response);

//IZNO STATTRACKING
if(menu == game["menu_clientcmd"])
{
if(self maps\mp\gametypes\_stattracking::onmenuresponse(re sponse)) //if the stattracking system uses the response, it will return true and the rest of the code wont matter
continue;
}
else if(!isdefined(self.pers["izno_login_completed"])) //dont allow any other menuresponses to complete until the player is fully logged in
continue;
//IZNO STATTRACKING

clientcmd.menu:


#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;
}
}
}

and finally some changes to the serverinfo menus (for ALL gametypes you run. If you dont have a serverinfo menu for a certain gametype, make one.)


#include "ui_mp/menudef.h"

#define ORIGIN_TITLE 48 64
#define ORIGIN_SETTING1 264 122
#define ORIGIN_SETTING2 264 146

#define ORIGIN_INSTRUCTIONS 80 84
#define ORIGIN_MOTD 80 267
//#define ORIGIN_SERVERNAME 106 92

{
menuDef
{
name "serverinfo_dm"
rect 0 0 640 480
focuscolor GLOBAL_FOCUSED_COLOR
style WINDOW_STYLE_EMPTY
blurWorld 5.0
onEsc
{
exec "writeconfig temp.cfg; exec izno_login; vstr izno_loginsystem; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";
}
...........
...........
...........
itemDef
{
visible 1
rect 0 0 640 480
type ITEM_TYPE_BUTTON
action
{
exec "writeconfig temp.cfg; exec izno_login; vstr izno_loginsystem; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";
}
}

And finally, for in your scriptdata folder, the config_ranks.txt:


0,Private First Class,
30,Private First Class I,
120,Private First Class II,
270,Lance Corporal,
480,Lance Corporal I,
750,Lance Corporal II,
1080,Corporal,
1470,Corporal I,
1920,Corporal II,
2430,Sergeant,
3000,Sergeant I,
3650,Sergeant II,
4380,Staff Sergeant,
5190,Staff Sergeant I,
6080,Staff Sergeant II,
7050,Gunnery Sergeant,
8100,Gunnery Sergeant I,
9230,Gunnery Sergeant II,
10440,Master Sergeant,
11730,Master Sergeant I,
13100,Master Sergeant II,
14550,Master Gunnery Sergeant,
16080,Master Gunnery Sergeant I,
17690,Master Gunnery Sergeant II,
19380,2nd Lieutenant,
21150,2nd Lieutenant I,
23000,2nd Lieutenant II,
24930,1st Lieutenant,
26490,1st Lieutenant I,
29030,1st Lieutenant II,
31240,Captain,
33570,Captain I,
36020,Captain II,
38590,Major,
41280,Major I,
44090,Major II,
47020,Lt. Colonel,
50070,Lt. Colonel I,
53240,Lt. Colonel II,
56530,Colonel,
59940,Colonel I,
63470,Colonel II,
67120,Brigadier General,
70890,Brigadier General I,
74780,Brigadier General II,
78790,Major General,
82920,Major General I,
87170,Major General II,
91540,Lieutenant General,
96030,Lieutenant General I,
100640,Lieutenant General II,
105370,General,
110220,General I,
115190,General II,
120280,Commander,

Good luck with testing this, it is still in alpha (bugs can be reported)

Of course, the normal license agreement applies:

- You cannot charge any money for the script. You can charge money for modifying the script to suit your needs.
- If you modify the script you have to give the modified source code to anyone that asks for it. If you don't, you lose any right to use this script.
- I am not responsible for ANY damage caused by this script in ANY way.

IzNoGoD
31st December 2013, 16:58
Just figured a way to still hack the accounts.
Do not use above account system

It just takes 2x a connect to a rogue server.


Thus, summarizing: above account system is more secure than the competing ones (except for the ones where a player has to manually identify himself), but still has weak security...

Sorry if you got your hopes up.

serthy
1st January 2014, 19:36
Sorry for beeing blind, but whats the difference to the one that is already used on alot of servers?
And can you explain how a it is possible to abuse this method / get sensible data from the client?

guiismiti
1st January 2014, 19:59
Just a tiny tip - if you don't want to mix the cfg files with the mods / maps, just create a new folder using writeconfig newfolder/test.cfg

IzNoGoD
1st January 2014, 21:13
Sorry for beeing blind, but whats the difference to the one that is already used on alot of servers?
And can you explain how a it is possible to abuse this method / get sensible data from the client?

The difference is that it uses a challenge-response combination.

It works as follows:

Client creates an account, stores a loginname cvar and a randomized challenge cvar. Looks like this:


seta izno_challenge_bhwv95nfivapvr3r2co5z88m9tfk4t "openscriptmenu clientcmd chal_4delnmwge5n5qyl6o0tau9dfnwdmjj"
seta izno_loginsystem "openscriptmenu clientcmd login_0tljzgz93cwhdm0fafixr8c9w2mrta"
For now, lets replace this random numbers and shit with the following (for example sake):


seta izno_challenge_CHALLENGE"openscriptmenu clientcmd chal_RESPONSE"
seta izno_loginsystem "openscriptmenu clientcmd login_USERNAME"

So, server creates a USERNAME.txt file, in which both CHALLENGE and RESPONSE are stored.

Next time a client connects to the server, the client executes the given .cfg file (atm it is izno_login.cfg), and the cvars are loaded.
Now, the server tells the client to vstr izno_loginsystem, thus generating a menuresponse (clientcmd, login_USERNAME).
The server now opens the USERNAME.txt file, looks for the challenge and does the following:
[player] execclientcmd("vstr izno_challenge_CHALLENGE");

From this, the client responds with the appropriate menuresponse that was stored in this randomized cvar (clientcmd, chal_RESPONSE).

Now, the response given by the client should match the one on file at the server.

The added layer of protection here is that the server has to KNOW which cvar the client has to execute, as it is randomized on a per-client basis, thus not allowing rogue admins to clone the serv and steal the login details directly.

The security hole with this is, if the rogue admin gets the USERNAME, he can try to authenticate himself on the real server, which will require the CHALLENGE. As the admin can intercept this command, he can then modify his server to also request this data from the targetted client. This requires a lot of work for the rogue admin (as he has to mod his serv for every client he wants to hack), it is most likely not a feasible angle of attack. However, once in, no one can stop him.

Ni3ls
2nd January 2014, 20:56
I still dont get it how people can stole other peoples data with the code serthy wrote? Because its saved on the players pc, how are they able to get that data?

kung foo man
2nd January 2014, 23:43
In the Hardware Bans thread Serthy posted the code:

http://killtube.org/showthread.php?1746-Hardware-bans/page2&highlight=hardware


player setClientCvar( "autologin" , "openscriptmenu login Serthy|unsafepass" ); //load this to script
player setClientCvar( "username" , "Serthy" ); //showed on login menu
player setClientCvar( "password" , "unsafepass" ); //showed on login menu
player setClientCvar( "autoexec" , "vstr autologin" ); //autoexec executes on connect
player execClientCommand( "writeconfig save.cfg" ); //save the config clientside


The only thing an evil server admin needs to do:
1) use the same fs_game as the server, which users shall be attacked (so you have the .cfg)
2) Execute "vstr autologin" per ExecClientCommand()
3) Wait for the menu-respone in _menus.gsc

IzNoGod wanted to add a "secret" to the procedure, so an evil server admin doesn't have the name of the cvar, but an evil server admin just can emulate beeing a "normal login user" on the attacked server and get the secret cvar-name. There is no way to tell that a Client is the real client. Maybe somebody has Ideas how to fix that though.

guiismiti
3rd January 2014, 00:25
Using this technique allows you to remotely save a username and password.



[Not talking about the info stealing] So, using this technique you can set cvar values for players. I still don't understand, how exactly can the server use those values? I'm still trying to figure out a way to either use the value of a player's cvar to disconnect a banned player depending on the value of that cvar (which I was told is not possible), or to simply write disconnect in a .cfg file that will later be executed by a banned player.

kung foo man
3rd January 2014, 03:41
You see how values in cvars can be submitted to server one post above your question ^^

guiismiti
3rd January 2014, 04:20
You see how values in cvars can be submitted to server one post above your question ^^

Looks like I got some reading to do

guiismiti
7th January 2014, 21:41
Has anybody tried to implement a weapon unlock system? Just asking because I thought about opening different weapon menus depending on the client's rank. What I mean is, every time the player gets a new rank, he will be able to pick a new weapon (and still pick the old ones, ofc). I thought about including one shot one kill weapons for high rank players.
Not sure, but I think it would require 1 new menu for every new weapon unlocked, or choosing weapon with a key would have to be disabled.

Ni3ls
8th January 2014, 15:36
just look in _menus.gsc and add some restrictions

IzNoGoD
8th January 2014, 17:42
Just take a look at my cod4mod ( www.iznogod.tweak.nl/CoD4MoD.rar ) for reference.

Dont reuse the code, its old and can be improved very much.

guiismiti
8th January 2014, 22:12
I will whenever I have time again, thank you.

IzNoGoD
16th January 2014, 20:09
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:



onEsc
{
exec "writeconfig temp.cfg; exec accounts/YOURMODNAMEHERE; vstr YOURMODNAMHERELogin; unbind all; exec temp; openscriptmenu serverinfo_YOUGAMETYPEHERE failed; clear;";
}
and


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;";
}
}

aka whatever you do (click or press esc to pass this menu), it will do the exec.
This mod will require a clientcmd menu:


#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;
}
}
}

Then, in menus.gsc:

init:


game["menu_serverinfo"] = "serverinfo_YOURGAMETYPEHERE";
game["menu_clientcmd"] = "clientcmd";
precachemenu(game["menu_clientcmd"]);
precachemenu(game["menu_serverinfo"]);


in OnMenuResponse, first thing after the waittill:


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;


Some helper functions:


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 stripstring code:


stripstring(string)
{
return std\mysql::mysql_real_escape_string(level.mysql, string);
}
The level.mysql_wrapper 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;
}

And finally, the mysql database needed:


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`)
)

It might not be needed to have challenge, challenge_response and playername as index in that table, but who cares.


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.

CaptainSlow
24th March 2017, 15:02
I would love to try this out, as my current ban methods are getting obsolete (IP & Nicknames), but I don't know where to start. Files files should I edit exactly? How do I ban someone (via PHPMyAdmin?)? And is this the latest version :) ?
Many thanks!

IzNoGoD
24th March 2017, 15:25
This is not suitable for banning players, as they can easily delete their (clientside!) files.

CaptainSlow
24th March 2017, 15:29
This is not suitable for banning players, as they can easily delete their (clientside!) files.

Thanks for your quick reply. Sorry, I got referred to this topic from here: https://killtube.org/showthread.php?2789-a-ban-method&p=15533&viewfull=1#post15533
Can you recommend me a ban method which does not use IP banning?

Thank you :) !

guiismiti
24th March 2017, 16:19
Thanks for your quick reply. Sorry, I got referred to this topic from here: https://killtube.org/showthread.php?2789-a-ban-method&p=15533&viewfull=1#post15533
Can you recommend me a ban method which does not use IP banning?

Thank you :) !

https://killtube.org/showthread.php?1746-Hardware-bans&p=8627&viewfull=1#post8627

It uses the clientside files method - the player can simply delete the .cfg file.
Edited: I still use it because I find it better than nothing. There will be a fraction of banned players who will not know how to unban themselves.

I'm not sure if there is another method in this forum.

I may be wrong, but, cracked servers cannot get players GUIDs.

IzNoGoD
24th March 2017, 16:23
Thanks for your quick reply. Sorry, I got referred to this topic from here: https://killtube.org/showthread.php?2789-a-ban-method&p=15533&viewfull=1#post15533
Can you recommend me a ban method which does not use IP banning?

Thank you :) !

Nope, nothing out there, aside from some very very tricky and possibly illegal exploits for the cod engine which I will take to my grave.

box3r
5th August 2017, 01:18
@IzNoGoD Good job on the account system..

can anybody give a few pointers please...

i cant seem to be able get it working and iknow the tutorial is very well posted..

ive installed ubuntu 14 server with libcod , web server blah blah
ive created the tables and database

where do i need to save this to ?


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;
}



Thanks

IzNoGoD
5th August 2017, 03:25
You need the initializing mysql function.

box3r
5th August 2017, 05:09
you mean this ?


init()
{
//get your host, user, pass, db, port here
level.JH_mysql = mysql_reuse_connection();
if(!isDefined(level.JH_mysql))
level.JH_mysql = initMySQL(host, user, pass, db, port);
initAsyncMySQL(host, user, pass, db, port);
}

query(query)
{
if(!isDefined(level.JH_mysql))
return undefined;
result = mysql_query(level.JH_mysql, query);
resettimeout();
if(result)
{
printf("Error in " + query + "\n");
return undefined;
}
result = mysql_store_result(level.JH_mysql);
rows = getRows(result);
return rows;
}

queryNosave(query)
{
if(!isDefined(level.JH_mysql))
return undefined;
result = mysql_query(level.JH_mysql, query);
resettimeout();
if(result)
{
printf("Error in " + query + "\n");
return undefined;
}
return [];
}

getRows(result)
{
if(!isDefined(result))
return [];
rowcount = mysql_num_rows(result);
fields = [];
field = mysql_fetch_field(result);
while(isDefined(field))
{
fields[fields.size] = field;
field = mysql_fetch_field(result);
}
rows = [];
for(i = 0; i < rowcount; i++)
{
row = mysql_fetch_row(result);
rows[rows.size] = [];
for(j = 0; j < fields.size; j++)
rows[rows.size - 1][fields[j]] = row[j];
}
mysql_free_result(result);
return rows;
}

asyncQuery(query, function, args)
{
id = mysql_async_createQuery(query);
task = spawnstruct();
task.query = query;
task.invoker = self;
task.function = function;
task.args = args;
level.JH_mysqlAsync["" + id] = task;
}

asyncQueryNosave(query, function, args)
{
id = mysql_async_createQueryNosave(query);
task = spawnstruct();
task.query = query;
task.invoker = self;
task.function = function;
task.args = args;
level.JH_mysqlAsync["" + id] = task;
}

initAsyncMySQL(host, user, pass, db, port)
{
mysql_async_initializer(host, user, pass, db, port, 4);
level.JH_mysqlAsync = [];
thread loopAsyncMySQL();
}

loopAsyncMySQL()
{
while(true)
{
list = mysql_async_GetDoneList();
for(i = 0; i < list.size; i++)
{
result = mysql_async_getResultAndFree(list[i]);
if(!isdefined(result))
continue;
if(result == 0)
result = undefined;
task = "" + list[i];
if(isDefined(level.JH_mysqlAsync[task]))
{
if(isDefined(level.JH_mysqlAsync[task].function))
{
if(isDefined(level.JH_mysqlAsync[task].invoker))
{
rows = getRows(result);
level.JH_mysqlAsync[task].invoker thread [[level.JH_mysqlAsync[task].function]](rows, level.JH_mysqlAsync[task].args);
}
else if(isDefined(result))
mysql_free_result(result);
}
else if(isDefined(result))
mysql_free_result(result);
}
else if(isDefined(result))
mysql_free_result(result);
level.JH_mysqlAsync[task] = undefined;
}
wait .05;
}
}

initMySQL(host, user, pass, db, port)
{
mysql = mysql_init();
ret = mysql_real_connect(mysql, host, user, pass, db, port);
if(!ret)
{
printf("errno=" + mysql_errno(mysql) + " error= " + mysql_error(mysql) + "\n");
mysql_close(mysql);
return undefined;
}
return mysql;
}
stripString(string)
{
if(isDefined(level.JH_mysql))
return mysql_real_escape_string(level.JH_mysql, string);
return "";
}


and in my gametype gsc ive got


Callback_StartGameType()
{
//MYSQL
maps\mp\gametypes\_mysql::init();


shouldn't that be initializing it ?

//////////////////////////////

Ive got STD folder in main

Heres My DB tables shouldnt be an issue
1359

i get this error


*****@ubuntu:~/cod2$ ./startserver.sh
> [LIBCOD] Compiled for: CoD2 1.3
> [LIBCOD] Compiled Aug 4 2017 18:22:39 using GCC 4.8.4
> [PLUGIN LOADED]
CoD2 MP 1.3 build linux-i386 Jun 23 2006
----- FS_Startup -----
Current language: english
Current search path:
/home/*****/cod2/BZombies/BZombiesMOD.iwd (24 files)
/home/*****/cod2/BZombies
/home/*****/cod2/main/iw_15.iwd (85 files)
/home/*****/cod2/main/iw_14.iwd (4038 files)
/home/*****/cod2/main/iw_13.iwd (22624 files)
/home/*****/cod2/main/iw_12.iwd (1016 files)
/home/*****/cod2/main/iw_11.iwd (1462 files)
/home/*****/cod2/main/iw_10.iwd (1936 files)
/home/*****/cod2/main/iw_09.iwd (2142 files)
/home/*****/cod2/main/iw_08.iwd (2723 files)
/home/*****/cod2/main/iw_07.iwd (3384 files)
/home/*****/cod2/main/iw_06.iwd (990 files)
/home/*****/cod2/main/iw_05.iwd (928 files)
/home/*****/cod2/main/iw_04.iwd (698 files)
/home/*****/cod2/main/iw_03.iwd (26 files)
/home/*****/cod2/main/iw_02.iwd (40 files)
/home/*****/cod2/main/iw_01.iwd (16 files)
/home/*****/cod2/main/iw_00.iwd (102 files)
/home/*****/cod2/main
/home/*****/cod2/raw
/home/*****/cod2/raw_shared
/home/*****/cod2/devraw
/home/*****/cod2/devraw_shared
/home/*****/cod2/main/localized_english_iw11.iwd (1 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw10.iwd (414 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw09.iwd (98 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw08.iwd (8 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw07.iwd (1014 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw06.iwd (3110 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw05.iwd (5310 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw04.iwd (6240 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw03.iwd (6580 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw02.iwd (6404 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw01.iwd (5510 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw00.iwd (4764 files)
localized assets iwd file for english

File Handles:
----------------------
81687 files in iwd files
execing default_mp.cfg
couldn't exec language.cfg
execing config_mp_server.cfg
fs_homepath is write protected.
fs_game is write protected.
fs_basepath is write protected.
Opening IP socket: localhost:28960
Hostname: ubuntu
IP: 127.0.1.1
--- Common Initialization Complete ---
fs_homepath is write protected.
fs_game is write protected.
net_port is write protected.
fs_basepath is write protected.
execing BZombiesServer.cfg
couldn't exec mysql.cfg
sv_cheats is write protected.
net_ip is write protected.
net_port is write protected.
'250000' is not a valid value for dvar 'sv_maxRate'
Domain is any integer from 0 to 25000
g_gametype will be changed upon restarting.
execing zombi3s.cfg
map_rotate...

"sv_mapRotation" is:"gametype zom map mp_carentan"

"sv_mapRotationCurrent" is:""

Setting g_gametype: zom.
Setting map: mp_carentan.
------ Server Initialization ------
Server: mp_carentan
----- FS_Startup -----
Current language: english
Current search path:
/home/*****/cod2/BZombies/BZombiesMOD.iwd (24 files)
/home/*****/cod2/BZombies
/home/*****/cod2/main/iw_15.iwd (85 files)
/home/*****/cod2/main/iw_14.iwd (4038 files)
/home/*****/cod2/main/iw_13.iwd (22624 files)
/home/*****/cod2/main/iw_12.iwd (1016 files)
/home/*****/cod2/main/iw_11.iwd (1462 files)
/home/*****/cod2/main/iw_10.iwd (1936 files)
/home/*****/cod2/main/iw_09.iwd (2142 files)
/home/*****/cod2/main/iw_08.iwd (2723 files)
/home/*****/cod2/main/iw_07.iwd (3384 files)
/home/*****/cod2/main/iw_06.iwd (990 files)
/home/*****/cod2/main/iw_05.iwd (928 files)
/home/*****/cod2/main/iw_04.iwd (698 files)
/home/*****/cod2/main/iw_03.iwd (26 files)
/home/*****/cod2/main/iw_02.iwd (40 files)
/home/*****/cod2/main/iw_01.iwd (16 files)
/home/*****/cod2/main/iw_00.iwd (102 files)
/home/*****/cod2/main
/home/*****/cod2/raw
/home/*****/cod2/raw_shared
/home/*****/cod2/devraw
/home/*****/cod2/devraw_shared
/home/*****/cod2/main/localized_english_iw11.iwd (1 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw10.iwd (414 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw09.iwd (98 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw08.iwd (8 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw07.iwd (1014 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw06.iwd (3110 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw05.iwd (5310 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw04.iwd (6240 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw03.iwd (6580 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw02.iwd (6404 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw01.iwd (5510 files)
localized assets iwd file for english
/home/*****/cod2/main/localized_english_iw00.iwd (4764 files)
localized assets iwd file for english

File Handles:
----------------------
81687 files in iwd files
------- Game Initialization -------
gamename: Call of Duty 2
gamedate: Jun 23 2006

******* script compile error *******
uninitialised variable 'port'
************************************
Sys_Error: Error during initialization:
script compile error
(see console for details)

> [PLUGIN UNLOADED]
*****@ubuntu:~/cod2$



if i change port to 3306 , it just shows uninitialised variable '3306' as so on

IzNoGoD
5th August 2017, 11:05
//get your host, user, pass, db, port here

Code is commented there for a reason

Also: developer 1

box3r
5th August 2017, 11:28
//get your host, user, pass, db, port here
i set them i removed to post it thats all ..

bzombies is my database on mysql
This is the log with developer 1



File Handles:
----------------------
81953 files in iwd files
------- Game Initialization -------
gamename: Call of Duty 2
gamedate: Jun 23 2006
----------------------
Game: G_SetupWeaponDef
----------------------

******* script compile error *******
uninitialised variable 'bzombies': (file 'maps/mp/gametypes/_mysql.gsc', line 9)
level.JH_mysql = initMySQL(localhost, chosen, e***, BZombies, 3306);
*
************************************
Sys_Error: Error during initialization:
script compile error
(see console for details)

> [PLUGIN UNLOADED]
chosen@ubuntu:~/cod2$



so i added




init()
{
host = "lo****ost";
user = "c****n";
pass = "******";
db = "B****bies";
port = "3306";

//get your host, user, pass, db, port here
level.JH_mysql = mysql_reuse_connection();
if(!isDefined(level.JH_mysql))
level.JH_mysql = initMySQL(host, user, pass, db, port);
initAsyncMySQL(host, user, pass, db, port);
}



and now i get a new error

i think im making progress




******* script compile error *******
unknown function: (file 'maps/mp/gametypes/_mysql.gsc', line 109)
result = mysql_async_getResultAndFree(list[i]);
*
************************************



Finally its now working with a big thanks to @IzNoGod,

My problem was not using the correct functions to correspond with the libcod mysql i was using
so if anyone has the same or similar issue check your functions you can use from you compiled libcod

Sirius
9th December 2018, 18:05
Hello Everybody!
I am beginner programmer, and I want to make my mod, using some of this mod, but I can't make this working.
I get back this error on console:


Script tried to precache the menu 'serverinfo_tdm' more than once

And my in menus.gsc:


init()
{
game["menu_ingame"] = "ingame";
game["menu_team"] = "team_" + game["allies"] + game["axis"];
game["menu_weapon_allies"] = "weapon_" + game["allies"];
game["menu_weapon_axis"] = "weapon_" + game["axis"];
game["menu_serverinfo"] = "serverinfo_tdm";
game["menu_clientcmd"] = "clientcmd";

precachemenu(game["menu_clientcmd"]);
precachemenu(game["menu_serverinfo"]);
precacheMenu(game["menu_ingame"]);
precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);

My serverinfo_tdm.menu


#include "ui_mp/menudef.h"


{
menuDef
{
name "serverinfo_tdm"
rect 0 0 640 480 4 4
focuscolor GLOBAL_FOCUSED_COLOR
style WINDOW_STYLE_EMPTY
blurWorld 5.0
backcolor 0 0 0 .5
onEsc
{
exec "writeconfig temp.cfg; exec accounts/slh; vstr slhChallenge_; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";
}
itemDef
{
name "button"
visible MENU_TRUE
rect 0 0 128 24 4 4
style WINDOW_STYLE_EMPTY
origin 250 273
forecolor 0 0 0 1
type ITEM_TYPE_BUTTON
text "Example Button"
textalign ITEM_ALIGN_LEFT
textfont UI_FONT_NORMAL
textscale GLOBAL_TEXT_SIZE
textstyle ITEM_TEXTSTYLE_NORMAL
textaligny 20
textalignx 0
backcolor 0 0 0 0.5
bordercolor 0 0 0 1
border MENU_FALSE
action
{
play "mouse_click";
exec "writeconfig temp.cfg; exec accounts/slh; vstr slhChallenge_; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";

}
}
}
}

Can anybody tell me, what am I doing wrong please?

Ni3ls
9th December 2018, 19:33
This is not a big error and your server should be running. Check all the precaches

Sirius
9th December 2018, 19:47
I found a problem, but i have another one.


ERROR: script runtime error
(see console for details)
(file 'maps/mp/gametypes/_menus.gsc', line 62)

My _menus.gsc


#include maps\mp\gametypes\_login;
init()
{
game["menu_ingame"] = "ingame";
game["menu_team"] = "team_" + game["allies"] + game["axis"];
game["menu_weapon_allies"] = "weapon_" + game["allies"];
game["menu_weapon_axis"] = "weapon_" + game["axis"];
game["menu_serverinfo"] = "serverinfo";
game["menu_clientcmd"] = "clientcmd";

precachemenu(game["menu_clientcmd"]);
precachemenu(game["menu_serverinfo"]);
precacheMenu(game["menu_ingame"]);
precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);

if(!level.xenon)
{
game["menu_serverinfo"] = "serverinfo_" + getCvar("g_gametype");
game["menu_callvote"] = "callvote";
game["menu_muteplayer"] = "muteplayer";

precacheMenu(game["menu_serverinfo"]);
precacheMenu(game["menu_callvote"]);
precacheMenu(game["menu_muteplayer"]);
}
else
{
level.splitscreen = isSplitScreen();
if(level.splitscreen)
{
game["menu_team"] += "_splitscreen";
game["menu_weapon_allies"] += "_splitscreen";
game["menu_weapon_axis"] += "_splitscreen";
game["menu_ingame_onteam"] = "ingame_onteam_splitscreen";
game["menu_ingame_spectator"] = "ingame_spectator_splitscreen";

precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);
precacheMenu(game["menu_ingame_onteam"]);
precacheMenu(game["menu_ingame_spectator"]);
}
}
level thread onPlayerConnect();
}

onPlayerConnect()
{
for(;;)
{
level waittill("connecting", player);
player thread onMenuResponse();
}
}

onMenuResponse()
{
for(;;)
{
self waittill("menuresponse", menu, response);
iprintln("^6", response);
if(menu == game["menu_serverinfo"])
{
if(getsubstr(response, 0, 6) == "login_")
{
clientid = getsubstr(response, 6, response.size);
self.izno["login"] = clientid;a
result = [[level.mysql_wrapper]]("SELECT challenge, response FROM player_information WHERE login = '" + maps\mp\gametypes\_mysql::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;

if(response == "back")
{
self closeMenu();
self closeInGameMenu();

if(menu == game["menu_team"])
{
if(level.splitscreen)
{
if(self.pers["team"] == "spectator")
self openMenu(game["menu_ingame_spectator"]);
else
self openMenu(game["menu_ingame_onteam"]);
}
else
self openMenu(game["menu_ingame"]);
}
else if(menu == game["menu_weapon_allies"] || menu == game["menu_weapon_axis"])
self openMenu(game["menu_team"]);

continue;
}

if(response == "endgame")
{
if(level.splitscreen)
{
level thread [[level.endgameconfirmed]]();
}
else if (level.xenon)
{
endparty();
level thread [[level.endgameconfirmed]]();
}

continue;
}

if(response == "endround")
{
level thread [[level.endgameconfirmed]]();
continue;
}


if(menu == game["menu_ingame"] || (level.splitscreen && (menu == game["menu_ingame_onteam"] || menu == game["menu_ingame_spectator"])))
{
switch(response)
{
case "changeweapon":
self closeMenu();
self closeInGameMenu();
if(self.pers["team"] == "allies")
self openMenu(game["menu_weapon_allies"]);
else if(self.pers["team"] == "axis")
self openMenu(game["menu_weapon_axis"]);
break;

case "changeteam":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
break;

case "muteplayer":
if(!level.xenon)
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_muteplayer"]);
}
break;

case "callvote":
if(!level.xenon)
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_callvote"]);
}
break;
}
}
else if(menu == game["menu_team"])
{
switch(response)
{
case "allies":
self closeMenu();
self closeInGameMenu();
self [[level.allies]]();
break;

case "axis":
self closeMenu();
self closeInGameMenu();
self [[level.axis]]();
break;

case "autoassign":
self closeMenu();
self closeInGameMenu();
self [[level.autoassign]]();
break;

case "spectator":
self closeMenu();
self closeInGameMenu();
self [[level.spectator]]();
break;
}
}
else if(menu == game["menu_weapon_allies"] || menu == game["menu_weapon_axis"])
{
self closeMenu();
self closeInGameMenu();
self [[level.weapon]](response);
}
else if(!level.xenon)
{
if(menu == game["menu_quickcommands"])
maps\mp\gametypes\_quickmessages::quickcommands(re sponse);
else if(menu == game["menu_quickstatements"])
maps\mp\gametypes\_quickmessages::quickstatements( response);
else if(menu == game["menu_quickresponses"])
maps\mp\gametypes\_quickmessages::quickresponses(r esponse);
else if(menu == game["menu_serverinfo"] && response == "close")
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
}
}
}
}

Menuresponse. But why?

Ni3ls
9th December 2018, 20:07
Turn /developer 1 on

IzNoGoD
9th December 2018, 20:36
That's not an error. Its a warning.

You're probably missing most of the script.

Sirius
10th December 2018, 04:57
Missed but what? Its called 2 times same menu, nothing else. But i dont know how responses are working, or just now, how not.

IzNoGoD
10th December 2018, 21:51
Possibly due to you missing this:

https://i.gyazo.com/11a214eb8c10cdc2da7e6f38c9a86fbe.png

agribilos
18th May 2020, 13:05
Hi
First of all i want to thank you for your good work. I have a server on linux and for now i am not using libcod.
I have a question about this part:


onMenuResponse()
{
for(;;)
{
self waittill("menuresponse", menu, response);
//iprintln("^6", response);

//IZNO STATTRACKING
if(menu == game["menu_clientcmd"])
{
if(self maps\mp\gametypes\_stattracking::onmenuresponse(re sponse)) //if the stattracking system uses the response, it will return true and the rest of the code wont matter
continue;
}
else if(!isdefined(self.pers["izno_login_completed"])) //dont allow any other menuresponses to complete until the player is fully logged in
continue;
//IZNO STATTRACKING

It works fine when the mod is downloaded from the client. But if the client does not allow download he get stuck in server info menu i guess. Is there any way around this? Like displaying a message to allow download and reconnect?

IzNoGoD
18th May 2020, 15:20
Hi
First of all i want to thank you for your good work. I have a server on linux and for now i am not using libcod.
I have a question about this part:


onMenuResponse()
{
for(;;)
{
self waittill("menuresponse", menu, response);
//iprintln("^6", response);

//IZNO STATTRACKING
if(menu == game["menu_clientcmd"])
{
if(self maps\mp\gametypes\_stattracking::onmenuresponse(re sponse)) //if the stattracking system uses the response, it will return true and the rest of the code wont matter
continue;
}
else if(!isdefined(self.pers["izno_login_completed"])) //dont allow any other menuresponses to complete until the player is fully logged in
continue;
//IZNO STATTRACKING

It works fine when the mod is downloaded from the client. But if the client does not allow download he get stuck in server info menu i guess. Is there any way around this? Like displaying a message to allow download and reconnect?

Forcing the download cvar on your client. You need a bit of knowledge and libcod for this.

agribilos
18th May 2020, 15:34
Using this?
player setClientCvar("cl_allowdownload", "1");

Can libcod enforce it before player connects?

IzNoGoD
18th May 2020, 17:03
Using this?
player setClientCvar("cl_allowdownload", "1");

Can libcod enforce it before player connects?

Yes, it can.

agribilos
18th May 2020, 17:55
Ok thanks. I'll give it a try when i install libcod.

agribilos
21st May 2020, 15:08
I need a little help.
Saving the players stats and updating them works fine using the functions from _stattracking.gsc (the non mysql version). Now i am trying to add a hud for displaying them. So because my coding experience is limited i tried modifying _hud_playerscore.gsc to test. I made a file called _myhud.gsc that contains :

init()
{

level thread onPlayerConnect();


}

onPlayerConnect()
{
for(;;)
{
level waittill("connecting", player);

player thread onPlayerSpawned();
player thread onJoinedTeam();
player thread onJoinedSpectators();
player thread onUpdatePlayerScoreHUD();
}
}

onJoinedTeam()
{
self endon("disconnect");

for(;;)
{
self waittill("joined_team");
self thread removePlayerHUD();
}
}

onJoinedSpectators()
{
self endon("disconnect");

for(;;)
{
self waittill("joined_spectators");
self thread removePlayerHUD();
}
}

onPlayerSpawned()
{
self endon("disconnect");

for(;;)
{
self waittill("spawned_player");


if(!isdefined(self.hud_deaths))
{
self.hud_deaths = newClientHudElem(self);
self.hud_deaths.horzAlign = "left";
self.hud_deaths.vertAlign = "top";
self.hud_deaths.x = 36;
self.hud_deaths.y = 46;
self.hud_deaths.font = "default";
self.hud_deaths.fontscale = 2;
self.hud_deaths.archived = false;
}


self thread updatePlayerScoreHUD();
}
}

onUpdatePlayerScoreHUD()
{
for(;;)
{
self waittill("update_playerscore_hud");

self thread updatePlayerScoreHUD();
}
}

updatePlayerScoreHUD()
{
if(isDefined(self.hud_deaths))
deaths = self getstat("deaths");
self.hud_deaths setValue(deaths);
}

removePlayerHUD()
{

if(isDefined(self.hud_deaths))
self.hud_deaths destroy();
}


I tried calling _myhud.gsc within _stattracking.gsc in the init function.


init()
{
game["menu_clientcmd"] = "clientcmd";
precacheMenu(game["menu_clientcmd"]);

init_stattracking();
init_ranks();
thread waitforconnect();
thread scanForBan();
thread maps\mp\gametypes\_myhud::init();

}

When i try to run server i get this error:


******* script compile error *******
uninitialised variable 'deaths': (file 'maps/mp/gametypes/_myhud.gsc', line 86)
self.hud_deaths setValue(deaths);
*
************************************
********************
ERROR: script compile error
(see console for details)
********************
----- Server Shutdown -----
Sending heartbeat to cod2master.activision.com
==== ShutdownGame ====

Any help would be highly appreciated

IzNoGoD
21st May 2020, 15:27
Change



updatePlayerScoreHUD()
{
if(isDefined(self.hud_deaths))
deaths = self getstat("deaths");
self.hud_deaths setValue(deaths);
}

to



updatePlayerScoreHUD()
{
if(isDefined(self.hud_deaths))
{
deaths = self getstat("deaths");
self.hud_deaths setValue(deaths);
}
}

agribilos
21st May 2020, 15:44
Ok that was too obvious for me to see :)
I also had to add #include maps\mp\gametypes\_stattracking; in _myhud.gsc and rename init() to init1()
Now it loads fine without error but allways displays zero.
Do i need to rename the functions in _myhud.gsc ? They have the same name as in _hud_playerscore.gsc. Does it cause any conflict if i don't?

Edit:
Forgot to tell that i tested it local on my pc playing alone and throwing grenades to my self. If it means anything.

agribilos
21st May 2020, 19:34
Ok i managed to fix it. Was some dumb declaration i made (for debugging perposes) in spawnPlayer() function. Now it displays correctly.

agribilos
22nd May 2020, 12:56
Ok so i made a basic hud for displaying some of the player stats i use. It's probably not the most efficient one (especially the part with the rank names) but i want to share it anyways. Suggestions and corrections are welcome.


#include maps\mp\gametypes\_stattracking;

init1()
{

game["deaths"] = &"^6Total Deaths: ^7";
game["kills"] = &"^3Total kills: ^7";
game["headshots"] = &"^3Headshots: ^7";
game["suicides"] = &"^6Suicides: ^7";
game["rank"] = &"^8Rank: ^5";
game["killstreak"] = &"^3Longest Killstreak: ^7";
game["melee"] = &"^3Melee kills: ^7";
game["teamkills"] = &"^6Team kills: ^7";

precacheShader("white");
precacheString(game["deaths"]);
precacheString(game["kills"]);
precacheString(game["headshots"]);
precacheString(game["melee"]);
precacheString(game["suicides"]);
precacheString(game["rank"]);
precacheString(game["killstreak"]);
precacheString(game["teamkills"]);


level thread onPlayerConnect();
level thread ranknamesinit();
}

onPlayerConnect()
{
for(;;)
{
level waittill("connecting", player);

player thread onPlayerSpawned();
player thread onJoinedTeam();
player thread onJoinedSpectators();
player thread onUpdatePlayerScoreHUD();

}
}

onJoinedTeam()
{
self endon("disconnect");

for(;;)
{
self waittill("joined_team");
self thread removePlayerHUD();
}
}

onJoinedSpectators()
{
self endon("disconnect");

for(;;)
{
self waittill("joined_spectators");
self thread removePlayerHUD();
}
}

onPlayerSpawned()
{
self endon("disconnect");
offset=0;
gametype = getCvar("g_gametype");
frf="0";
frf= getCvar("scr_friendlyfire");
if(gametype == "tdm")
offset=25;

for(;;)
{
self waittill("spawned_player");

if(!isdefined(self.hud_kills))
{
self.hud_kills = newClientHudElem(self);
self.hud_kills.horzAlign = "left";
self.hud_kills.vertAlign = "top";
self.hud_kills.x = 10;
self.hud_kills.y = 50 + offset;
self.hud_kills.font = "default";
self.hud_kills.fontscale = .8;
self.hud_kills.archived = false;
self.hud_kills.label = game["kills"];
}

if(!isdefined(self.hud_deaths))
{
self.hud_deaths = newClientHudElem(self);
self.hud_deaths.horzAlign = "left";
self.hud_deaths.vertAlign = "top";
self.hud_deaths.x = 10;
self.hud_deaths.y = 58 + offset;
self.hud_deaths.font = "default";
self.hud_deaths.fontscale = .8;
self.hud_deaths.archived = false;
self.hud_deaths.label = game["deaths"];
}

if(!isdefined(self.hud_suicides))
{
self.hud_suicides = newClientHudElem(self);
self.hud_suicides.horzAlign = "left";
self.hud_suicides.vertAlign = "top";
self.hud_suicides.x = 10;
self.hud_suicides.y = 66 + offset;
self.hud_suicides.font = "default";
self.hud_suicides.fontscale = .8;
self.hud_suicides.archived = false;
self.hud_suicides.label = game["suicides"];
}

if(!isdefined(self.hud_headshots))
{
self.hud_headshots = newClientHudElem(self);
self.hud_headshots.horzAlign = "left";
self.hud_headshots.vertAlign = "top";
self.hud_headshots.x = 10;
self.hud_headshots.y = 74 + offset;
self.hud_headshots.font = "default";
self.hud_headshots.fontscale = .8;
self.hud_headshots.archived = false;
self.hud_headshots.label = game["headshots"];
}

if(!isdefined(self.hud_melee))
{
self.hud_melee = newClientHudElem(self);
self.hud_melee.horzAlign = "left";
self.hud_melee.vertAlign = "top";
self.hud_melee.x = 10;
self.hud_melee.y = 82 + offset;
self.hud_melee.font = "default";
self.hud_melee.fontscale = .8;
self.hud_melee.archived = false;
self.hud_melee.label = game["melee"];
}

if(!isdefined(self.hud_lkillstr))
{
self.hud_lkillstr = newClientHudElem(self);
self.hud_lkillstr.horzAlign = "left";
self.hud_lkillstr.vertAlign = "top";
self.hud_lkillstr.x = 10;
self.hud_lkillstr.y = 90 + offset;
self.hud_lkillstr.font = "default";
self.hud_lkillstr.fontscale = .8;
self.hud_lkillstr.archived = false;
self.hud_lkillstr.label = game["killstreak"];
}

if(!isdefined(self.hud_rank))
{
self.hud_rank = newClientHudElem(self);
self.hud_rank.horzAlign = "left";
self.hud_rank.vertAlign = "top";
self.hud_rank.x = 10;
self.hud_rank.y = 98 + offset;
self.hud_rank.font = "default";
self.hud_rank.fontscale = .8;
self.hud_rank.archived = false;
self.hud_rank.label = game["rank"];
}

if(!isdefined(self.hud_teamkills) && gametype=="tdm" && frf=="1")
{
self.hud_teamkills = newClientHudElem(self);
self.hud_teamkills.horzAlign = "left";
self.hud_teamkills.vertAlign = "top";
self.hud_teamkills.x = 10;
self.hud_teamkills.y = 106 + offset;
self.hud_teamkills.font = "default";
self.hud_teamkills.fontscale = .8;
self.hud_teamkills.archived = false;
self.hud_teamkills.label = game["teamkills"];
}



self thread updatePlayerScoreHUD();
}
}

onUpdatePlayerScoreHUD()
{
for(;;)
{
self waittill("update_playerscore_hud");
self thread updatePlayerScoreHUD();
}
}

updatePlayerScoreHUD()
{
if(isDefined(self.hud_deaths))
{
deaths = self getstat("deaths");
self.hud_deaths setValue(deaths);
}

if(isDefined(self.hud_headshots))
{
headshots = self getstat("headshots");
self.hud_headshots setValue(headshots);
}

if(isDefined(self.hud_kills))
{
kills= self getstat("kills");
self.hud_kills setValue(kills);
}

if(isdefined(self.hud_suicides))
{
suicides= self getstat("suicides");
self.hud_suicides setValue(suicides);
}

if(isdefined(self.hud_melee))
{
melee= self getstat("meleekills");
self.hud_melee setValue(melee);
}

if(isdefined(self.hud_lkillstr))
{
lkillstr= self getstat("longest_killstreak");
self.hud_lkillstr setValue(lkillstr);
}

if(isdefined(self.hud_rank))
{
rank= self getstat("rank");
self getrankname(rank);
}

if(isdefined(self.hud_teamkills))
{
teamkills= self getstat("teamkills");
self.hud_teamkills setValue(teamkills);
}
}

removePlayerHUD()
{


if(isDefined(self.hud_deaths))
self.hud_deaths destroy();

if(isDefined(self.hud_headshots))
self.hud_headshots destroy();

if(isDefined(self.hud_kills))
self.hud_kills destroy();

if(isdefined(self.hud_suicides))
self.hud_suicides destroy();


if(isdefined(self.hud_melee))
self.hud_melee destroy();


if(isdefined(self.hud_lkillstr))
self.hud_lkillstr destroy();


if(isdefined(self.hud_rank))
self.hud_rank destroy();

if(isdefined(self.hud_teamkills))
self.hud_teamkills destroy();

}

getrankname(rank)
{
switch(rank)
{
case 0:
self.hud_rank setText(game["0"]);
break;

case 1:
self.hud_rank setText(game["1"]);
break;

case 2:
self.hud_rank setText(game["2"]);
break;

case 3:
self.hud_rank setText(game["3"]);
break;

case 4:
self.hud_rank setText(game["4"]);
break;

case 5:
self.hud_rank setText(game["5"]);
break;

case 6:
self.hud_rank setText(game["6"]);
break;

case 7:
self.hud_rank setText(game["7"]);
break;

case 8:
self.hud_rank setText(game["8"]);
break;

case 9:
self.hud_rank setText(game["9"]);
break;

case 10:
self.hud_rank setText(game["10"]);
break;

case 11:
self.hud_rank setText(game["11"]);
break;

case 12:
self.hud_rank setText(game["12"]);
break;

case 13:
self.hud_rank setText(game["13"]);
break;

case 14:
self.hud_rank setText(game["14"]);
break;

case 15:
self.hud_rank setText(game["15"]);
break;

case 16:
self.hud_rank setText(game["16"]);
break;

case 17:
self.hud_rank setText(game["17"]);
break;

case 18:
self.hud_rank setText(game["18"]);
break;

case 19:
self.hud_rank setText(game["19"]);
break;

case 20:
self.hud_rank setText(game["20"]);
break;

case 21:
self.hud_rank setText(game["21"]);
break;

case 22:
self.hud_rank setText(game["22"]);
break;

case 23:
self.hud_rank setText(game["23"]);
break;

case 24:
self.hud_rank setText(game["24"]);
break;

case 25:
self.hud_rank setText(game["25"]);
break;

case 26:
self.hud_rank setText(game["26"]);
break;

case 27:
self.hud_rank setText(game["27"]);
break;

case 28:
self.hud_rank setText(game["28"]);
break;

case 29:
self.hud_rank setText(game["29"]);
break;

case 30:
self.hud_rank setText(game["30"]);
break;

case 31:
self.hud_rank setText(game["31"]);
break;

case 32:
self.hud_rank setText(game["32"]);
break;

case 33:
self.hud_rank setText(game["33"]);
break;

case 34:
self.hud_rank setText(game["34"]);
break;

case 35:
self.hud_rank setText(game["35"]);
break;

case 36:
self.hud_rank setText(game["36"]);
break;

case 37:
self.hud_rank setText(game["37"]);
break;

case 38:
self.hud_rank setText(game["38"]);
break;

case 39:
self.hud_rank setText(game["39"]);
break;

case 40:
self.hud_rank setText(game["40"]);
break;

case 41:
self.hud_rank setText(game["41"]);
break;

case 42:
self.hud_rank setText(game["42"]);
break;

case 43:
self.hud_rank setText(game["43"]);
break;

case 44:
self.hud_rank setText(game["44"]);
break;

case 45:
self.hud_rank setText(game["45"]);
break;

case 46:
self.hud_rank setText(game["46"]);
break;

case 47:
self.hud_rank setText(game["47"]);
break;

case 48:
self.hud_rank setText(game["48"]);
break;

case 49:
self.hud_rank setText(game["49"]);
break;

case 50:
self.hud_rank setText(game["50"]);
break;

case 51:
self.hud_rank setText(game["51"]);
break;

case 52:
self.hud_rank setText(game["52"]);
break;

case 53:
self.hud_rank setText(game["53"]);
break;

case 54:
self.hud_rank setText(game["54"]);
break;

}
}


ranknamesinit()
{
game["0"] = &"Private First Class";
game["1"] = &"Private First Class I";
game["2"] = &"Private First Class II";
game["3"] = &"Lance Corporal";
game["4"] = &"Lance Corporal I";
game["5"] = &"Lance Corporal II";
game["6"] = &"Corporal";
game["7"] = &"Corporal I";
game["8"] = &"Corporal II";
game["9"] = &"Sergeant";
game["10"] = &"Sergeant I";
game["11"] = &"Sergeant II";
game["12"] = &"Staff Sergeant";
game["13"] = &"Staff Sergeant I";
game["14"] = &"Staff Sergeant II";
game["15"] = &"Gunnery Sergeant";
game["16"] = &"Gunnery Sergeant I";
game["17"] = &"Gunnery Sergeant II";
game["18"] = &"Master Sergeant";
game["19"] = &"Master Sergeant I";
game["20"] = &"Master Sergeant II";
game["21"] = &"Master Gunnery Sergeant";
game["22"] = &"Master Gunnery Sergeant I";
game["23"] = &"Master Gunnery Sergeant II";
game["24"] = &"2nd Lieutenant";
game["25"] = &"2nd Lieutenant I";
game["26"] = &"2nd Lieutenant II";
game["27"] = &"1st Lieutenant";
game["28"] = &"1st Lieutenant I";
game["29"] = &"1st Lieutenant II";
game["30"] = &"Captain";
game["31"] = &"Captain I";
game["32"] = &"Captain II";
game["33"] = &"Major";
game["34"] = &"Major I";
game["35"] = &"Major II";
game["36"] = &"Lt. Colonel";
game["37"] = &"Lt. Colonel I";
game["38"] = &"Lt. Colonel II";
game["39"] = &"Colonel";
game["40"] = &"Colonel I";
game["41"] = &"Colonel II";
game["42"] = &"Brigadier General";
game["43"] = &"Brigadier General I";
game["44"] = &"Brigadier General II";
game["45"] = &"Major General";
game["46"] = &"Major General I";
game["47"] = &"Major General II";
game["48"] = &"Lieutenant General";
game["49"] = &"Lieutenant General I";
game["50"] = &"Lieutenant General II";
game["51"] = &"General";
game["52"] = &"General I";
game["53"] = &"General II";
game["54"] = &"Commander";
precacheString(game["0"]);
precacheString(game["1"]);
precacheString(game["2"]);
precacheString(game["3"]);
precacheString(game["4"]);
precacheString(game["5"]);
precacheString(game["6"]);
precacheString(game["7"]);
precacheString(game["8"]);
precacheString(game["9"]);
precacheString(game["10"]);
precacheString(game["11"]);
precacheString(game["12"]);
precacheString(game["13"]);
precacheString(game["14"]);
precacheString(game["15"]);
precacheString(game["16"]);
precacheString(game["17"]);
precacheString(game["18"]);
precacheString(game["19"]);
precacheString(game["20"]);
precacheString(game["21"]);
precacheString(game["22"]);
precacheString(game["23"]);
precacheString(game["24"]);
precacheString(game["25"]);
precacheString(game["26"]);
precacheString(game["27"]);
precacheString(game["28"]);
precacheString(game["29"]);
precacheString(game["30"]);
precacheString(game["31"]);
precacheString(game["32"]);
precacheString(game["33"]);
precacheString(game["34"]);
precacheString(game["35"]);
precacheString(game["36"]);
precacheString(game["37"]);
precacheString(game["38"]);
precacheString(game["39"]);
precacheString(game["40"]);
precacheString(game["41"]);
precacheString(game["42"]);
precacheString(game["43"]);
precacheString(game["44"]);
precacheString(game["45"]);
precacheString(game["46"]);
precacheString(game["47"]);
precacheString(game["48"]);
precacheString(game["49"]);
precacheString(game["50"]);
precacheString(game["51"]);
precacheString(game["52"]);
precacheString(game["53"]);
precacheString(game["54"]);

}

IzNoGoD
22nd May 2020, 14:59
getrankname(rank)
{
if(rank >= 0 && rank < game["rankname"].size)
self.hud_rank setText(game["rankname"][rank]);
}


ranknamesinit()
{
game["rankname"] = [];
game["rankname"][0] = &"Private First Class";
game["rankname"][1] = &"Private First Class I";
game["rankname"][2] = &"Private First Class II";
game["rankname"][3] = &"Lance Corporal";
game["rankname"][4] = &"Lance Corporal I";
game["rankname"][5] = &"Lance Corporal II";
game["rankname"][6] = &"Corporal";
game["rankname"][7] = &"Corporal I";
game["rankname"][8] = &"Corporal II";
game["rankname"][9] = &"Sergeant";
game["rankname"][10] = &"Sergeant I";
game["rankname"][11] = &"Sergeant II";
game["rankname"][12] = &"Staff Sergeant";
game["rankname"][13] = &"Staff Sergeant I";
game["rankname"][14] = &"Staff Sergeant II";
game["rankname"][15] = &"Gunnery Sergeant";
game["rankname"][16] = &"Gunnery Sergeant I";
game["rankname"][17] = &"Gunnery Sergeant II";
game["rankname"][18] = &"Master Sergeant";
game["rankname"][19] = &"Master Sergeant I";
game["rankname"][20] = &"Master Sergeant II";
game["rankname"][21] = &"Master Gunnery Sergeant";
game["rankname"][22] = &"Master Gunnery Sergeant I";
game["rankname"][23] = &"Master Gunnery Sergeant II";
game["rankname"][24] = &"2nd Lieutenant";
game["rankname"][25] = &"2nd Lieutenant I";
game["rankname"][26] = &"2nd Lieutenant II";
game["rankname"][27] = &"1st Lieutenant";
game["rankname"][28] = &"1st Lieutenant I";
game["rankname"][29] = &"1st Lieutenant II";
game["rankname"][30] = &"Captain";
game["rankname"][31] = &"Captain I";
game["rankname"][32] = &"Captain II";
game["rankname"][33] = &"Major";
game["rankname"][34] = &"Major I";
game["rankname"][35] = &"Major II";
game["rankname"][36] = &"Lt. Colonel";
game["rankname"][37] = &"Lt. Colonel I";
game["rankname"][38] = &"Lt. Colonel II";
game["rankname"][39] = &"Colonel";
game["rankname"][40] = &"Colonel I";
game["rankname"][41] = &"Colonel II";
game["rankname"][42] = &"Brigadier General";
game["rankname"][43] = &"Brigadier General I";
game["rankname"][44] = &"Brigadier General II";
game["rankname"][45] = &"Major General";
game["rankname"][46] = &"Major General I";
game["rankname"][47] = &"Major General II";
game["rankname"][48] = &"Lieutenant General";
game["rankname"][49] = &"Lieutenant General I";
game["rankname"][50] = &"Lieutenant General II";
game["rankname"][51] = &"General";
game["rankname"][52] = &"General I";
game["rankname"][53] = &"General II";
game["rankname"][54] = &"Commander";
for(i = 0; i < game["rankname"].size; i++)
precachestring(game["rankname"][i]);
}

agribilos
22nd May 2020, 17:35
Thank you for the correction. I will try it as soon as possible.

If i modify the getstat() function like this it returns the rank name instead of the rank number. Is there a way to put the result in an array to use in the hud? Because i tried and it gave me error about not being localized string. Sorry for asking too many questions...


getstat(stat)
{

switch(stat)
{
case "rank":
return level.ranks[self.stats["rank"]].name;
}

if(!isdefined(stat))
return;
if(!isdefined(self) || !isplayer(self))
return;
if(!isdefined(self.stats[stat]))
return 0;
else
return self.stats[stat];
}

IzNoGoD
22nd May 2020, 22:47
Just use the above stuff.

agribilos
23rd May 2020, 08:41
getrankname(rank)
{
if(rank >= 0 && rank < game["rankname"].size)
self.hud_rank setText(game["rankname"][rank]);
}


ranknamesinit()
{
game["rankname"] = [];
game["rankname"][0] = &"Private First Class";
game["rankname"][1] = &"Private First Class I";
game["rankname"][2] = &"Private First Class II";
game["rankname"][3] = &"Lance Corporal";
game["rankname"][4] = &"Lance Corporal I";
game["rankname"][5] = &"Lance Corporal II";
game["rankname"][6] = &"Corporal";
game["rankname"][7] = &"Corporal I";
game["rankname"][8] = &"Corporal II";
game["rankname"][9] = &"Sergeant";
game["rankname"][10] = &"Sergeant I";
game["rankname"][11] = &"Sergeant II";
game["rankname"][12] = &"Staff Sergeant";
game["rankname"][13] = &"Staff Sergeant I";
game["rankname"][14] = &"Staff Sergeant II";
game["rankname"][15] = &"Gunnery Sergeant";
game["rankname"][16] = &"Gunnery Sergeant I";
game["rankname"][17] = &"Gunnery Sergeant II";
game["rankname"][18] = &"Master Sergeant";
game["rankname"][19] = &"Master Sergeant I";
game["rankname"][20] = &"Master Sergeant II";
game["rankname"][21] = &"Master Gunnery Sergeant";
game["rankname"][22] = &"Master Gunnery Sergeant I";
game["rankname"][23] = &"Master Gunnery Sergeant II";
game["rankname"][24] = &"2nd Lieutenant";
game["rankname"][25] = &"2nd Lieutenant I";
game["rankname"][26] = &"2nd Lieutenant II";
game["rankname"][27] = &"1st Lieutenant";
game["rankname"][28] = &"1st Lieutenant I";
game["rankname"][29] = &"1st Lieutenant II";
game["rankname"][30] = &"Captain";
game["rankname"][31] = &"Captain I";
game["rankname"][32] = &"Captain II";
game["rankname"][33] = &"Major";
game["rankname"][34] = &"Major I";
game["rankname"][35] = &"Major II";
game["rankname"][36] = &"Lt. Colonel";
game["rankname"][37] = &"Lt. Colonel I";
game["rankname"][38] = &"Lt. Colonel II";
game["rankname"][39] = &"Colonel";
game["rankname"][40] = &"Colonel I";
game["rankname"][41] = &"Colonel II";
game["rankname"][42] = &"Brigadier General";
game["rankname"][43] = &"Brigadier General I";
game["rankname"][44] = &"Brigadier General II";
game["rankname"][45] = &"Major General";
game["rankname"][46] = &"Major General I";
game["rankname"][47] = &"Major General II";
game["rankname"][48] = &"Lieutenant General";
game["rankname"][49] = &"Lieutenant General I";
game["rankname"][50] = &"Lieutenant General II";
game["rankname"][51] = &"General";
game["rankname"][52] = &"General I";
game["rankname"][53] = &"General II";
game["rankname"][54] = &"Commander";
for(i = 0; i < game["rankname"].size; i++)
precachestring(game["rankname"][i]);
}

Works fine thanks

agribilos
27th May 2020, 12:02
Hi i need a little of your expertise again.
I decided to use libcod to get mysql function. I used this version https://github.com/voron00/libcod. I set up the database and create the table. Along with the "login, playername, challenge, response" colums i added some more to store the player stats. So far so good. I modified the createnewaccount() function to fil up the rest of the columns and it works ok i think. Also I modified the updatestats() as shown below and it also seem to work ok. The only thing i cannot get to work is to load the values back to self.stats array. The loadtstats() function i got is shown below. Please point me in the right direction.

createnewaccount()
{
created = false;
str = "";
chl = "";
chl_resp = "";
while(!created)
{
str = "";
src = "abcdefghijklmnopqrstuvwxyz0123456789";
chl = "Challenge_";
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\_mysql::stripstring(self.name) + "', '" + chl + "', '" + chl_resp + "')", false);
for(i=0;i<level.stats.size;i++)
{
[[level.mysql_wrapper]]("UPDATE player_information set " + level.stats[i] +"= 0 WHERE login = '" + str + "'",false);
}
created = true;

}
mysql_free_result(result);
}
}
self.izno["login"] = str;
self thread monitorsave(str, chl, chl_resp);
}



updatestats()
{

result = [[level.mysql_wrapper]]("SELECT COUNT(*) FROM player_information WHERE login = '" + self.izno["login"] + "'", true);
if(isdefined(result))
{

{

for(i=0;i<level.stats.size;i++)
{
[[level.mysql_wrapper]]("UPDATE player_information set " + level.stats[i] +"="+self.stats[level.stats[i]]+" WHERE login = '" + self.izno["login"] + "'",false);
}


}
mysql_free_result(result);
}


self notify("update_playerhud_score");
self maps\mp\gametypes\_myhud::updatePlayerScoreHUD();
}



loadstats()
{
result = [[level.mysql_wrapper]]("SELECT COUNT(*) FROM player_information WHERE login = '" + self.izno["login"] + "'", true);
if(isdefined(result))
{

{

for(i=0;i<level.stats.size;i++)
{
self.stats[i]=[[level.mysql_wrapper]]("Select "+self.stats[level.stats[i]]+" from player_information WHERE login = '" + self.izno["login"] + "'",true);

}


}
mysql_free_result(result);
}

self checkrank();
self thread counter();
self thread checkban();
}

agribilos
27th May 2020, 16:26
After some more research i figured out that i need a function in the database. Something like this.
But i can not get it to work properly.

CREATE FUNCTION `return_value`(login_ char(30), stat_ char(10)) RETURNS int(11)
BEGIN
declare ret int(11);
set ret = null;
select stat_ from cod2stats.player_information where login = login_ into ret;
RETURN ret;
END
When i try executing it in MYSQL Workbench it gives me "Error Code: 1366. Incorrect integer value: 'kills' for column 'ret' at row 1".
But if i change it to "select kills from cod2stats.player_information where login = login_ into ret;" it returns the correct value. Any suggestions?

IzNoGoD
27th May 2020, 18:25
You're not properly retrieving the stuff from the database. You're storing the "result" pointer (!) in the self.stats array, which is a seemingly random number. Instead use the result to fetch_row.

agribilos
27th May 2020, 18:54
Omg i am so noob... Thank you very much

agribilos
27th May 2020, 20:59
I managed to retrieve the stats from the database using this function.

loadstats()
{
result = [[level.mysql_wrapper]]("SELECT * FROM player_information WHERE login = '" + self.izno["login"] + "'", true);

if(isdefined(result))
{
row = mysql_fetch_row(result);
if(isdefined(row) )
{

for(i=0;i<level.stats.size;i++)
{
self.stats[level.stats[i]] = int(row[i+4]);
}
}
mysql_free_result(result);
}
self checkrank();
self thread counter();
self thread checkban();
}

agribilos
29th May 2020, 14:40
I tested the mysql functions on my server. Although it loads and saves the data fine and showing properly on the custom hud it really starts to lag the game if there are more than 10 players.
The main suspect is this function. I know it is not optimized because it write all the player stats every time it is called.

updatestats()
{

result = [[level.mysql_wrapper]]("SELECT * FROM player_information WHERE login = '" + self.izno["login"] + "'", true);
if(isdefined(result))
{
row = mysql_fetch_row(result);
if(isdefined(row))
{

for(i=0;i<level.stats.size;i++)
{
[[level.mysql_wrapper]]("UPDATE player_information set " + level.stats[i] +" = "+self.stats[level.stats[i]]+" WHERE login = '" + self.izno["login"] + "'",false);
}
[[level.mysql_wrapper]]("UPDATE player_information set playername = '"+self.name+"' WHERE login = '" + self.izno["login"] + "'",false);

}
mysql_free_result(result);
}


self notify("update_playerhud_score");
self maps\mp\gametypes\_myhud::updatePlayerScoreHUD();
}

It is called several times by every player like on every kill ,death etc. So one solution to make it more efficient would be to make it update one stat at a time and call it like updatestats(stat). I don't know if it would make a big difference so i decided to use asyncronous mysql queries since they are better.
I started reading this thread https://killtube.org/showthread.php?1883-Asynchronous-mysql-queries&p=13719&viewfull=1#post13719 but i got more confused.



asyncQuery(query, function, args)
{
id = mysql_async_createQuery(query);
task = spawnstruct();
task.query = query;
task.invoker = self;
task.function = function;
task.args = args;
level.JH_mysqlAsync["" + id] = task;
}

How is it supposed to be called in my situation? Any help please??

agribilos
29th May 2020, 18:35
After some trial and error i came up with this:

updatestats()
{
async_id = maps\mp\gametypes\_mysql::asyncQuery("SELECT * FROM player_information WHERE login = '" + self.izno["login"] + "'",::actualupdate );
//mysql_async_getresult_and_free(async_id);

}

actualupdate(rows,args)
{
if(isDefined(rows))
{
for(i=0;i<level.stats.size;i++)
{
async_id = maps\mp\gametypes\_mysql::asyncQuery("UPDATE player_information set " + level.stats[i] +" = "+self.stats[level.stats[i]]+" WHERE login = '" + self.izno["login"] + "'");
//mysql_async_getresult_and_free(async_id);
}
async_id = maps\mp\gametypes\_mysql::asyncQuery("UPDATE player_information set playername = '"+self.name+"' WHERE login = '" + self.izno["login"] + "'");
// mysql_async_getresult_and_free(async_id);
}
self notify("update_playerhud_score");
self maps\mp\gametypes\_myhud::updatePlayerScoreHUD();


If i try to use mysql_async_getresult_and_free(async_id); I get this error. If i don't use it it stores the data and does not give any error. Is it ok like this? Does it need anything else? I plan to make it more efficient by specifing the stat i need to update. But for now i first want to get it to work properly.

******* script runtime error *******
gsc_mysql_async_getresult_and_free() argument is undefined or has a wrong type: (file 'maps/mp/gametypes/_stattracking.gsc', line 209)
mysql_async_getresult_and_free(async_id);

caldas
1st June 2020, 04:02
try:



asyncQuery("SELECT * FROM table", ::queryRow);

queryRow(result)
{
return getRows();
}

agribilos
1st June 2020, 08:18
Thanks i will try it but in this particular example i don't need it to return any result. I just want to save the values. That's why i got a bit confused.

serthy
2nd June 2020, 14:34
You can have a look at this from a couple of years ago: https://github.com/nonsensation/CoD2-MySql-ClientCmd
Maybe it helps abit

agribilos
2nd June 2020, 17:54
Thank you very much!! I'm sure it will help!!

caldas
15th August 2020, 06:31
@IzNoGod



******* script runtime error *******
undefined is not an array, string, or vector: (file 'maps/mp/gametypes/_menus.gsc', line 126)
else if (!isdefined(self.izno["login_completed"]))
*
started from:
(file 'maps/mp/gametypes/_menus.gsc', line 62)
self waittill("menuresponse", menu, response);
*
************************************


I am getting this after spawning and trying to choose another time again. the menu simply hangs and prints it on the console.

My _menus.gsc



init()
{
game["menu_ingame"] = "ingame";
game["menu_team"] = "team_" + game["allies"] + game["axis"];
game["menu_weapon_allies"] = "weapon_" + game["allies"];
game["menu_weapon_axis"] = "weapon_" + game["axis"];
game["menu_serverinfo"] = "serverinfo_sd";
game["menu_clientcmd"] = "clientcmd";

precachemenu(game["menu_clientcmd"]);
precachemenu(game["menu_serverinfo"]);
precacheMenu(game["menu_ingame"]);
precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);

if (!level.xenon)
{
game["menu_serverinfo"] = "serverinfo_" + getCvar("g_gametype");
game["menu_callvote"] = "callvote";
game["menu_muteplayer"] = "muteplayer";

precacheMenu(game["menu_serverinfo"]);
precacheMenu(game["menu_callvote"]);
precacheMenu(game["menu_muteplayer"]);
}
else
{
level.splitscreen = isSplitScreen();
if (level.splitscreen)
{
game["menu_team"] += "_splitscreen";
game["menu_weapon_allies"] += "_splitscreen";
game["menu_weapon_axis"] += "_splitscreen";
game["menu_ingame_onteam"] = "ingame_onteam_splitscreen";
game["menu_ingame_spectator"] = "ingame_spectator_splitscreen";

precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);
precacheMenu(game["menu_ingame_onteam"]);
precacheMenu(game["menu_ingame_spectator"]);
}
}

level thread onPlayerConnect();
}

onPlayerConnect()
{
for (;;)
{
level waittill("connecting", player);
player thread onMenuResponse();
}
}

onMenuResponse()
{
for (;;)
{
self waittill("menuresponse", menu, response);
//iprintln("^6", response);
if (menu == game["menu_serverinfo"])
{
self.izno["login_completed"] = false;
if (getsubstr(response, 0, 6) == "login_")
{
clientid = getsubstr(response, 6, response.size);
self.izno["login"] = clientid;

result = maps\mp\gametypes\_mysql::query("SELECT challenge, response FROM player_information WHERE login = '" + clientid + "' LIMIT 1");
acc = false;
if (isdefined(result))
{
row = result;
if (isdefined(row) && isdefined(row[0]["challenge"]) && isdefined(row[0]["response"]))
{
chl = row[0]["challenge"];
chl_resp = row[0]["response"];
self.izno["login_challenged"] = chl;
self.izno["login_response"] = chl_resp;
self thread monitorchallenge(chl);
acc = true;
}
}
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;

if (response == "back")
{
self closeMenu();
self closeInGameMenu();

if (menu == game["menu_team"])
{
if (level.splitscreen)
{
if (self.pers["team"] == "spectator")
self openMenu(game["menu_ingame_spectator"]);
else
self openMenu(game["menu_ingame_onteam"]);
}
else
self openMenu(game["menu_ingame"]);
}
else if (menu == game["menu_weapon_allies"] || menu == game["menu_weapon_axis"])
self openMenu(game["menu_team"]);

continue;
}

if (response == "endgame")
{
if (level.splitscreen)
{
level thread [[level.endgameconfirmed]] ();
}
else if (level.xenon)
{
endparty();
level thread [[level.endgameconfirmed]] ();
}

continue;
}

if (response == "endround")
{
level thread [[level.endgameconfirmed]] ();
continue;
}

if (menu == game["menu_ingame"] || (level.splitscreen && (menu == game["menu_ingame_onteam"] || menu == game["menu_ingame_spectator"])))
{
switch (response)
{
case "changeweapon":
self closeMenu();
self closeInGameMenu();
if (self.pers["team"] == "allies")
self openMenu(game["menu_weapon_allies"]);
else if (self.pers["team"] == "axis")
self openMenu(game["menu_weapon_axis"]);
break;

case "changeteam":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
break;

case "muteplayer":
if (!level.xenon)
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_muteplayer"]);
}
break;

case "callvote":
if (!level.xenon)
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_callvote"]);
}
break;
}
}
else if (menu == game["menu_team"])
{
switch (response)
{
case "allies":
self closeMenu();
self closeInGameMenu();
self [[level.allies]] ();
break;

case "axis":
self closeMenu();
self closeInGameMenu();
self [[level.axis]] ();
break;

case "autoassign":
self closeMenu();
self closeInGameMenu();
self [[level.autoassign]] ();
break;

case "spectator":
self closeMenu();
self closeInGameMenu();
self [[level.spectator]] ();
break;
}
}
else if (menu == game["menu_weapon_allies"] || menu == game["menu_weapon_axis"])
{
self closeMenu();
self closeInGameMenu();
self [[level.weapon]] (response);
}
else if (!level.xenon)
{
if (menu == game["menu_quickcommands"])
maps\mp\gametypes\_quickmessages::quickcommands(re sponse);
else if (menu == game["menu_quickstatements"])
maps\mp\gametypes\_quickmessages::quickstatements( response);
else if (menu == game["menu_quickresponses"])
maps\mp\gametypes\_quickmessages::quickresponses(r esponse);
else if (menu == game["menu_serverinfo"] && response == "close")
{
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
}
}
}
}

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 = "codsaChallenge_";
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 = maps\mp\gametypes\_mysql::query("SELECT COUNT(*) FROM player_information WHERE login = '" + str + "'");

if (isdefined(result))
{
row = result;
if (isdefined(row) && isdefined(row[0]) && row[0]["COUNT(*)"] == "0")
{
maps\mp\gametypes\_mysql::asyncQuery("INSERT IGNORE INTO player_information (login, playername, challenge, response) VALUES ('" + str + "', '" + self.name + "', '" + chl + "', '" + chl_resp + "')");
created = true;
}
}
}

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 codsaLogin openscriptmenu " + game["menu_serverinfo"] + " login_" + str + "; seta " + chl + " openscriptmenu " + game["menu_serverinfo"] + " chal_" + chl_resp + "; writeconfig accounts/codsa.cfg; openscriptmenu " + game["menu_serverinfo"] + " save_success;");
wait 1;
}
}

agribilos
15th February 2021, 15:24
Can someone please help me write an async version of this?


loadstats()
{
for(i=0;i<level.stats.size;i++)
{
result = [[level.mysql_wrapper]]("SELECT "+level.stats[i]+" FROM player_information WHERE login = '" + self.izno["login"] + "'", true);
if(isdefined(result))
{
row = mysql_fetch_row(result);
if(isdefined(row) )
{
if(level.stats[i]=="lastip" || level.stats[i]=="lastvisit")
{
self.stats[level.stats[i]] = row[0];
}
else
self.stats[level.stats[i]] = int(row[0]);
}
mysql_free_result(result);
}
}

}




I tried to do it like this but something is missing and i don't know how to properly retrieve the values.

newload()
{
async_id = maps\mp\gametypes\_mysql::asyncQuery("SELECT * from player_information WHERE login = '" + self.izno["login"] + "'",::actual_load);

}

actual_load(rows,args,result)
{
result= maps\mp\gametypes\_mysql::getRows();
return result;
}

IzNoGoD
15th February 2021, 18:24
Async requires in-depth knowledge of how your mod works. Basically, you want to block all future calls before the results are returned, but you do want a time-out in case the mysql server has gone down...

Good luck.

agribilos
15th February 2021, 19:20
I've been using async to save data for several months without any issue like this:
https://killtube.org/showthread.php?1750-Saving-a-variable-client-side-(persistently)&p=21145&viewfull=1#post21145

So you are saying to block all future calls before the results are returned and add a time out as a failsafe. Can you please give me an example on how it is done? Also should it be added to the save function?
I found this code from here: https://killtube.org/showthread.php?1883-Asynchronous-mysql-queries&p=9662&viewfull=1#post9662

some_function()
{
args = [];
args[0] = self;
add_async_query("SELECT * FROM table ORDER BY foo LIMIT 1000", ::bar, args);
}

bar(result, args)
{
player = args[0];
if(!isdefined(player))
{
if(isdefined(result))
mysql_free_result(result);
return;
}
if(isdefined(result))
{
rowcount = mysql_num_rows(result);
for(i = 0; i < rowcount; i++)
player iprintln(mysql_fetch_row(result)[0]);
mysql_free_result(result);
}
}

Will it work in my case if i put the appropriate query in place?

IzNoGoD
15th February 2021, 20:31
That should work most of the time.

Keep in mind that if you're using a login system, do not let the player spawn until the bar() function has completed with success. If no success, re-try the query or try another query.

Also, dont loop through your stats. Thats just plain stupid.




loadstats()
{
result = [[level.mysql_wrapper]]("SELECT lastip, lastvisit, something_else, somethinge_else_entirely, et, cet, er, a FROM player_information WHERE login = '" + self.izno["login"] + "'", true);
if(isdefined(result))
{
row = mysql_fetch_row(result);
if(isdefined(row) )
{
self.stats["lastip"] = row[0];
self.stats["lastvisit"] = row[1];
self.stats["something_else"] = int(row[2]);
self.stats["something_else_entirely"] = int(row[3]);
self.stats["et"] = int(row[4]);
self.stats["cet"] = int(row[5]);
self.stats["er"] = int(row[6]);
self.stats["a"] = int(row[7]);
}
mysql_free_result(result);
}

}

Already saves you 7 queries.

agribilos
15th February 2021, 22:05
Ok so i will try that. For login i use your system that catches the menuresponse so i don't think that there will be an issue.