- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
\r
- - EXPECT=5bbaf1e67c9a18ac159614a7e59b8d1e\r
+ - EXPECT=757e5413dab5085242502ca47740b83c\r
- HASH=$(${ENGINE} +timestamps 1 +exec serverbench.cfg\r
| tee /dev/stderr\r
| sed -e 's,^\[[^]]*\] ,,'\r
void PrintScoresLabels() { Label_getInfo(string_null, 1); }
string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
-#define SB_EXTRA_SORTING_FIELDS 5
-PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
void Scoreboard_InitScores()
{
int i, f;
ps_primary = ps_secondary = NULL;
ts_primary = ts_secondary = -1;
FOREACH(Scores, true, {
+ if(scores_flags(it) & SFL_NOT_SORTABLE)
+ continue;
f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
if(f == SFL_SORT_PRIO_PRIMARY)
ps_primary = it;
if(f == SFL_SORT_PRIO_SECONDARY)
ps_secondary = it;
- if(ps_primary == it || ps_secondary == it)
- continue;
- if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
- if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
- if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
- if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
- if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
});
if(ps_secondary == NULL)
ps_secondary = ps_primary;
return false;
}
- entity fld = NULL;
- int r;
- for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
- {
- if (i < 0)
- {
- if (!fld) fld = ps_primary;
- else if (ps_secondary == ps_primary) continue;
- else fld = ps_secondary;
- }
- else
- {
- fld = sb_extra_sorting_field[i];
- if (fld == ps_primary || fld == ps_secondary) continue;
- }
- if (!fld) continue;
+ int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
+ if (res >= 0) return res;
- r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
- if (r >= 0) return r;
+ if (ps_secondary && ps_secondary != ps_primary)
+ {
+ res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
+ if (res >= 0) return res;
}
+ FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
+ res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
+ if (res >= 0) return res;
+ });
+
if (left.sv_entnum < right.sv_entnum)
return true;
#define REGISTER_SP(id) REGISTER(Scores, SP, id, m_id, new_pure(PlayerScoreField))
REGISTRY(Scores, MAX_SCORE);
REGISTER_REGISTRY(Scores)
-REGISTRY_SORT(Scores);
+// do not sort alphabetically, player sort priority is based on score registration order
+//REGISTRY_SORT(Scores);
REGISTRY_CHECK(Scores);
REGISTRY_DEFINE_GET(Scores, NULL)
/*
* Score indices
*/
-
#ifdef GAMEQC
-// fields not networked via the score system
-REGISTER_SP(END);
-
-REGISTER_SP(PING);
-REGISTER_SP(PL);
-REGISTER_SP(NAME);
-REGISTER_SP(SEPARATOR);
-
-REGISTER_SP(KDRATIO); // kills / deaths
-REGISTER_SP(SUM); // kills - deaths
-REGISTER_SP(FRAGS); // kills - suicides
-
// networked fields
+// NOTE: score registration order is used as player sort priority (after primary and secondary)
-REGISTER_SP(SCORE);
-
-REGISTER_SP(DMG);
-REGISTER_SP(DMGTAKEN);
+// TODO: move gamemode scores to gamemode files
+// TODO: allow gamemodes to fully customize player sorting priority, even the common ones
-REGISTER_SP(KILLS);
-REGISTER_SP(DEATHS);
-REGISTER_SP(SUICIDES);
-REGISTER_SP(TEAMKILLS);
-
-REGISTER_SP(ELO);
-
-REGISTER_SP(FPS);
-
-// TODO: move to common mutators
-
-REGISTER_SP(RACE_TIME);
REGISTER_SP(RACE_LAPS);
+REGISTER_SP(RACE_TIME);
REGISTER_SP(RACE_FASTEST);
-//REGISTER_SP(CTS_TIME);
-//REGISTER_SP(CTS_LAPS);
-//REGISTER_SP(CTS_FASTEST);
-
REGISTER_SP(ASSAULT_OBJECTIVES);
-REGISTER_SP(CTF_PICKUPS);
+REGISTER_SP(CTF_CAPS);
REGISTER_SP(CTF_FCKILLS);
REGISTER_SP(CTF_RETURNS);
-REGISTER_SP(CTF_CAPS);
-REGISTER_SP(CTF_CAPTIME);
REGISTER_SP(CTF_DROPS);
+REGISTER_SP(CTF_PICKUPS);
+REGISTER_SP(CTF_CAPTIME);
REGISTER_SP(DOM_TAKES);
REGISTER_SP(DOM_TICKS);
REGISTER_SP(FREEZETAG_REVIVALS);
-REGISTER_SP(KEEPAWAY_PICKUPS);
REGISTER_SP(KEEPAWAY_BCTIME);
REGISTER_SP(KEEPAWAY_CARRIERKILLS);
+REGISTER_SP(KEEPAWAY_PICKUPS);
-REGISTER_SP(KH_PICKUPS);
REGISTER_SP(KH_CAPS);
REGISTER_SP(KH_KCKILLS);
-REGISTER_SP(KH_PUSHES);
-REGISTER_SP(KH_DESTROYS);
REGISTER_SP(KH_LOSSES);
+REGISTER_SP(KH_DESTROYS);
+REGISTER_SP(KH_PUSHES);
+REGISTER_SP(KH_PICKUPS);
REGISTER_SP(LMS_RANK);
REGISTER_SP(LMS_LIVES);
REGISTER_SP(NEXBALL_GOALS);
REGISTER_SP(NEXBALL_FAULTS);
-REGISTER_SP(ONS_TAKES);
REGISTER_SP(ONS_CAPS);
+REGISTER_SP(ONS_TAKES);
+
+REGISTER_SP(SCORE);
+REGISTER_SP(KILLS);
+REGISTER_SP(DEATHS);
+REGISTER_SP(TEAMKILLS);
+REGISTER_SP(SUICIDES);
+REGISTER_SP(DMG);
+REGISTER_SP(DMGTAKEN);
+
+REGISTER_SP(ELO); // not sortable
+REGISTER_SP(FPS); // not sortable
+
+// fields not networked via the score system
+REGISTER_SP(END);
+
+REGISTER_SP(PING);
+REGISTER_SP(PL);
+REGISTER_SP(NAME);
+REGISTER_SP(SEPARATOR);
+
+REGISTER_SP(KDRATIO); // kills / deaths
+REGISTER_SP(SUM); // kills - deaths
+REGISTER_SP(FRAGS); // kills - suicides
#endif
*/
const int SFL_TIME = BIT(6);
+const int SFL_NOT_SORTABLE = BIT(7); // don't sort by this field
+
// not an extra constant yet
#define SFL_ZERO_IS_WORST SFL_TIME
/**
* Scoring priority (NOTE: PRIMARY is used for fraglimit)
+ * NOTE: SFL_SORT_PRIO_SECONDARY value must be lower than SFL_SORT_PRIO_PRIMARY's
*/
-const int SFL_SORT_PRIO_SECONDARY = 4;
-const int SFL_SORT_PRIO_PRIMARY = 8;
-const int SFL_SORT_PRIO_MASK = 12;
+const int SFL_SORT_PRIO_SECONDARY = BIT(2);
+const int SFL_SORT_PRIO_PRIMARY = BIT(3);
+const int SFL_SORT_PRIO_MASK = SFL_SORT_PRIO_PRIMARY | SFL_SORT_PRIO_SECONDARY;
#define IS_INCREASING(x) ( (x) & SFL_LOWER_IS_BETTER )
#define IS_DECREASING(x) ( !((x) & SFL_LOWER_IS_BETTER) )
var .float teamscores_primary;
float scores_flags_primary;
float teamscores_flags_primary;
+var .float scores_secondary;
+float scores_flags_secondary;
-vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, vector previous, bool strict) // returns: cmp value, best prio
+// returns cmp value
+int ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, int previous)
{
- if(!strict && !(fieldflags & SFL_SORT_PRIO_MASK)) // column does not sort
- return previous;
- if((fieldflags & SFL_SORT_PRIO_MASK) < previous.y)
+ if(fieldflags & SFL_NOT_SORTABLE) // column does not sort
return previous;
if (t1.(field) == t2.(field))
return previous;
- previous.y = fieldflags & SFL_SORT_PRIO_MASK;
-
if(fieldflags & SFL_ZERO_IS_WORST)
{
if (t1.(field) == 0)
{
- previous.x = -1;
+ previous = -1;
return previous;
}
else if (t2.(field) == 0)
{
- previous.x = +1;
+ previous = +1;
return previous;
}
}
if (fieldflags & SFL_LOWER_IS_BETTER)
- previous.x = (t2.(field) - t1.(field));
+ previous = (t2.(field) - t1.(field));
else
- previous.x = (t1.(field) - t2.(field));
+ previous = (t1.(field) - t2.(field));
return previous;
}
return TeamScore_AddToTeam(player.team, scorefield, score);
}
-float TeamScore_Compare(entity t1, entity t2, bool strict)
+// strict: compare others fields too besides primary and secondary
+int TeamScore_Compare(entity t1, entity t2, bool strict)
{
if(!t1 || !t2) return (!t2) - !t1;
- vector result = '0 0 0';
- float i;
- for(i = 0; i < MAX_TEAMSCORE; ++i)
+ // supporting MAX_TEAMSCORE > 2 requires keeping track of primary and secondary teamscore
+ if (MAX_TEAMSCORE > 2)
+ error("MAX_TEAMSCORE > 2 not supported");
+
+ // first compare primary, then others (don't check secondary flag since there are only 2 teamscores)
+ int result = 0;
+ int i = boolean(teamscores_primary && teamscores_primary == teamscores(1));
+ result = ScoreField_Compare(t1, t2, teamscores(i), teamscores_flags(i), result);
+ if (result == 0 && strict)
{
- var .float f;
- f = teamscores(i);
- result = ScoreField_Compare(t1, t2, f, teamscores_flags(i), result, strict);
+ i = (i + 1) % MAX_TEAMSCORE;
+ result = ScoreField_Compare(t1, t2, teamscores(i), teamscores_flags(i), result);
+ if (result == 0)
+ result = t1.team - t2.team;
}
- if (result.x == 0 && strict)
- result.x = t1.team - t2.team;
-
- return result.x;
+ return result;
}
/*
scores_primary = scores(i);
scores_flags_primary = scoreflags;
}
+ else if((scoreflags & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
+ {
+ scores_secondary = scores(i);
+ scores_flags_secondary = scoreflags;
+ }
if(label != "")
{
PlayerStats_GameReport_AddEvent(strcat(PLAYERSTATS_TOTAL, label));
return r;
}
+// strict: compare others fields too besides primary and secondary
float PlayerScore_Compare(entity t1, entity t2, bool strict)
{
if(!t1 || !t2) return (!t2) - !t1;
- vector result = '0 0 0';
- FOREACH(Scores, true, {
- var .float f = scores(it);
- result = ScoreField_Compare(t1, t2, f, scores_flags(it), result, strict);
- });
+ int result = 0;
+ result = ScoreField_Compare(t1, t2, scores_primary, scores_flags_primary, result);
+ // NOTE: if (scores_secondary) doesn't work because it's a field pointer
+ if (result == 0 && scores_flags_secondary)
+ result = ScoreField_Compare(t1, t2, scores_secondary, scores_flags_secondary, result);
- if (result.x == 0 && strict)
- result.x = t1.owner.playerid - t2.owner.playerid;
+ if (result == 0 && strict)
+ {
+ FOREACH(Scores, true, {
+ if (scores_flags(it) & SFL_SORT_PRIO_MASK)
+ continue;
+ if (scores_label(it) == "")
+ continue;
+ var .float f = scores(it);
+ result = ScoreField_Compare(t1, t2, f, scores_flags(it), result);
+ if (result) break;
+ });
+ if (result == 0)
+ result = t1.owner.playerid - t2.owner.playerid;
+ }
- return result.x;
+ return result;
}
void WinningConditionHelper(entity this)
for(t = 0; t < 16; ++t)
{
sk = teamscorekeepers[t];
- c = TeamScore_Compare(winnerscorekeeper, sk, 1);
+ c = TeamScore_Compare(winnerscorekeeper, sk, true);
if(c < 0)
{
WinningConditionHelper_secondteam = WinningConditionHelper_winnerteam;
}
else
{
- c = TeamScore_Compare(secondscorekeeper, sk, 1);
+ c = TeamScore_Compare(secondscorekeeper, sk, true);
if(c < 0)
{
WinningConditionHelper_secondteam = t + 1;
}
}
- WinningConditionHelper_equality = (TeamScore_Compare(winnerscorekeeper, secondscorekeeper, 0) == 0);
+ WinningConditionHelper_equality = (TeamScore_Compare(winnerscorekeeper, secondscorekeeper, false) == 0);
if(WinningConditionHelper_equality)
WinningConditionHelper_winnerteam = WinningConditionHelper_secondteam = -1;
return out;
}
-float PlayerTeamScore_Compare(entity p1, entity p2, float teams, bool strict)
+// strict: compare others fields too besides primary and secondary
+int PlayerTeamScore_Compare(entity p1, entity p2, float teams, bool strict)
{
if(teams && teamscores_entities_count)
{
if (!INDEPENDENT_PLAYERS)
{
ScoreInfo_SetLabel_PlayerScore(SP_SUICIDES, "suicides", SFL_LOWER_IS_BETTER);
- ScoreInfo_SetLabel_PlayerScore(SP_TEAMKILLS, "teamkills", SFL_LOWER_IS_BETTER);
+ if (teamplay)
+ ScoreInfo_SetLabel_PlayerScore(SP_TEAMKILLS, "teamkills", SFL_LOWER_IS_BETTER);
}
if(score_enabled)
ScoreInfo_SetLabel_PlayerScore(SP_DMG, "dmg", 0);
ScoreInfo_SetLabel_PlayerScore(SP_DMGTAKEN, "dmgtaken", SFL_LOWER_IS_BETTER);
- ScoreInfo_SetLabel_PlayerScore(SP_ELO, "elo", 0);
+ ScoreInfo_SetLabel_PlayerScore(SP_ELO, "elo", SFL_NOT_SORTABLE);
if(STAT(SHOWFPS))
- ScoreInfo_SetLabel_PlayerScore(SP_FPS, "fps", 0);
+ ScoreInfo_SetLabel_PlayerScore(SP_FPS, "fps", SFL_NOT_SORTABLE);
}
void ScoreRules_basics_end()