From 507d299d550dd8c2971d7e6ff433085000bd7456 Mon Sep 17 00:00:00 2001 From: fruitiex Date: Fri, 15 Jan 2010 21:53:43 +0000 Subject: [PATCH] race/cts rankings (default 15 best) git-svn-id: svn://svn.icculus.org/nexuiz/trunk@8499 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/defaultNexuiz.cfg | 1 + data/qcsrc/client/Main.qc | 48 +++++ data/qcsrc/client/main.qh | 3 + data/qcsrc/client/sbar.qc | 63 +++++++ data/qcsrc/common/constants.qh | 2 + data/qcsrc/common/gamecommand.qc | 5 + data/qcsrc/common/util.qh | 2 +- data/qcsrc/server/cl_client.qc | 10 +- data/qcsrc/server/clientcommands.qc | 2 + data/qcsrc/server/g_world.qc | 1 + data/qcsrc/server/gamecommand.qc | 13 ++ data/qcsrc/server/miscfunctions.qc | 35 ++++ data/qcsrc/server/race.qc | 272 +++++++++++++++++++++++----- 13 files changed, 404 insertions(+), 53 deletions(-) diff --git a/data/defaultNexuiz.cfg b/data/defaultNexuiz.cfg index bb64392b5..74aee70a4 100644 --- a/data/defaultNexuiz.cfg +++ b/data/defaultNexuiz.cfg @@ -1538,6 +1538,7 @@ set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by set g_showweaponspawns 0 "1: display sprites for weapon spawns found on the map when a weapon key is pressed and the weapon is not available" alias records "cmd records" +alias rankings "cmd rankings" // ballistics use physical units, but qu based // Quake-Newton: 1 qN = 1 qu * 1 g / 1 s^2 diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index d32594029..dac8ff175 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -1048,12 +1048,60 @@ void Net_ReadRace() break; case RACE_NET_SPEED_AWARD: race_speedaward = ReadShort(); + if(race_speedaward_holder) + strunzone(race_speedaward_holder); race_speedaward_holder = strzone(ReadString()); break; case RACE_NET_SPEED_AWARD_BEST: race_speedaward_alltimebest = ReadShort(); + if(race_speedaward_alltimebest_holder) + strunzone(race_speedaward_alltimebest_holder); race_speedaward_alltimebest_holder = strzone(ReadString()); break; + case RACE_NET_SERVER_RANKINGS: + float pos, prevpos, del; + pos = ReadShort(); + prevpos = ReadShort(); + del = ReadShort(); + + // move other rankings out of the way + float i; + if (prevpos) { + for (i=prevpos-1;i>pos-1;--i) { + grecordtime[i] = grecordtime[i-1]; + if(grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } else if (del) { // a record has been deleted by the admin + for (i=pos-1; i<= RANKINGS_CNT-1; ++i) { + if (i == RANKINGS_CNT-1) { // clear out last record + grecordtime[i] = 0; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = string_null; + } + else { + grecordtime[i] = grecordtime[i+1]; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i+1]); + } + } + } else { // player has no ranked record yet + for (i=RANKINGS_CNT-1;i>pos-1;--i) { + grecordtime[i] = grecordtime[i-1]; + if(grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } + + // store new ranking + if(grecordholder[pos-1] != "") + strunzone(grecordholder[pos-1]); + grecordholder[pos-1] = strzone(ReadString()); + grecordtime[pos-1] = ReadInt24_t(); } } diff --git a/data/qcsrc/client/main.qh b/data/qcsrc/client/main.qh index 1bd0512e6..81a75774e 100644 --- a/data/qcsrc/client/main.qh +++ b/data/qcsrc/client/main.qh @@ -98,6 +98,9 @@ float teamscores_flags[MAX_SCORE]; vector sbar_fontsize; vector sbar_fontsize_spec; +float RANKINGS_RECEIVED_CNT; +string grecordholder[RANKINGS_CNT]; +float grecordtime[RANKINGS_CNT]; //float csqc_flags; entity playerslots[255]; // 255 is engine limit on maxclients diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index bc1dad796..c8d97b40f 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -1239,6 +1239,68 @@ vector Sbar_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) return pos; } +string race_PlaceName(float pos) { + if(pos == 1) + return "1st"; + else if(pos == 2) + return "2nd"; + else if(pos == 3) + return "3rd"; + else + return strcat(ftos(pos), "th"); +} + +vector Sbar_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size) +{ + float i; + RANKINGS_RECEIVED_CNT = 0; + for (i=RANKINGS_CNT-1; i>=0; --i) + if (grecordtime[i]) + RANKINGS_RECEIVED_CNT = RANKINGS_RECEIVED_CNT + 1; + + if (RANKINGS_RECEIVED_CNT == 0) + return pos; + + float is_spec; + is_spec = (GetPlayerColor(pl.sv_entnum) == COLOR_SPECTATOR); + vector hl_rgb; + hl_rgb_x = cvar("sbar_color_bg_r") + 0.5; + hl_rgb_y = cvar("sbar_color_bg_g") + 0.5; + hl_rgb_z = cvar("sbar_color_bg_b") + 0.5; + + pos_y += sbar_fontsize_y; + drawstring(pos, strcat("Rankings"), sbar_fontsize, '1 1 1', sbar_scoreboard_alpha_fg, DRAWFLAG_NORMAL); + pos_y += sbar_fontsize_y; + vector tmp; + tmp_x = sbwidth; + tmp_y = sbar_fontsize_y * RANKINGS_RECEIVED_CNT; + + drawpic_tiled(pos, "gfx/hud/sb_scoreboard_bg", bg_size, tmp, rgb * sbar_color_bg_team, sbar_scoreboard_alpha_bg, DRAWFLAG_NORMAL); + drawborderlines(sbar_border_thickness, pos, tmp, '0 0 0', sbar_scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL); + + // row highlighting + for(i = 0; i= 2) diff --git a/data/qcsrc/common/util.qh b/data/qcsrc/common/util.qh index d963343d0..3133458f5 100644 --- a/data/qcsrc/common/util.qh +++ b/data/qcsrc/common/util.qh @@ -161,7 +161,7 @@ void check_unacceptable_compiler_bugs(); float compressShotOrigin(vector v); vector decompressShotOrigin(float f); -string records_reply, lsmaps_reply, maplist_reply; // cached replies +string records_reply, rankings_reply, lsmaps_reply, maplist_reply; // cached replies float RandomSelection_totalweight; float RandomSelection_best_priority; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index 4c9b1fba6..3948f071f 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -1,4 +1,5 @@ -void race_send_recordtime(float t, float msg); +void race_send_recordtime(float msg); +void race_SendRankings(float pos, float prevpos, float del, float msg); void send_CSQC_teamnagger() { WriteByte(0, SVC_TEMPENTITY); @@ -1492,12 +1493,17 @@ void ClientConnect (void) rr = RACE_RECORD; t = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time"))); - race_send_recordtime(t, MSG_ONE); + race_send_recordtime(MSG_ONE); race_send_speedaward(MSG_ONE); speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); speedaward_alltimebest_holder = db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/netname")); race_send_speedaward_alltimebest(MSG_ONE); + + float i; + for (i = 1; i <= RANKINGS_CNT; ++i) { + race_SendRankings(i, 0, 0, MSG_ONE); + } } else if(cvar("sv_teamnagger") && !g_ca) // teamnagger is currently bad for ca send_CSQC_teamnagger(); diff --git a/data/qcsrc/server/clientcommands.qc b/data/qcsrc/server/clientcommands.qc index a5abbc95a..53e474a3b 100644 --- a/data/qcsrc/server/clientcommands.qc +++ b/data/qcsrc/server/clientcommands.qc @@ -340,6 +340,8 @@ void SV_ParseClientCommand(string s) { sprint(self, lsmaps_reply); } else if(cmd == "records") { sprint(self, records_reply); + } else if(cmd == "rankings") { + sprint(self, rankings_reply); } else if(cmd == "voice") { if(tokens >= 3) VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2))); diff --git a/data/qcsrc/server/g_world.qc b/data/qcsrc/server/g_world.qc index 9b0d9a2e8..e05fb6cb7 100644 --- a/data/qcsrc/server/g_world.qc +++ b/data/qcsrc/server/g_world.qc @@ -674,6 +674,7 @@ void spawnfunc_worldspawn (void) MapInfo_ClearTemps(); records_reply = strzone(getrecords()); + rankings_reply = strzone(getrankings()); ClientInit_Spawn(); RandomSeed_Spawn(); diff --git a/data/qcsrc/server/gamecommand.qc b/data/qcsrc/server/gamecommand.qc index 5759ac544..316f5b82b 100644 --- a/data/qcsrc/server/gamecommand.qc +++ b/data/qcsrc/server/gamecommand.qc @@ -1,4 +1,5 @@ string GotoMap(string m); +void race_DeleteTime(float pos); float FullTraceFraction(vector a, vector mi, vector ma, vector b) { @@ -930,6 +931,13 @@ void GameCommand(string command) print(records_reply); return; } + if (argv(0) == "rankings") + { + strunzone(rankings_reply); + rankings_reply = strzone(getrankings()); + print(rankings_reply); + return; + } if(argv(0) == "cointoss") { @@ -1326,6 +1334,11 @@ void GameCommand(string command) return; } + if(argv(0) == "delrec") + { + race_DeleteTime(stof(argv(1))); + return; + } print("Invalid command. For a list of supported commands, try sv_cmd help.\n"); } diff --git a/data/qcsrc/server/miscfunctions.qc b/data/qcsrc/server/miscfunctions.qc index 196eac8c4..328370092 100644 --- a/data/qcsrc/server/miscfunctions.qc +++ b/data/qcsrc/server/miscfunctions.qc @@ -5,6 +5,10 @@ void droptofloor(); void() spawnfunc_info_player_deathmatch; // needed for the other spawnpoints void() spawnpoint_use; +float race_GetTime(float pos); +string race_GetName(float pos); +string race_PlaceName(float pos); +string GetMapname(); string ColoredTeamName(float t); string admin_name(void) @@ -2230,6 +2234,37 @@ string getrecords() return strcat("Records on this server:\n", s); } +string getrankings() +{ + string n; + float t; + float i; + string s; + string p; + string map; + + s = ""; + + map = GetMapname(); + + for (i = 1; i <= RANKINGS_CNT; ++i) + { + t = race_GetTime(i); + if (t == 0) + continue; + n = race_GetName(i); + p = race_PlaceName(i); + s = strcat(s, strpad(8, p), " ", strpad(-8, TIME_ENCODED_TOSTRING(t)), " ", n, "\n"); + } + + MapInfo_ClearTemps(); + + if (s == "") + return strcat("No records are available for the map: ", map, "\n"); + else + return strcat("Records for ", map, ":\n", s); +} + float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance) { float m, i; diff --git a/data/qcsrc/server/race.qc b/data/qcsrc/server/race.qc index 377bf9f2d..2208910aa 100644 --- a/data/qcsrc/server/race.qc +++ b/data/qcsrc/server/race.qc @@ -96,13 +96,220 @@ void race_InitSpectator() race_SendNextCheckpoint(msg_entity.enemy, 1); } -void race_send_recordtime(float t, float msg) +string rr; +float grecordtime[RANKINGS_CNT]; +string grecordholder[RANKINGS_CNT]; +float worst_time; // last ranked time +float have_recs; // have we already read the records from the database before? +float race_GetTime(float pos) { + if(g_cts) + rr = CTS_RECORD; + else + rr = RACE_RECORD; + + if (!have_recs) { // I guess this is better than checking if the first array item is empty, rumor has it that arrays are slow + float i; + for(i=0;i= once before + return grecordholder[pos-1]; +} + +float race_CheckName(string netname) { // Does the name already exist in rankings? In that case, where? (otherwise 0) + float i; + for (i=RANKINGS_CNT-1;i>=0;--i) + if(grecordholder[i] == netname) + return i+1; + return 0; +} + +float race_GetPos(float t) { + float i; + + if(worst_time == 0) + for (i=0;i t) + return i+1; + + for (i=RANKINGS_CNT-1;i>=0;--i) + if (grecordtime[i] > t) + return i+1; + return 0; +} + +void race_send_recordtime(float msg) { // send the server best time WriteByte(msg, SVC_TEMPENTITY); WriteByte(msg, TE_CSQC_RACE); WriteByte(msg, RACE_NET_SERVER_RECORD); - WriteInt24_t(msg, t); + WriteInt24_t(msg, race_GetTime(1)); +} + +void race_SendRankings(float pos, float prevpos, float del, float msg) +{ + WriteByte(msg, SVC_TEMPENTITY); + WriteByte(msg, TE_CSQC_RACE); + WriteByte(msg, RACE_NET_SERVER_RANKINGS); + WriteShort(msg, pos); + WriteShort(msg, prevpos); + WriteShort(msg, del); + WriteString(msg, race_GetName(pos)); + WriteInt24_t(msg, race_GetTime(pos)); +} + +string race_PlaceName(float pos) { + if(pos == 1) + return "1st"; + else if(pos == 2) + return "2nd"; + else if(pos == 3) + return "3rd"; + else + return strcat(ftos(pos), "th"); +} + +void race_SetTime(entity e, float t) { + if (worst_time && t > worst_time) + return; + + float oldrec; + string oldname; + float pos; + pos = race_GetPos(t); + + float prevpos = race_CheckName(e.netname); + if (prevpos && (prevpos < pos || !pos)) + { + oldrec = race_GetTime(prevpos); + string recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - oldrec), "]"); + bprint(e.netname, "^1 couldn't break their ", race_PlaceName(prevpos), " place record of ", TIME_ENCODED_TOSTRING(oldrec), recorddifference, "\n"); + + return; + } else if (!pos) { // no ranking, time worse than the worst ranked + string recorddifference = strcat(" ^1[+", TIME_ENCODED_TOSTRING(t - grecordtime[RANKINGS_CNT-1]), "]"); + bprint(e.netname, "^1 couldn't break the ", race_PlaceName(RANKINGS_CNT), " place record of ", TIME_ENCODED_TOSTRING(grecordtime[RANKINGS_CNT-1]), recorddifference, "\n"); + return; + } + + + // move other rankings out of the way + float i; + if (prevpos) { // player improved his existing record + if(prevpos == pos) { + oldrec = grecordtime[pos-1]; + } + for (i=prevpos-1;i>pos-1;--i) { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1])); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]); + if(i == prevpos-1) { + oldrec = grecordtime[i]; + oldname = grecordholder[i]; + } + grecordtime[i] = grecordtime[i-1]; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } else { // player has no ranked record yet + for (i=RANKINGS_CNT-1;i>pos-1;--i) { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i-1])); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i-1]); + grecordtime[i] = grecordtime[i-1]; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } + + // store new ranking + if (pos == 1) { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname); + grecordtime[0] = t; + if (grecordholder[0]) + strunzone(grecordholder[0]); + grecordholder[0] = strzone(e.netname); + write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t)); + race_send_recordtime(MSG_ALL); + } else { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(pos-1)), ftos(t)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(pos-1)), e.netname); + grecordtime[pos-1] = t; + if (grecordholder[pos-1]) + strunzone(grecordholder[pos-1]); + grecordholder[pos-1] = strzone(e.netname); + } + + if (pos == RANKINGS_CNT) + worst_time = t; + + race_SendRankings(pos, prevpos, 0, MSG_ALL); + if(rankings_reply) + strunzone(rankings_reply); + rankings_reply = strzone(getrankings()); + if(pos == prevpos) { + string recorddifference = strcat(" ^3[-", TIME_ENCODED_TOSTRING(oldrec - t), "]"); + bprint(e.netname, "^3 improved their ", race_PlaceName(pos), " ^3place record with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n"); + } + else if(oldname == "") + bprint(e.netname, "^2 set the ", race_PlaceName(pos), " ^2place record with ", TIME_ENCODED_TOSTRING(t), "\n"); + else { + string recorddifference = strcat(" ^2[-", TIME_ENCODED_TOSTRING(oldrec - t), "]"); + bprint(e.netname, "^2 broke ", oldname, "^2's ", race_PlaceName(pos), " ^2place, record ", strcat(TIME_ENCODED_TOSTRING(oldrec), " ^2with ", TIME_ENCODED_TOSTRING(t), recorddifference, "\n")); + } +} + +void race_DeleteTime(float pos) { + float i; + + for (i = pos-1; i <= RANKINGS_CNT-1; ++i) { + if (i == 0) { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(grecordtime[1])); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), grecordholder[1]); + grecordtime[0] = grecordtime[1]; + if (grecordholder[i]) + strunzone(grecordholder[0]); + grecordholder[0] = strzone(grecordholder[1]); + } + else if (i == RANKINGS_CNT-1) { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), string_null); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), string_null); + grecordtime[i] = 0; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = string_null; + } + else { + db_put(ServerProgsDB, strcat(GetMapname(), rr, "time", ftos(i)), ftos(grecordtime[i+1])); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname", ftos(i)), grecordholder[i+1]); + grecordtime[i] = grecordtime[i+1]; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i+1]); + } + } + + race_SendRankings(pos, 0, 1, MSG_ALL); + if(pos == 1) + race_send_recordtime(MSG_ALL); + + if(rankings_reply) + strunzone(rankings_reply); + rankings_reply = strzone(getrankings()); + + worst_time = 0; } void race_SendTime(entity e, float cp, float t, float tvalid) @@ -160,60 +367,25 @@ void race_SendTime(entity e, float cp, float t, float tvalid) recordholder = ""; if(t != 0) - if(t < recordtime || recordtime == 0) + if(t < worst_time || worst_time == 0) { - race_checkpoint_records[cp] = t; - if(race_checkpoint_recordholders[cp]) - strunzone(race_checkpoint_recordholders[cp]); - race_checkpoint_recordholders[cp] = strzone(e.netname); if(cp == race_timed_checkpoint) { - float grecordtime; - string grecordholder, recorddifference; - string rr; - if(g_cts) - rr = CTS_RECORD; - else - rr = RACE_RECORD; - grecordtime = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time"))); - grecordholder = db_get(ServerProgsDB, strcat(GetMapname(), rr, "netname")); - if(grecordholder == e.netname) - grecordholder = ""; - if(grecordtime == 0) - { - bprint(e.netname, "^7 set the all-time fastest lap record with ", TIME_ENCODED_TOSTRING(t), "\n"); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname); - write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t)); - race_send_recordtime(t, MSG_ALL); - } - else if(t < grecordtime) - { - recorddifference = strcat("^2", " [-", TIME_ENCODED_TOSTRING(grecordtime-t), "]"); - if(grecordholder == "") - bprint(e.netname, strcat("^7 broke their all-time fastest lap record ", TIME_ENCODED_TOSTRING(grecordtime), " with ", TIME_ENCODED_TOSTRING(t), recorddifference), "\n"); - else - bprint(e.netname, strcat("^7 broke ", grecordholder, "^7's all-time fastest lap record ", TIME_ENCODED_TOSTRING(grecordtime), " with ", TIME_ENCODED_TOSTRING(t), recorddifference), "\n"); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "time"), ftos(t)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "netname"), e.netname); - write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t)); - race_send_recordtime(t, MSG_ALL); - } - else - { - recorddifference = strcat("^1", " [+", TIME_ENCODED_TOSTRING(t-grecordtime), "]"); - if(grecordholder == "") - bprint(e.netname, strcat("^7's new fastest lap ", TIME_ENCODED_TOSTRING(t), " could not break their all-time fastest lap record of ", TIME_ENCODED_TOSTRING(grecordtime), recorddifference), "\n"); - else - bprint(e.netname, strcat("^7's new fastest lap ", TIME_ENCODED_TOSTRING(t), " could not break ", grecordholder, "^7's all-time fastest lap record of ", TIME_ENCODED_TOSTRING(grecordtime), recorddifference), "\n"); - } + race_SetTime(e, t); } - if(g_race_qualifying) + if(t < recordtime || recordtime == 0) { - FOR_EACH_REALPLAYER(p) - if(p.race_checkpoint == cp) - race_SendNextCheckpoint(p, 0); + race_checkpoint_records[cp] = t; + if(race_checkpoint_recordholders[cp]) + strunzone(race_checkpoint_recordholders[cp]); + race_checkpoint_recordholders[cp] = strzone(e.netname); + if(g_race_qualifying) + { + FOR_EACH_REALPLAYER(p) + if(p.race_checkpoint == cp) + race_SendNextCheckpoint(p, 0); + } } } } -- 2.39.2