6 #include "scores_rules.qh"
10 #include "command/vote.qh"
12 #include "mutators/_mod.qh"
14 #include "../common/deathtypes/all.qh"
15 #include "../common/gamemodes/_mod.qh"
16 #include "../common/teams.qh"
18 void TeamchangeFrags(entity e)
23 void LogTeamchange(float player_id, float team_number, float type)
25 if(!autocvar_sv_eventlog)
31 GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
34 void default_delayedinit(entity this)
36 if(!scores_initialized)
40 void ActivateTeamplay()
42 serverflags |= SERVERFLAG_TEAMPLAY;
44 cvar_set("teamplay", "2"); // DP needs this for sending proper getstatus replies.
47 void InitGameplayMode()
51 // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
55 // currently, NetRadiant's limit is 131072 qu for each side
56 // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
57 // set the distance according to map size but don't go over the limit to avoid issues with float precision
58 // in case somebody makes extremely large maps
59 max_shot_distance = min(230000, vlen(world.maxs - world.mins));
61 MapInfo_LoadMapSettings(mapname);
62 serverflags &= ~SERVERFLAG_TEAMPLAY;
64 cvar_set("teamplay", "0"); // DP needs this for sending proper getstatus replies.
66 if (!cvar_value_issafe(world.fog))
68 LOG_INFO("The current map contains a potentially harmful fog setting, ignored\n");
69 world.fog = string_null;
71 if(MapInfo_Map_fog != "")
72 if(MapInfo_Map_fog == "none")
73 world.fog = string_null;
75 world.fog = strzone(MapInfo_Map_fog);
76 clientstuff = strzone(MapInfo_Map_clientstuff);
80 gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
82 cache_mutatormsg = strzone("");
83 cache_lastmutatormsg = strzone("");
85 InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
88 string GetClientVersionMessage(entity this)
90 if (this.version_mismatch) {
91 if(this.version < autocvar_gameversion) {
92 return strcat("This is Xonotic ", autocvar_g_xonoticversion,
93 "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
95 return strcat("This is Xonotic ", autocvar_g_xonoticversion,
96 "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
99 return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
103 string getwelcomemessage(entity this)
105 MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
106 string modifications = M_ARGV(0, string);
110 if(g_weaponarena_random)
111 modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
113 modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
115 else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
116 modifications = strcat(modifications, ", No start weapons");
117 if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
118 modifications = strcat(modifications, ", Low gravity");
119 if(g_weapon_stay && !g_cts)
120 modifications = strcat(modifications, ", Weapons stay");
122 modifications = strcat(modifications, ", Jet pack");
123 if(autocvar_g_powerups == 0)
124 modifications = strcat(modifications, ", No powerups");
125 if(autocvar_g_powerups > 0)
126 modifications = strcat(modifications, ", Powerups");
127 modifications = substring(modifications, 2, strlen(modifications) - 2);
129 string versionmessage = GetClientVersionMessage(this);
130 string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
132 if(modifications != "")
133 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
135 if(cache_lastmutatormsg != autocvar_g_mutatormsg)
137 if(cache_lastmutatormsg)
138 strunzone(cache_lastmutatormsg);
140 strunzone(cache_mutatormsg);
141 cache_lastmutatormsg = strzone(autocvar_g_mutatormsg);
142 cache_mutatormsg = strzone(cache_lastmutatormsg);
145 if (cache_mutatormsg != "") {
146 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
149 string mutator_msg = "";
150 MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
151 mutator_msg = M_ARGV(0, string);
153 s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
155 string motd = autocvar_sv_motd;
157 s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
162 void setcolor(entity this, int clr)
165 this.clientcolors = clr;
166 this.team = (clr & 15) + 1;
168 builtin_setcolor(this, clr);
172 void SetPlayerColors(entity player, float _color)
174 float pants = _color & 0x0F;
175 float shirt = _color & 0xF0;
178 setcolor(player, 16 * pants + pants);
182 setcolor(player, shirt + pants);
186 bool SetPlayerTeamSimple(entity player, int teamnum)
188 if (player.team == teamnum)
190 // This is important when players join the game and one of their color
191 // matches the team color while other doesn't. For example [BOT]Lion.
192 SetPlayerColors(player, teamnum - 1);
195 if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
196 player.team), Team_TeamToNumber(teamnum)) == true)
198 // Mutator has blocked team change.
201 int oldteam = player.team;
202 SetPlayerColors(player, teamnum - 1);
203 MUTATOR_CALLHOOK(Player_ChangedTeam, player, oldteam, player.team);
207 void SetPlayerTeam(entity player, int destinationteam, int sourceteam, bool noprint)
209 int teamnum = Team_NumberToTeam(destinationteam);
210 if (!SetPlayerTeamSimple(player, teamnum))
214 LogTeamchange(player.playerid, player.team, 3); // log manual team join
219 bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(sourceteam), "^7 to ", Team_NumberToColoredFullName(destinationteam), "\n");
222 // set c1...c4 to show what teams are allowed
223 void CheckAllowedTeams (entity for_whom)
227 c1 = c2 = c3 = c4 = -1;
228 numbotsteam1 = numbotsteam2 = numbotsteam3 = numbotsteam4 = 0;
230 string teament_name = string_null;
232 bool mutator_returnvalue = MUTATOR_CALLHOOK(CheckAllowedTeams, teams_mask, teament_name, for_whom);
233 teams_mask = M_ARGV(0, float);
234 teament_name = M_ARGV(1, string);
236 if(!mutator_returnvalue)
238 if(teams_mask & BIT(0)) c1 = 0;
239 if(teams_mask & BIT(1)) c2 = 0;
240 if(teams_mask & BIT(2)) c3 = 0;
241 if(teams_mask & BIT(3)) c4 = 0;
244 // find out what teams are allowed if necessary
247 entity head = find(NULL, classname, teament_name);
252 case NUM_TEAM_1: c1 = 0; break;
253 case NUM_TEAM_2: c2 = 0; break;
254 case NUM_TEAM_3: c3 = 0; break;
255 case NUM_TEAM_4: c4 = 0; break;
258 head = find(head, classname, teament_name);
262 // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
263 if(AvailableTeams() == 2)
264 if(autocvar_bot_vs_human && for_whom)
266 if(autocvar_bot_vs_human > 0)
268 // find last team available
270 if(IS_BOT_CLIENT(for_whom))
272 if(c4 >= 0) { c3 = c2 = c1 = -1; }
273 else if(c3 >= 0) { c4 = c2 = c1 = -1; }
274 else { c4 = c3 = c1 = -1; }
275 // no further cases, we know at least 2 teams exist
279 if(c1 >= 0) { c2 = c3 = c4 = -1; }
280 else if(c2 >= 0) { c1 = c3 = c4 = -1; }
281 else { c1 = c2 = c4 = -1; }
282 // no further cases, bots have one of the teams
287 // find first team available
289 if(IS_BOT_CLIENT(for_whom))
291 if(c1 >= 0) { c2 = c3 = c4 = -1; }
292 else if(c2 >= 0) { c1 = c3 = c4 = -1; }
293 else { c1 = c2 = c4 = -1; }
294 // no further cases, we know at least 2 teams exist
298 if(c4 >= 0) { c3 = c2 = c1 = -1; }
299 else if(c3 >= 0) { c4 = c2 = c1 = -1; }
300 else { c4 = c3 = c1 = -1; }
301 // no further cases, bots have one of the teams
309 // if player has a forced team, ONLY allow that one
310 if(for_whom.team_forced == NUM_TEAM_1 && c1 >= 0)
312 else if(for_whom.team_forced == NUM_TEAM_2 && c2 >= 0)
314 else if(for_whom.team_forced == NUM_TEAM_3 && c3 >= 0)
316 else if(for_whom.team_forced == NUM_TEAM_4 && c4 >= 0)
320 float PlayerValue(entity p)
323 // FIXME: it always returns 1...
326 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
327 // teams that are allowed will now have their player counts stored in c1...c4
328 void GetTeamCounts(entity ignore)
330 if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
334 MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1, numbotsteam1,
335 lowestplayerteam1, lowestbotteam1);
336 c1 = M_ARGV(2, float);
337 numbotsteam1 = M_ARGV(3, float);
338 lowestplayerteam1 = M_ARGV(4, entity);
339 lowestbotteam1 = M_ARGV(5, entity);
343 MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2, numbotsteam2,
344 lowestplayerteam2, lowestbotteam2);
345 c2 = M_ARGV(2, float);
346 numbotsteam2 = M_ARGV(3, float);
347 lowestplayerteam2 = M_ARGV(4, entity);
348 lowestbotteam2 = M_ARGV(5, entity);
352 MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3, numbotsteam3,
353 lowestplayerteam3, lowestbotteam3);
354 c3 = M_ARGV(2, float);
355 numbotsteam3 = M_ARGV(3, float);
356 lowestplayerteam3 = M_ARGV(4, entity);
357 lowestbotteam3 = M_ARGV(5, entity);
361 MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore, c4, numbotsteam4,
362 lowestplayerteam4, lowestbotteam4);
363 c4 = M_ARGV(2, float);
364 numbotsteam4 = M_ARGV(3, float);
365 lowestplayerteam4 = M_ARGV(4, entity);
366 lowestbotteam4 = M_ARGV(5, entity);
372 // now count how many players are on each team already
373 float lowestplayerscore1 = FLOAT_MAX;
374 float lowestbotscore1 = FLOAT_MAX;
375 float lowestplayerscore2 = FLOAT_MAX;
376 float lowestbotscore2 = FLOAT_MAX;
377 float lowestplayerscore3 = FLOAT_MAX;
378 float lowestbotscore3 = FLOAT_MAX;
379 float lowestplayerscore4 = FLOAT_MAX;
380 float lowestbotscore4 = FLOAT_MAX;
381 FOREACH_CLIENT(true, LAMBDA(
383 if (IS_PLAYER(it) || it.caplayer)
387 else if (it.team_forced > 0)
389 t = it.team_forced; // reserve the spot
399 value = PlayerValue(it);
400 if (IS_BOT_CLIENT(it))
421 numbotsteam1 += bvalue;
422 float tempscore = PlayerScore_Get(it, SP_SCORE);
425 if (tempscore < lowestplayerscore1)
427 lowestplayerteam1 = it;
428 lowestplayerscore1 = tempscore;
432 if (tempscore < lowestbotscore1)
435 lowestbotscore1 = tempscore;
446 numbotsteam2 += bvalue;
447 float tempscore = PlayerScore_Get(it, SP_SCORE);
450 if (tempscore < lowestplayerscore2)
452 lowestplayerteam2 = it;
453 lowestplayerscore2 = tempscore;
457 if (tempscore < lowestbotscore2)
460 lowestbotscore2 = tempscore;
471 numbotsteam3 += bvalue;
472 float tempscore = PlayerScore_Get(it, SP_SCORE);
475 if (tempscore < lowestplayerscore3)
477 lowestplayerteam3 = it;
478 lowestplayerscore3 = tempscore;
482 if (tempscore < lowestbotscore3)
485 lowestbotscore3 = tempscore;
496 numbotsteam4 += bvalue;
497 float tempscore = PlayerScore_Get(it, SP_SCORE);
500 if (tempscore < lowestplayerscore4)
502 lowestplayerteam4 = it;
503 lowestplayerscore4 = tempscore;
507 if (tempscore < lowestbotscore4)
510 lowestbotscore4 = tempscore;
518 // if the player who has a forced team has not joined yet, reserve the spot
519 if(autocvar_g_campaign)
521 switch(autocvar_g_campaign_forceteam)
523 case 1: if(c1 == numbotsteam1) ++c1; break;
524 case 2: if(c2 == numbotsteam2) ++c2; break;
525 case 3: if(c3 == numbotsteam3) ++c3; break;
526 case 4: if(c4 == numbotsteam4) ++c4; break;
531 bool IsTeamSmallerThanTeam(int teama, int teamb, entity e, bool usescore)
538 // we assume that CheckAllowedTeams and GetTeamCounts have already been called
539 float numplayersteama = -1, numplayersteamb = -1;
540 float numbotsteama = 0, numbotsteamb = 0;
541 float scoreteama = 0, scoreteamb = 0;
545 case 1: numplayersteama = c1; numbotsteama = numbotsteam1; scoreteama = team1_score; break;
546 case 2: numplayersteama = c2; numbotsteama = numbotsteam2; scoreteama = team2_score; break;
547 case 3: numplayersteama = c3; numbotsteama = numbotsteam3; scoreteama = team3_score; break;
548 case 4: numplayersteama = c4; numbotsteama = numbotsteam4; scoreteama = team4_score; break;
552 case 1: numplayersteamb = c1; numbotsteamb = numbotsteam1; scoreteamb = team1_score; break;
553 case 2: numplayersteamb = c2; numbotsteamb = numbotsteam2; scoreteamb = team2_score; break;
554 case 3: numplayersteamb = c3; numbotsteamb = numbotsteam3; scoreteamb = team3_score; break;
555 case 4: numplayersteamb = c4; numbotsteamb = numbotsteam4; scoreteamb = team4_score; break;
559 if (numplayersteama < 0 || numplayersteamb < 0)
562 if ((IS_REAL_CLIENT(e) && bots_would_leave))
564 numplayersteama -= numbotsteama;
565 numplayersteamb -= numbotsteamb;
569 return numplayersteama < numplayersteamb;
571 if (numplayersteama < numplayersteamb)
575 if (numplayersteama > numplayersteamb)
579 return scoreteama < scoreteamb;
582 bool IsTeamEqualToTeam(int teama, int teamb, entity e, bool usescore)
589 // we assume that CheckAllowedTeams and GetTeamCounts have already been called
590 float numplayersteama = -1, numplayersteamb = -1;
591 float numbotsteama = 0, numbotsteamb = 0;
592 float scoreteama = 0, scoreteamb = 0;
596 case 1: numplayersteama = c1; numbotsteama = numbotsteam1; scoreteama = team1_score; break;
597 case 2: numplayersteama = c2; numbotsteama = numbotsteam2; scoreteama = team2_score; break;
598 case 3: numplayersteama = c3; numbotsteama = numbotsteam3; scoreteama = team3_score; break;
599 case 4: numplayersteama = c4; numbotsteama = numbotsteam4; scoreteama = team4_score; break;
603 case 1: numplayersteamb = c1; numbotsteamb = numbotsteam1; scoreteamb = team1_score; break;
604 case 2: numplayersteamb = c2; numbotsteamb = numbotsteam2; scoreteamb = team2_score; break;
605 case 3: numplayersteamb = c3; numbotsteamb = numbotsteam3; scoreteamb = team3_score; break;
606 case 4: numplayersteamb = c4; numbotsteamb = numbotsteam4; scoreteamb = team4_score; break;
610 if (numplayersteama < 0 || numplayersteamb < 0)
613 if ((IS_REAL_CLIENT(e) && bots_would_leave))
615 numplayersteama -= numbotsteama;
616 numplayersteamb -= numbotsteamb;
620 return numplayersteama == numplayersteamb;
622 if (numplayersteama < numplayersteamb)
626 if (numplayersteama > numplayersteamb)
630 return scoreteama == scoreteamb;
633 int FindBestTeams(entity player, bool usescore)
635 if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
637 return M_ARGV(1, float);
640 int previousteam = 0;
648 if (IsTeamSmallerThanTeam(2, previousteam, player, usescore))
653 else if (IsTeamEqualToTeam(2, previousteam, player, usescore))
661 if (IsTeamSmallerThanTeam(3, previousteam, player, usescore))
666 else if (IsTeamEqualToTeam(3, previousteam, player, usescore))
674 if (IsTeamSmallerThanTeam(4, previousteam, player, usescore))
678 else if (IsTeamEqualToTeam(4, previousteam, player, usescore))
686 // returns # of smallest team (1, 2, 3, 4)
687 // NOTE: Assumes CheckAllowedTeams has already been called!
688 float FindSmallestTeam(entity pl, float ignore_pl)
690 // count how many players are in each team
699 int teambits = FindBestTeams(pl, true);
702 error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
704 RandomSelection_Init();
705 if ((teambits & BIT(0)) != 0)
707 RandomSelection_AddFloat(1, 1, 1);
709 if ((teambits & BIT(1)) != 0)
711 RandomSelection_AddFloat(2, 1, 1);
713 if ((teambits & BIT(2)) != 0)
715 RandomSelection_AddFloat(3, 1, 1);
717 if ((teambits & BIT(3)) != 0)
719 RandomSelection_AddFloat(4, 1, 1);
721 return RandomSelection_chosen_float;
724 int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
726 // don't join a team if we're not playing a team game
732 // find out what teams are available
733 CheckAllowedTeams(this);
737 // if we don't care what team he ends up on, put him on whatever team he entered as.
738 // if he's not on a valid team, then let other code put him on the smallest team
741 if( c1 >= 0 && this.team == NUM_TEAM_1)
742 selectedteam = this.team;
743 else if(c2 >= 0 && this.team == NUM_TEAM_2)
744 selectedteam = this.team;
745 else if(c3 >= 0 && this.team == NUM_TEAM_3)
746 selectedteam = this.team;
747 else if(c4 >= 0 && this.team == NUM_TEAM_4)
748 selectedteam = this.team;
752 if (selectedteam > 0)
754 if (!only_return_best)
756 SetPlayerTeamSimple(this, selectedteam);
758 // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
759 // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
760 LogTeamchange(this.playerid, this.team, 99);
764 // otherwise end up on the smallest team (handled below)
767 float bestteam = FindSmallestTeam(this, true);
768 if (only_return_best || this.bot_forced_team)
772 bestteam = Team_NumberToTeam(bestteam);
775 error("JoinBestTeam: invalid team\n");
777 int oldteam = Team_TeamToNumber(this.team);
778 TeamchangeFrags(this);
779 SetPlayerTeamSimple(this, bestteam);
780 LogTeamchange(this.playerid, this.team, 2); // log auto join
781 if (!IS_BOT_CLIENT(this))
783 AutoBalanceBots(oldteam, Team_TeamToNumber(bestteam));
785 if (!IS_DEAD(this) && (MUTATOR_CALLHOOK(Player_ChangeTeamKill, this) ==
788 Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
793 void SV_ChangeTeam(entity this, float _color)
795 float sourcecolor, destinationcolor, sourceteam, destinationteam;
797 // in normal deathmatch we can just apply the color and we're done
799 SetPlayerColors(this, _color);
803 // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
804 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
811 sourcecolor = this.clientcolors & 0x0F;
812 destinationcolor = _color & 0x0F;
814 sourceteam = Team_TeamToNumber(sourcecolor + 1);
815 destinationteam = Team_TeamToNumber(destinationcolor + 1);
817 CheckAllowedTeams(this);
819 if (destinationteam == 1 && c1 < 0) destinationteam = 4;
820 if (destinationteam == 4 && c4 < 0) destinationteam = 3;
821 if (destinationteam == 3 && c3 < 0) destinationteam = 2;
822 if (destinationteam == 2 && c2 < 0) destinationteam = 1;
824 // not changing teams
825 if (sourcecolor == destinationcolor)
827 SetPlayerTeam(this, destinationteam, sourceteam, true);
831 if (autocvar_g_campaign || (autocvar_g_changeteam_banned && this.wasplayer))
833 Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
834 return; // changing teams is not allowed
837 // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
838 if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
841 if ((BIT(destinationteam - 1) & FindBestTeams(this, false)) == 0)
843 Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
847 if(IS_PLAYER(this) && sourceteam != destinationteam)
849 // reduce frags during a team change
850 TeamchangeFrags(this);
852 SetPlayerTeam(this, destinationteam, sourceteam, !IS_CLIENT(this));
853 AutoBalanceBots(sourceteam, destinationteam);
854 if (!IS_PLAYER(this) || (sourceteam == destinationteam))
858 // kill player when changing teams
859 if (IS_DEAD(this) || (MUTATOR_CALLHOOK(Player_ChangeTeamKill, this) == true))
863 Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
866 void AutoBalanceBots(int sourceteam, int destinationteam)
868 if ((sourceteam == -1) || (destinationteam == -1))
872 if (!autocvar_g_balance_teams ||
873 !autocvar_g_balance_teams_prevent_imbalance)
877 int numplayerssourceteam = 0;
878 int numplayersdestinationteam = 0;
879 entity lowestbotdestinationteam = NULL;
884 numplayerssourceteam = c1;
889 numplayerssourceteam = c2;
894 numplayerssourceteam = c3;
899 numplayerssourceteam = c4;
903 switch (destinationteam)
907 numplayersdestinationteam = c1;
908 lowestbotdestinationteam = lowestbotteam1;
913 numplayersdestinationteam = c2;
914 lowestbotdestinationteam = lowestbotteam2;
919 numplayersdestinationteam = c3;
920 lowestbotdestinationteam = lowestbotteam3;
925 numplayersdestinationteam = c4;
926 lowestbotdestinationteam = lowestbotteam4;
930 if ((numplayersdestinationteam > numplayerssourceteam) && (lowestbotdestinationteam != NULL))
932 SetPlayerTeamSimple(lowestbotdestinationteam, Team_NumberToTeam(sourceteam));
936 void ShufflePlayerOutOfTeam (float source_team)
938 float smallestteam, smallestteam_count, steam;
939 float lowest_bot_score, lowest_player_score;
940 entity lowest_bot, lowest_player, selected;
943 smallestteam_count = 999999999;
945 if(c1 >= 0 && c1 < smallestteam_count)
948 smallestteam_count = c1;
950 if(c2 >= 0 && c2 < smallestteam_count)
953 smallestteam_count = c2;
955 if(c3 >= 0 && c3 < smallestteam_count)
958 smallestteam_count = c3;
960 if(c4 >= 0 && c4 < smallestteam_count)
963 smallestteam_count = c4;
968 bprint("warning: no smallest team\n");
974 else if(source_team == 2)
976 else if(source_team == 3)
978 else // if(source_team == 4)
982 lowest_bot_score = 999999999;
983 lowest_player = NULL;
984 lowest_player_score = 999999999;
986 // find the lowest-scoring player & bot of that team
987 FOREACH_CLIENT(IS_PLAYER(it) && it.team == steam, LAMBDA(
990 if(it.totalfrags < lowest_bot_score)
993 lowest_bot_score = it.totalfrags;
998 if(it.totalfrags < lowest_player_score)
1001 lowest_player_score = it.totalfrags;
1006 // prefers to move a bot...
1007 if(lowest_bot != NULL)
1008 selected = lowest_bot;
1009 // but it will move a player if it has to
1011 selected = lowest_player;
1012 // don't do anything if it couldn't find anyone
1015 bprint("warning: couldn't find a player to move from team\n");
1019 // smallest team gains a member
1020 if(smallestteam == 1)
1024 else if(smallestteam == 2)
1028 else if(smallestteam == 3)
1032 else if(smallestteam == 4)
1038 bprint("warning: destination team invalid\n");
1041 // source team loses a member
1042 if(source_team == 1)
1046 else if(source_team == 2)
1050 else if(source_team == 3)
1054 else if(source_team == 4)
1060 bprint("warning: source team invalid\n");
1064 // move the player to the new team
1065 TeamchangeFrags(selected);
1066 SetPlayerTeam(selected, smallestteam, source_team, false);
1068 if (IS_DEAD(selected) || MUTATOR_CALLHOOK(Player_ChangeTeamKill, selected) == true)
1072 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE.m_id, selected.origin, '0 0 0');
1073 Send_Notification(NOTIF_ONE, selected, MSG_CENTER, CENTER_DEATH_SELF_AUTOTEAMCHANGE, selected.team);