r3992 - in trunk/data/qcsrc: client common server

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Fri Aug 1 02:58:59 EDT 2008


Author: div0
Date: 2008-08-01 02:58:59 -0400 (Fri, 01 Aug 2008)
New Revision: 3992

Modified:
   trunk/data/qcsrc/client/Main.qc
   trunk/data/qcsrc/client/main.qh
   trunk/data/qcsrc/client/miscfunctions.qc
   trunk/data/qcsrc/client/sbar.qc
   trunk/data/qcsrc/client/teamplay.qc
   trunk/data/qcsrc/common/constants.qh
   trunk/data/qcsrc/server/cl_client.qc
   trunk/data/qcsrc/server/scores.qc
   trunk/data/qcsrc/server/scores.qh
   trunk/data/qcsrc/server/scores_rules.qc
Log:
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


Modified: trunk/data/qcsrc/client/Main.qc
===================================================================
--- trunk/data/qcsrc/client/Main.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/client/Main.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -79,7 +79,7 @@
 	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 @@
 	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 @@
 	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 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();
+	o = self.owner = GetTeam(self.team, true);
 
-	if(isNew)
-		RegisterTeam(self);
-
 	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 @@
 			}
 		}
 	}
-	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 @@
 					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_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_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 @@
 			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 @@
 			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;

Modified: trunk/data/qcsrc/client/main.qh
===================================================================
--- trunk/data/qcsrc/client/main.qh	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/client/main.qh	2008-08-01 06:58:59 UTC (rev 3992)
@@ -101,3 +101,8 @@
 #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;

Modified: trunk/data/qcsrc/client/miscfunctions.qc
===================================================================
--- trunk/data/qcsrc/client/miscfunctions.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/client/miscfunctions.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -9,7 +9,7 @@
 	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 @@
 		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 @@
 	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 @@
 		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)

Modified: trunk/data/qcsrc/client/sbar.qc
===================================================================
--- trunk/data/qcsrc/client/sbar.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/client/sbar.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -20,8 +20,6 @@
 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_UpdatePlayerPos(entity pl);
+float SetTeam(entity pl, float Team);
+//float lastpnum;
 void Sbar_UpdatePlayerTeams()
 {
 	float Team;
@@ -173,15 +173,8 @@
 	{
 		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 @@
 				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 @@
 			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:

Modified: trunk/data/qcsrc/client/teamplay.qc
===================================================================
--- trunk/data/qcsrc/client/teamplay.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/client/teamplay.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -16,8 +16,10 @@
 
 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;
 	else

Modified: trunk/data/qcsrc/common/constants.qh
===================================================================
--- trunk/data/qcsrc/common/constants.qh	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/common/constants.qh	2008-08-01 06:58:59 UTC (rev 3992)
@@ -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_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;

Modified: trunk/data/qcsrc/server/cl_client.qc
===================================================================
--- trunk/data/qcsrc/server/cl_client.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/server/cl_client.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -1138,6 +1138,7 @@
 			MapVote_SendData(MSG_ONE);
 			MapVote_UpdateData(MSG_ONE);
 		}
+		ScoreInfo_Write(MSG_ONE);
 	}
 
 	if(g_lms)

Modified: trunk/data/qcsrc/server/scores.qc
===================================================================
--- trunk/data/qcsrc/server/scores.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/server/scores.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -121,22 +121,22 @@
 		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 @@
 		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);
 }
 
 /*

Modified: trunk/data/qcsrc/server/scores.qh
===================================================================
--- trunk/data/qcsrc/server/scores.qh	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/server/scores.qh	2008-08-01 06:58:59 UTC (rev 3992)
@@ -51,12 +51,6 @@
 #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.
  */
 void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags);
@@ -67,6 +61,18 @@
 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).
  */
 void Score_ClearAll();

Modified: trunk/data/qcsrc/server/scores_rules.qc
===================================================================
--- trunk/data/qcsrc/server/scores_rules.qc	2008-07-31 06:23:51 UTC (rev 3991)
+++ trunk/data/qcsrc/server/scores_rules.qc	2008-08-01 06:58:59 UTC (rev 3992)
@@ -3,9 +3,10 @@
 
 // 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 @@
 	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 @@
 	}
 	else
 		ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY);
+	ScoreRules_basics_end();
 }
 
 // g_ctf
@@ -59,6 +65,7 @@
 	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 @@
 	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 @@
 	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 @@
 	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();
 }




More information about the nexuiz-commits mailing list