Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
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>
11#include <server/damage.qh>
12#include <server/gamelog.qh>
14#include <server/race.qh>
15#include <server/scores.qh>
17
19enum
20{
26};
27
29const int TEAM_NOT_ALLOWED = -1;
30
33
40
45
47
48void _SpawnTeam(string teament_classname, int team_num)
49{
50 entity this = new_pure(teament_classname);
51 this.netname = Static_Team_ColorName(team_num);
52 this.cnt = team_num - 1;
53 this.spawnfunc_checked = true;
54 this.team = team_num;
55}
56int Team_MapEnts_FindOrSpawn(string ent_classname, int defaultmask)
57{
58 int teams = 0;
59 entity head = findchain(classname, ent_classname);
60 while (head)
61 {
62 if (Team_IsValidTeam(head.team))
63 teams |= Team_TeamToBit(head.team);
64 head = head.chain;
65 }
66 if (teams)
67 return teams;
68
69 LOG_TRACEF("No \"%s\" entities found on this map, creating them anyway.", ent_classname);
70 for (int i = 1; i <= NUM_TEAMS; ++i)
71 if (defaultmask & Team_IndexToBit(i))
72 _SpawnTeam(ent_classname, Team_IndexToTeam(i));
73 return defaultmask;
74}
75
77{
78 if (g_team_entities[0])
79 return;
80 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
81 {
82 g_team_entities[i] = new_pure(team_entity);
83 }
84}
85
87{
88 if (!Team_IsValidIndex(index))
89 {
90 LOG_FATALF("Index is invalid: %f", index);
91 }
92 return g_team_entities[index - 1];
93}
94
95entity Team_GetTeam(int team_num)
96{
97 if (!Team_IsValidTeam(team_num))
98 {
99 LOG_FATALF("Value is invalid: %f", team_num);
100 }
101 return g_team_entities[Team_TeamToIndex(team_num) - 1];
102}
103
105{
106 return team_ent.m_team_score;
107}
108
109void Team_SetTeamScore(entity team_ent, float score)
110{
111 team_ent.m_team_score = score;
112}
113
115{
116 return team_ent.m_num_players_alive;
117}
118
120{
121 team_ent.m_num_players_alive = number;
122}
123
125{
126 int winner = 0;
127 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
128 {
130 {
131 if (winner)
132 return 0;
133 winner = Team_IndexToTeam(i + 1);
134 }
135 }
136 return (winner ? winner : -1);
137}
138
140{
141 int result = 0;
142 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
143 {
145 {
146 ++result;
147 }
148 }
149 return result;
150}
151
152int Team_GetWinnerTeam_WithOwnedItems(int min_control_points)
153{
154 int winner = 0;
155 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
156 {
157 if (g_team_entities[i].m_num_owned_items >= min_control_points)
158 {
159 if (winner)
160 return 0;
161 winner = Team_IndexToTeam(i + 1);
162 }
163 }
164 return (winner ? winner : -1);
165}
166
168{
169 return team_ent.m_num_owned_items;
170}
171
173{
174 team_ent.m_num_owned_items = number;
175}
176
178{
179 int result = 0;
180 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
181 {
183 {
184 ++result;
185 }
186 }
187 return result;
188}
189
190void setcolor(entity this, int clr)
191{
192#if 1
193 this.clientcolors = clr;
194 if(teamplay)
195 this.team = (clr & 15) + 1;
196 else
197 this.team = -1;
198#else
199 // sets clientcolors and team (even in FFA games)
200 // and sends notification to all clients
201 builtin_setcolor(this, clr);
202#endif
203}
204
206{
207 return Team_IsValidTeam(this.team);
208}
209
211{
212 return Team_TeamToIndex(this.team);
213}
214
216{
217 int index = Entity_GetTeamIndex(this);
218 if (!Team_IsValidIndex(index))
219 {
220 return NULL;
221 }
222 return Team_GetTeamFromIndex(index);
223}
224
225void SetPlayerColors(entity player, float _color)
226{
227 float pants = _color & 0x0F;
228 float shirt = _color & 0xF0;
229 if (teamplay)
230 {
231 setcolor(player, 16 * pants + pants);
232 }
233 else
234 {
235 setcolor(player, shirt + pants);
236 }
237}
238
239bool Player_SetTeamIndex(entity player, int index)
240{
241 int new_team = Team_IndexToTeam(index);
242 if (player.team == new_team)
243 return true;
244
245 int old_index = Team_TeamToIndex(player.team);
246 if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, old_index, index) == true)
247 {
248 // Mutator has blocked team change.
249 return false;
250 }
251 if (new_team == -1)
252 {
253 if (teamplay) // sanity check, if false this code path should never be reached anyway
254 player.clientcolors = 0;
255 player.team = -1;
256 }
257 else
258 {
259 SetPlayerColors(player, new_team - 1);
260 }
261 MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_index, index);
262 return true;
263}
264
269bool QueuedPlayersReady(entity this, bool checkspecificteam)
270{
271 int numplayersqueued = 0;
272
273 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this
274 && (checkspecificteam ? it.wants_join > 0 : it.wants_join),
275 {
276 LOG_DEBUGF("Player %s is waiting to join team %d", it.netname, it.wants_join);
277 ++numplayersqueued;
278 if (numplayersqueued >= AVAILABLE_TEAMS - 1)
279 return true;
280 });
281
282 LOG_DEBUG("No players waiting to join.");
283 return false;
284}
285
286bool SetPlayerTeam(entity player, int team_index, int type)
287{
288 int old_team_index = Entity_GetTeamIndex(player);
289
290 if (!Player_SetTeamIndex(player, team_index))
291 return false;
292
293 LogTeamChange(player.playerid, player.team, type);
294
295 if (team_index != old_team_index)
296 {
298 PlayerScore_Clear(player);
299
300 if (!IS_BOT_CLIENT(player))
302
303 if (team_index != -1)
304 {
305 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
306 player.team_selected = team_index; // no autoselect in Join()
307
308 if (warmup_stage)
309 ReadyCount(); // teams might be balanced now
310 }
311 }
312
313 if (team_index == -1)
314 {
315 if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
316 {
317 // this done here so it happens even when manually speccing during the countdown
318 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
319 CS(player).idlekick_lasttimeleft = 0;
320 }
321 else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
322 {
323 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
324 }
325 }
326
327 return true;
328}
329
330bool MoveToTeam(entity client, int team_index, int type)
331{
332 //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
333 int lockteams_backup = lockteams; // backup any team lock
334 lockteams = 0; // disable locked teams
335 if (!SetPlayerTeam(client, team_index, type))
336 {
337 lockteams = lockteams_backup; // restore the team lock
338 return false;
339 }
340 lockteams = lockteams_backup; // restore the team lock
341 return true;
342}
343
345{
346 return player.team_forced > TEAM_FORCE_DEFAULT;
347}
348
350{
351 return player.team_forced;
352}
353
354void Player_SetForcedTeamIndex(entity player, int team_index)
355{
356 switch (team_index)
357 {
360 {
361 player.team_forced = team_index;
362 break;
363 }
364 default:
365 {
366 if (!Team_IsValidIndex(team_index))
367 {
368 LOG_FATAL("Invalid team index.");
369 }
370 else
371 {
372 player.team_forced = team_index;
373 break;
374 }
375 }
376 }
377}
378
380{
382 {
383 if (IS_REAL_CLIENT(player)) // only players, not bots
384 {
386 {
387 player.team_forced = autocvar_g_campaign_forceteam;
388 }
389 else
390 {
391 player.team_forced = TEAM_FORCE_DEFAULT;
392 }
393 }
394 }
396 {
397 player.team_forced = 1;
398 }
400 {
401 player.team_forced = 2;
402 }
404 {
405 player.team_forced = 3;
406 }
408 {
409 player.team_forced = 4;
410 }
411 else
412 {
414 {
415 case "red":
416 {
417 player.team_forced = 1;
418 break;
419 }
420 case "blue":
421 {
422 player.team_forced = 2;
423 break;
424 }
425 case "yellow":
426 {
427 player.team_forced = 3;
428 break;
429 }
430 case "pink":
431 {
432 player.team_forced = 4;
433 break;
434 }
435 case "spectate":
436 case "spectator":
437 {
438 player.team_forced = TEAM_FORCE_SPECTATOR;
439 break;
440 }
441 default:
442 {
443 player.team_forced = TEAM_FORCE_DEFAULT;
444 break;
445 }
446 }
447 }
448 if (!teamplay && Player_HasRealForcedTeam(player))
449 {
450 player.team_forced = TEAM_FORCE_DEFAULT;
451 }
452}
453
455{
456 //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
457 if (!teamplay)
458 {
459 return;
460 }
461 if (player.bot_forced_team)
462 {
463 return;
464 }
465 entity balance = TeamBalance_CheckAllowedTeams(player);
466 if (Player_HasRealForcedTeam(player))
467 {
468 int forced_team_index = player.team_forced;
469 bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
470 forced_team_index);
471 TeamBalance_Destroy(balance);
472 if (!is_team_allowed)
473 {
474 return;
475 }
476 if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
477 {
478 return;
479 }
480 return;
481 }
482 int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
483 TeamBalance_Destroy(balance);
484 if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
485 {
486 return;
487 }
488}
489
491{
492 entity balance = spawn();
493 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
494 {
495 entity team_ent = balance.m_team_balance_team[i] = spawn();
496 team_ent.m_team_score = g_team_entities[i].m_team_score;
497 team_ent.m_num_players = teamplay_bitmask & BIT(i) ? 0 : TEAM_NOT_ALLOWED;
498 }
500 balance.nextthink = time;
501
502 // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
503 if (autocvar_bot_vs_human && AVAILABLE_TEAMS == 2 && for_whom)
504 {
505 if (autocvar_bot_vs_human > 0)
506 {
507 // find last team available
508 if (IS_BOT_CLIENT(for_whom))
509 {
510 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
511 {
512 TeamBalance_BanTeamsExcept(balance, 4);
513 }
514 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
515 {
516 TeamBalance_BanTeamsExcept(balance, 3);
517 }
518 else
519 {
520 TeamBalance_BanTeamsExcept(balance, 2);
521 }
522 // no further cases, we know at least 2 teams exist
523 }
524 else
525 {
526 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
527 {
528 TeamBalance_BanTeamsExcept(balance, 1);
529 }
530 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
531 {
532 TeamBalance_BanTeamsExcept(balance, 2);
533 }
534 else
535 {
536 TeamBalance_BanTeamsExcept(balance, 3);
537 }
538 // no further cases, bots have one of the teams
539 }
540 }
541 else
542 {
543 // find first team available
544 if (IS_BOT_CLIENT(for_whom))
545 {
546 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
547 {
548 TeamBalance_BanTeamsExcept(balance, 1);
549 }
550 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
551 {
552 TeamBalance_BanTeamsExcept(balance, 2);
553 }
554 else
555 {
556 TeamBalance_BanTeamsExcept(balance, 3);
557 }
558 // no further cases, we know at least 2 teams exist
559 }
560 else
561 {
562 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
563 {
564 TeamBalance_BanTeamsExcept(balance, 4);
565 }
566 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
567 {
568 TeamBalance_BanTeamsExcept(balance, 3);
569 }
570 else
571 {
572 TeamBalance_BanTeamsExcept(balance, 2);
573 }
574 // no further cases, bots have one of the teams
575 }
576 }
577 }
578
579 if (!for_whom)
580 {
581 balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
582 return balance;
583 }
584
585 // if player has a forced team, ONLY allow that one
586 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
587 {
588 if (for_whom.team_forced == i &&
590 {
591 TeamBalance_BanTeamsExcept(balance, i);
592 break;
593 }
594 }
595 balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
596 return balance;
597}
598
600{
601 if (balance == NULL)
602 {
603 return;
604 }
605 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
606 {
607 delete(balance.(m_team_balance_team[i]));
608 }
609 delete(balance);
610}
611
613{
614 if (balance == NULL)
615 {
616 LOG_FATAL("Team balance entity is NULL.");
617 }
618 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
619 {
620 LOG_FATAL("Team balance entity is not initialized.");
621 }
622 int result = 0;
623 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
624 {
625 if (TeamBalance_IsTeamAllowedInternal(balance, i))
626 {
628 }
629 }
630 return result;
631}
632
634{
635 if (!teamplay)
636 return 0;
637
638 entity balance = TeamBalance_CheckAllowedTeams(ignore);
639 TeamBalance_GetTeamCounts(balance, ignore);
640
641 int ts_min = 255, ts_max = 0;
642 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
643 {
644 int ts = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
645 if (ts_min > ts)
646 ts_min = ts;
647 if (ts_max < ts)
648 ts_max = ts;
649 }
650
651 TeamBalance_Destroy(balance);
652
653 return ts_max - ts_min;
654}
655
656bool TeamBalance_AreEqual(entity ignore, bool would_leave)
657{
658 entity balance = TeamBalance_CheckAllowedTeams(ignore);
659 TeamBalance_GetTeamCounts(balance, ignore);
660
661 int prev_size = 0, i = 1;
662
663 for (; i <= AVAILABLE_TEAMS; ++i)
664 {
665 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
666 int team_size = would_leave ? team_ent.m_num_players_net : team_ent.m_num_players;
667 if (i > 1 && team_size != prev_size)
668 break;
669 prev_size = team_size;
670 }
671
672 TeamBalance_Destroy(balance);
673 return i > AVAILABLE_TEAMS;
674}
675
678{
679 if(this.lifetime <= 0 || TeamBalance_AreEqual(NULL, false))
680 {
681 if(this.lifetime <= 0)
682 {
683 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_REMOVE, playername(remove_countdown.enemy.netname, remove_countdown.enemy.team, true));
684 PutObserverInServer(remove_countdown.enemy, true, true);
685 }
686
687 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_REMOVE);
688
689 delete(this);
691
692 TeamBalance_RemoveExcessPlayers(NULL); // Check again for excess players in case someone also left while in countdown
693 return;
694 }
695
696 --this.lifetime;
697 this.nextthink = time + 1;
698}
699
700// FIXME: support more than 2 teams, the notification will be... awkward
701// FIXME: also don't kick the fc/bc/kc lol
703{
704 if(AVAILABLE_TEAMS != 2 || autocvar_g_campaign) return;
705
706 entity balance = TeamBalance_CheckAllowedTeams(ignore);
707 TeamBalance_GetTeamCounts(balance, ignore);
708
709 int min = 0;
710
711 for(int i = 1; i <= AVAILABLE_TEAMS; ++i)
712 {
713 int cur = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
714 if(i == 1 || cur < min)
715 min = cur;
716 }
717
718 for(int tmi = 1; tmi <= AVAILABLE_TEAMS; ++tmi)
719 {
720 int cur = TeamBalance_GetTeamFromIndex(balance, tmi).m_num_players;
721 if(cur > 0 && cur > min) // If this team has excess players
722 {
723 // Get newest player
724 int latest_join = 0;
725 entity latest_join_pl = NULL;
726
728 if(it.team == Team_IndexToTeam(tmi) && CS(it).startplaytime > latest_join)
729 {
730 latest_join = CS(it).startplaytime;
731 latest_join_pl = it;
732 }
733 });
734
735 // Force player to spectate
736 if(latest_join_pl)
737 {
738 // Send player to spectate
740 {
741 // Give a warning before moving to spect
742 if (!remove_countdown)
743 {
746 remove_countdown.nextthink = time;
747 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MOVETOSPEC_REMOVE, playername(latest_join_pl.netname, latest_join_pl.team, true), autocvar_g_balance_teams_remove_wait);
748 }
749 remove_countdown.enemy = latest_join_pl;
751 }
752 else
753 {
754 // Move to spects immediately
755 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_REMOVE, latest_join_pl.netname);
756 PutObserverInServer(latest_join_pl, true, true);
757 }
758 }
759 }
760 }
761
762 TeamBalance_Destroy(balance);
763}
764
766{
767 if (!teamplay)
768 return true;
769
770 int j, teamplayers_deficit = 0, teamplayers_max = 0;
771 entity it, balance = TeamBalance_CheckAllowedTeams(ignore);
772 TeamBalance_GetTeamCounts(balance, ignore);
773
774 for (int j = 1; j <= AVAILABLE_TEAMS; ++j)
775 {
776 it = TeamBalance_GetTeamFromIndex(balance, j);
777 // find the largest team size
778 if (it.m_num_players_net > teamplayers_max)
779 teamplayers_max = it.m_num_players_net;
780 }
781 // find how many players we'd need to join to achieve balanced numbers that way
782 for (j = 1; j <= AVAILABLE_TEAMS; ++j)
783 teamplayers_deficit += teamplayers_max - TeamBalance_GetTeamFromIndex(balance, j).m_num_players_net;
784
785 // first pass: find clients(s) who want to play on a specific team
786 for (j = 1; teamplayers_deficit > 0 && j <= maxclients; ++j)
787 {
788 it = ftoe(j);
789 if (it.wants_join <= 0 || it == ignore) continue;
790 if (TeamBalance_GetTeamFromIndex(balance, it.wants_join).m_num_players_net >= teamplayers_max) continue;
791 Join(it, false);
792 ++TeamBalance_GetTeam(balance, it.team).m_num_players_net;
793 --teamplayers_deficit;
794 }
795
796 // second pass: find clients(s) who want to play on any team
797 for (j = 1; teamplayers_deficit > 0 && j <= maxclients; ++j)
798 {
799 it = ftoe(j);
800 if (it.wants_join >= 0 || it == ignore) continue;
801 Join(it, false);
802 --teamplayers_deficit; // don't need to update .m_num_players_net as we won't read it again
803 }
804
805 TeamBalance_Destroy(balance);
806 return teamplayers_deficit <= 0; // return true if teams are now balanced
807}
808
809bool TeamBalance_IsTeamAllowed(entity balance, int index)
810{
811 if (balance == NULL)
812 {
813 LOG_FATAL("Team balance entity is NULL.");
814 }
815 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
816 {
817 LOG_FATAL("Team balance entity is not initialized.");
818 }
819 if (!Team_IsValidIndex(index))
820 {
821 LOG_FATALF("Team index is invalid: %f",
822 index);
823 }
824 return TeamBalance_IsTeamAllowedInternal(balance, index);
825}
826
827// maybe include qcsrc/server/clientkill.qh instead of declaring this?
830{
831 if (balance == NULL)
832 LOG_FATAL("Team balance entity is NULL.");
833 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
834 LOG_FATAL("Team balance entity is not initialized.");
835
837 {
838 // Mutator has overriden the configuration.
839 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
840 {
841 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
842 if (TeamBalanceTeam_IsAllowed(team_ent))
843 {
844 MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
845 team_ent.m_num_players = M_ARGV(2, float);
846 team_ent.m_num_bots = M_ARGV(3, float);
847 }
848 }
849 }
850 else
851 {
852 float skill_weight = 0, skill_weight_sum = 0;
854 // Manually count all players.
855 FOREACH_CLIENT(true,
856 {
857 if (it.m_skill_mu && it.m_skill_var)
858 {
859 skill_weight = 1 / it.m_skill_var;
860 server_skill_average += it.m_skill_mu * skill_weight;
861 skill_weight_sum += skill_weight;
862 }
863
864 if (it == ignore)
865 continue;
866
867 int team_num = it.killindicator_teamchange > 0 ? it.killindicator_teamchange : it.team;
868 if (team_num <= 0)
869 {
870 if (Player_HasRealForcedTeam(it)) // Do we really need this? Probably not.
871 team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
872 else
873 continue;
874 }
875 if (!Team_IsValidTeam(team_num))
876 continue;
877
878 entity team_ent = TeamBalance_GetTeam(balance, team_num);
879 if (!TeamBalanceTeam_IsAllowed(team_ent))
880 continue;
881
882 team_ent.m_num_players_net = ++team_ent.m_num_players;
883 if (IS_BOT_CLIENT(it))
884 ++team_ent.m_num_bots;
885
886 if (it.m_skill_mu && it.m_skill_var)
887 {
888 // skill_weight calculated above
889 team_ent.m_skill_mu += it.m_skill_mu * skill_weight;
890 team_ent.mass += skill_weight;
891 ++team_ent.count;
892 }
893 });
894
895 // deduct bots that would leave if a player joined their team, distributing the deductions between teams
896 // bones_was_here: not sure if it's safe to apply this to .m_num_players so using .m_num_players_net
897 for (int i = 1, bots_todo = bots_would_leave; bots_todo > 0 && i <= AVAILABLE_TEAMS; ++i)
898 {
899 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
900 int to_remove = min(team_ent.m_num_bots, ceil(bots_todo / AVAILABLE_TEAMS));
901 team_ent.m_num_players_net -= to_remove;
902 bots_todo -= to_remove;
903 }
904
905 // Calculate inverse-variance weighted mean skill ratings,
906 // for each unranked client use a fraction of the mean of all clients' skill ratings.
909 : 1000;
910 float server_skill_average_var = 0; skill_weight = 0; // will only be calculated once, if needed
911 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
912 {
913 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
914 int unranked_clients = team_ent.m_num_players_net - team_ent.count;
915 if (unranked_clients)
916 {
917 if (!server_skill_average_var)
918 {
919 server_skill_average_var = (server_skill_average * 0.25)**2;
920 skill_weight = 1 / server_skill_average_var;
921 }
922 team_ent.m_skill_mu += (server_skill_average * skill_weight) * unranked_clients;
923 team_ent.mass += skill_weight * unranked_clients;
924 }
925 if (team_ent.mass)
926 {
927 // .mass is storing the arithmetic sum of members' inverse-variance weightings
928 // Hence the team's mean is: team_ent.m_skill_mu / team_ent.mass
929 // Hence the team's variance is: 1 / team_ent.mass
930 team_ent.m_skill_mu /= team_ent.mass;
931 team_ent.m_skill_var = 1 / team_ent.mass;
932 }
933 }
934 }
935
936 // if the player who has a forced team has not joined yet, reserve the spot
939 {
941 if (team_ent.m_num_players == team_ent.m_num_bots)
942 ++team_ent.m_num_players;
943 }
944 balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
945}
946
948{
949 if (balance == NULL)
950 {
951 LOG_FATAL("Team balance entity is NULL.");
952 }
953 if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
954 {
955 LOG_FATAL("TeamBalance_GetTeamCounts has not been called.");
956 }
957 if (!Team_IsValidIndex(index))
958 {
959 LOG_FATALF("Team index is invalid: %f", index);
960 }
961 return balance.m_team_balance_team[index - 1].m_num_players;
962}
963
964int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
965{
966 if (balance == NULL)
967 {
968 LOG_FATAL("Team balance entity is NULL.");
969 }
970 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
971 {
972 LOG_FATAL("Team balance entity is not initialized.");
973 }
974 // count how many players are in each team
975 if (ignore_player)
976 {
977 TeamBalance_GetTeamCounts(balance, player);
978 }
979 else
980 {
982 }
983 int team_bits = TeamBalance_FindBestTeams(balance, player, true);
984 if (team_bits == 0)
985 {
986 LOG_FATALF("No teams available for %s\n", GetGametype());
987 }
988
989 // don't punish players for UI mistakes by changing teams randomly when already on a best team
990 if (player.team > 0 && (team_bits & Team_TeamToBit(player.team)))
991 return Team_TeamToIndex(player.team);
992
994 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
995 {
996 if (team_bits & Team_IndexToBit(i))
997 {
999 }
1000 }
1002}
1003
1004int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
1005{
1006 if (balance == NULL)
1007 LOG_FATAL("Team balance entity is NULL.");
1008 if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
1009 LOG_FATAL("TeamBalance_GetTeamCounts has not been called.");
1010
1011 if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
1012 return M_ARGV(1, float);
1013
1014 int team_bits = 0;
1015 int previous_team = 0;
1016 float player_skill_mu = 0, player_skill_var = 0;
1017 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1018 {
1019 if (!TeamBalance_IsTeamAllowedInternal(balance, i))
1020 continue;
1021 if (previous_team == 0)
1022 {
1023 team_bits = Team_IndexToBit(i);
1024 previous_team = i;
1025 continue;
1026 }
1027
1028 entity team_i = TeamBalance_GetTeamFromIndex(balance, i);
1029 entity team_p = TeamBalance_GetTeamFromIndex(balance, previous_team);
1030
1031 // During warmup the "best" team isn't the smallest or weakest one,
1032 // that method puts all the noobs on the same team when players join in the "wrong" order.
1033 // This picks the team(s) whose weighted mean skill differs the most from the joining player's skill,
1034 // unless they're all close enough in which case it picks smaller team(s).
1035 // Warmup-only as it must unbalance team sizes in extreme cases, swaps needed should be minimal.
1037 {
1038 if (!player_skill_mu)
1039 {
1040 player_skill_mu = player.m_skill_mu ? player.m_skill_mu : server_skill_average;
1041 player_skill_var = player.m_skill_var ? player.m_skill_var : (server_skill_average * 0.25)**2;
1042 }
1043 // To determine significance, we'll use a z-score of the inverse-variance weightings
1044 // with an adjustable threshold
1045 float team_i_z = (team_i.m_skill_mu - player_skill_mu) / sqrt(team_i.m_skill_var + player_skill_var);
1046 float team_p_z = (team_p.m_skill_mu - player_skill_mu) / sqrt(team_p.m_skill_var + player_skill_var);
1047 bool significant = fabs(team_i_z - team_p_z) > autocvar_g_balance_teams_skill_significance_threshold;
1048 // Do fabs afterwards, since the player can be in the middle of the teams (one z +ve, other -ve)
1049 team_i_z = fabs(team_i_z);
1050 team_p_z = fabs(team_p_z);
1051 if ((!significant && team_i.m_num_players_net < team_p.m_num_players_net)
1052 || ((significant || team_i.m_num_players_net == team_p.m_num_players_net) && team_i_z > team_p_z))
1053 {
1054 team_bits = Team_IndexToBit(i);
1055 previous_team = i;
1056 }
1057 else if (team_i_z == team_p_z) // rather unlikely
1058 {
1059 team_bits |= Team_IndexToBit(i);
1060 previous_team = i;
1061 }
1062 continue;
1063 }
1064
1065 int compare = TeamBalance_CompareTeamsInternal(team_i, team_p, use_score);
1066 if (compare == TEAMS_COMPARE_LESS)
1067 {
1068 team_bits = Team_IndexToBit(i);
1069 previous_team = i;
1070 }
1071 else if (compare == TEAMS_COMPARE_EQUAL)
1072 {
1073 team_bits |= Team_IndexToBit(i);
1074 previous_team = i;
1075 }
1076 }
1077 return team_bits;
1078}
1079
1081{
1082 // checks disabled because we always want auto-balanced bots
1083 //if (!autocvar_g_balance_teams_prevent_imbalance)
1084 // return;
1085
1086 if (intermission_running) return;
1087
1090 int smallest_team_index = 0;
1091 int smallest_team_player_count = 0;
1092 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1093 {
1094 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1095 if (!TeamBalanceTeam_IsAllowed(team_))
1096 {
1097 continue;
1098 }
1099 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1100 if (smallest_team_index == 0)
1101 {
1102 smallest_team_index = i;
1103 smallest_team_player_count = playercount;
1104 }
1105 else if (playercount < smallest_team_player_count)
1106 {
1107 smallest_team_index = i;
1108 smallest_team_player_count = playercount;
1109 }
1110 }
1111 //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
1112 //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
1113 entity switchable_bot = NULL;
1114 int teams = BITS(AVAILABLE_TEAMS);
1115 while (teams != 0)
1116 {
1117 int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
1118 teams);
1119 if (smallest_team_index == largest_team_index)
1120 {
1121 TeamBalance_Destroy(balance);
1122 return;
1123 }
1124 entity largest_team = TeamBalance_GetTeamFromIndex(balance,
1125 largest_team_index);
1126 int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
1127 largest_team);
1128 if (largest_team_player_count - smallest_team_player_count < 2)
1129 {
1130 TeamBalance_Destroy(balance);
1131 return;
1132 }
1133 //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
1134 //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
1135 switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
1136 smallest_team_index, true);
1137 if (switchable_bot != NULL)
1138 {
1139 break;
1140 }
1141 teams &= ~Team_IndexToBit(largest_team_index);
1142 }
1143 TeamBalance_Destroy(balance);
1144 if (switchable_bot == NULL)
1145 {
1146 //PrintToChatAll("No bot found after searching through all the teams");
1147 return;
1148 }
1149 SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
1150}
1151
1153{
1154 int largest_team_index = 0;
1155 int largest_team_player_count = 0;
1156 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1157 {
1158 if (!(Team_IndexToBit(i) & teams))
1159 {
1160 continue;
1161 }
1162 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1163 if (!TeamBalanceTeam_IsAllowed(team_))
1164 {
1165 continue;
1166 }
1167 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1168 if (largest_team_index == 0)
1169 {
1170 largest_team_index = i;
1171 largest_team_player_count = playercount;
1172 }
1173 else if (playercount > largest_team_player_count)
1174 {
1175 largest_team_index = i;
1176 largest_team_player_count = playercount;
1177 }
1178 }
1179 return largest_team_index;
1180}
1181
1183 int destination_team_index, bool is_bot)
1184{
1186 destination_team_index, is_bot))
1187 {
1188 return M_ARGV(3, entity);
1189 }
1190 entity lowest_player = NULL;
1191 float lowest_score = FLOAT_MAX;
1192 FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
1193 {
1194 if (IS_BOT_CLIENT(it) != is_bot)
1195 {
1196 continue;
1197 }
1198 float temp_score = PlayerScore_Get(it, SP_SCORE);
1199 if (temp_score >= lowest_score)
1200 {
1201 continue;
1202 }
1203 //PrintToChatAll(sprintf(
1204 // "Found %s with lowest score, checking allowed teams", it.netname));
1206 if (TeamBalance_IsTeamAllowed(balance, source_team_index))
1207 {
1208 //PrintToChatAll("Allowed");
1209 lowest_player = it;
1210 lowest_score = temp_score;
1211 }
1212 else
1213 {
1214 //PrintToChatAll("Not allowed");
1215 }
1216 TeamBalance_Destroy(balance);
1217 });
1218 return lowest_player;
1219}
1220
1221void LogTeamChange(float player_id, float team_number, int type)
1222{
1224 {
1225 return;
1226 }
1227 if (player_id < 1)
1228 {
1229 return;
1230 }
1231 GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
1232}
1233
1235{
1236 if (IS_DEAD(player))
1237 {
1238 return;
1239 }
1240 if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
1241 {
1242 return;
1243 }
1244 Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
1245 player.origin, '0 0 0');
1246}
1247
1249{
1250 return index <= AVAILABLE_TEAMS
1251 && balance.m_team_balance_team[index - 1].m_num_players != TEAM_NOT_ALLOWED;
1252}
1253
1254void TeamBalance_BanTeamsExcept(entity balance, int index)
1255{
1256 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1257 {
1258 if (i != index)
1259 {
1260 balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
1261 }
1262 }
1263}
1264
1266{
1267 if (!Team_IsValidIndex(index))
1268 {
1269 LOG_FATALF("Index is invalid: %f", index);
1270 }
1271 return balance.m_team_balance_team[index - 1];
1272}
1273
1274entity TeamBalance_GetTeam(entity balance, int team_num)
1275{
1276 return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
1277}
1278
1280{
1281 return team_ent && team_ent.m_num_players != TEAM_NOT_ALLOWED;
1282}
1283
1285{
1286 return team_ent.m_num_players;
1287}
1288
1290{
1291 return team_ent.m_num_bots;
1292}
1293
1294int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, bool use_score)
1295{
1296 if (team_a == team_b)
1297 return TEAMS_COMPARE_EQUAL;
1298
1299 if (team_a.m_num_players_net < team_b.m_num_players_net)
1300 return TEAMS_COMPARE_LESS;
1301 if (team_a.m_num_players_net > team_b.m_num_players_net)
1302 return TEAMS_COMPARE_GREATER;
1303 if (!use_score)
1304 return TEAMS_COMPARE_EQUAL;
1305
1306// Compare skill and score together as team strength.
1307 // Teams without players have 0 skill_mu but may have a score which must still apply.
1308 bool use_skill = team_a.m_skill_var && team_b.m_skill_var && autocvar_g_balance_teams_skill
1309 // z-score, <= threshold SDs means difference isn't significant, formula squared for perf
1310 && (((team_a.m_skill_mu - team_b.m_skill_mu)**2) / (team_a.m_skill_var + team_b.m_skill_var) > autocvar_g_balance_teams_skill_significance_threshold**2);
1311 float team_a_strength = use_skill ? team_a.m_skill_mu : 1;
1312 float team_b_strength = use_skill ? team_b.m_skill_mu : 1;
1313 // Early in the match scores are ~random due to too few samples,
1314 // as the match progresses scores become more reliable indicators of team strength.
1315 // 0.96875 (31/32): if at halftime one team has score 0 and the other has 2 its skill is multiplied by 1.376,
1316 // < 1 so that a team with 1 point is better than one with 0 (but not by much as sample size is small).
1317 // Applying full score ratio to one team is equivalent here to applying half of it to each.
1319 team_a_strength *= (1 + ((team_a.m_team_score ? team_a.m_team_score : 0.96875) / (team_b.m_team_score ? team_b.m_team_score : 0.96875) - 1)
1320 * ((time - game_starttime) / (autocvar_timelimit * 60))**1.5);
1321
1322 if (team_a_strength < team_b_strength)
1323 return TEAMS_COMPARE_LESS;
1324 if (team_a_strength > team_b_strength)
1325 return TEAMS_COMPARE_GREATER;
1326 return TEAMS_COMPARE_EQUAL;
1327}
1328
1329void SV_ChangeTeam(entity player, int new_color)
1330{
1331 if (!teamplay)
1332 SetPlayerColors(player, new_color);
1333}
1334
1335bool QueueNeeded(entity client, int team_index)
1336{
1338 return false;
1339
1340 int human_clients = 0;
1342 {
1343 if (++human_clients > 1)
1344 {
1345 if (TeamBalance_AreEqual(client, true))
1346 return true;
1347 if (team_index <= 0)
1348 return false; // auto-select will join a smaller team
1349
1350 entity balance = TeamBalance_CheckAllowedTeams(client);
1351 TeamBalance_GetTeamCounts(balance, client);
1352 if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance, client, false)) == 0)
1353 {
1354 TeamBalance_Destroy(balance);
1355 return true;
1356 }
1357 TeamBalance_Destroy(balance);
1358 return false;
1359 }
1360 });
1361 return false;
1362}
int bots_would_leave
how many bots would leave so humans can replace them
Definition api.qh:101
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
#define BITS(n)
Definition bits.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
string netname
Definition powerups.qc:20
float lifetime
Definition powerups.qc:23
float cnt
Definition powerups.qc:24
bool warmup_stage
Definition main.qh:120
int team
Definition main.qh:188
entity teams
Definition main.qh:58
int team_size
Definition main.qh:189
int killindicator_teamchange
Definition clientkill.qh:8
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:244
#define autocvar_timelimit
Definition stats.qh:92
float game_starttime
Definition stats.qh:82
string playername(string thename, int teamid, bool team_colorize)
Definition util.qc:2178
const int FRAGS_SPECTATOR
Definition constants.qh:4
string classname
float maxclients
float time
float nextthink
#define spawn
int autocvar_bot_vs_human
Definition cvars.qh:67
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:483
#define DMG_NOWEP
Definition damage.qh:104
float clientcolors
const float FLOAT_MAX
Definition float.qh:3
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
string GetGametype()
bool intermission_running
#define SV_ChangeTeam
Definition _all.inc:280
#define LOG_FATALF(...)
Definition log.qh:51
#define LOG_TRACEF(...)
Definition log.qh:75
#define LOG_DEBUG(...)
Definition log.qh:78
#define LOG_FATAL(...)
Definition log.qh:50
bool autocvar_g_campaign
Definition menu.qc:752
float ceil(float f)
float sqrt(float f)
float min(float f,...)
float fabs(float f)
#define ftoe(i)
Definition misc.qh:26
s1 s2 s1 s2 FLAG s1 s2 FLAG spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 CPID_REMOVE
Definition all.inc:696
s1 s2 s1 s2 FLAG s1 s2 FLAG spree_cen s1 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:694
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
void Kill_Notification(NOTIF broadcast, entity client, MSG net_type, CPID net_cpid)
Definition all.qc:1464
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:88
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:66
#define NULL
Definition post.qh:14
#define setcolor
Definition pre.qh:11
entity result
Definition promise.qc:45
ERASEABLE void RandomSelection_Init()
Definition random.qc:4
float RandomSelection_chosen_float
Definition random.qh:6
#define RandomSelection_AddFloat(f, weight, priority)
Definition random.qh:15
int ts_min
Definition scoreboard.qh:27
int ts_max
team size
Definition scoreboard.qh:27
bool PlayerScore_Clear(entity player)
Initialize the score of this player if needed.
Definition scores.qc:286
#define AVAILABLE_TEAMS
Number of teams that exist currently.
#define setthink(e, f)
float autocvar_g_campaign_forceteam
Definition campaign.qh:7
void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
putting a client as observer in the server
Definition client.qc:261
bool PlayerInList(entity player, string list)
Definition client.qc:1047
void Join(entity this, bool queued_join)
it's assumed this isn't called for bots (campaign_bots_may_start, centreprints)
Definition client.qc:2074
float startplaytime
Definition client.qh:67
bool autocvar_sv_teamnagger
Definition client.qh:58
bool just_joined
Definition client.qh:76
float autocvar_sv_maxidle_playertospectator
Definition client.qh:39
void ReadyCount()
Definition vote.qc:553
int int number
Definition impulse.qc:89
#define PlayerScore_Get(player, scorefield)
Returns the player's score.
Definition scores.qh:42
bool spawnfunc_checked
Definition spawnfunc.qh:8
ClientState CS(Client this)
Definition state.qh:47
#define INGAME(it)
Definition sv_rules.qh:24
int Team_GetWinnerTeam_WithOwnedItems(int min_control_points)
Returns the winner team.
Definition teamplay.qc:152
bool QueuedPlayersReady(entity this, bool checkspecificteam)
Returns true when enough players are queued that the next will join directly to the only available te...
Definition teamplay.qc:269
bool TeamBalanceTeam_IsAllowed(entity team_ent)
Returns whether the team is allowed.
Definition teamplay.qc:1279
entity remove_countdown
Definition teamplay.qc:676
string autocvar_g_forced_team_red
Definition teamplay.qc:41
void _SpawnTeam(string teament_classname, int team_num)
Definition teamplay.qc:48
int Entity_GetTeamIndex(entity this)
Returns the team index of the given entity.
Definition teamplay.qc:210
bool TeamBalance_QueuedPlayersTagIn(entity ignore)
Joins queued player(s) to team(s) with a shortage, this should be more robust than only replacing the...
Definition teamplay.qc:765
int Team_GetNumberOfAlivePlayers(entity team_ent)
Returns the number of alive players in a team.
Definition teamplay.qc:114
entity Team_GetTeam(int team_num)
Returns the global team entity that corresponds to the given TEAM_NUM value.
Definition teamplay.qc:95
void TeamBalance_AutoBalanceBots()
Switches a bot from one team to another if teams are not balanced.
Definition teamplay.qc:1080
void TeamBalance_Destroy(entity balance)
Destroy the team balance entity.
Definition teamplay.qc:599
void TeamBalance_BanTeamsExcept(entity balance, int index)
Bans team change to all teams except the given one.
Definition teamplay.qc:1254
void TeamBalance_RemoveExcessPlayers(entity ignore)
Definition teamplay.qc:702
bool QueueNeeded(entity client, int team_index)
Definition teamplay.qc:1335
entity Entity_GetTeam(entity this)
Returns the team entity of the given entity.
Definition teamplay.qc:215
const int TEAM_NOT_ALLOWED
Indicates that the player is not allowed to join a team.
Definition teamplay.qc:29
int m_num_players_net
.m_num_players but excluding bots that would leave if a human joined their team.
Definition teamplay.qc:36
void Player_DetermineForcedTeam(entity player)
Determines the forced team of the player using current global config.
Definition teamplay.qc:379
void TeamBalance_JoinBestTeam(entity player)
Assigns the given player to a team that will make the game most balanced.
Definition teamplay.qc:454
bool Player_HasRealForcedTeam(entity player)
Returns whether player has real forced team.
Definition teamplay.qc:344
void LogTeamChange(float player_id, float team_number, int type)
Definition teamplay.qc:1221
int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, bool use_score)
Compares two teams for the purposes of game balance.
Definition teamplay.qc:1294
int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
Returns the number of players (both humans and bots) in a team.
Definition teamplay.qc:1284
void Team_SetTeamScore(entity team_ent, float score)
Sets the score of the team.
Definition teamplay.qc:109
int m_num_owned_items
Number of items owned by a team.
Definition teamplay.qc:39
int Team_MapEnts_FindOrSpawn(string ent_classname, int defaultmask)
Finds any team map entities and returns their bitmask, else spawns them.
Definition teamplay.qc:56
int TeamBalance_GetNumberOfPlayers(entity balance, int index)
Returns the number of players (both humans and bots) in a team.
Definition teamplay.qc:947
bool SetPlayerTeam(entity player, int team_index, int type)
Sets the team of the player.
Definition teamplay.qc:286
void Team_InitTeams()
Definition teamplay.qc:76
entity g_team_entities[NUM_TEAMS]
Holds global team entities.
Definition teamplay.qc:46
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:1152
bool Player_SetTeamIndex(entity player, int index)
Sets the team of the player using its index.
Definition teamplay.qc:239
@ TEAM_BALANCE_TEAM_COUNTS_FILLED
TeamBalance_GetTeamCounts has been called.
Definition teamplay.qc:25
@ TEAM_BALANCE_TEAMS_CHECKED
TeamBalance_CheckAllowedTeams has been called.
Definition teamplay.qc:23
@ TEAM_BALANCE_UNINITIALIZED
The team balance has not been initialized.
Definition teamplay.qc:21
int TeamBalanceTeam_GetNumberOfBots(entity team_ent)
Returns the number of bots in a team.
Definition teamplay.qc:1289
int Team_GetNumberOfTeamsWithOwnedItems()
Returns the number of teams that own items.
Definition teamplay.qc:177
void Remove_Countdown(entity this)
Definition teamplay.qc:677
int m_num_bots
Number of bots in a team.
Definition teamplay.qc:37
int Team_GetWinnerAliveTeam()
Returns the winner team.
Definition teamplay.qc:124
void Team_SetNumberOfOwnedItems(entity team_ent, int number)
Sets the number of items owned by a team.
Definition teamplay.qc:172
void SetPlayerColors(entity player, float _color)
Definition teamplay.qc:225
int m_team_balance_state
Holds the state of the team balance data entity.
Definition teamplay.qc:31
void TeamBalance_GetTeamCounts(entity balance, entity ignore)
Counts the number of players and various other information about each team.
Definition teamplay.qc:829
string autocvar_g_forced_team_blue
Definition teamplay.qc:42
int Team_GetNumberOfOwnedItems(entity team_ent)
Returns the number of items owned by a team.
Definition teamplay.qc:167
int Player_GetForcedTeamIndex(entity player)
Returns the index of the forced team of the given player.
Definition teamplay.qc:349
bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
Returns whether the team change to the specified team is allowed.
Definition teamplay.qc:1248
string autocvar_g_forced_team_yellow
Definition teamplay.qc:43
string autocvar_g_forced_team_pink
Definition teamplay.qc:44
int m_num_players
Number of players (both humans and bots) in a team.
Definition teamplay.qc:35
float Team_GetTeamScore(entity team_ent)
Returns the score of the team.
Definition teamplay.qc:104
bool Entity_HasValidTeam(entity this)
Returns whether the given entity belongs to a valid team.
Definition teamplay.qc:205
int TeamBalance_GetAllowedTeams(entity balance)
Returns the bitmask of allowed teams.
Definition teamplay.qc:612
int m_num_players_alive
Number of alive players in a team.
Definition teamplay.qc:38
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings.
Definition teamplay.qc:490
entity m_team_balance_team[NUM_TEAMS]
???
Definition teamplay.qc:32
bool TeamBalance_IsTeamAllowed(entity balance, int index)
Returns whether the team change to the specified team is allowed.
Definition teamplay.qc:809
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:1004
bool TeamBalance_AreEqual(entity ignore, bool would_leave)
Definition teamplay.qc:656
int Team_GetNumberOfAliveTeams()
Returns the number of alive teams.
Definition teamplay.qc:139
void KillPlayerForTeamChange(entity player)
Kills player as a result of team change.
Definition teamplay.qc:1234
void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
Sets the number of alive players in a team.
Definition teamplay.qc:119
entity Team_GetTeamFromIndex(int index)
Returns the global team entity at the given index.
Definition teamplay.qc:86
int TeamBalance_SizeDifference(entity ignore)
Returns the size difference between the largest and smallest team (bots included).
Definition teamplay.qc:633
float m_team_score
The score of the team.
Definition teamplay.qc:34
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:1182
void Player_SetForcedTeamIndex(entity player, int team_index)
Sets the index of the forced team of the given player.
Definition teamplay.qc:354
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:964
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:1274
entity TeamBalance_GetTeamFromIndex(entity balance, int index)
Returns the team entity of the team balance entity at the given index.
Definition teamplay.qc:1265
bool MoveToTeam(entity client, int team_index, int type)
Moves player to the specified team.
Definition teamplay.qc:330
int autocvar_g_balance_teams_remove_wait
Definition teamplay.qh:11
int autocvar_g_balance_teams_skill
Definition teamplay.qh:12
string autocvar_g_forced_team_otherwise
Definition teamplay.qh:16
bool autocvar_g_balance_teams_queue
Definition teamplay.qh:9
@ TEAM_FORCE_DEFAULT
Don't force any team.
Definition teamplay.qh:153
@ TEAM_FORCE_SPECTATOR
Force the player to spectator team.
Definition teamplay.qh:152
float autocvar_g_balance_teams_skill_unranked_factor
Definition teamplay.qh:13
int teamplay_bitmask
The set of currently available teams (AVAILABLE_TEAMS is the number of them).
Definition teamplay.qh:18
float server_skill_average
Scaled inverse-variance weighted mean of all clients' m_skill_mu, for unranked clients.
Definition teamplay.qh:21
@ TEAM_CHANGE_AUTO
The team was selected by autobalance.
Definition teamplay.qh:129
float autocvar_g_balance_teams_skill_significance_threshold
Definition teamplay.qh:14
@ TEAMS_COMPARE_GREATER
First team the greater than the second one.
Definition teamplay.qh:265
@ TEAMS_COMPARE_LESS
First team is less than the second one.
Definition teamplay.qh:263
@ TEAMS_COMPARE_EQUAL
Both teams are equal.
Definition teamplay.qh:264
bool lockteams
Definition teamplay.qh:20
string Static_Team_ColorName(int teamid)
Definition teams.qh:103
bool Team_IsValidTeam(int team_num)
Returns whether team value is valid.
Definition teams.qh:133
int Team_TeamToBit(int team_num)
Converts team value into bit value that is used in team bitmasks.
Definition teams.qh:199
bool Team_IsValidIndex(int index)
Returns whether the team index is valid.
Definition teams.qh:151
int Team_TeamToIndex(int team_num)
Converts team value into team index.
Definition teams.qh:184
int Team_IndexToTeam(int index)
Converts team index into team value.
Definition teams.qh:169
const int NUM_TEAMS
Max number of teams that could exist, prefer AVAILABLE_TEAMS.
Definition teams.qh:3
bool teamplay
Definition teams.qh:59
int Team_IndexToBit(int index)
Converts team index into bit value that is used in team bitmasks.
Definition teams.qh:211
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15