Xonotic
teamplay.qc
Go to the documentation of this file.
1 #include "teamplay.qh"
2 
5 #include <common/teams.qh>
6 #include <server/bot/api.qh>
8 #include <server/campaign.qh>
9 #include <server/client.qh>
10 #include <server/command/vote.qh>
11 #include <server/damage.qh>
12 #include <server/gamelog.qh>
13 #include <server/mutators/_mod.qh>
14 #include <server/race.qh>
15 #include <server/scores.qh>
16 #include <server/scores_rules.qh>
17 
19 enum
20 {
26 };
27 
29 const int TEAM_NOT_ALLOWED = -1;
30 
33 
34 .float m_team_score;
36 .int m_num_bots;
39 
44 
46 
48 {
49  if (g_team_entities[0])
50  return;
51  for (int i = 0; i < NUM_TEAMS; ++i)
52  {
53  g_team_entities[i] = new_pure(team_entity);
54  }
55 }
56 
58 {
59  if (!Team_IsValidIndex(index))
60  {
61  LOG_FATALF("Team_GetTeamFromIndex: Index is invalid: %f", index);
62  }
63  return g_team_entities[index - 1];
64 }
65 
66 entity Team_GetTeam(int team_num)
67 {
68  if (!Team_IsValidTeam(team_num))
69  {
70  LOG_FATALF("Team_GetTeam: Value is invalid: %f", team_num);
71  }
72  return g_team_entities[Team_TeamToIndex(team_num) - 1];
73 }
74 
75 float Team_GetTeamScore(entity team_ent)
76 {
77  return team_ent.m_team_score;
78 }
79 
80 void Team_SetTeamScore(entity team_ent, float score)
81 {
82  team_ent.m_team_score = score;
83 }
84 
86 {
87  return team_ent.m_num_players_alive;
88 }
89 
91 {
92  team_ent.m_num_players_alive = number;
93 }
94 
96 {
97  int winner = 0;
98  for (int i = 0; i < NUM_TEAMS; ++i)
99  {
101  {
102  if (winner)
103  return 0;
104  winner = Team_IndexToTeam(i + 1);
105  }
106  }
107  return (winner ? winner : -1);
108 }
109 
111 {
112  int result = 0;
113  for (int i = 0; i < NUM_TEAMS; ++i)
114  {
116  {
117  ++result;
118  }
119  }
120  return result;
121 }
122 
123 int Team_GetWinnerTeam_WithOwnedItems(int min_control_points)
124 {
125  int winner = 0;
126  for (int i = 0; i < NUM_TEAMS; ++i)
127  {
128  if (g_team_entities[i].m_num_owned_items >= min_control_points)
129  {
130  if (winner)
131  return 0;
132  winner = Team_IndexToTeam(i + 1);
133  }
134  }
135  return (winner ? winner : -1);
136 }
137 
139 {
140  return team_ent.m_num_owned_items;
141 }
142 
144 {
145  team_ent.m_num_owned_items = number;
146 }
147 
149 {
150  int result = 0;
151  for (int i = 0; i < NUM_TEAMS; ++i)
152  {
154  {
155  ++result;
156  }
157  }
158  return result;
159 }
160 
161 void setcolor(entity this, int clr)
162 {
163 #if 0
164  this.clientcolors = clr;
165  this.team = (clr & 15) + 1;
166 #else
167  builtin_setcolor(this, clr);
168 #endif
169 }
170 
172 {
173  return Team_IsValidTeam(this.team);
174 }
175 
177 {
178  return Team_TeamToIndex(this.team);
179 }
180 
182 {
183  int index = Entity_GetTeamIndex(this);
184  if (!Team_IsValidIndex(index))
185  {
186  return NULL;
187  }
188  return Team_GetTeamFromIndex(index);
189 }
190 
191 void SetPlayerColors(entity player, float _color)
192 {
193  float pants = _color & 0x0F;
194  float shirt = _color & 0xF0;
195  if (teamplay)
196  {
197  setcolor(player, 16 * pants + pants);
198  }
199  else
200  {
201  setcolor(player, shirt + pants);
202  }
203 }
204 
205 bool Player_SetTeamIndex(entity player, int index)
206 {
207  int new_team = Team_IndexToTeam(index);
208  if (player.team == new_team)
209  {
210  if (new_team != -1)
211  {
212  // This is important when players join the game and one of their
213  // color matches the team color while other doesn't. For example
214  // [BOT]Lion: color 0 4.
215  SetPlayerColors(player, new_team - 1);
216  }
217  return true;
218  }
219  int old_index = Team_TeamToIndex(player.team);
220  if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true)
221  {
222  // Mutator has blocked team change.
223  return false;
224  }
225  if (new_team == -1)
226  {
227  player.team = -1;
228  }
229  else
230  {
231  SetPlayerColors(player, new_team - 1);
232  }
233  MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index);
234  return true;
235 }
236 
237 bool SetPlayerTeam(entity player, int team_index, int type)
238 {
239  int old_team_index = Entity_GetTeamIndex(player);
240 
241  if (!Player_SetTeamIndex(player, team_index))
242  return false;
243 
244  LogTeamChange(player.playerid, player.team, type);
245 
246  if (team_index != old_team_index)
247  {
248  KillPlayerForTeamChange(player);
249  PlayerScore_Clear(player);
250  CS(player).parm_idlesince = time;
251 
252  if (!IS_BOT_CLIENT(player))
254 
255  if (team_index != -1)
256  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
257  }
258 
259  if (team_index == -1)
260  {
261  if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
262  {
263  // this done here so it happens even when manually speccing during the countdown
264  Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
265  CS(player).idlekick_lasttimeleft = 0;
266  }
267  else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
268  {
269  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
270  }
271  }
272 
273  return true;
274 }
275 
276 void Player_SetTeamIndexChecked(entity player, int team_index)
277 {
278  if (!teamplay)
279  {
280  return;
281  }
282  if (!Team_IsValidIndex(team_index))
283  {
284  return;
285  }
287  CS(player).wasplayer))
288  {
289  Send_Notification(NOTIF_ONE, player, MSG_INFO,
290  INFO_TEAMCHANGE_NOTALLOWED);
291  return;
292  }
293  entity balance = TeamBalance_CheckAllowedTeams(player);
294  if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
295  {
296  team_index = 4;
297  }
298  if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
299  {
300  team_index = 3;
301  }
302  if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
303  {
304  team_index = 2;
305  }
306  if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
307  {
308  team_index = 1;
309  }
310  // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
312  {
313  TeamBalance_GetTeamCounts(balance, player);
314  if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
315  player, false)) == 0)
316  {
317  Send_Notification(NOTIF_ONE, player, MSG_INFO,
318  INFO_TEAMCHANGE_LARGERTEAM);
319  TeamBalance_Destroy(balance);
320  return;
321  }
322  }
323  TeamBalance_Destroy(balance);
324  SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
325 }
326 
327 bool MoveToTeam(entity client, int team_index, int type)
328 {
329  //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
330  int lockteams_backup = lockteams; // backup any team lock
331  lockteams = 0; // disable locked teams
332  if (!SetPlayerTeam(client, team_index, type))
333  {
334  lockteams = lockteams_backup; // restore the team lock
335  return false;
336  }
337  lockteams = lockteams_backup; // restore the team lock
338  return true;
339 }
340 
342 {
343  return player.team_forced > TEAM_FORCE_DEFAULT;
344 }
345 
347 {
348  return player.team_forced;
349 }
350 
351 void Player_SetForcedTeamIndex(entity player, int team_index)
352 {
353  switch (team_index)
354  {
356  case TEAM_FORCE_DEFAULT:
357  {
358  player.team_forced = team_index;
359  break;
360  }
361  default:
362  {
363  if (!Team_IsValidIndex(team_index))
364  {
365  LOG_FATAL("Player_SetForcedTeamIndex: Invalid team index.");
366  }
367  else
368  {
369  player.team_forced = team_index;
370  break;
371  }
372  }
373  }
374 }
375 
377 {
379  {
380  if (IS_REAL_CLIENT(player)) // only players, not bots
381  {
383  {
384  player.team_forced = autocvar_g_campaign_forceteam;
385  }
386  else
387  {
388  player.team_forced = TEAM_FORCE_DEFAULT;
389  }
390  }
391  }
392  else if (PlayerInList(player, autocvar_g_forced_team_red))
393  {
394  player.team_forced = 1;
395  }
396  else if (PlayerInList(player, autocvar_g_forced_team_blue))
397  {
398  player.team_forced = 2;
399  }
401  {
402  player.team_forced = 3;
403  }
404  else if (PlayerInList(player, autocvar_g_forced_team_pink))
405  {
406  player.team_forced = 4;
407  }
408  else
409  {
411  {
412  case "red":
413  {
414  player.team_forced = 1;
415  break;
416  }
417  case "blue":
418  {
419  player.team_forced = 2;
420  break;
421  }
422  case "yellow":
423  {
424  player.team_forced = 3;
425  break;
426  }
427  case "pink":
428  {
429  player.team_forced = 4;
430  break;
431  }
432  case "spectate":
433  case "spectator":
434  {
435  player.team_forced = TEAM_FORCE_SPECTATOR;
436  break;
437  }
438  default:
439  {
440  player.team_forced = TEAM_FORCE_DEFAULT;
441  break;
442  }
443  }
444  }
445  if (!teamplay && Player_HasRealForcedTeam(player))
446  {
447  player.team_forced = TEAM_FORCE_DEFAULT;
448  }
449 }
450 
452 {
453  //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
454  if (!teamplay)
455  {
456  return;
457  }
458  if (player.bot_forced_team)
459  {
460  return;
461  }
462  entity balance = TeamBalance_CheckAllowedTeams(player);
463  if (Player_HasRealForcedTeam(player))
464  {
465  int forced_team_index = player.team_forced;
466  bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
467  forced_team_index);
468  TeamBalance_Destroy(balance);
469  if (!is_team_allowed)
470  {
471  return;
472  }
473  if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
474  {
475  return;
476  }
477  return;
478  }
479  int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
480  TeamBalance_Destroy(balance);
481  if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
482  {
483  return;
484  }
485 }
486 
488 {
489  entity balance = spawn();
490  for (int i = 0; i < NUM_TEAMS; ++i)
491  {
492  entity team_ent = balance.m_team_balance_team[i] = spawn();
493  team_ent.m_team_score = g_team_entities[i].m_team_score;
494  team_ent.m_num_players = TEAM_NOT_ALLOWED;
495  team_ent.m_num_bots = 0;
496  }
497  setthink(balance, TeamBalance_Destroy);
498  balance.nextthink = time;
499 
500  int teams_mask = 0;
501  string teament_name = string_null;
502  bool mutator_returnvalue = MUTATOR_CALLHOOK(TeamBalance_CheckAllowedTeams,
503  teams_mask, teament_name, for_whom);
504  teams_mask = M_ARGV(0, float);
505  teament_name = M_ARGV(1, string);
506  if (mutator_returnvalue)
507  {
508  for (int i = 0; i < NUM_TEAMS; ++i)
509  {
510  if (teams_mask & BIT(i))
511  {
512  balance.m_team_balance_team[i].m_num_players = 0;
513  }
514  }
515  }
516 
517  if (teament_name)
518  {
519  entity head = find(NULL, classname, teament_name);
520  while (head)
521  {
522  if (Team_IsValidTeam(head.team))
523  {
524  TeamBalance_GetTeam(balance, head.team).m_num_players = 0;
525  }
526  head = find(head, classname, teament_name);
527  }
528  }
529 
530  // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
531  if (autocvar_bot_vs_human && AvailableTeams() == 2 && for_whom)
532  {
533  if (autocvar_bot_vs_human > 0)
534  {
535  // find last team available
536  if (IS_BOT_CLIENT(for_whom))
537  {
538  if (TeamBalance_IsTeamAllowedInternal(balance, 4))
539  {
540  TeamBalance_BanTeamsExcept(balance, 4);
541  }
542  else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
543  {
544  TeamBalance_BanTeamsExcept(balance, 3);
545  }
546  else
547  {
548  TeamBalance_BanTeamsExcept(balance, 2);
549  }
550  // no further cases, we know at least 2 teams exist
551  }
552  else
553  {
554  if (TeamBalance_IsTeamAllowedInternal(balance, 1))
555  {
556  TeamBalance_BanTeamsExcept(balance, 1);
557  }
558  else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
559  {
560  TeamBalance_BanTeamsExcept(balance, 2);
561  }
562  else
563  {
564  TeamBalance_BanTeamsExcept(balance, 3);
565  }
566  // no further cases, bots have one of the teams
567  }
568  }
569  else
570  {
571  // find first team available
572  if (IS_BOT_CLIENT(for_whom))
573  {
574  if (TeamBalance_IsTeamAllowedInternal(balance, 1))
575  {
576  TeamBalance_BanTeamsExcept(balance, 1);
577  }
578  else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
579  {
580  TeamBalance_BanTeamsExcept(balance, 2);
581  }
582  else
583  {
584  TeamBalance_BanTeamsExcept(balance, 3);
585  }
586  // no further cases, we know at least 2 teams exist
587  }
588  else
589  {
590  if (TeamBalance_IsTeamAllowedInternal(balance, 4))
591  {
592  TeamBalance_BanTeamsExcept(balance, 4);
593  }
594  else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
595  {
596  TeamBalance_BanTeamsExcept(balance, 3);
597  }
598  else
599  {
600  TeamBalance_BanTeamsExcept(balance, 2);
601  }
602  // no further cases, bots have one of the teams
603  }
604  }
605  }
606 
607  if (!for_whom)
608  {
609  balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
610  return balance;
611  }
612 
613  // if player has a forced team, ONLY allow that one
614  for (int i = 1; i <= NUM_TEAMS; ++i)
615  {
616  if (for_whom.team_forced == i &&
618  {
619  TeamBalance_BanTeamsExcept(balance, i);
620  break;
621  }
622  }
623  balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
624  return balance;
625 }
626 
628 {
629  if (balance == NULL)
630  {
631  return;
632  }
633  for (int i = 0; i < NUM_TEAMS; ++i)
634  {
635  delete(balance.(m_team_balance_team[i]));
636  }
637  delete(balance);
638 }
639 
641 {
642  if (balance == NULL)
643  {
644  LOG_FATAL("TeamBalance_GetAllowedTeams: Team balance entity is NULL.");
645  }
646  if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
647  {
648  LOG_FATAL("TeamBalance_GetAllowedTeams: "
649  "Team balance entity is not initialized.");
650  }
651  int result = 0;
652  for (int i = 1; i <= NUM_TEAMS; ++i)
653  {
654  if (TeamBalance_IsTeamAllowedInternal(balance, i))
655  {
656  result |= Team_IndexToBit(i);
657  }
658  }
659  return result;
660 }
661 
662 bool TeamBalance_IsTeamAllowed(entity balance, int index)
663 {
664  if (balance == NULL)
665  {
666  LOG_FATAL("TeamBalance_IsTeamAllowed: Team balance entity is NULL.");
667  }
668  if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
669  {
670  LOG_FATAL("TeamBalance_IsTeamAllowed: "
671  "Team balance entity is not initialized.");
672  }
673  if (!Team_IsValidIndex(index))
674  {
675  LOG_FATALF("TeamBalance_IsTeamAllowed: Team index is invalid: %f",
676  index);
677  }
678  return TeamBalance_IsTeamAllowedInternal(balance, index);
679 }
680 
682 {
683  if (balance == NULL)
684  {
685  LOG_FATAL("TeamBalance_GetTeamCounts: Team balance entity is NULL.");
686  }
687  if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
688  {
689  LOG_FATAL("TeamBalance_GetTeamCounts: "
690  "Team balance entity is not initialized.");
691  }
693  {
694  // Mutator has overriden the configuration.
695  for (int i = 1; i <= NUM_TEAMS; ++i)
696  {
697  entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
698  if (TeamBalanceTeam_IsAllowed(team_ent))
699  {
700  MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
701  team_ent.m_num_players = M_ARGV(2, float);
702  team_ent.m_num_bots = M_ARGV(3, float);
703  }
704  }
705  }
706  else
707  {
708  // Manually count all players.
709  FOREACH_CLIENT(true,
710  {
711  if (it == ignore)
712  {
713  continue;
714  }
715  int team_num;
716  // TODO: Reconsider when the player is truly on the team.
717  if (IS_CLIENT(it) || INGAME(it))
718  {
719  team_num = it.team;
720  }
721  else if (Player_HasRealForcedTeam(it))
722  {
723  // Do we really need this? Probably not.
724  team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
725  }
726  else
727  {
728  continue;
729  }
730  if (!Team_IsValidTeam(team_num))
731  {
732  continue;
733  }
734  entity team_ent = TeamBalance_GetTeam(balance, team_num);
735  if (!TeamBalanceTeam_IsAllowed(team_ent))
736  {
737  continue;
738  }
739  ++team_ent.m_num_players;
740  if (IS_BOT_CLIENT(it))
741  {
742  ++team_ent.m_num_bots;
743  }
744  });
745  }
746 
747  // if the player who has a forced team has not joined yet, reserve the spot
749  {
751  {
752  entity team_ent = TeamBalance_GetTeamFromIndex(balance,
754  if (team_ent.m_num_players == team_ent.m_num_bots)
755  {
756  ++team_ent.m_num_players;
757  }
758  }
759  }
760  balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
761 }
762 
763 int TeamBalance_GetNumberOfPlayers(entity balance, int index)
764 {
765  if (balance == NULL)
766  {
767  LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
768  "Team balance entity is NULL.");
769  }
770  if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
771  {
772  LOG_FATAL("TeamBalance_GetNumberOfPlayers: "
773  "TeamBalance_GetTeamCounts has not been called.");
774  }
775  if (!Team_IsValidIndex(index))
776  {
777  LOG_FATALF("TeamBalance_GetNumberOfPlayers: Team index is invalid: %f",
778  index);
779  }
780  return balance.m_team_balance_team[index - 1].m_num_players;
781 }
782 
783 int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
784 {
785  if (balance == NULL)
786  {
787  LOG_FATAL("TeamBalance_FindBestTeam: Team balance entity is NULL.");
788  }
789  if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
790  {
791  LOG_FATAL("TeamBalance_FindBestTeam: "
792  "Team balance entity is not initialized.");
793  }
794  // count how many players are in each team
795  if (ignore_player)
796  {
797  TeamBalance_GetTeamCounts(balance, player);
798  }
799  else
800  {
802  }
803  int team_bits = TeamBalance_FindBestTeams(balance, player, true);
804  if (team_bits == 0)
805  {
806  LOG_FATALF("TeamBalance_FindBestTeam: No teams available for %s\n",
808  }
810  for (int i = 1; i <= NUM_TEAMS; ++i)
811  {
812  if (team_bits & Team_IndexToBit(i))
813  {
814  RandomSelection_AddFloat(i, 1, 1);
815  }
816  }
818 }
819 
820 int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
821 {
822  if (balance == NULL)
823  {
824  LOG_FATAL("TeamBalance_FindBestTeams: Team balance entity is NULL.");
825  }
826  if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
827  {
828  LOG_FATAL("TeamBalance_FindBestTeams: "
829  "TeamBalance_GetTeamCounts has not been called.");
830  }
831  if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
832  {
833  return M_ARGV(1, float);
834  }
835  int team_bits = 0;
836  int previous_team = 0;
837  for (int i = 1; i <= NUM_TEAMS; ++i)
838  {
839  if (!TeamBalance_IsTeamAllowedInternal(balance, i))
840  {
841  continue;
842  }
843  if (previous_team == 0)
844  {
845  team_bits = Team_IndexToBit(i);
846  previous_team = i;
847  continue;
848  }
849  int compare = TeamBalance_CompareTeams(balance, i, previous_team,
850  player, use_score);
851  if (compare == TEAMS_COMPARE_LESS)
852  {
853  team_bits = Team_IndexToBit(i);
854  previous_team = i;
855  continue;
856  }
857  if (compare == TEAMS_COMPARE_EQUAL)
858  {
859  team_bits |= Team_IndexToBit(i);
860  previous_team = i;
861  }
862  }
863  return team_bits;
864 }
865 
866 int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
867  entity player, bool use_score)
868 {
869  if (balance == NULL)
870  {
871  LOG_FATAL("TeamBalance_CompareTeams: Team balance entity is NULL.");
872  }
873  if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
874  {
875  LOG_FATAL("TeamBalance_CompareTeams: "
876  "TeamBalance_GetTeamCounts has not been called.");
877  }
878  if (!Team_IsValidIndex(team_index_a))
879  {
880  LOG_FATALF("TeamBalance_CompareTeams: team_index_a is invalid: %f",
881  team_index_a);
882  }
883  if (!Team_IsValidIndex(team_index_b))
884  {
885  LOG_FATALF("TeamBalance_CompareTeams: team_index_b is invalid: %f",
886  team_index_b);
887  }
888  if (team_index_a == team_index_b)
889  {
890  return TEAMS_COMPARE_EQUAL;
891  }
892  entity team_a = TeamBalance_GetTeamFromIndex(balance, team_index_a);
893  entity team_b = TeamBalance_GetTeamFromIndex(balance, team_index_b);
894  return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score);
895 }
896 
898 {
899  // checks disabled because we always want auto-balanced bots
900  //if (!(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance))
901  // return;
902 
905  int smallest_team_index = 0;
906  int smallest_team_player_count = 0;
907  for (int i = 1; i <= NUM_TEAMS; ++i)
908  {
909  entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
910  if (!TeamBalanceTeam_IsAllowed(team_))
911  {
912  continue;
913  }
914  int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
915  if (smallest_team_index == 0)
916  {
917  smallest_team_index = i;
918  smallest_team_player_count = playercount;
919  }
920  else if (playercount < smallest_team_player_count)
921  {
922  smallest_team_index = i;
923  smallest_team_player_count = playercount;
924  }
925  }
926  //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
927  //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
928  entity switchable_bot = NULL;
929  int teams = BITS(NUM_TEAMS);
930  while (teams != 0)
931  {
932  int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
933  teams);
934  if (smallest_team_index == largest_team_index)
935  {
936  TeamBalance_Destroy(balance);
937  return;
938  }
939  entity largest_team = TeamBalance_GetTeamFromIndex(balance,
940  largest_team_index);
941  int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
942  largest_team);
943  if (largest_team_player_count - smallest_team_player_count < 2)
944  {
945  TeamBalance_Destroy(balance);
946  return;
947  }
948  //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
949  //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
950  switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
951  smallest_team_index, true);
952  if (switchable_bot != NULL)
953  {
954  break;
955  }
956  teams &= ~Team_IndexToBit(largest_team_index);
957  }
958  TeamBalance_Destroy(balance);
959  if (switchable_bot == NULL)
960  {
961  //PrintToChatAll("No bot found after searching through all the teams");
962  return;
963  }
964  SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
965 }
966 
968 {
969  int largest_team_index = 0;
970  int largest_team_player_count = 0;
971  for (int i = 1; i <= NUM_TEAMS; ++i)
972  {
973  if (!(Team_IndexToBit(i) & teams))
974  {
975  continue;
976  }
977  entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
978  if (!TeamBalanceTeam_IsAllowed(team_))
979  {
980  continue;
981  }
982  int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
983  if (largest_team_index == 0)
984  {
985  largest_team_index = i;
986  largest_team_player_count = playercount;
987  }
988  else if (playercount > largest_team_player_count)
989  {
990  largest_team_index = i;
991  largest_team_player_count = playercount;
992  }
993  }
994  return largest_team_index;
995 }
996 
998  int destination_team_index, bool is_bot)
999 {
1001  destination_team_index, is_bot))
1002  {
1003  return M_ARGV(3, entity);
1004  }
1005  entity lowest_player = NULL;
1006  float lowest_score = FLOAT_MAX;
1007  FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
1008  {
1009  if (IS_BOT_CLIENT(it) != is_bot)
1010  {
1011  continue;
1012  }
1013  float temp_score = PlayerScore_Get(it, SP_SCORE);
1014  if (temp_score >= lowest_score)
1015  {
1016  continue;
1017  }
1018  //PrintToChatAll(sprintf(
1019  // "Found %s with lowest score, checking allowed teams", it.netname));
1020  entity balance = TeamBalance_CheckAllowedTeams(it);
1021  if (TeamBalance_IsTeamAllowed(balance, source_team_index))
1022  {
1023  //PrintToChatAll("Allowed");
1024  lowest_player = it;
1025  lowest_score = temp_score;
1026  }
1027  else
1028  {
1029  //PrintToChatAll("Not allowed");
1030  }
1031  TeamBalance_Destroy(balance);
1032  });
1033  return lowest_player;
1034 }
1035 
1036 void LogTeamChange(float player_id, float team_number, int type)
1037 {
1038  if (!autocvar_sv_eventlog)
1039  {
1040  return;
1041  }
1042  if (player_id < 1)
1043  {
1044  return;
1045  }
1046  GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
1047 }
1048 
1050 {
1051  if (IS_DEAD(player))
1052  {
1053  return;
1054  }
1055  if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
1056  {
1057  return;
1058  }
1059  Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
1060  player.origin, '0 0 0');
1061 }
1062 
1064 {
1065  return balance.m_team_balance_team[index - 1].m_num_players !=
1067 }
1068 
1069 void TeamBalance_BanTeamsExcept(entity balance, int index)
1070 {
1071  for (int i = 1; i <= NUM_TEAMS; ++i)
1072  {
1073  if (i != index)
1074  {
1075  balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
1076  }
1077  }
1078 }
1079 
1081 {
1082  if (!Team_IsValidIndex(index))
1083  {
1084  LOG_FATALF("TeamBalance_GetTeamFromIndex: Index is invalid: %f", index);
1085  }
1086  return balance.m_team_balance_team[index - 1];
1087 }
1088 
1089 entity TeamBalance_GetTeam(entity balance, int team_num)
1090 {
1091  return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
1092 }
1093 
1095 {
1096  return team_ent.m_num_players != TEAM_NOT_ALLOWED;
1097 }
1098 
1100 {
1101  return team_ent.m_num_players;
1102 }
1103 
1105 {
1106  return team_ent.m_num_bots;
1107 }
1108 
1110  entity player, bool use_score)
1111 {
1112  if (team_a == team_b)
1113  {
1114  return TEAMS_COMPARE_EQUAL;
1115  }
1116  if (!TeamBalanceTeam_IsAllowed(team_a) ||
1117  !TeamBalanceTeam_IsAllowed(team_b))
1118  {
1119  return TEAMS_COMPARE_INVALID;
1120  }
1121  int num_players_team_a = team_a.m_num_players;
1122  int num_players_team_b = team_b.m_num_players;
1123  if (IS_REAL_CLIENT(player) && bots_would_leave)
1124  {
1125  num_players_team_a -= team_a.m_num_bots;
1126  num_players_team_b -= team_b.m_num_bots;
1127  }
1128  if (num_players_team_a < num_players_team_b)
1129  {
1130  return TEAMS_COMPARE_LESS;
1131  }
1132  if (num_players_team_a > num_players_team_b)
1133  {
1134  return TEAMS_COMPARE_GREATER;
1135  }
1136  if (!use_score)
1137  {
1138  return TEAMS_COMPARE_EQUAL;
1139  }
1140  if (team_a.m_team_score < team_b.m_team_score)
1141  {
1142  return TEAMS_COMPARE_LESS;
1143  }
1144  if (team_a.m_team_score > team_b.m_team_score)
1145  {
1146  return TEAMS_COMPARE_GREATER;
1147  }
1148  return TEAMS_COMPARE_EQUAL;
1149 }
1150 
1151 void SV_ChangeTeam(entity player, int new_color)
1152 {
1153  if (!teamplay)
1154  {
1155  SetPlayerColors(player, new_color);
1156  }
1157  if(!IS_CLIENT(player))
1158  {
1159  return;
1160  }
1161  if (!teamplay)
1162  {
1163  return;
1164  }
1165  Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) + 1));
1166 }
#define INGAME(it)
Definition: sv_rules.qh:20
const int NUM_TEAMS
Number of teams in the game.
Definition: teams.qh:3
int Team_IndexToBit(int index)
Converts team index into bit value that is used in team bitmasks.
Definition: teams.qh:211
int Team_GetWinnerTeam_WithOwnedItems(int min_control_points)
Returns the winner team.
Definition: teamplay.qc:123
string MapInfo_Type_ToString(Gametype t)
Definition: mapinfo.qc:616
int Team_GetNumberOfTeamsWithOwnedItems()
Returns the number of teams that own items.
Definition: teamplay.qc:148
#define APP_TEAM_NUM(num, prefix)
Definition: all.qh:85
One or both teams are invalid.
Definition: teamplay.qh:233
void TeamBalance_AutoBalanceBots()
Switches a bot from one team to another if teams are not balanced.
Definition: teamplay.qc:897
bool MoveToTeam(entity client, int team_index, int type)
Moves player to the specified team.
Definition: teamplay.qc:327
int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, entity player, bool use_score)
Compares two teams for the purposes of game balance.
Definition: teamplay.qc:1109
string string_null
Definition: nil.qh:9
bool SetPlayerTeam(entity player, int team_index, int type)
Sets the team of the player.
Definition: teamplay.qc:237
bool wasplayer
Definition: client.qh:69
entity g_team_entities[NUM_TEAMS]
Holds global team entities.
Definition: teamplay.qc:45
string autocvar_g_forced_team_otherwise
Definition: teamplay.qh:11
int int number
Definition: impulse.qc:89
#define PlayerScore_Get(player, scorefield)
Returns the player&#39;s score.
Definition: scores.qh:43
int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b, entity player, bool use_score)
Compares two teams for the purposes of game balance.
Definition: teamplay.qc:866
#define IS_CLIENT(v)
Definition: utils.qh:13
int team
Definition: main.qh:157
void Team_SetTeamScore(entity team_ent, float score)
Sets the score of the team.
Definition: teamplay.qc:80
ERASEABLE void RandomSelection_Init()
Definition: random.qc:4
int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
Returns the bitmask of the teams that will make the game most balanced if the player joins any of the...
Definition: teamplay.qc:820
void SetPlayerColors(entity player, float _color)
Definition: teamplay.qc:191
void TeamBalance_Destroy(entity balance)
Destroy the team balance entity.
Definition: teamplay.qc:627
TeamBalance_CheckAllowedTeams has been called.
Definition: teamplay.qc:23
entity() spawn
int Team_GetNumberOfAlivePlayers(entity team_ent)
Returns the number of alive players in a team.
Definition: teamplay.qc:85
ClientState CS(Client this)
Definition: state.qh:47
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
bool Team_IsValidIndex(int index)
Returns whether the team index is valid.
Definition: teams.qh:151
The team was selected by autobalance.
Definition: teamplay.qh:111
void TeamBalance_JoinBestTeam(entity player)
Assigns the given player to a team that will make the game most balanced.
Definition: teamplay.qc:451
float RandomSelection_chosen_float
Definition: random.qh:6
bool autocvar_g_changeteam_banned
Definition: teamplay.qh:5
entity m_team_balance_team[NUM_TEAMS]
???
Definition: teamplay.qc:32
string classname
Definition: csprogsdefs.qc:107
int autocvar_bot_vs_human
Definition: cvars.qh:70
int Entity_GetTeamIndex(entity this)
Returns the team index of the given entity.
Definition: teamplay.qc:176
string autocvar_g_forced_team_red
Definition: teamplay.qc:40
entity result
Definition: promise.qc:43
Player has manually selected their team.
Definition: teamplay.qh:112
#define DMG_NOWEP
Definition: damage.qh:126
bool TeamBalanceTeam_IsAllowed(entity team_ent)
Returns whether the team is allowed.
Definition: teamplay.qc:1094
int Team_GetNumberOfOwnedItems(entity team_ent)
Returns the number of items owned by a team.
Definition: teamplay.qc:138
entity Entity_GetTeam(entity this)
Returns the team entity of the given entity.
Definition: teamplay.qc:181
void Player_SetTeamIndexChecked(entity player, int team_index)
Sets the team of the player with all sanity checks.
Definition: teamplay.qc:276
void Team_SetNumberOfOwnedItems(entity team_ent, int number)
Sets the number of items owned by a team.
Definition: teamplay.qc:143
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
int m_num_players
Number of players (both humans and bots) in a team.
Definition: teamplay.qc:35
entity teams
Definition: main.qh:44
void setcolor(entity this, int clr)
Definition: teamplay.qc:161
bool Entity_HasValidTeam(entity this)
Returns whether the given entity belongs to a valid team.
Definition: teamplay.qc:171
float autocvar_g_campaign_forceteam
Definition: campaign.qh:7
string autocvar_g_forced_team_blue
Definition: teamplay.qc:41
Force the player to spectator team.
Definition: teamplay.qh:137
int TeamBalance_GetLargestTeamIndex(entity balance, int teams)
Returns the index of the team with most players that is contained in the given bitmask of teams...
Definition: teamplay.qc:967
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
void KillPlayerForTeamChange(entity player)
Kills player as a result of team change.
Definition: teamplay.qc:1049
entity Team_GetTeam(int team_num)
Returns the global team entity that corresponds to the given TEAM_NUM value.
Definition: teamplay.qc:66
bool autocvar_g_balance_teams_prevent_imbalance
Definition: teamplay.qh:9
Gametype MapInfo_CurrentGametype()
Definition: mapinfo.qc:1150
int TeamBalance_GetAllowedTeams(entity balance)
Returns the bitmask of allowed teams.
Definition: teamplay.qc:640
void SV_ChangeTeam(entity player, int new_color)
Called when the player changes color with the "color" command.
Definition: teamplay.qc:1151
int Player_GetForcedTeamIndex(entity player)
Returns the index of the forced team of the given player.
Definition: teamplay.qc:346
entity TeamBalance_GetTeam(entity balance, int team_num)
Returns the team entity of the team balance entity that corresponds to the given TEAM_NUM value...
Definition: teamplay.qc:1089
entity TeamBalance_GetTeamFromIndex(entity balance, int index)
Returns the team entity of the team balance entity at the given index.
Definition: teamplay.qc:1080
bool just_joined
Definition: client.qh:74
void TeamBalance_GetTeamCounts(entity balance, entity ignore)
Counts the number of players and various other information about each team.
Definition: teamplay.qc:681
const int TEAM_NOT_ALLOWED
Indicates that the player is not allowed to join a team.
Definition: teamplay.qc:29
float autocvar_sv_maxidle_playertospectator
Definition: client.qh:38
void Player_SetForcedTeamIndex(entity player, int team_index)
Sets the index of the forced team of the given player.
Definition: teamplay.qc:351
int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
Finds the team that will make the game most balanced if the player joins it.
Definition: teamplay.qc:783
bool bots_would_leave
Definition: api.qh:101
#define NULL
Definition: post.qh:17
bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
Returns whether the team change to the specified team is allowed.
Definition: teamplay.qc:1063
void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
Sets the number of alive players in a team.
Definition: teamplay.qc:90
void TeamBalance_BanTeamsExcept(entity balance, int index)
Bans team change to all teams except the given one.
Definition: teamplay.qc:1069
float PlayerScore_Clear(entity player)
Initialize the score of this player if needed.
Definition: scores.qc:267
int AvailableTeams()
Definition: scores_rules.qc:22
void GameLogEcho(string s)
Definition: gamelog.qc:12
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: damage.qc:583
float teamplay
Definition: progsdefs.qc:31
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
int Team_GetNumberOfAliveTeams()
Returns the number of alive teams.
Definition: teamplay.qc:110
entity Team_GetTeamFromIndex(int index)
Returns the global team entity at the given index.
Definition: teamplay.qc:57
string autocvar_g_forced_team_pink
Definition: teamplay.qc:43
bool lockteams
Definition: teamplay.qh:13
string autocvar_g_forced_team_yellow
Definition: teamplay.qc:42
int Team_TeamToIndex(int team_num)
Converts team value into team index.
Definition: teams.qh:184
First team is less than the second one.
Definition: teamplay.qh:234
int TeamBalanceTeam_GetNumberOfBots(entity team_ent)
Returns the number of bots in a team.
Definition: teamplay.qc:1104
bool Player_SetTeamIndex(entity player, int index)
Sets the team of the player using its index.
Definition: teamplay.qc:205
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 CPID_IDLING
Definition: all.inc:654
#define RandomSelection_AddFloat(f, weight, priority)
Definition: random.qh:15
bool TeamBalance_IsTeamAllowed(entity balance, int index)
Returns whether the team change to the specified team is allowed.
Definition: teamplay.qc:662
Don&#39;t force any team.
Definition: teamplay.qh:138
The team balance has not been initialized.
Definition: teamplay.qc:21
const int FRAGS_SPECTATOR
Definition: constants.qh:4
#define LOG_FATALF(...)
Definition: log.qh:59
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
Returns the number of players (both humans and bots) in a team.
Definition: teamplay.qc:1099
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition: utils.qh:15
float clientcolors
float m_team_score
The score of the team.
Definition: teamplay.qc:34
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
bool Player_HasRealForcedTeam(entity player)
Returns whether player has real forced team.
Definition: teamplay.qc:341
bool autocvar_g_balance_teams
Definition: teamplay.qh:8
#define setthink(e, f)
void Player_DetermineForcedTeam(entity player)
Determines the forced team of the player using current global config.
Definition: teamplay.qc:376
int TeamBalance_GetNumberOfPlayers(entity balance, int index)
Returns the number of players (both humans and bots) in a team.
Definition: teamplay.qc:763
int Team_GetWinnerAliveTeam()
Returns the winner team.
Definition: teamplay.qc:95
int Team_IndexToTeam(int index)
Converts team index into team value.
Definition: teams.qh:169
bool autocvar_g_campaign
Definition: campaign.qh:6
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings...
Definition: teamplay.qc:487
bool autocvar_sv_eventlog
Definition: gamelog.qh:3
bool PlayerInList(entity player, string list)
Definition: client.qc:995
int m_team_balance_state
Holds the state of the team balance entity.
Definition: teamplay.qc:31
float time
Definition: csprogsdefs.qc:16
void LogTeamChange(float player_id, float team_number, int type)
Definition: teamplay.qc:1036
#define LOG_FATAL(...)
Definition: log.qh:58
int m_num_owned_items
Number of items owned by a team.
Definition: teamplay.qc:38
float Team_GetTeamScore(entity team_ent)
Returns the score of the team.
Definition: teamplay.qc:75
TeamBalance_GetTeamCounts has been called.
Definition: teamplay.qc:25
bool Team_IsValidTeam(int team_num)
Returns whether team value is valid.
Definition: teams.qh:133
#define BITS(n)
Definition: bits.qh:9
void Team_InitTeams()
Definition: teamplay.qc:47
entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index, int destination_team_index, bool is_bot)
Returns the player who is the most suitable for switching between the given teams.
Definition: teamplay.qc:997
const float FLOAT_MAX
Definition: float.qh:3
int m_num_bots
Number of bots in a team.
Definition: teamplay.qc:36
int m_num_players_alive
Number of alive players in a team.
Definition: teamplay.qc:37
First team the greater than the second one.
Definition: teamplay.qh:236
Both teams are equal.
Definition: teamplay.qh:235