]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/playerstats.qc
91ca32dc1115e23ac27ef03f06e176eafc4a1624
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / playerstats.qc
1 #ifdef SVQC
2 //float PS_PM_IN_DB;   // playerstats_prematch_in_db      // db for info COLLECTED at the beginning of a match
3 float PS_GR_OUT_DB;  // playerstats_gamereport_out_db   // db of info SENT at the end of a match
4 //float PS_GR_IN_DB;   // playerstats_gamereport_in_db    // db for info COLLECTED at the end of a match
5 //float PS_B_IN_DB;    // playerstats_playerbasic_in_db   // db for info COLLECTED for basic player info (ELO)
6 // http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt
7 #endif
8
9 #ifdef MENUQC
10 //float PS_D_IN_DB; // playerstats_playerdetail_in_db  // db for info COLLECTED for detailed player profile display
11 // http://stats.xonotic.org/player/me
12 #endif
13
14 #ifdef SVQC
15 //string PS_PM_IN_EVL;   // playerstats_prematch_in_events_last
16 string PS_GR_OUT_TL;   // playerstats_gamereport_out_teams_last
17 string PS_GR_OUT_PL;   // playerstats_gamereport_out_players_las
18 string PS_GR_OUT_EVL;  // playerstats_gamereport_out_events_last
19 //string PS_GR_IN_PL;    // playerstats_gamereport_in_players_last
20 //string PS_GR_IN_EVL;   // playerstats_gamereport_in_events_last
21 //string PS_B_IN_PL;     // playerstats_playerbasic_in_players_las
22 //string PS_B_IN_EVL;    // playerstats_playerbasic_in_events_last
23 #endif
24
25 #ifdef MENUQC
26 //string PS_D_IN_EVL; // playerstats_playerdetail_in_events_last
27 #endif
28
29 #ifdef SVQC
30 void PlayerStats_Prematch(void)
31 {
32         //foobar
33 }
34
35 void PlayerStats_GameReport_AddPlayer(entity e)
36 {
37         if((PS_GR_OUT_DB < 0) || (e.playerstats_id)) { return; }
38
39         // set up player identification
40         string s = string_null;
41
42         if((e.crypto_idfp != "") && (e.cvar_cl_allow_uidtracking == 1))
43                 { s = e.crypto_idfp; }
44         else if(IS_BOT_CLIENT(e))
45                 { s = sprintf("bot#%g#%s", skill, e.cleanname); }
46                 
47         if((s == "") || find(world, playerstats_id, s)) // already have one of the ID - next one can't be tracked then!
48         {
49                 if(IS_BOT_CLIENT(e))
50                         { s = sprintf("bot#%d", e.playerid); }
51                 else
52                         { s = sprintf("player#%d", e.playerid); }
53         }
54         
55         e.playerstats_id = strzone(s);
56
57         // now add the player to the database
58         string key = sprintf("%s:*", e.playerstats_id);
59         string p = db_get(PS_GR_OUT_DB, key);
60         
61         if(p == "")
62         {
63                 if(PS_GR_OUT_PL)
64                 {
65                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_PL);
66                         strunzone(PS_GR_OUT_PL);
67                 }
68                 else { db_put(PS_GR_OUT_DB, key, "#"); }
69                 PS_GR_OUT_PL = strzone(e.playerstats_id);
70         }
71 }
72
73 void PlayerStats_GameReport_AddTeam(float t)
74 {
75         if(PS_GR_OUT_DB < 0) { return; }
76
77         string key = sprintf("%d", t);
78         string p = db_get(PS_GR_OUT_DB, key);
79         
80         if(p == "")
81         {
82                 if(PS_GR_OUT_TL)
83                 {
84                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_TL);
85                         strunzone(PS_GR_OUT_TL);
86                 }
87                 else { db_put(PS_GR_OUT_DB, key, "#"); }
88                 PS_GR_OUT_TL = strzone(key);
89         }
90 }
91
92 void PlayerStats_GameReport_AddEvent(string event_id)
93 {
94         if(PS_GR_OUT_DB < 0) { return; }
95
96         string key = sprintf("*:%s", event_id);
97         string p = db_get(PS_GR_OUT_DB, key);
98         
99         if(p == "")
100         {
101                 if(PS_GR_OUT_EVL)
102                 {
103                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_EVL);
104                         strunzone(PS_GR_OUT_EVL);
105                 }
106                 else { db_put(PS_GR_OUT_DB, key, "#"); }
107                 PS_GR_OUT_EVL = strzone(event_id);
108         }
109 }
110
111 // referred to by PS_GR_P_ADDVAL and PS_GR_T_ADDVAL
112 float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
113 {
114         if((prefix == "") || PS_GR_OUT_DB < 0) { return 0; }
115
116         string key = sprintf("%s:%s", prefix, event_id);
117         float val = stof(db_get(PS_GR_OUT_DB, key));
118         val += value;
119         db_put(PS_GR_OUT_DB, key, ftos(val));
120         return val;
121 }
122
123 void PlayerStats_GameReport_Accuracy(entity p)
124 {
125     entity w;
126     float i;
127
128         #define PAC p.accuracy
129     for(i = WEP_FIRST; i <= WEP_LAST; ++i)
130     {
131         w = get_weaponinfo(i);
132         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-hit"), PAC.(accuracy_hit[i-1]));
133         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-fired"), PAC.(accuracy_fired[i-1]));
134         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-hit"), PAC.(accuracy_cnt_hit[i-1]));
135         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-fired"), PAC.(accuracy_cnt_fired[i-1]));
136         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-frags"), PAC.(accuracy_frags[i-1]));
137     }
138     #undef PAC
139 }
140
141 void PlayerStats_GameReport_FinalizePlayer(entity p)
142 {
143         if((p.playerstats_id == "") || PS_GR_OUT_DB < 0) { return; }
144
145         // add global info!
146         if(p.alivetime)
147         {
148                 PS_GR_P_ADDVAL(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
149                 p.alivetime = 0;
150         }
151
152         db_put(PS_GR_OUT_DB, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid));
153
154         if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p))
155                 db_put(PS_GR_OUT_DB, sprintf("%s:_netname", p.playerstats_id), p.netname);
156
157         if(teamplay)
158                 db_put(PS_GR_OUT_DB, sprintf("%s:_team", p.playerstats_id), ftos(p.team));
159
160         if(stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0)
161                 PS_GR_P_ADDVAL(p, PLAYERSTATS_JOINS, 1);
162
163         PlayerStats_GameReport_Accuracy(p);
164
165         if(IS_REAL_CLIENT(p))
166         {
167                 if(p.latency_cnt)
168                 {
169                         float latency = (p.latency_sum / p.latency_cnt);
170                         if(latency) { PS_GR_P_ADDVAL(p, PLAYERSTATS_AVGLATENCY, latency); }
171                 }
172         }
173
174         strunzone(p.playerstats_id);
175         p.playerstats_id = string_null;
176 }
177
178 void PlayerStats_GameReport_EndMatch(float finished)
179 {
180         if(PS_GR_OUT_DB < 0) { return; }
181         
182         PlayerScore_Sort(score_dummyfield, 0, 0, 0);
183         PlayerScore_Sort(scoreboard_pos, 1, 1, 1);
184         if(teamplay) { PlayerScore_TeamStats(); }
185
186         entity p;
187         FOR_EACH_CLIENT(p)
188         {
189                 // add personal score rank
190                 PS_GR_P_ADDVAL(p, PLAYERSTATS_RANK, p.score_dummyfield);
191
192                 // scoreboard data
193                 if(p.scoreboard_pos)
194                 {
195                         // scoreboard is valid!
196                         PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
197
198                         // add scoreboard position
199                         PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
200
201                         // add scoreboard data
202                         PlayerScore_PlayerStats(p);
203
204                         // if the match ended normally, add winning info
205                         if(finished)
206                         {
207                                 PS_GR_P_ADDVAL(p, PLAYERSTATS_WINS, p.winning);
208                                 PS_GR_P_ADDVAL(p, PLAYERSTATS_MATCHES, 1);
209                         }
210                 }
211
212                 // collect final player information
213                 PlayerStats_GameReport_FinalizePlayer(p);
214         }
215
216         if(autocvar_g_playerstats_gamereport_uri != "")
217         {
218                 PlayerStats_GameReport_DelayMapVote = TRUE;
219                 url_multi_fopen(
220                         autocvar_g_playerstats_gamereport_uri,
221                         FILE_APPEND,
222                         PlayerStats_GameReport_Handler,
223                         world
224                 );
225         }
226         else
227         {
228                 PlayerStats_GameReport_DelayMapVote = FALSE;
229                 db_close(PS_GR_OUT_DB);
230                 PS_GR_OUT_DB = -1;
231         }
232 }
233
234 void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that scores are added properly
235 {
236         if(autocvar_g_playerstats_gamereport_uri == "") { return; }
237
238         PS_GR_OUT_DB = -1;
239         PS_GR_OUT_DB = db_create();
240
241         if(PS_GR_OUT_DB >= 0)
242         {
243                 PlayerStats_GameReport_DelayMapVote = TRUE;
244
245                 serverflags |= SERVERFLAG_PLAYERSTATS;
246
247                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ALIVETIME);
248                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_AVGLATENCY);
249                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_WINS);
250                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_MATCHES);
251                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_JOINS);
252                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_VALID);
253                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_POS);
254                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_RANK);
255
256                 // accuracy stats
257                 entity w;
258                 float i;
259                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
260                 {
261                         w = get_weaponinfo(i);
262                         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-hit"));
263                         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-fired"));
264                         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-hit"));
265                         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-fired"));
266                         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-frags"));
267                 }
268
269                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
270                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
271                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
272                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15);
273                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20);
274                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25);
275                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30);
276                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
277                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
278                 PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
279         }
280         else { PlayerStats_GameReport_DelayMapVote = FALSE; }
281 }
282
283 void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
284 {
285         string t, tn;
286         string p, pn;
287         string e, en;
288         string nn, tt;
289         string s;
290
291         switch(status)
292         {
293                 // ======================================
294                 // -- OUTGOING GAME REPORT INFORMATION --
295                 // ======================================
296                 /* SPECIFICATIONS:
297                  * V: format version (always a fixed number) - this MUST be the first line!
298                  * #: comment (MUST be ignored by any parser)
299                  * R: release information on the server
300                  * G: game type
301                  * O: mod name (icon request) as in server browser
302                  * M: map name
303                  * I: match ID (see "matchid" in g_world.qc)
304                  * S: "hostname" of the server
305                  * C: number of "unpure" cvar changes
306                  * U: UDP port number of the server
307                  * D: duration of the match
308                  * L: "ladder" in which the server is participating in
309                  * P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
310                  * Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!)
311                  * i: player index
312                  * n: nickname of the player (optional)
313                  * t: team ID
314                  * e: followed by an event name, a space, and the event count/score
315                  *  event names can be:
316                  *   alivetime: total playing time of the player
317                  *   avglatency: average network latency compounded throughout the match
318                  *   wins: number of games won (can only be set if matches is set)
319                  *   matches: number of matches played to the end (not aborted by map switch)
320                  *   joins: number of matches joined (always 1 unless player never played during the match)
321                  *   scoreboardvalid: set to 1 if the player was there at the end of the match
322                  *   total-<scoreboardname>: total score of that scoreboard item
323                  *   scoreboard-<scoreboardname>: end-of-game score of that scoreboard item (can differ in non-team games)
324                  *   achievement-<achievementname>: achievement counters (their "count" is usually 1 if nonzero at all)
325                  *   kills-<index>: number of kills against the indexed player
326                  *   rank <number>: rank of player
327                  *   acc-<weapon netname>-hit: total damage dealt
328                  *   acc-<weapon netname>-fired: total damage that all fired projectiles *could* have dealt
329                  *   acc-<weapon netname>-cnt-hit: amount of shots that actually hit
330                  *   acc-<weapon netname>-cnt-fired: amount of fired shots
331                  *   acc-<weapon netname>-frags: amount of frags dealt by weapon
332                  */
333                 case URL_READY_CANWRITE:
334                 {
335                         url_fputs(fh, "V 9\n");
336                         #ifdef WATERMARK
337                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
338                         #endif
339                         url_fputs(fh, sprintf("G %s\n", GetGametype()));
340                         url_fputs(fh, sprintf("O %s\n", modname));
341                         url_fputs(fh, sprintf("M %s\n", GetMapname()));
342                         url_fputs(fh, sprintf("I %s\n", matchid));
343                         url_fputs(fh, sprintf("S %s\n", cvar_string("hostname")));
344                         url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count));
345                         url_fputs(fh, sprintf("U %d\n", cvar("port")));
346                         url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime)));
347                         url_fputs(fh, sprintf("L %s\n", autocvar_g_playerstats_gamereport_ladder));
348
349                         // TEAMS
350                         if(teamplay)
351                         {
352                                 for(t = PS_GR_OUT_TL; (tn = db_get(PS_GR_OUT_DB, sprintf("%d", stof(t)))) != ""; t = tn)
353                                 {
354                                         // start team section
355                                         url_fputs(fh, sprintf("Q team#%s\n", t));
356
357                                         // output team events // todo: does this do unnecessary loops? perhaps we should do a separate "team_events_last" tracker..."
358                                         for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
359                                         {
360                                                 float v = stof(db_get(PS_GR_OUT_DB, sprintf("team#%d:%s", stof(t), e)));
361                                                 if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
362                                         }
363                                 }
364                         }
365
366                         // PLAYERS
367                         for(p = PS_GR_OUT_PL; (pn = db_get(PS_GR_OUT_DB, sprintf("%s:*", p))) != ""; p = pn)
368                         {
369                                 // start player section
370                                 url_fputs(fh, sprintf("P %s\n", p));
371
372                                 // playerid/index (entity number for this server)
373                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_playerid", p));
374                                 if(nn != "") { url_fputs(fh, sprintf("i %s\n", nn)); }
375
376                                 // player name 
377                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_netname", p));
378                                 if(nn != "") { url_fputs(fh, sprintf("n %s\n", nn)); }
379
380                                 // team identification number
381                                 if(teamplay)
382                                 {
383                                         tt = db_get(PS_GR_OUT_DB, sprintf("%s:_team", p));
384                                         url_fputs(fh, sprintf("t %s\n", tt));
385                                 }
386
387                                 // output player events
388                                 for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
389                                 {
390                                         float v = stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p, e)));
391                                         if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
392                                 }
393                         }
394                         url_fputs(fh, "\n");
395                         url_fclose(fh);
396                         break;
397                 }
398
399                 // ======================================
400                 // -- INCOMING GAME REPORT INFORMATION --
401                 // ======================================
402                 /* SPECIFICATIONS:
403                  * stuff
404                  */
405                 case URL_READY_CANREAD:
406                 {
407                         // url_fclose is processing, we got a response for writing the data
408                         // this must come from HTTP
409                         print("Got response from player stats server:\n");
410                         while((s = url_fgets(fh))) { print("  ", s, "\n"); }
411                         print("End of response.\n");
412                         url_fclose(fh);
413                         break;
414                 }
415                 
416                 case URL_READY_CLOSED:
417                 {
418                         // url_fclose has finished
419                         print("Player stats written\n");
420                         PlayerStats_GameReport_DelayMapVote = FALSE;
421                         db_close(PS_GR_OUT_DB);
422                         PS_GR_OUT_DB = -1;
423                         break;
424                 }
425                 
426                 case URL_READY_ERROR:
427                 default:
428                 {
429                         print("Player stats writing failed: ", ftos(status), "\n");
430                         PlayerStats_GameReport_DelayMapVote = FALSE;
431                         if(PS_GR_OUT_DB >= 0)
432                         {
433                                 db_close(PS_GR_OUT_DB);
434                                 PS_GR_OUT_DB = -1;
435                         }
436                         break;
437                 }
438         }
439 }
440 #endif // SVQC
441
442 #ifdef MENUQC
443 void PlayerStats_PlayerDetail_Handler(entity fh, entity p, float status)
444 {
445         switch(status)
446         {
447                 case URL_READY_CANWRITE:
448                 {
449                         print("-- Sending data to player stats server\n");
450                         /*url_fputs(fh, "V 1\n");
451                         #ifdef WATERMARK
452                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
453                         #endif
454                         url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
455                         url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
456                         url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
457                         url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
458                         url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
459                         */url_fputs(fh, "\n");
460                         url_fclose(fh);
461                         break;
462                 }
463                 
464                 case URL_READY_CANREAD:
465                 {
466                         string s = "";
467                         print("-- Got response from player stats server:\n");
468                         //string gametype = string_null;
469                         while((s = url_fgets(fh)))
470                         {
471                                 print("  ", s, "\n");
472                                 /*
473                                 string key = "", value = "", data = "";
474
475                                 n = tokenizebyseparator(s, " "); // key (value) data
476                                 if (n == 1)
477                                         continue;
478                                 else if (n == 2)
479                                 {
480                                 key = argv(0);
481                                 data = argv(1);
482                                 }
483                                 else if (n >= 3)
484                                 {
485                                                                 key = argv(0);
486                                                                 value = argv(1);
487                                                                 data = argv(2);
488                                 }
489
490                                 if (data == "")
491                                 continue;
492
493                                 if (key == "#")
494                                 continue;
495                                 else if (key == "V")
496                                 PlayerInfo_AddItem(p, "_version", data);
497                                 else if (key == "R")
498                                 PlayerInfo_AddItem(p, "_release", data);
499                                 else if (key == "T")
500                                 PlayerInfo_AddItem(p, "_time", data);
501                                 else if (key == "S")
502                                 PlayerInfo_AddItem(p, "_statsurl", data);
503                                 else if (key == "P")
504                                 PlayerInfo_AddItem(p, "_hashkey", data);
505                                 else if (key == "n")
506                                 PlayerInfo_AddItem(p, "_playernick", data);
507                                 else if (key == "i")
508                                 PlayerInfo_AddItem(p, "_playerid", data);
509                                 else if (key == "G")
510                                 gametype = data;
511                                 else if (key == "e" && value != "")
512                                 {
513                                 if (gametype == "")
514                                 PlayerInfo_AddItem(p, value, data);
515                                 else
516                                 PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data);
517                                 }
518                                 else
519                                 continue;
520                                 */
521                         }
522                         print("-- End of response.\n");
523                         url_fclose(fh);
524                         break;
525                 }
526                 case URL_READY_CLOSED:
527                 {
528                         // url_fclose has finished
529                         print("Player stats synchronized with server\n");
530                         break;
531                 }
532                 
533                 case URL_READY_ERROR:
534                 default:
535                 {
536                         print("Receiving player stats failed: ", ftos(status), "\n");
537                         break;
538                 }
539         }
540 }
541
542 void PlayerStats_PlayerDetail()
543 {
544         //PS_D_IN_DB = -1;
545         //PS_D_IN_DB = db_create();
546
547         //if(PS_D_IN_DB < 0) { return; }
548
549         if((autocvar_g_playerstats_playerdetail_uri != "") && (crypto_getmyidstatus(0) > 0))
550         {
551                 //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
552                 print("Retrieving playerstats from URL: ", autocvar_g_playerstats_playerdetail_uri, "\n");
553                 url_single_fopen(
554                         autocvar_g_playerstats_playerdetail_uri,
555                         FILE_APPEND,
556                         PlayerStats_PlayerDetail_Handler,
557                         world
558                 );
559         }
560 }
561 #endif
562
563 /*
564 void PlayerInfo_AddPlayer(entity e)
565 {
566         if(playerinfo_db < 0)
567                 return;
568
569         string key;
570         key = sprintf("#%d:*", e.playerid); // TODO: use hashkey instead?
571
572         string p;
573         p = db_get(playerinfo_db, key);
574         if(p == "")
575         {
576                 if(playerinfo_last)
577                 {
578                         db_put(playerinfo_db, key, playerinfo_last);
579                         strunzone(playerinfo_last);
580                 }
581                 else
582                         db_put(playerinfo_db, key, "#");
583                 playerinfo_last = strzone(ftos(e.playerid));
584                 print("  Added player ", ftos(e.playerid), " to playerinfo_db\n");//DEBUG//
585         }
586 }
587
588 void PlayerInfo_AddItem(entity e, string item_id, string val)
589 {
590         if(playerinfo_db < 0)
591                 return;
592
593         string key;
594         key = sprintf("*:%s", item_id);
595
596         string p;
597         p = db_get(playerinfo_db, key);
598         if(p == "")
599         {
600                 if(playerinfo_events_last)
601                 {
602                         db_put(playerinfo_db, key, playerinfo_events_last);
603                         strunzone(playerinfo_events_last);
604                 }
605                 else
606                         db_put(playerinfo_db, key, "#");
607                 playerinfo_events_last = strzone(item_id);
608         }
609
610         key = sprintf("#%d:%s", e.playerid, item_id);
611         db_put(playerinfo_db, key, val);
612         print("  Added item ", key, "=", val, " to playerinfo_db\n");//DEBUG//
613 }
614
615 string PlayerInfo_GetItem(entity e, string item_id)
616 {
617         if(playerinfo_db < 0)
618                 return "";
619
620         string key;
621         key = sprintf("#%d:%s",  e.playerid, item_id);
622         return db_get(playerinfo_db, key);
623 }
624
625 string PlayerInfo_GetItemLocal(string item_id)
626 {
627         entity p = spawn();
628         p.playerid = 0;
629         return PlayerInfo_GetItem(p, item_id);
630 }
631
632 void PlayerInfo_ready(entity fh, entity p, float status)
633 {
634         float n;
635         string s;
636
637         PlayerInfo_AddPlayer(p);
638
639         switch(status)
640         {
641                 case URL_READY_CANWRITE:
642                         print("-- Sending data to player stats server\n");
643                         url_fputs(fh, "V 1\n");
644 #ifdef WATERMARK
645                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
646 #endif
647 #ifdef MENUQC
648                         url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
649                         url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
650                         url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
651                         url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
652                         url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
653 #endif
654                         url_fputs(fh, "\n");
655                         url_fclose(fh);
656                         break;
657                 case URL_READY_CANREAD:
658                         print("-- Got response from player stats server:\n");
659                         string gametype = string_null;
660                         while((s = url_fgets(fh)))
661                         {
662                                 print("  ", s, "\n");
663
664                                 string key = "", value = "", data = "";
665
666                                 n = tokenizebyseparator(s, " "); // key (value) data
667                                 if (n == 1)
668                                         continue;
669                                 else if (n == 2)
670                                 {
671                                         key = argv(0);
672                                         data = argv(1);
673                                 }
674                                 else if (n >= 3)
675                                 {
676                                         key = argv(0);
677                                         value = argv(1);
678                                         data = argv(2);
679                                 }
680
681                                 if (data == "")
682                                         continue;
683
684                                 if (key == "#")
685                                         continue;
686                                 else if (key == "V")
687                                         PlayerInfo_AddItem(p, "_version", data);
688                                 else if (key == "R")
689                                         PlayerInfo_AddItem(p, "_release", data);
690                                 else if (key == "T")
691                                         PlayerInfo_AddItem(p, "_time", data);
692                                 else if (key == "S")
693                                         PlayerInfo_AddItem(p, "_statsurl", data);
694                                 else if (key == "P")
695                                         PlayerInfo_AddItem(p, "_hashkey", data);
696                                 else if (key == "n")
697                                         PlayerInfo_AddItem(p, "_playernick", data);
698                                 else if (key == "i")
699                                         PlayerInfo_AddItem(p, "_playerid", data);
700                                 else if (key == "G")
701                                         gametype = data;
702                                 else if (key == "e" && value != "")
703                                 {
704                                         if (gametype == "")
705                                                 PlayerInfo_AddItem(p, value, data);
706                                         else
707                                                 PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data);
708                                 }
709                                 else
710                                         continue;
711                         }
712                         print("-- End of response.\n");
713                         url_fclose(fh);
714                         break;
715                 case URL_READY_CLOSED:
716                         // url_fclose has finished
717                         print("Player stats synchronized with server\n");
718                         break;
719                 case URL_READY_ERROR:
720                 default:
721                         print("Receiving player stats failed: ", ftos(status), "\n");
722                         break;
723         }
724 }
725
726 void PlayerInfo_Init()
727 {
728         playerinfo_db = -1;
729         playerinfo_db = db_create();
730 }
731
732 #ifdef SVQC
733 void PlayerInfo_Basic(entity p)
734 {
735         print("-- Getting basic PlayerInfo for player ",ftos(p.playerid)," (SVQC)\n");
736
737         if(playerinfo_db < 0)
738                 return;
739
740         string uri;
741         uri = autocvar_g_playerinfo_uri;
742         if(uri != "" && p.crypto_idfp != "")
743         {
744                 uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp));
745                 print("Retrieving playerstats from URL: ", uri, "\n");
746                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
747         }
748 }
749 #endif
750
751 #ifdef MENUQC
752 void PlayerInfo_Details()
753 {
754         print("-- Getting detailed PlayerInfo for local player (MENUQC)\n");
755
756         if(playerinfo_db < 0)
757                 return;
758
759         string uri;
760         uri = autocvar_g_playerinfo_uri; // FIXME
761         if(uri != "" && crypto_getmyidstatus(0) > 0)
762         {
763                 //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
764                 uri = strcat(uri, "/player/me");
765                 print("Retrieving playerstats from URL: ", uri, "\n");
766                 url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, world);
767         }
768 }
769 #endif
770
771 #ifdef CSQC
772 /*
773  * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885)
774 void PlayerInfo_Details()
775 {
776         print("-- Getting detailed PlayerInfo for local player (CSQC)\n");
777
778         if(playerinfo_db < 0)
779                 return;
780
781         string uri;
782         uri = autocvar_g_playerinfo_uri; // FIXME
783         if(uri != "" && crypto_getmyidstatus(0) > 0)
784         {
785                 uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
786                 print("Retrieving playerstats from URL: ", uri, "\n");
787                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
788         }
789 }
790
791 #endif
792 */