From fc52af822a15abdc78924963b5f424975605cbb2 Mon Sep 17 00:00:00 2001 From: div0 Date: Fri, 1 Aug 2008 06:58:59 +0000 Subject: [PATCH] make CSQC scores safer against packet loss/rearrangements not REALLY needed as LH is going to fix the CSQC networking bug anyway, but this way of working with the player entities also allows to show not yet connected (still downloading) players git-svn-id: svn://svn.icculus.org/nexuiz/trunk@3992 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/qcsrc/client/Main.qc | 225 +++++++++++++++++------------ data/qcsrc/client/main.qh | 5 + data/qcsrc/client/miscfunctions.qc | 40 ++--- data/qcsrc/client/sbar.qc | 21 ++- data/qcsrc/client/teamplay.qc | 4 +- data/qcsrc/common/constants.qh | 2 + data/qcsrc/server/cl_client.qc | 1 + data/qcsrc/server/scores.qc | 23 ++- data/qcsrc/server/scores.qh | 18 ++- data/qcsrc/server/scores_rules.qc | 12 +- 10 files changed, 201 insertions(+), 150 deletions(-) diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index ce92b3bb9..2da3592d8 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -79,7 +79,7 @@ void CSQC_Init(void) teams = Sort_Spawn(); players = Sort_Spawn(); - teamspec = AddTeam(COLOR_SPECTATOR); // add specs first + GetTeam(COLOR_SPECTATOR, true); // add specs first } // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc) @@ -98,6 +98,83 @@ void CSQC_Shutdown(void) buf_del(databuf); } +.float has_team; +float SetTeam(entity o, float Team) +{ + entity tm; + if(Team == -1) // leave + { + if(o.has_team) + { + //print("(DISCONNECT) leave team ", ftos(o.team), "\n"); + tm = GetTeam(o.team, false); + tm.team_size -= 1; + o.has_team = 0; + return TRUE; + } + } + else + { + if not(o.has_team) + { + //print("(CONNECT) enter team ", ftos(o.team), "\n"); + o.team = Team; + tm = GetTeam(Team, true); + tm.team_size += 1; + o.has_team = 1; + return TRUE; + } + else if(Team != o.team) + { + //print("(CHANGE) leave team ", ftos(o.team), "\n"); + tm = GetTeam(o.team, false); + tm.team_size -= 1; + o.team = Team; + //print("(CHANGE) enter team ", ftos(o.team), "\n"); + tm = GetTeam(Team, true); + tm.team_size += 1; + return TRUE; + } + } + return FALSE; +} + +void Playerchecker_Think() +{ + entity pl; + float i; + for(i = 0; i < maxclients; ++i) + { + if(getplayerkey(i, "name") == "") + { + if(playerslots[i].sort_prev) + { + //print("playerchecker: KILL KILL KILL\n"); + // player disconnected + SetTeam(playerslots[i], -1); + RemovePlayer(playerslots[i]); + playerslots[i].sort_prev = world; + playerslots[i].gotscores = 0; + } + } + else + { + if not(playerslots[i].sort_prev) + { + //print("playerchecker: SPAWN SPAWN SPAWN\n"); + // player connected + if not(playerslots[i]) + playerslots[i] = spawn(); + playerslots[i].sv_entnum = i; + playerslots[i].gotscores = 0; + SetTeam(playerslots[i], COLOR_SPECTATOR); + RegisterPlayer(playerslots[i]); + } + } + } + self.nextthink = time + 0.2; +} + void PostInit(void) { float i; @@ -107,13 +184,15 @@ void PostInit(void) 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"); } localcmd(strcat("\nsbar_columns_set ", cvar_string("sbar_columns"), ";\n")); + entity playerchecker; + playerchecker = spawn(); + playerchecker.think = Playerchecker_Think; + playerchecker.nextthink = time + 0.2; + postinit = true; } @@ -230,70 +309,45 @@ void Ent_RemoveONS() } } -void Gamemode_Init(); -void Ent_ReadScoresInfo() -{ - float i; - gametype = ReadByte(); - for(i = 0; i < MAX_SCORE; ++i) - { - scores_label[i] = strzone(ReadString()); - scores_flags[i] = ReadByte(); - } - for(i = 0; i < MAX_TEAMSCORE; ++i) - { - teamscores_label[i] = strzone(ReadString()); - teamscores_flags[i] = ReadByte(); - } - Sbar_InitScores(); - Gamemode_Init(); -} - void Ent_ReadPlayerScore(float isNew) { float i, Team; - entity tm; + entity tm, o; // damnit -.- don't want to go change every single .sv_entnum in sbar.qc AGAIN // (no I've never heard of M-x replace-string, sed, or anything like that) + isNew = !self.owner; // workaround for DP bug self.sv_entnum = ReadByte()-1; Team = GetPlayerColor(self.sv_entnum); - if(isNew) - RegisterPlayer(self); + if not(playerslots[self.sv_entnum]) + playerslots[self.sv_entnum] = spawn(); + o = self.owner = playerslots[self.sv_entnum]; + o.sv_entnum = self.sv_entnum; + o.gotscores = 1; + if not(o.sort_prev) + RegisterPlayer(o); - if(isNew || Team != self.team) - { - if(!isNew) - { - tm = GetTeam(self.team, false); - tm.team_size -= 1; - } - - self.team = Team; - tm = GetTeam(Team, true); - tm.team_size += 1; - } + SetTeam(o, Team); for(i = 0; i < MAX_SCORE; ++i) - self.(scores[i]) = ReadShort(); + o.(scores[i]) = ReadShort(); - Sbar_UpdatePlayerPos(self); + Sbar_UpdatePlayerPos(o); } void Ent_ReadTeamScore(float isNew) { float i; + entity o; self.team = ReadByte(); - - if(isNew) - RegisterTeam(self); + o = self.owner = GetTeam(self.team, true); for(i = 0; i < MAX_TEAMSCORE; ++i) - self.(teamscores[i]) = ReadShort(); + o.(teamscores[i]) = ReadShort(); - Sbar_UpdateTeamPos(self); + Sbar_UpdateTeamPos(o); } // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured. @@ -317,8 +371,6 @@ void(float bIsNewEntity) CSQC_Ent_Update = } } } - else if(self.enttype == ENT_CLIENT_SCORES_INFO) - Ent_ReadScoresInfo(); else if(self.enttype == ENT_CLIENT_SCORES) Ent_ReadPlayerScore(bIsNewEntity); else if(self.enttype == ENT_CLIENT_TEAMSCORES) @@ -347,20 +399,23 @@ void CSQC_Ent_Remove() ent.chain = self.chain; } } - } else if(self.enttype == ENT_CLIENT_SCORES_INFO) - { - // OH NOES!! WE LOST DA SCORES INFO ENTITY - print("The world is going to explode."); - // kkthxbai } else if(self.enttype == ENT_CLIENT_SCORES) { entity tm; - tm = GetTeam(self.team, false); - tm.team_size -= 1; - RemovePlayer(self); + if(self.owner) + { + SetTeam(self.owner, -1); + RemovePlayer(self.owner); + self.owner.sort_prev = NULL; + } } else if(self.enttype == ENT_CLIENT_TEAMSCORES) { - RemoveTeam(self); + /* + if(self.owner) + RemoveTeam(self.owner); + */ + // we don't NEED to remove them... they won't display anyway + // plus, svqc never does this anyway } remove(self); } @@ -440,6 +495,25 @@ void CSQC_Parse_CenterPrint(string strMessage) void CSQC_CheckRevision(); +void Gamemode_Init(); +void Net_ReadScoresInfo() +{ + float i; + gametype = ReadByte(); + for(i = 0; i < MAX_SCORE; ++i) + { + scores_label[i] = strzone(ReadString()); + scores_flags[i] = ReadByte(); + } + for(i = 0; i < MAX_TEAMSCORE; ++i) + { + teamscores_label[i] = strzone(ReadString()); + teamscores_flags[i] = ReadByte(); + } + Sbar_InitScores(); + Gamemode_Init(); +} + void Net_ReadInit() { csqc_revision = ReadShort(); @@ -458,29 +532,6 @@ void Net_ReadPings() } } -void Net_ReadCaptures() -{ - float plnum, caps, mode; - mode = ReadByte(); - caps_team1 = ReadByte(); - caps_team2 = ReadByte(); - for(plnum = ReadByte(); plnum != 0; plnum = ReadByte()) - { - caps = ReadByte(); - bufstr_set(databuf, DATABUF_CAPTURES + plnum-1, ftos(caps)); - } -} - -void Net_ReadDatabuf(float ofs) -{ - float plnum, data; - for(plnum = ReadByte(); plnum != 0; plnum = ReadByte()) - { - data = ReadByte(); - bufstr_set(databuf, ofs + plnum-1, ftos(data)); - } -} - string Net_ReadPicture() { string img; @@ -533,18 +584,6 @@ float CSQC_Parse_TempEntity() Net_ReadPings(); bHandled = true; break; - case TE_CSQC_CAPTURES: - Net_ReadCaptures(); - bHandled = true; - break; - case TE_CSQC_RETURNS: - Net_ReadDatabuf(DATABUF_RETURNS); - bHandled = true; - break; - case TE_CSQC_DEATHS: - Net_ReadDatabuf(DATABUF_DEATHS); - bHandled = true; - break; case TE_CSQC_MAPVOTE: Net_Mapvote(); bHandled = true; @@ -553,6 +592,10 @@ float CSQC_Parse_TempEntity() Net_Config(); bHandled = true; break; + case TE_CSQC_SCORESINFO: + Net_ReadScoresInfo(); + bHandled = true; + break; default: // No special logic for this temporary entity; return 0 so the engine can handle it bHandled = false; diff --git a/data/qcsrc/client/main.qh b/data/qcsrc/client/main.qh index 5505436ec..3adb2243a 100644 --- a/data/qcsrc/client/main.qh +++ b/data/qcsrc/client/main.qh @@ -101,3 +101,8 @@ float csqc_flags; #define CSQC_FLAG_READPICTURE 1 string config_get(string key, string defaultvalue); + +entity playerslots[255]; // 255 is engine limit on maxclients +entity teamslots[17]; // 17 teams (including "spectator team") +.float gotscores; +.entity owner; diff --git a/data/qcsrc/client/miscfunctions.qc b/data/qcsrc/client/miscfunctions.qc index ec3f88375..37df4aab1 100644 --- a/data/qcsrc/client/miscfunctions.qc +++ b/data/qcsrc/client/miscfunctions.qc @@ -9,7 +9,7 @@ float RegisterPlayer(entity player) entity pl; for(pl = players.sort_next; pl; pl = pl.sort_next) if(pl == player) - return false; + error("Player already registered!"); player.sort_next = players.sort_next; player.sort_prev = players; if(players.sort_next) @@ -35,19 +35,6 @@ void RemovePlayer(entity player) player.sort_next.sort_prev = parent; } -entity AddTeam(float num) -{ - entity tm; - tm = spawn(); - tm.team = num; - tm.sort_next = teams.sort_next; - tm.sort_prev = teams; - if(teams.sort_next) - teams.sort_next.sort_prev = tm; - teams.sort_next = tm; - return tm; -} - void MoveToLast(entity e) { other = e.sort_next; @@ -65,7 +52,7 @@ float RegisterTeam(entity Team) entity tm; for(tm = teams.sort_next; tm; tm = tm.sort_next) if(tm == Team) - return false; + error("Team already registered!"); Team.sort_next = teams.sort_next; Team.sort_prev = teams; if(teams.sort_next) @@ -91,19 +78,20 @@ void RemoveTeam(entity Team) Team.sort_next.sort_prev = parent; } -entity GetTeam(float num, float add) +entity GetTeam(float Team, float add) { + float num; entity tm; - for(tm = teams.sort_next; tm; tm = tm.sort_next) - { - if(tm.team == num) - return tm; - } - - if(add) - return AddTeam(num); - - return NULL; + num = (Team == COLOR_SPECTATOR) ? 16 : Team; + if(teamslots[num]) + return teamslots[num]; + if not(add) + return NULL; + tm = spawn(); + tm.team = Team; + teamslots[num] = tm; + RegisterTeam(tm); + return tm; } float stringwidth_oldfont(string text, float handleColors) diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index 0d5ba5f25..cdff61aa4 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -20,8 +20,6 @@ entity sortedTeams; float ps_primary, ps_secondary; float ts_primary, ts_secondary; -entity team1, team2, team3, team4, teamspec; - void CSQC_kh_hud(); void CSQC_ctf_hud(); void MapVote_Draw(); @@ -162,6 +160,8 @@ void Sbar_InitScores() } void Sbar_UpdatePlayerPos(entity pl); +float SetTeam(entity pl, float Team); +//float lastpnum; void Sbar_UpdatePlayerTeams() { float Team; @@ -173,15 +173,8 @@ void Sbar_UpdatePlayerTeams() { num += 1; Team = GetPlayerColor(pl.sv_entnum); - if(pl.team != Team) + if(SetTeam(pl, Team)) { - tmp = GetTeam(pl.team, false); - tmp.team_size -= 1; - tmp = GetTeam(Team, true); - tmp.team_size += 1; - - pl.team = Team; - tmp = pl.sort_prev; Sbar_UpdatePlayerPos(pl); if(tmp) @@ -190,7 +183,11 @@ void Sbar_UpdatePlayerTeams() pl = players.sort_next; } } - //print(strcat("PNUM: ", ftos(num), "\n")); + /* + if(num != lastpnum) + print(strcat("PNUM: ", ftos(num), "\n")); + lastpnum = num; + */ } float Sbar_ComparePlayerScores(entity left, entity right) @@ -502,6 +499,8 @@ string Sbar_GetField(entity pl, float field) return str; case SP_NAME: + if not(pl.gotscores) + return strcat(getplayerkey(pl.sv_entnum, "name"), " ^7(connecting)"); return getplayerkey(pl.sv_entnum, "name"); case SP_KDRATIO: diff --git a/data/qcsrc/client/teamplay.qc b/data/qcsrc/client/teamplay.qc index 0b172cf8f..c07d4396e 100644 --- a/data/qcsrc/client/teamplay.qc +++ b/data/qcsrc/client/teamplay.qc @@ -16,7 +16,9 @@ float TeamByColor(float color) float GetPlayerColor(float i) { - if(getplayerkey(i, "frags") == "-666") + if not(playerslots[i].gotscores) // unconnected + return COLOR_SPECTATOR; + else if(getplayerkey(i, "frags") == "-666") return COLOR_SPECTATOR; else if(!teamplay) return 0; diff --git a/data/qcsrc/common/constants.qh b/data/qcsrc/common/constants.qh index 2ceeefae2..6740d98b7 100644 --- a/data/qcsrc/common/constants.qh +++ b/data/qcsrc/common/constants.qh @@ -4,6 +4,7 @@ // Revision 3: optimized map vote protocol // Revision 4: CSQC config var system // Revision 5: mapvote time fix +// Revision 6: more robust against packet loss/delays, also show not yet connected clients #define CSQC_REVISION 5 // probably put these in common/ @@ -180,6 +181,7 @@ const float TE_CSQC_DEATHS = 104; const float TE_CSQC_PICTURE = 105; const float TE_CSQC_MAPVOTE = 106; const float TE_CSQC_CONFIG = 107; +const float TE_CSQC_SCORESINFO = 108; const float STAT_KH_KEYS = 32; const float STAT_CTF_STATE = 33; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index 094f96790..b23c361d4 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -1138,6 +1138,7 @@ void ClientConnect (void) MapVote_SendData(MSG_ONE); MapVote_UpdateData(MSG_ONE); } + ScoreInfo_Write(MSG_ONE); } if(g_lms) diff --git a/data/qcsrc/server/scores.qc b/data/qcsrc/server/scores.qc index 01067ee65..e07d8f3cd 100644 --- a/data/qcsrc/server/scores.qc +++ b/data/qcsrc/server/scores.qc @@ -121,22 +121,22 @@ void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags) teamscores_primary = teamscores[i]; } -float ScoreInfo_SendEntity(entity to) +void ScoreInfo_Write(float targ) { - WriteByte(MSG_ENTITY, ENT_CLIENT_SCORES_INFO); float i; - WriteByte(MSG_ENTITY, game); + WriteByte(targ, SVC_TEMPENTITY); + WriteByte(targ, TE_CSQC_SCORESINFO); + WriteByte(targ, game); for(i = 0; i < MAX_SCORE; ++i) { - WriteString(MSG_ENTITY, scores_label[i]); - WriteByte(MSG_ENTITY, scores_flags[i]); + WriteString(targ, scores_label[i]); + WriteByte(targ, scores_flags[i]); } for(i = 0; i < MAX_TEAMSCORE; ++i) { - WriteString(MSG_ENTITY, teamscores_label[i]); - WriteByte(MSG_ENTITY, teamscores_flags[i]); + WriteString(targ, teamscores_label[i]); + WriteByte(targ, teamscores_flags[i]); } - return TRUE; } void ScoreInfo_Init(float teams) @@ -150,12 +150,7 @@ void ScoreInfo_Init(float teams) TeamScore_Spawn(COLOR_TEAM3, "Yellow"); if(teams >= 4) TeamScore_Spawn(COLOR_TEAM4, "Pink"); - entity si; - si = spawn(); - Net_LinkEntity(si); - si.classname = "csqc_score_info"; - si.SendEntity = ScoreInfo_SendEntity; - si.Version = 1; + ScoreInfo_Write(MSG_ALL); } /* diff --git a/data/qcsrc/server/scores.qh b/data/qcsrc/server/scores.qh index 4f88cb74d..e66a5a7f6 100644 --- a/data/qcsrc/server/scores.qh +++ b/data/qcsrc/server/scores.qh @@ -50,12 +50,6 @@ void PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, fl */ #define PlayerTeamScore_AddScore(p,s) PlayerTeamScore_Add(p, SP_SCORE, ST_SCORE, s) -/** - * Initialize the scores info for the given number of teams. - * Immediately set all labels afterwards. - */ -void ScoreInfo_Init(float teams); - /** * Set the label of a team score item, as well as the scoring flags. */ @@ -66,6 +60,18 @@ void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags); */ void ScoreInfo_SetLabel_PlayerScore(float i, string label, float scoreflags); +/** + * Initialize the scores info for the given number of teams. + * Set all labels right before this call. + */ +void ScoreInfo_Init(float teams); + +/** + * Writes the scores info to the given stream. Use this in ClientConnect to + * notify newly connecting players. + */ +void ScoreInfo_Write(float targ); + /** * Clear ALL scores (for ready-restart). */ diff --git a/data/qcsrc/server/scores_rules.qc b/data/qcsrc/server/scores_rules.qc index 88c189ee2..de72fda5f 100644 --- a/data/qcsrc/server/scores_rules.qc +++ b/data/qcsrc/server/scores_rules.qc @@ -3,9 +3,10 @@ void CheckAllowedTeams (entity for_whom); // NOTE: SP_ constants may not be >= MAX_SCORE; ST_constants may not be >= MAX_TEAMSCORE // scores that should be in all modes: +float ScoreRules_teams; void ScoreRules_basics(float teams, float sprio) { - ScoreInfo_Init(teams); + ScoreRules_teams = teams; if(sprio) ScoreInfo_SetLabel_TeamScore (ST_SCORE, "score", sprio); ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "kills", 0); @@ -14,6 +15,10 @@ void ScoreRules_basics(float teams, float sprio) if(sprio) ScoreInfo_SetLabel_PlayerScore(SP_SCORE, "score", sprio); } +void ScoreRules_basics_end() +{ + ScoreInfo_Init(ScoreRules_teams); +} void ScoreRules_generic() { CheckAllowedTeams(world); @@ -24,6 +29,7 @@ void ScoreRules_generic() } else ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); } // g_ctf @@ -59,6 +65,7 @@ void ScoreRules_ctf() ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0); ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0); ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0); + ScoreRules_basics_end(); } // g_domination @@ -78,6 +85,7 @@ void ScoreRules_dom() ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks); ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks); ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); + ScoreRules_basics_end(); } // LMS stuff @@ -88,6 +96,7 @@ void ScoreRules_lms() ScoreRules_basics(0, 0); ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY); ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE); + ScoreRules_basics_end(); } // Key hunt stuff @@ -108,4 +117,5 @@ void ScoreRules_kh(float teams) ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS, "pickups", 0); ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS, "kckills", 0); ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); + ScoreRules_basics_end(); } -- 2.39.2