float audit_teams_time;
-float IsTeamBalanceForced()
-{
- if(intermission_running)
- return 0; // no rebalancing whatsoever please
- if(!teamplay)
- return 0;
- if(autocvar_g_campaign)
- return 0;
- if(autocvar_bot_vs_human && (c3==-1 && c4==-1))
- return 0;
- if(!autocvar_g_balance_teams_force)
- return -1;
- return 1;
-}
-
void TeamchangeFrags(entity e)
{
PlayerScore_Clear(e);
GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
}
-void WriteGameCvars()
-{
- cvar_set("g_dm", ftos(g_dm));
- cvar_set("g_tdm", ftos(g_tdm));
- cvar_set("g_domination", ftos(g_domination));
- cvar_set("g_ctf", ftos(g_ctf));
- cvar_set("g_runematch", ftos(g_runematch));
- cvar_set("g_lms", ftos(g_lms));
- cvar_set("g_arena", ftos(g_arena));
- cvar_set("g_ca", ftos(g_ca));
- cvar_set("g_keyhunt", ftos(g_keyhunt));
- cvar_set("g_assault", ftos(g_assault));
- cvar_set("g_onslaught", ftos(g_onslaught));
- cvar_set("g_race", ftos(g_race));
- cvar_set("g_nexball", ftos(g_nexball));
- cvar_set("g_cts", ftos(g_cts));
- cvar_set("g_freezetag", ftos(g_freezetag));
- cvar_set("g_keepaway", ftos(g_keepaway));
-}
-
-void ReadGameCvars()
-{
- float found;
- float prev;
- float i;
-
- found = 0;
- prev = autocvar_gamecfg;
- for(i = 0; i < 2; ++i)
- {
-//#NO AUTOCVARS START
- found += (g_dm = (!found && (prev != GAME_DEATHMATCH) && cvar("g_dm")));
- found += (g_tdm = (!found && (prev != GAME_TEAM_DEATHMATCH) && cvar("g_tdm")));
- found += (g_domination = (!found && (prev != GAME_DOMINATION) && cvar("g_domination")));
- found += (g_ctf = (!found && (prev != GAME_CTF) && cvar("g_ctf")));
- found += (g_runematch = (!found && (prev != GAME_RUNEMATCH) && cvar("g_runematch")));
- found += (g_lms = (!found && (prev != GAME_LMS) && cvar("g_lms")));
- found += (g_arena = (!found && (prev != GAME_ARENA) && cvar("g_arena")));
- found += (g_ca = (!found && (prev != GAME_CA) && cvar("g_ca")));
- found += (g_keyhunt = (!found && (prev != GAME_KEYHUNT) && cvar("g_keyhunt")));
- found += (g_assault = (!found && (prev != GAME_ASSAULT) && cvar("g_assault")));
- found += (g_onslaught = (!found && (prev != GAME_ONSLAUGHT) && cvar("g_onslaught")));
- found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race")));
- found += (g_nexball = (!found && (prev != GAME_NEXBALL) && cvar("g_nexball")));
- found += (g_cts = (!found && (prev != GAME_CTS) && cvar("g_cts")));
- found += (g_freezetag = (!found && (prev != GAME_FREEZETAG) && cvar("g_freezetag")));
- found += (g_keepaway = (!found && (prev != GAME_KEEPAWAY) && cvar("g_keepaway")));
-//#NO AUTOCVARS END
-
- if(found)
- break;
-
- prev = -1; // second attempt takes place WITHOUT prev set
- }
-
- if(!found)
- g_dm = 1;
-
- teamplay = 0;
- serverflags &~= SERVERFLAG_TEAMPLAY;
-}
-
void default_delayedinit()
{
if(!scores_initialized)
VoteReset();
- // make sure only ONE type is selected
- ReadGameCvars();
- WriteGameCvars();
-
// find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
get_mi_min_max(1);
world.mins = mi_min;
world.maxs = mi_max;
MapInfo_LoadMapSettings(mapname);
+ teamplay = 0;
+ serverflags &~= SERVERFLAG_TEAMPLAY;
if not(cvar_value_issafe(world.fog))
{
MapInfo_ClearTemps();
- // in case mapinfo switched the type
- ReadGameCvars();
-
// set both here, gamemode can override it later
timelimit_override = autocvar_timelimit_override;
fraglimit_override = autocvar_fraglimit_override;
leadlimit_override = autocvar_leadlimit_override;
+ gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
if(g_dm)
{
- game = GAME_DEATHMATCH;
- gamemode_name = "Deathmatch";
}
if(g_tdm)
{
- game = GAME_TEAM_DEATHMATCH;
- gamemode_name = "Team Deathmatch";
ActivateTeamplay();
tdm_init();
if(autocvar_g_tdm_team_spawns)
if(g_domination)
{
- game = GAME_DOMINATION;
- gamemode_name = "Domination";
ActivateTeamplay();
fraglimit_override = autocvar_g_domination_point_limit;
leadlimit_override = autocvar_g_domination_point_leadlimit;
if(g_ctf)
{
- game = GAME_CTF;
- gamemode_name = "Capture the Flag";
ActivateTeamplay();
g_ctf_ignore_frags = autocvar_g_ctf_ignore_frags;
- if(g_ctf_win_mode == 2)
- {
- fraglimit_override = autocvar_g_ctf_capture_limit;
- leadlimit_override = autocvar_g_ctf_capture_leadlimit;
- }
- else
- {
- fraglimit_override = autocvar_capturelimit_override;
- leadlimit_override = autocvar_captureleadlimit_override;
- }
+ fraglimit_override = autocvar_capturelimit_override;
+ leadlimit_override = autocvar_captureleadlimit_override;
ctf_init();
have_team_spawns = -1; // request team spawns
}
if(g_runematch)
{
- game = GAME_RUNEMATCH;
- gamemode_name = "Rune Match";
// ActivateTeamplay();
fraglimit_override = autocvar_g_runematch_point_limit;
leadlimit_override = autocvar_g_runematch_point_leadlimit;
if(g_lms)
{
- game = GAME_LMS;
- gamemode_name = "Last Man Standing";
fraglimit_override = autocvar_g_lms_lives_override;
leadlimit_override = 0; // not supported by LMS
if(fraglimit_override == 0)
if(g_arena)
{
- game = GAME_ARENA;
- gamemode_name = "Arena";
fraglimit_override = autocvar_g_arena_point_limit;
leadlimit_override = autocvar_g_arena_point_leadlimit;
maxspawned = autocvar_g_arena_maxspawned;
if(g_ca)
{
- game = GAME_CA;
- gamemode_name = "Clan Arena";
ActivateTeamplay();
fraglimit_override = autocvar_g_ca_point_limit;
leadlimit_override = autocvar_g_ca_point_leadlimit;
}
if(g_keyhunt)
{
- game = GAME_KEYHUNT;
- gamemode_name = "Key Hunt";
ActivateTeamplay();
fraglimit_override = autocvar_g_keyhunt_point_limit;
leadlimit_override = autocvar_g_keyhunt_point_leadlimit;
if(g_freezetag)
{
- game = GAME_FREEZETAG;
- gamemode_name = "Freeze Tag";
ActivateTeamplay();
fraglimit_override = autocvar_g_freezetag_point_limit;
leadlimit_override = autocvar_g_freezetag_point_leadlimit;
if(g_assault)
{
- game = GAME_ASSAULT;
- gamemode_name = "Assault";
ActivateTeamplay();
ScoreRules_assault();
have_team_spawns = -1; // request team spawns
if(g_onslaught)
{
- game = GAME_ONSLAUGHT;
- gamemode_name = "Onslaught";
ActivateTeamplay();
have_team_spawns = -1; // request team spawns
+ MUTATOR_ADD(gamemode_onslaught);
}
if(g_race)
{
- game = GAME_RACE;
- gamemode_name = "Race";
if(autocvar_g_race_teams)
{
if(g_cts)
{
- game = GAME_CTS;
- gamemode_name = "CTS";
g_race_qualifying = 1;
fraglimit_override = 0;
leadlimit_override = 0;
if(g_nexball)
{
- game = GAME_NEXBALL;
- gamemode_name = "Nexball";
fraglimit_override = autocvar_g_nexball_goallimit;
leadlimit_override = autocvar_g_nexball_goalleadlimit;
ActivateTeamplay();
have_team_spawns = -1; // request team spawns
-
MUTATOR_ADD(gamemode_nexball);
}
-
if(g_keepaway)
{
- game = GAME_KEEPAWAY;
- gamemode_name = "Keepaway";
MUTATOR_ADD(gamemode_keepaway);
}
if(teamplay)
entcs_init();
- // save it (for the next startup)
- cvar_set("gamecfg", ftos(game));
-
cache_mutatormsg = strzone("");
cache_lastmutatormsg = strzone("");
c1 = c2 = c3 = c4 = -1;
cb1 = cb2 = cb3 = cb4 = 0;
+ teament_name = string_null;
if(g_onslaught)
{
// onslaught is special
float PlayerValue(entity p)
{
- if(IsTeamBalanceForced() == 1)
- return 1;
return 1;
// FIXME: it always returns 1...
}
}
}
+float TeamSmallerEqThanTeam(float ta, float tb, entity e)
+{
+ // we assume that CheckAllowedTeams and GetTeamCounts have already been called
+ float f;
+ float ca = -1, cb = -1, cba = 0, cbb = 0, sa = 0, sb = 0;
+
+ switch(ta)
+ {
+ case 1: ca = c1; cba = cb1; sa = team1_score; break;
+ case 2: ca = c2; cba = cb2; sa = team2_score; break;
+ case 3: ca = c3; cba = cb3; sa = team3_score; break;
+ case 4: ca = c4; cba = cb4; sa = team4_score; break;
+ }
+ switch(tb)
+ {
+ case 1: cb = c1; cbb = cb1; sb = team1_score; break;
+ case 2: cb = c2; cbb = cb2; sb = team2_score; break;
+ case 3: cb = c3; cbb = cb3; sb = team3_score; break;
+ case 4: cb = c4; cbb = cb4; sb = team4_score; break;
+ }
+
+ // invalid
+ if(ca < 0 || cb < 0)
+ return FALSE;
+
+ // equal
+ if(ta == tb)
+ return TRUE;
+
+ if(clienttype(e) == CLIENTTYPE_REAL)
+ {
+ if(bots_would_leave)
+ {
+ ca -= cba * 0.999;
+ cb -= cbb * 0.999;
+ }
+ }
+
+ // keep teams alive (teams of size 0 always count as smaller, ignoring score)
+ if(ca < 1)
+ if(cb >= 1)
+ return TRUE;
+ if(ca >= 1)
+ if(cb < 1)
+ return FALSE;
+
+ // first, normalize
+ f = max(ca, cb, 1);
+ ca /= f;
+ cb /= f;
+ f = max(sa, sb, 1);
+ sa /= f;
+ sb /= f;
+
+ // the more we're at the end of the match, the more take scores into account
+ f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1);
+ ca += (sa - ca) * f;
+ cb += (sb - cb) * f;
+
+ return ca <= cb;
+}
+
// returns # of smallest team (1, 2, 3, 4)
// NOTE: Assumes CheckAllowedTeams has already been called!
float FindSmallestTeam(entity pl, float ignore_pl)
{
- float totalteams, balance_type, maxc;
+ float totalteams, t;
totalteams = 0;
// find out what teams are available
else
GetTeamCounts(world);
- // c1...c4 now have counts of each team
- // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
-
- // 2 gives priority to what team you're already on, 1 goes in order
- // 2 doesn't seem to work though...
- balance_type = 1;
-
- if(bots_would_leave)
- //if(pl.classname != "player")
- if(clienttype(pl) != CLIENTTYPE_BOT)
- {
- c1 -= cb1 * 255.0/256.0;
- c2 -= cb2 * 255.0/256.0;
- c3 -= cb3 * 255.0/256.0;
- c4 -= cb4 * 255.0/256.0;
- }
- maxc = max4(c1, c2, c3, c4);
-
RandomSelection_Init();
- if(balance_type == 1)
- {
- // 1: use team count, then score (note: can only use 8 significant bits of score)
- if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + float2range01(-team1_score) / 256.0);
- if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c2) + float2range01(-team2_score) / 256.0);
- if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c3) + float2range01(-team3_score) / 256.0);
- if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c4) + float2range01(-team4_score) / 256.0);
- }
- else if(balance_type == 2)
- {
- // 1: use team count, if equal prefer own team
- if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM1) / 512.0);
- if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM2) / 512.0);
- if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM3) / 512.0);
- if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c1) + (self.team == COLOR_TEAM4) / 512.0);
- }
- else if(balance_type == 3)
- {
- // 1: use team count, then score, if equal prefer own team (probably fails due to float accuracy problems)
- if(c1 >= 0) RandomSelection_Add(world, 1, string_null, 1, (maxc - c1) + float2range01(-team1_score + 0.5 * (self.team == COLOR_TEAM1)) / 256.0);
- if(c2 >= 0) RandomSelection_Add(world, 2, string_null, 1, (maxc - c2) + float2range01(-team2_score + 0.5 * (self.team == COLOR_TEAM2)) / 256.0);
- if(c3 >= 0) RandomSelection_Add(world, 3, string_null, 1, (maxc - c3) + float2range01(-team3_score + 0.5 * (self.team == COLOR_TEAM3)) / 256.0);
- if(c4 >= 0) RandomSelection_Add(world, 4, string_null, 1, (maxc - c4) + float2range01(-team4_score + 0.5 * (self.team == COLOR_TEAM4)) / 256.0);
- }
+
+ t = 1;
+ if(TeamSmallerEqThanTeam(2, t, pl))
+ t = 2;
+ if(TeamSmallerEqThanTeam(3, t, pl))
+ t = 3;
+ if(TeamSmallerEqThanTeam(4, t, pl))
+ t = 4;
+
+ // now t is the minimum, or A minimum!
+ if(t == 1 || TeamSmallerEqThanTeam(1, t, pl))
+ RandomSelection_Add(world, 1, string_null, 1, 1);
+ if(t == 2 || TeamSmallerEqThanTeam(2, t, pl))
+ RandomSelection_Add(world, 2, string_null, 1, 1);
+ if(t == 3 || TeamSmallerEqThanTeam(3, t, pl))
+ RandomSelection_Add(world, 3, string_null, 1, 1);
+ if(t == 4 || TeamSmallerEqThanTeam(4, t, pl))
+ RandomSelection_Add(world, 4, string_null, 1, 1);
+
return RandomSelection_chosen_float;
}
return; // changing teams is not allowed
}
- if(autocvar_g_balance_teams_prevent_imbalance)
+ // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
+ if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
{
- // only allow changing to a smaller or equal size team
-
- // find out what teams are available
- //CheckAllowedTeams();
- // count how many players on each team
- GetTeamCounts(world);
-
- // get desired team
- if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
- {
- dcount = c1;
- dbotcount = cb1;
- }
- else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
- {
- dcount = c2;
- dbotcount = cb2;
- }
- else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
+ GetTeamCounts(self);
+ if(!TeamSmallerEqThanTeam(dteam, steam, self))
{
- dcount = c3;
- dbotcount = cb3;
- }
- else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
- {
- dcount = c4;
- dbotcount = cb4;
- }
- else
- {
- sprint(self, "Cannot change to an invalid team\n");
-
+ sprint(self, "Cannot change to a larger/better/shinier team\n");
return;
}
-
- // get starting team
- if(steam == 1)//scolor == COLOR_TEAM1 - 1)
- scount = c1;
- else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
- scount = c2;
- else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
- scount = c3;
- else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
- scount = c4;
-
- if(scount) // started at a valid, nonempty team
- {
- // check if we're trying to change to a larger team that doens't have bots to swap with
- if(dcount >= scount && dbotcount <= 0)
- {
- sprint(self, "Cannot change to a larger team\n");
- return; // can't change to a larger team
- }
- }
}
// bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
steam = COLOR_TEAM2;
else if(source_team == 3)
steam = COLOR_TEAM3;
- else if(source_team == 4)
+ else // if(source_team == 4)
steam = COLOR_TEAM4;
lowest_bot = world;
centerprint(selected, strcat("You have been moved into a different team to improve team balance\nYou are now on: ", ColoredTeamName(selected.team)));
}
-void CauseRebalance(float source_team, float howmany_toomany)
-{
- if(IsTeamBalanceForced() == 1)
- {
- bprint("Rebalancing Teams\n");
- ShufflePlayerOutOfTeam(source_team);
- }
-}
-
-// part of g_balance_teams_force
-// occasionally perform an audit of the teams to make
-// sure they're more or less balanced in player count.
-void AuditTeams()
-{
- float numplayers, numteams, smallest, toomany;
- float balance;
- balance = IsTeamBalanceForced();
- if(balance == 0)
- return;
-
- if(audit_teams_time > time)
- return;
-
- audit_teams_time = time + 4 + random();
-
-// bprint("Auditing teams\n");
-
- CheckAllowedTeams(world);
- GetTeamCounts(world);
-
-
- numteams = numplayers = smallest = 0;
- if(c1 >= 0)
- {
- numteams = numteams + 1;
- numplayers = numplayers + c1;
- smallest = c1;
- }
- if(c2 >= 0)
- {
- numteams = numteams + 1;
- numplayers = numplayers + c2;
- if(c2 < smallest)
- smallest = c2;
- }
- if(c3 >= 0)
- {
- numteams = numteams + 1;
- numplayers = numplayers + c3;
- if(c3 < smallest)
- smallest = c3;
- }
- if(c4 >= 0)
- {
- numteams = numteams + 1;
- numplayers = numplayers + c4;
- if(c4 < smallest)
- smallest = c4;
- }
-
- if(numplayers <= 0)
- return; // no players to move around
- if(numteams < 2)
- return; // don't bother shuffling if for some reason there aren't any teams
-
- toomany = smallest + 1;
-
- if(c1 && c1 > toomany)
- CauseRebalance(1, c1 - toomany);
- if(c2 && c2 > toomany)
- CauseRebalance(2, c2 - toomany);
- if(c3 && c3 > toomany)
- CauseRebalance(3, c3 - toomany);
- if(c4 && c4 > toomany)
- CauseRebalance(4, c4 - toomany);
-
- // if teams are still unbalanced, balance them further in the next audit,
- // which will happen sooner (keep doing rapid audits until things are in order)
- audit_teams_time = time + 0.7 + random()*0.3;
-}
-
// code from here on is just to support maps that don't have team entities
void tdm_spawnteam (string teamname, float teamcolor)
{