From 3bfee0f9cd1690b508f2baccb879500ed1d5b631 Mon Sep 17 00:00:00 2001 From: blub0 Date: Tue, 8 Jul 2008 18:41:39 +0000 Subject: [PATCH] Keep track of CSQC compatibility. Count flag returns now. Send flag returns and deaths over the net. Dynamically create scoreboard columns. Store sbar columns in sbar_columns, update the layout with the cmd sbar_columns_set. sbar_columns_help for information about it. git-svn-id: svn://svn.icculus.org/nexuiz/trunk@3795 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/qcsrc/client/Main.qc | 84 +++++- data/qcsrc/client/main.qh | 28 +- data/qcsrc/client/sbar.qc | 426 +++++++++++++++++++++------- data/qcsrc/common/constants.qh | 11 +- data/qcsrc/server/cl_client.qc | 39 ++- data/qcsrc/server/cl_player.qc | 14 + data/qcsrc/server/clientcommands.qc | 4 - data/qcsrc/server/ctf.qc | 54 ++-- data/qcsrc/server/defs.qh | 1 + 9 files changed, 499 insertions(+), 162 deletions(-) diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index 3c3981058..7937610e7 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -20,6 +20,7 @@ float using_gps; void CSQC_Init(void) { + float i; drawfont = 0; menu_visible = FALSE; menu_show = menu_show_error; @@ -30,27 +31,37 @@ void CSQC_Init(void) //registercmd("ctf_menu"); registercmd("ons_map"); //registercmd("menu_action"); + registercmd("sbar_columns_set"); + registercmd("sbar_columns_help"); registercvar("sbar_usecsqc", "1"); + registercvar("sbar_columns", "ping name | caps returns frags deaths"); gametype = 0; gps_start = world; + // sbar_fields uses strunzone on the titles! + for(i = 0; i < MAX_SBAR_FIELDS; ++i) + sbar_title[i] = strzone("(null)"); + + localcmd(strcat("\nsbar_columns_set ", cvar_string("sbar_columns"), ";\n")); + postinit = false; } void PostInit(void) { float i; - entity pl; print(strcat("PostInit\n maxclients = ", ftos(maxclients), "\n")); databuf = buf_create(); for(i = 0; i < maxclients; ++i) { bufstr_set(databuf, DATABUF_PING + i, "N/A"); + bufstr_set(databuf, DATABUF_DEATHS + i, "0"); bufstr_set(databuf, DATABUF_CAPTURES + i, "0"); + bufstr_set(databuf, DATABUF_RETURNS + i, "0"); } postinit = true; @@ -63,13 +74,13 @@ void CSQC_Shutdown(void) // CSQC_ConsoleCommand : Used to parse commands in the console that have been registered with the "registercmd" function // Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it. +void Cmd_Sbar_SetFields(float); +void Cmd_Sbar_Help(float); float CSQC_ConsoleCommand(string strMessage) { - local float nReturn; - nReturn = FALSE; - + float argc; // Tokenize String - tokenize(strMessage); + argc = tokenize(strMessage); // Acquire Command local string strCmd; @@ -77,14 +88,20 @@ float CSQC_ConsoleCommand(string strMessage) /*if(strCmd == "ctf_menu") { ctf_menu_show(); - nReturn = TRUE; + nReturn = true; } else*/ if(strCmd == "ons_map") { Cmd_ons_map(); - nReturn = TRUE; + return true; + } else if(strCmd == "sbar_columns_set") { + Cmd_Sbar_SetFields(argc); + return true; + } else if(strCmd == "sbar_columns_help") { + Cmd_Sbar_Help(argc); + return true; } - return nReturn; + return false; } // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client. // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine. @@ -274,9 +291,13 @@ void CSQC_Parse_CenterPrint(string strMessage) cprint(strMessage); } +void CSQC_CheckRevision(); void ReadInit() { + csqc_revision = ReadShort(); maxclients = ReadByte(); + + CSQC_CheckRevision(); } void ReadPings() @@ -291,19 +312,27 @@ void ReadPings() void ReadCaptures() { - float plnum, caps; - entity pl; + float plnum, caps, mode; + mode = ReadByte(); caps_team1 = ReadByte(); caps_team2 = ReadByte(); for(plnum = ReadByte(); plnum != 0; plnum = ReadByte()) { caps = ReadByte(); - //print(strcat("Cap update: ", ftos(plnum), " has ", ftos(caps), " caps\n")); - //print(strcat("Index: ", ftos(DATABUF_CAPTURES + plnum-1), " -- I AM: ", ftos(player_localentnum), "\n")); bufstr_set(databuf, DATABUF_CAPTURES + plnum-1, ftos(caps)); } } +void ReadDatabuf(float ofs) +{ + float plnum, data; + for(plnum = ReadByte(); plnum != 0; plnum = ReadByte()) + { + data = ReadByte(); + bufstr_set(databuf, ofs + plnum-1, ftos(data)); + } +} + // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer. // You must ALWAYS first acquire the temporary ID, which is sent as a byte. // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event. @@ -329,6 +358,14 @@ float CSQC_Parse_TempEntity() ReadCaptures(); bHandled = true; break; + case TE_CSQC_RETURNS: + ReadDatabuf(DATABUF_RETURNS); + bHandled = true; + break; + case TE_CSQC_DEATHS: + ReadDatabuf(DATABUF_DEATHS); + bHandled = true; + break; default: // No special logic for this temporary entity; return 0 so the engine can handle it bHandled = false; @@ -340,3 +377,26 @@ float CSQC_Parse_TempEntity() return bHandled; } + +// COMMIT-TODO: Update if necessare, before committing +float csqc_svn_map[CSQC_REVISION] = +{ + 3795, +}; + +// COMMIT-TODO: Update if necessare, before committing +void CSQC_CheckRevision() +{ + if(csqc_revision == CSQC_REVISION) + { + print("^2SVQC and CSQC revisions are compatible.\n"); + } else if(csqc_revision < CSQC_REVISION) { + print("^1Your csprogs.dat (CSQC) version is newer than the one on the server.\n"); + print("^1The last known svn revision for the server's CSQC is: ^7"); + print(ftos(csqc_svn_map[csqc_revision])); // don't use strcat, fteqcc loves screwing up arrays... + print("\n"); + } else if(csqc_revision > CSQC_REVISION) { + print("^1Your csprogs.dat (CSQC) is too old for this server.\n"); + print("^1Please update to a newer version.\n"); + } +} diff --git a/data/qcsrc/client/main.qh b/data/qcsrc/client/main.qh index 3291e977b..938e71774 100644 --- a/data/qcsrc/client/main.qh +++ b/data/qcsrc/client/main.qh @@ -3,7 +3,10 @@ #define DATABUF_PING 0 #define DATABUF_CAPTURES (1*maxclients) -#define DATABUF_NEXT (2*maxclients) +#define DATABUF_DEATHS (2*maxclients) +#define DATABUF_RETURNS (3*maxclients) + +#define DATABUF_NEXT (5*maxclients) void() menu_show_error; void() menu_sub_null; @@ -32,6 +35,8 @@ float ons_showmap; // -------------------------------------------------------------------------- // General stuff +float csqc_revision; + float drawfont; float postinit; float gametype; @@ -57,3 +62,24 @@ const float COLOR_SPECTATOR = 1337; #define FONT_DEFAULT 0 #define FONT_USER 8 + +// -------------------------------------------------------------------------- +// Scoreboard stuff + +#define MAX_SBAR_FIELDS 16 + +#define SBF_END 0 +#define SBF_PING 1 +#define SBF_NAME 2 +#define SBF_CAPS 3 +#define SBF_RETS 4 +#define SBF_FRAGS 5 +#define SBF_DEATHS 6 +#define SBF_KDRATIO 7 + +#define SBF_SEPARATOR 100 + +float sbar_field[MAX_SBAR_FIELDS + 1]; +float sbar_size[MAX_SBAR_FIELDS + 1]; +string sbar_title[MAX_SBAR_FIELDS + 1]; +float sbar_num_fields; diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index fb313335f..15c85465c 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -239,142 +239,364 @@ void Sbar_SortFrags() } } } -float xmin, xmax, ymin, ymax; -void Sbar_PrintScoreboardItem(vector pos, entity pl, float is_self, float mask) + +void Cmd_Sbar_Help(float argc) { - vector tmp; - string str; - entity player; - - tmp_y = tmp_z = 0; - pos_x += 56; + print("You can modify the scoreboard using the\n"); + print("^3|---------------------------------------------------------------|\n"); + print("^2sbar_columns^7 cvar and the ^2sbar_columns_set command.\n"); + print("^2sbar_columns^7 specifies the default layout and\n"); + print("^2sbar_columns_set^7 actually changes the layout.\n"); + print("You can call ^2sbar_columns_set^7 with the new layout\n"); + print("as parameters, or eithout parameters it will read the cvar.\n\n"); + print("Usage:\n"); + print("^2sbar_columns_set ^7filed1 field2 ...\n"); + print("Fields which are not relevant to the current gametype\n"); + print("won't be displayed\n\n"); + print("The following field names are recognized (case INsensitive):\n"); + print("^3name^7 or ^3nick^7 Name of a player\n"); + print("^3caps^7 or ^3captures^7 Number of flags captured\n"); + print("^3rets^7 or ^3returns^7 Number of flags returned\n"); + print("^3frags^7 or ^3kills^7 Frags\n"); + print("^3deaths^7 or ^3dths^7 Number of deaths\n"); + print("^3kd^7 or ^3kdr^7 or ^3kdratio^7 or ^3k/d\n"); + print(" The kill-death ratio\n"); + print("^3ping^7 Ping time\n\n"); + print("You can use a ^3|^7 to start the right-aligned fields.\n"); + print("Example: ping name | caps rets frags k/d\n"); + print("This will put the ping and the name on the left side.\n"); + print("The captures, returns, frags and kill-death ratio will be\n"); + print("rendered beginning on the right side.\n"); + +} - str = bufstr_get(databuf, DATABUF_PING + pl.sb_player); - tmp_x = 4*8 - strlen(str) * 8 - 56; - drawstring(pos + tmp, str, '8 8 0', '0.8 0.8 0.8', 0.8, 0); +void Cmd_Sbar_SetFields(float argc) +{ + float i; + string str; - if(!(mask & 1)) // not a spectator: + if(argc < 2) + argc = tokenize(strcat("x ", cvar_string("sbar_columns"))); + + argc = min(MAX_SBAR_FIELDS, argc); + sbar_num_fields = 0; + for(i = 0; i < argc-1; ++i) { - if(gametype == GAME_CTF) - { - str = bufstr_get(databuf, DATABUF_CAPTURES + pl.sb_player); - tmp_x = xmax - strlen(str)*8 - pos_x; - drawstring(pos + tmp, str, '8 8 0', '1 1 1', 1, 0); + str = argv(i+1); + strunzone(sbar_title[i]); + sbar_title[i] = strzone(str); + sbar_size[i] = strlen(str)*8; + str = strtolower(str); + if(str == "ping") { + sbar_field[i] = SBF_PING; + } else if(str == "name" || str == "nick") { + sbar_field[i] = SBF_NAME; + sbar_size[i] = 24*8; // minimum size? any use? + } else if(str == "caps" || str == "captures") { + sbar_field[i] = SBF_CAPS; + } else if(str == "rets" || str == "returns") { + sbar_field[i] = SBF_RETS; + } else if(str == "frags" || str == "kills") { + sbar_field[i] = SBF_FRAGS; + } else if(str == "deaths" || str == "dths") { + sbar_field[i] = SBF_DEATHS; + } else if(str == "kdratio") { + sbar_field[i] = SBF_KDRATIO; + } else if(str == "kdr" || str == "k/d") { + sbar_field[i] = SBF_KDRATIO; + } else if(str == "kd") { + sbar_field[i] = SBF_KDRATIO; + } else if(str == "|") { + sbar_field[i] = SBF_SEPARATOR; + } else { + print(strcat("^1Error:^7 Unknown score field: '", str, "'\n")); + --sbar_num_fields; } - - str = ftos(pl.sb_frags); - tmp_x = 4*8 - strlen(str) * 8; - drawstring(pos + tmp, str, '8 8 0', '1 1 1', 1, 0); + ++sbar_num_fields; } + sbar_field[i] = SBF_END; +} - if(is_self) - drawstring(pos + '40 0 0', "\x0D", '8 8 0', '1 1 1', 1, 0); - str = getplayerkey(pl.sb_player, "name"); - tmp_x = 5*8 - strlen(str) * 8 + 56; - drawcolorcodedstring(pos + '48 0 0', str, '8 8 0', 1, 0); +vector sbar_field_rgb; +string Sbar_GetField(entity pl, float field) +{ + float tmp; + string str; + sbar_field_rgb = '1 1 1'; + switch(field) + { + case SBF_PING: + str = bufstr_get(databuf, DATABUF_PING + pl.sb_player); + tmp = max(0, min(220, stof(str)-80)) / 220; + sbar_field_rgb = '1 1 1' - '0 1 1'*tmp; + return str; + case SBF_NAME: return getplayerkey(pl.sb_player, "name"); + case SBF_CAPS: return ftos(pl.sb_caps); + case SBF_RETS: return bufstr_get(databuf, DATABUF_RETURNS + pl.sb_player); + case SBF_FRAGS: return ftos(pl.sb_frags); + case SBF_DEATHS: return bufstr_get(databuf, DATABUF_DEATHS + pl.sb_player); + case SBF_KDRATIO: + tmp = stof(bufstr_get(databuf, DATABUF_DEATHS + pl.sb_player)); + if(tmp == 0) { + sbar_field_rgb = '0 1 0'; + str = ftos(pl.sb_frags); + } else if(pl.sb_frags <= 0) { + sbar_field_rgb = '1 0 0'; + str = ftos(pl.sb_frags / tmp); + } else + str = ftos(pl.sb_frags / tmp); + + tmp = strstrofs(str, ".", 0); + if(tmp > 0) + str = substring(str, 0, tmp+2); + return str; + } } -void Sbar_PrintScoreboardTeamItem(vector pos, entity tm, vector rgb, string name) + +float Sbar_IsFieldMasked(float field, float mask) +{ + if(mask&1) // spectator + return (field != SBF_NAME && field != SBF_PING); + if(gametype != GAME_CTF) + return (field == SBF_CAPS || field == SBF_RETS); + return false; +} + +#define MAX_NAMELEN 24 + +float xmin, xmax, ymin, ymax, sbwidth, sbheight; +void Sbar_PrintScoreboardItem(vector pos, entity pl, float is_self, float mask) { vector tmp; - string str; + string str, tempstr; + float i, field, len; + + // Layout: + tmp_z = 0; + if(is_self) + { + tmp_x = sbwidth; + tmp_y = 8; + drawfill(pos - '1 1', tmp + '2 2', '1 1 1', 0.3, DRAWFLAG_NORMAL); + } + tmp_y = 0; - tmp_y = tmp_z = 0; - pos_x += 56; + for(i = 0; i < sbar_num_fields; ++i) + { + field = sbar_field[i]; + if(field == SBF_SEPARATOR) + break; + if(Sbar_IsFieldMasked(field, mask)) + continue; - str = ftos(tm.sb_frags); - tmp_x = 4*8 - strlen(str) * 8; - drawstring(pos + tmp, str, '8 8 0', '1 1 1', 1, 0); + str = Sbar_GetField(pl, field); - rgb += '0.3 0.3 0.3'; - rgb = normalize(rgb * 5); - drawstring(pos + '48 0 0', name, '8 8 0', rgb, 1, 0); + if(field == SBF_NAME) { + len = strlen(strdecolorize(str)); + if(len > MAX_NAMELEN) + { + while(len > MAX_NAMELEN) + { + // this way should be the fastest with 100% safety :P + // worst case: decolored length maxnamelen+1, and then only color codes =) + // cutting of reallength - (decolored-length - maxnamelen) characters + str = substring(str, 0, strlen(str) - (len-MAX_NAMELEN)); + len = strlen(strdecolorize(str)); + } + str = strcat(str, "^7..."); + len += 3; + } + len *= 8; + } else + len = 8*strlen(str); + + if(sbar_size[i] < len) + sbar_size[i] = len; + + pos_x += sbar_size[i] + 8; + if(field == SBF_NAME) { + tmp_x = sbar_size[i] + 8; + drawcolorcodedstring(pos - tmp, str, '8 8', 1, DRAWFLAG_NORMAL); + } else { + tmp_x = 8*strlen(str) + 8; + drawstring(pos - tmp, str, '8 8', sbar_field_rgb, 1, DRAWFLAG_NORMAL); + } + } + + if(sbar_field[i] == SBF_SEPARATOR) + { + pos_x = xmax; + for(i = sbar_num_fields-1; i > 0; --i) + { + field = sbar_field[i]; + if(field == SBF_SEPARATOR) + break; + if(Sbar_IsFieldMasked(field, mask)) + continue; + + str = Sbar_GetField(pl, field); + + if(field == SBF_NAME) { + len = strlen(strdecolorize(str)); + if(len > MAX_NAMELEN) + { + while(len > MAX_NAMELEN) + { + str = substring(str, 0, strlen(str) - (len-MAX_NAMELEN)); + len = strlen(strdecolorize(str)); + } + str = strcat(str, "^7..."); + len += 3; + } + len *= 8; + } else + len = 8*strlen(str); + //len = 8*strlen(str); + if(sbar_size[i] < len) + sbar_size[i] = len; + + if(field == SBF_NAME) { + tmp_x = len; // left or right aligned? let's put it right... + drawcolorcodedstring(pos - tmp, str, '8 8', 1, DRAWFLAG_NORMAL); + } else { + tmp_x = len; //strlen(str); + drawstring(pos - tmp, str, '8 8', sbar_field_rgb, 1, DRAWFLAG_NORMAL); + } + pos_x -= sbar_size[i] + 8; + } + } } void Sbar_DrawScoreboard() { - // Assume: frags are already sorted - //float xmin, xmax, ymin, ymax, plcount; - float plcount; - vector pos, teammin, teammax, rgb; + //float xmin, ymin, xmax, ymax; + vector rgb, pos, tmp, sbar_save; entity pl, tm; - float specs, minoffset; - specs = false; + float specs, i; + float center_x; + string str; + + xmin = vid_conwidth / 5; + ymin = 20; - xmin = vid_conwidth / 4; xmax = vid_conwidth - xmin; - ymin = 48 - 26; - ymax = vid_conheight - 50; + ymax = vid_conheight - 0.2*vid_conheight; + + sbwidth = xmax - xmin; + sbheight = ymax - ymin; + + center_x = xmin + 0.5*sbwidth; + //Sbar_UpdateFields(); + + // Initializes position + //pos_x = xmin; pos_y = ymin; pos_z = 0; - teammin = teammax = '0 0 0'; - teammin_x = xmin - 2; - teammax_x = xmax + 2; - - pos_x = 0.5 * (xmin + xmax) - (24*5); + // Heading drawfont = FONT_USER+0; - drawstring(pos, "Scoreboard", '24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL); - drawfont = 0; + pos_x = center_x - 4*24; + drawstring(pos, "Scoreboard", '24 24', '1 1 1', 1, DRAWFLAG_NORMAL); pos_x = xmin; - pos_y += 26; + pos_y += 24 + 4; + drawfont = FONT_DEFAULT; - drawstring(pos, "ping", '8 8 0', '1 1 1', 1, 0); - drawstring(pos + '48 0 0', "frags", '8 8 0', '1 1 1', 1, 0); - drawstring(pos + '104 0 0', "name", '8 8 0', '1 1 1', 1, 0); - if(gametype == GAME_CTF) + // Titlebar background: + tmp_x = sbwidth; + tmp_y = 8; + drawfill(pos - '1 1', tmp + '2 2', '0.5 0.5 0.5', 0.5, DRAWFLAG_NORMAL); + + for(i = 0; i < sbar_num_fields; ++i) { - pos_x = xmax - 4*8; - drawstring(pos, "caps", '8 8 0', '1 1 1', 1, 0); - pos_x = xmin; + if(sbar_field[i] == SBF_SEPARATOR) + break; + drawstring(pos, sbar_title[i], '8 8', '1 1 1', 1, DRAWFLAG_NORMAL); + pos_x += sbar_size[i] + 8; } - pos_y += 16; + if(sbar_field[i] == SBF_SEPARATOR) + { + pos_x = xmax + 8; + tmp_y = tmp_z = 0; + for(i = sbar_num_fields-1; i > 0; --i) + { + if(sbar_field[i] == SBF_SEPARATOR) + break; + + pos_x -= sbar_size[i] + 8; + /** + * FTEQCC BUG! + * Using the following line will fuck it all up: + ** + * tmp_x = sbar_size[i] - strlen(sbar_title[i])*8; + */ + tmp_x = sbar_size[i]; + tmp_x -= strlen(sbar_title[i])*8; + drawstring(pos + tmp, sbar_title[i], '8 8', '1 1 1', 1, DRAWFLAG_NORMAL); + } + } + + pos_x = xmin; + pos_y += 12; + + sbar_save = sbar; + sbar = '0 0 0'; if(teamplay) { for(tm = sortedTeams.sort_next; tm; tm = tm.sort_next) { - minoffset = pos_y + 24; if(!tm.sb_player || tm.sb_team == COLOR_SPECTATOR) // no players in it? continue; rgb = GetTeamRGB(tm.sb_team); + + pos_x = xmin - 4*24; if(gametype == GAME_CTF) { - minoffset = pos_y + 24 + 12; if(tm.sb_team == COLOR_TEAM1) - Sbar_DrawXNum(pos-'106 0 0'-sbar, caps_team1, 4, 24, rgb, 1, DRAWFLAG_NORMAL); + Sbar_DrawXNum(pos, caps_team1, 4, 24, rgb, 1, DRAWFLAG_NORMAL); else if(tm.sb_team == COLOR_TEAM2) - Sbar_DrawXNum(pos-'106 0 0'-sbar, caps_team2, 4, 24, rgb, 1, DRAWFLAG_NORMAL); - Sbar_DrawXNum(pos-'44 -24 0'-sbar, tm.sb_frags, 4, 10, rgb, 1, DRAWFLAG_NORMAL); + Sbar_DrawXNum(pos, caps_team2, 4, 24, rgb, 1, DRAWFLAG_NORMAL); + pos_x = xmin - 4*10; + Sbar_DrawXNum(pos + '0 24', tm.sb_frags, 4, 10, rgb, 1, DRAWFLAG_NORMAL); + pos_x = xmin; } else - Sbar_DrawXNum(pos-'106 0 0'-sbar, tm.sb_frags, 4, 24, rgb, 1, DRAWFLAG_NORMAL); + Sbar_DrawXNum(pos, tm.sb_frags, 4, 24, rgb, 1, DRAWFLAG_NORMAL); + pos_x = xmin; - teammin_y = pos_y - 2; - teammax_y = pos_y + 2 + 10 * (tm.sb_player); - drawfill(teammin, teammax - teammin, rgb, 0.2, DRAWFLAG_NORMAL); + // abuse specs as playerounter + specs = 0; + for(pl = sortedPlayers.sort_next; pl; pl = pl.sort_next) + { + if(pl.sb_team == tm.sb_team) + ++specs; + } + + if(specs < 2) + specs = 2; + if(gametype == GAME_CTF && specs < 4) + specs = 4; + + tmp_x = sbwidth; + tmp_y = 10 * specs; + drawfill(pos - '1 1', tmp + '2 0', rgb, 0.2, DRAWFLAG_NORMAL); - plcount = 0; for(pl = sortedPlayers.sort_next; pl; pl = pl.sort_next) { if(pl.sb_team != tm.sb_team) continue; Sbar_PrintScoreboardItem(pos, pl, (pl.sb_player == player_localentnum - 1), 0); pos_y += 10; - ++plcount; + tmp_y -= 10; } - - pos_y += 12; - if(pos_y < minoffset) - pos_y = minoffset; + pos_y += tmp_y + 12; } - // rgb := tempvector :) rgb = pos + '0 12 0'; - //pos += '64 24 0'; pos_y += 24; - //for(i = 0; i < maxclients; ++i) + specs = 0; for(pl = sortedPlayers.sort_next; pl; pl = pl.sort_next) { if(pl.sb_team != COLOR_SPECTATOR) @@ -382,40 +604,15 @@ void Sbar_DrawScoreboard() //drawcolorcodedstring(pos, getplayerkey(pl.sb_player, "name"), '8 8 0', 1, 0); Sbar_PrintScoreboardItem(pos, pl, (pl.sb_player == player_localentnum - 1), 1); pos += '0 10 0'; - specs = true; + ++specs; } - if(specs) - drawstring(rgb, "Spectators", '8 8 0', '1 1 1', 1, 0); - } else { - //for(i = 0; i < maxclients; ++i) - for(pl = sortedPlayers.sort_next; pl; pl = pl.sort_next) - { - if(pl.sb_team != COLOR_TEAM1) - continue; - //drawstring(pos, ftos(pl.sb_frags), '8 8 0', '1 1 1', 1, 0); - //drawcolorcodedstring(pos + '64 0 0', getplayerkey(pl.sb_player, "name"), '8 8 0', 1, 0); - Sbar_PrintScoreboardItem(pos, pl, (pl.sb_player == player_localentnum - 1), 0); - pos += '0 12 0'; - } - rgb = pos + '0 12 0'; - //pos += '64 24 0'; - pos_y += 24; - for(pl = sortedPlayers.sort_next; pl; pl = pl.sort_next) - { - if(pl.sb_team != COLOR_SPECTATOR) - continue; - specs = true; - //drawcolorcodedstring(pos, getplayerkey(pl.sb_player, "name"), '8 8 0', 1, 0); - Sbar_PrintScoreboardItem(pos, pl, (pl.sb_player == player_localentnum - 1), 1); - pos += '0 12 0'; - } if(specs) drawstring(rgb, "Spectators", '8 8 0', '1 1 1', 1, 0); } + sbar = sbar_save; } - void Sbar_Score(float margin) { float timelimit, timeleft, minutes, seconds, distribution, myplace; @@ -865,7 +1062,22 @@ void CSQC_ctf_hud(void) redflag = (stat_items/32768) & 3; blueflag = (stat_items/131072) & 3; - pos_x = (cvar("sbar_flagstatus_right")) ? vid_conwidth - 10 - sbar_x - 64 : 10 - sbar_x; + /** + * FTEQCC BUG! + * For some reason now not even THAT works there... + * Maybe the minus' precedence screws it up? The last one there, maybe I should use brackets + ** + * pos_x = (cvar("sbar_flagstatus_right")) ? vid_conwidth - 10 - sbar_x - 64 : 10 - sbar_x; + ** Should try those later: + * pos_x = (cvar("sbar_flagstatus_right")) ? (vid_conwidth - 10 - sbar_x - 64) : (10 - sbar_x); + * pos_x = ( (cvar("sbar_flagstatus_right")) ? vid_conwidth - 10 - 64 : 10 ) - sbar_x; + */ + + if(cvar("sbar_flagstatus_right")) + pos_x = vid_conwidth - 10 - sbar_x - 64; + else + pos_x = 10 - sbar_x; + pos_z = 0; if(sbar_hudselector == 1) diff --git a/data/qcsrc/common/constants.qh b/data/qcsrc/common/constants.qh index 15147aadd..3377064ab 100644 --- a/data/qcsrc/common/constants.qh +++ b/data/qcsrc/common/constants.qh @@ -1,3 +1,6 @@ +// COMMIT-TODO: Update if necessary before committing +#define CSQC_REVISION 1 + // probably put these in common/ // so server/ and client/ can be synced better const float GAME_DEATHMATCH = 1; @@ -154,13 +157,13 @@ const float TE_CSQC_START = 100; const float TE_CSQC_INIT = 100; const float TE_CSQC_PING = 101; const float TE_CSQC_CAPTURES = 102; +const float TE_CSQC_RETURNS = 103; +const float TE_CSQC_DEATHS = 104; -const float TE_CSQC_END = 101; +const float TE_CSQC_END = 105; const float STAT_KH_KEYS = 32; -const float STAT_CTF_CAPTURES = 33; - -const float STAT_CTF_STATE = 34; +const float STAT_CTF_STATE = 33; const float CTF_STATE_ATTACK = 1; const float CTF_STATE_DEFEND = 2; const float CTF_STATE_COMMANDER = 3; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index c03d734f0..c73025af1 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -348,7 +348,8 @@ PutObserverInServer putting a client as observer in the server ============= */ -void ctf_UpdateCaptures(); +void ctf_UpdateCaptures(float); +void ctf_UpdateReturns(float); void PutObserverInServer (void) { entity spot; @@ -376,7 +377,7 @@ void PutObserverInServer (void) if(g_ctf) { self.captures = 0; - ctf_UpdateCaptures(); + ctf_UpdateCaptures(MSG_BROADCAST); } if(self.frags <= 0 && self.frags > -666 && g_lms && self.killcount != -666) @@ -402,6 +403,8 @@ void PutObserverInServer (void) self.death_time = 0; self.dead_frame = 0; self.deaths = 0; + self.captures = 0; + self.returns = 0; self.alpha = 0; self.scale = 0; self.fade_time = 0; @@ -454,6 +457,8 @@ void PutObserverInServer (void) } else if(!g_lms) self.frags = -666; + + net_UpdateDeaths(MSG_BROADCAST); } float RestrictSkin(float s) @@ -662,6 +667,15 @@ void PutClientInServer (void) if(!g_arena) if(!g_lms) self.frags = 0; + if(g_ctf) + { + self.captures = 0; + self.returns = 0; + ctf_UpdateCaptures(MSG_BROADCAST); + ctf_UpdateReturns(MSG_BROADCAST); + } + self.deaths = 0; + net_UpdateDeaths(MSG_BROADCAST); } self.cnt = WEP_LASER; @@ -717,6 +731,7 @@ void SendCSQCInfo(void) msg_entity = self; WriteByte(MSG_ONE, SVC_TEMPENTITY); WriteByte(MSG_ONE, TE_CSQC_INIT); + WriteShort(MSG_ONE, CSQC_REVISION); WriteByte(MSG_ONE, maxclients-1); } @@ -768,8 +783,12 @@ void ClientKill_Now_TeamChange() if(g_ctf) { self.captures = 0; - ctf_UpdateCaptures(); + self.returns = 0; + ctf_UpdateCaptures(MSG_BROADCAST); + ctf_UpdateReturns(MSG_BROADCAST); } + self.deaths = 0; + net_UpdateDeaths(MSG_BROADCAST); if(self.killindicator_teamchange == -1) { self.team = -1; @@ -966,7 +985,6 @@ Called when a client connects to the server //void ctf_clientconnect(); string ColoredTeamName(float t); void DecodeLevelParms (void); -void ctf_SendCaptures(entity); //void dom_player_join_team(entity pl); void ClientConnect (void) { @@ -1121,8 +1139,15 @@ void ClientConnect (void) self.jointime = time; self.allowedTimeouts = cvar("sv_timeout_number"); - SendCSQCInfo(); - ctf_SendCaptures(self); + if(clienttype(self) == CLIENTTYPE_REAL) + { + sprint(self, strcat("nexuiz-csqc protocol ", ftos(CSQC_REVISION), "\n")); + SendCSQCInfo(); + msg_entity = self; + ctf_UpdateCaptures(MSG_ONE); + ctf_UpdateReturns(MSG_ONE); + net_UpdateDeaths(MSG_ONE); + } } /* @@ -1178,7 +1203,7 @@ void ClientDisconnect (void) if(g_ctf) { self.captures = 0; - ctf_UpdateCaptures(); + ctf_UpdateCaptures(MSG_BROADCAST); } save = self.flags; diff --git a/data/qcsrc/server/cl_player.qc b/data/qcsrc/server/cl_player.qc index efe74f8ab..99c07568b 100644 --- a/data/qcsrc/server/cl_player.qc +++ b/data/qcsrc/server/cl_player.qc @@ -351,6 +351,19 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float void ClientKill_Now_TeamChange(); +void net_UpdateDeaths(float msg_target) +{ + entity p; + WriteByte(msg_target, SVC_TEMPENTITY); + WriteByte(msg_target, TE_CSQC_DEATHS); + FOR_EACH_PLAYER(p) + { + WriteByte(msg_target, num_for_edict(p)); + WriteByte(msg_target, p.deaths); + } + WriteByte(msg_target, 0); +} + void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { local float take, save, waves, sdelay; @@ -447,6 +460,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht defer_ClientKill_Now_TeamChange = FALSE; self.deaths += 1; + net_UpdateDeaths(MSG_BROADCAST); // get rid of kill indicator if(self.killindicator) diff --git a/data/qcsrc/server/clientcommands.qc b/data/qcsrc/server/clientcommands.qc index b81b7e1b2..7d05afbc1 100644 --- a/data/qcsrc/server/clientcommands.qc +++ b/data/qcsrc/server/clientcommands.qc @@ -214,8 +214,6 @@ void SV_ParseClientCommand(string s) { kh_Key_DropAll(self, TRUE); WaypointSprite_PlayerDead(); DistributeFragsAmongTeam(self, self.team, 1.0); - self.captures = 0; - ctf_UpdateCaptures(); self.classname = "observer"; if(blockSpectators) sprint(self, strcat("^7You have to become a player within the next ", ftos(cvar("g_maxplayers_spectator_blocktime")), " seconds, otherwise you will be kicked, because spectators aren't allowed at this time!\n")); @@ -228,8 +226,6 @@ void SV_ParseClientCommand(string s) { if(isJoinAllowed()) { self.classname = "player"; self.frags = 0; - self.captures = 0; - ctf_UpdateCaptures(); bprint ("^4", self.netname, "^4 is playing now\n"); PutClientInServer(); } diff --git a/data/qcsrc/server/ctf.qc b/data/qcsrc/server/ctf.qc index efbfd1c12..f6c519988 100644 --- a/data/qcsrc/server/ctf.qc +++ b/data/qcsrc/server/ctf.qc @@ -230,37 +230,32 @@ void FlagThink() } }; -void ctf_SendCaptures(entity player) +void ctf_UpdateCaptures(float msg_target) { entity p; - if(clienttype(player) != CLIENTTYPE_REAL) - return; - msg_entity = player; - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_CSQC_CAPTURES); - WriteByte(MSG_ONE, caps_team1); - WriteByte(MSG_ONE, caps_team2); - FOR_EACH_CLIENT(p) + WriteByte(msg_target, SVC_TEMPENTITY); + WriteByte(msg_target, TE_CSQC_CAPTURES); + WriteByte(msg_target, cvar("g_ctf_win_mode")); + WriteByte(msg_target, caps_team1); + WriteByte(msg_target, caps_team2); + FOR_EACH_PLAYER(p) { - WriteByte(MSG_ONE, num_for_edict(p)); - WriteByte(MSG_ONE, p.captures); + WriteByte(msg_target, num_for_edict(p)); + WriteByte(msg_target, p.captures); } - WriteByte(MSG_ONE, 0); + WriteByte(msg_target, 0); } - -void ctf_UpdateCaptures() +void ctf_UpdateReturns(float msg_target) { entity p; - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_CAPTURES); - WriteByte(MSG_BROADCAST, caps_team1); - WriteByte(MSG_BROADCAST, caps_team2); - FOR_EACH_REALCLIENT(p) + WriteByte(msg_target, SVC_TEMPENTITY); + WriteByte(msg_target, TE_CSQC_RETURNS); + FOR_EACH_PLAYER(p) { - WriteByte(MSG_BROADCAST, num_for_edict(p)); - WriteByte(MSG_BROADCAST, p.captures); + WriteByte(msg_target, num_for_edict(p)); + WriteByte(msg_target, p.returns); } - WriteByte(MSG_BROADCAST, 0); + WriteByte(msg_target, 0); } void FlagTouch() @@ -323,7 +318,7 @@ void FlagTouch() caps_team2++; else print("Unknown team captured the flag!\n"); - ctf_UpdateCaptures(); + ctf_UpdateCaptures(MSG_BROADCAST); // FIXME: When counting captures, should the score be updated? LogCTF("capture", other.flagcarried.team, other); @@ -387,10 +382,15 @@ void FlagTouch() { // return flag bprint(other.netname, "^7 returned the ", self.netname, "\n"); - if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) - UpdateFrags(other, cvar("g_ctf_flagscore_return")); - else - UpdateFrags(other, cvar("g_ctf_flagscore_return_rogue")); + if(cvar("g_ctf_win_mode") == 2) + { + if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) + UpdateFrags(other, cvar("g_ctf_flagscore_return")); + else + UpdateFrags(other, cvar("g_ctf_flagscore_return_rogue")); + } + other.returns++; + ctf_UpdateReturns(MSG_BROADCAST); LogCTF("return", self.team, other); sound (self, CHAN_AUTO, self.noise1, 1, ATTN_NONE); ReturnFlag(self); diff --git a/data/qcsrc/server/defs.qh b/data/qcsrc/server/defs.qh index e830f9cb4..776d03d3d 100644 --- a/data/qcsrc/server/defs.qh +++ b/data/qcsrc/server/defs.qh @@ -438,4 +438,5 @@ float sv_maxidle_spectatorsareidle; float next_pingtime; .float captures; +.float returns; float caps_team1, caps_team2; -- 2.39.2