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 {
297 if (!IS_DEAD(player))
298 Damage(player, player, player, INFINITY, DEATH_TEAMCHANGE.m_id, DMG_NOWEP, player.origin, '0 0 0');
299 PlayerScore_Clear(player);
300
301 if (!IS_BOT_CLIENT(player))
303
304 if (team_index != -1)
305 {
306 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
307 player.team_selected = team_index; // no autoselect in Join()
308
309 if (warmup_stage)
310 ReadyCount(); // teams might be balanced now
311 }
312 }
313
314 if (team_index == -1)
315 {
316 if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
317 {
318 // this done here so it happens even when manually speccing during the countdown
319 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
320 CS(player).idlekick_lasttimeleft = 0;
321 }
322 else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
323 {
324 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
325 }
326 }
327
328 return true;
329}
330
331bool MoveToTeam(entity client, int team_index, int type)
332{
333 //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
334 int lockteams_backup = lockteams; // backup any team lock
335 lockteams = 0; // disable locked teams
336 return = SetPlayerTeam(client, team_index, type);
337 lockteams = lockteams_backup; // restore the team lock
338}
339
344
345void Player_SetForcedTeamIndex(entity player, int team_index)
346{
347 switch (team_index)
348 {
351 {
352 player.team_forced = team_index;
353 break;
354 }
355 default:
356 {
357 if (!Team_IsValidIndex(team_index))
358 {
359 LOG_FATAL("Invalid team index.");
360 }
361 else
362 {
363 player.team_forced = team_index;
364 break;
365 }
366 }
367 }
368}
369
371{
372 // NB: this is intended to work in !teamplay too
373
374 if (player.team_forced != TEAM_FORCE_DEFAULT)
375 return player.team_forced; // internally forced spec set by minigames etc wins
376
378 {
379 if (IS_REAL_CLIENT(player)) // only players, not bots
380 {
383 else
384 return TEAM_FORCE_DEFAULT;
385 }
386 }
388 return 1;
390 return 2;
392 return 3;
394 return 4;
395 else
396 {
398 {
399 case "red":
400 return 1;
401 case "blue":
402 return 2;
403 case "yellow":
404 return 3;
405 case "pink":
406 return 4;
407 case "spectate":
408 case "spectator":
410 }
411 }
412 return TEAM_FORCE_DEFAULT;
413}
414
416{
417 //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
418 entity balance = TeamBalance_CheckAllowedTeams(player);
419 int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
420 TeamBalance_Destroy(balance);
421 SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO);
422}
423
425{
426 entity balance = spawn();
427 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
428 {
429 entity team_ent = balance.m_team_balance_team[i] = spawn();
430 team_ent.m_team_score = g_team_entities[i].m_team_score;
431 team_ent.m_num_players = teamplay_bitmask & BIT(i) ? 0 : TEAM_NOT_ALLOWED;
432 }
434 balance.nextthink = time;
435
436 // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
437 if (autocvar_bot_vs_human && AVAILABLE_TEAMS == 2 && for_whom)
438 {
439 if (autocvar_bot_vs_human > 0)
440 {
441 // find last team available
442 if (IS_BOT_CLIENT(for_whom))
443 {
444 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
445 {
446 TeamBalance_BanTeamsExcept(balance, 4);
447 }
448 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
449 {
450 TeamBalance_BanTeamsExcept(balance, 3);
451 }
452 else
453 {
454 TeamBalance_BanTeamsExcept(balance, 2);
455 }
456 // no further cases, we know at least 2 teams exist
457 }
458 else
459 {
460 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
461 {
462 TeamBalance_BanTeamsExcept(balance, 1);
463 }
464 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
465 {
466 TeamBalance_BanTeamsExcept(balance, 2);
467 }
468 else
469 {
470 TeamBalance_BanTeamsExcept(balance, 3);
471 }
472 // no further cases, bots have one of the teams
473 }
474 }
475 else
476 {
477 // find first team available
478 if (IS_BOT_CLIENT(for_whom))
479 {
480 if (TeamBalance_IsTeamAllowedInternal(balance, 1))
481 {
482 TeamBalance_BanTeamsExcept(balance, 1);
483 }
484 else if (TeamBalance_IsTeamAllowedInternal(balance, 2))
485 {
486 TeamBalance_BanTeamsExcept(balance, 2);
487 }
488 else
489 {
490 TeamBalance_BanTeamsExcept(balance, 3);
491 }
492 // no further cases, we know at least 2 teams exist
493 }
494 else
495 {
496 if (TeamBalance_IsTeamAllowedInternal(balance, 4))
497 {
498 TeamBalance_BanTeamsExcept(balance, 4);
499 }
500 else if (TeamBalance_IsTeamAllowedInternal(balance, 3))
501 {
502 TeamBalance_BanTeamsExcept(balance, 3);
503 }
504 else
505 {
506 TeamBalance_BanTeamsExcept(balance, 2);
507 }
508 // no further cases, bots have one of the teams
509 }
510 }
511 }
512
513 if (!for_whom)
514 {
515 balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
516 return balance;
517 }
518
519 // if player has a forced team, ONLY allow that one
520 int i = Player_GetForcedTeamIndex(for_whom);
521 if (i != TEAM_FORCE_DEFAULT)
522 TeamBalance_BanTeamsExcept(balance, i);
523
524 balance.m_team_balance_state = TEAM_BALANCE_TEAMS_CHECKED;
525 return balance;
526}
527
529{
530 if (balance == NULL)
531 {
532 return;
533 }
534 for (int i = 0; i < AVAILABLE_TEAMS; ++i)
535 {
536 delete(balance.(m_team_balance_team[i]));
537 }
538 delete(balance);
539}
540
542{
543 if (balance == NULL)
544 {
545 LOG_FATAL("Team balance entity is NULL.");
546 }
547 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
548 {
549 LOG_FATAL("Team balance entity is not initialized.");
550 }
551 int result = 0;
552 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
553 {
554 if (TeamBalance_IsTeamAllowedInternal(balance, i))
555 {
557 }
558 }
559 return result;
560}
561
563{
564 if (!teamplay)
565 return 0;
566
567 entity balance = TeamBalance_CheckAllowedTeams(ignore);
568 TeamBalance_GetTeamCounts(balance, ignore);
569
570 int ts_min = 255, ts_max = 0;
571 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
572 {
573 int ts = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
574 if (ts_min > ts)
575 ts_min = ts;
576 if (ts_max < ts)
577 ts_max = ts;
578 }
579
580 TeamBalance_Destroy(balance);
581
582 return ts_max - ts_min;
583}
584
585bool TeamBalance_AreEqual(entity ignore, bool would_leave)
586{
587 entity balance = TeamBalance_CheckAllowedTeams(ignore);
588 TeamBalance_GetTeamCounts(balance, ignore);
589
590 int prev_size = 0, i = 1;
591
592 for (; i <= AVAILABLE_TEAMS; ++i)
593 {
594 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
595 int team_size = would_leave ? team_ent.m_num_players_net : team_ent.m_num_players;
596 if (i > 1 && team_size != prev_size)
597 break;
598 prev_size = team_size;
599 }
600
601 TeamBalance_Destroy(balance);
602 return i > AVAILABLE_TEAMS;
603}
604
607{
608 if(this.lifetime <= 0 || TeamBalance_AreEqual(NULL, false))
609 {
610 if(this.lifetime <= 0)
611 {
612 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_REMOVE, playername(remove_countdown.enemy.netname, remove_countdown.enemy.team, true));
613 PutObserverInServer(remove_countdown.enemy, true, true);
614 }
615
616 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_REMOVE);
617
618 delete(this);
620
621 TeamBalance_RemoveExcessPlayers(NULL); // Check again for excess players in case someone also left while in countdown
622 return;
623 }
624
625 --this.lifetime;
626 this.nextthink = time + 1;
627}
628
629// FIXME: support more than 2 teams, the notification will be... awkward
630// FIXME: also don't kick the fc/bc/kc lol
632{
633 if(AVAILABLE_TEAMS != 2 || autocvar_g_campaign) return;
634
635 entity balance = TeamBalance_CheckAllowedTeams(ignore);
636 TeamBalance_GetTeamCounts(balance, ignore);
637
638 int min = 0;
639
640 for(int i = 1; i <= AVAILABLE_TEAMS; ++i)
641 {
642 int cur = TeamBalance_GetTeamFromIndex(balance, i).m_num_players;
643 if(i == 1 || cur < min)
644 min = cur;
645 }
646
647 for(int tmi = 1; tmi <= AVAILABLE_TEAMS; ++tmi)
648 {
649 int cur = TeamBalance_GetTeamFromIndex(balance, tmi).m_num_players;
650 if(cur > 0 && cur > min) // If this team has excess players
651 {
652 // Get newest player
653 int latest_join = 0;
654 entity latest_join_pl = NULL;
655
657 if(it.team == Team_IndexToTeam(tmi) && CS(it).startplaytime > latest_join)
658 {
659 latest_join = CS(it).startplaytime;
660 latest_join_pl = it;
661 }
662 });
663
664 // Force player to spectate
665 if(latest_join_pl)
666 {
667 // Send player to spectate
669 {
670 // Give a warning before moving to spect
671 if (!remove_countdown)
672 {
675 remove_countdown.nextthink = time;
676 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);
677 }
678 remove_countdown.enemy = latest_join_pl;
680 }
681 else
682 {
683 // Move to spects immediately
684 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_REMOVE, latest_join_pl.netname);
685 PutObserverInServer(latest_join_pl, true, true);
686 }
687 }
688 }
689 }
690
691 TeamBalance_Destroy(balance);
692}
693
695{
696 if (!teamplay)
697 return true;
698
699 int j, teamplayers_deficit = 0, teamplayers_max = 0;
700 entity it, balance = TeamBalance_CheckAllowedTeams(ignore);
701 TeamBalance_GetTeamCounts(balance, ignore);
702
703 for (int j = 1; j <= AVAILABLE_TEAMS; ++j)
704 {
705 it = TeamBalance_GetTeamFromIndex(balance, j);
706 // find the largest team size
707 if (it.m_num_players_net > teamplayers_max)
708 teamplayers_max = it.m_num_players_net;
709 }
710 // find how many players we'd need to join to achieve balanced numbers that way
711 for (j = 1; j <= AVAILABLE_TEAMS; ++j)
712 teamplayers_deficit += teamplayers_max - TeamBalance_GetTeamFromIndex(balance, j).m_num_players_net;
713
714 // first pass: find clients(s) who want to play on a specific team
715 for (j = 1; teamplayers_deficit > 0 && j <= maxclients; ++j)
716 {
717 it = ftoe(j);
718 if (it.wants_join <= 0 || it == ignore) continue;
719 if (TeamBalance_GetTeamFromIndex(balance, it.wants_join).m_num_players_net >= teamplayers_max) continue;
720 Join(it, false);
721 ++TeamBalance_GetTeam(balance, it.team).m_num_players_net;
722 --teamplayers_deficit;
723 }
724
725 // second pass: find clients(s) who want to play on any team
726 for (j = 1; teamplayers_deficit > 0 && j <= maxclients; ++j)
727 {
728 it = ftoe(j);
729 if (it.wants_join >= 0 || it == ignore) continue;
730 Join(it, false);
731 --teamplayers_deficit; // don't need to update .m_num_players_net as we won't read it again
732 }
733
734 TeamBalance_Destroy(balance);
735 return teamplayers_deficit <= 0; // return true if teams are now balanced
736}
737
738bool TeamBalance_IsTeamAllowed(entity balance, int index)
739{
740 if (balance == NULL)
741 {
742 LOG_FATAL("Team balance entity is NULL.");
743 }
744 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
745 {
746 LOG_FATAL("Team balance entity is not initialized.");
747 }
748 if (!Team_IsValidIndex(index))
749 {
750 LOG_FATALF("Team index is invalid: %f",
751 index);
752 }
753 return TeamBalance_IsTeamAllowedInternal(balance, index);
754}
755
756// maybe include qcsrc/server/clientkill.qh instead of declaring this?
759{
760 if (balance == NULL)
761 LOG_FATAL("Team balance entity is NULL.");
762 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
763 LOG_FATAL("Team balance entity is not initialized.");
764
766 {
767 // Mutator has overriden the configuration.
768 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
769 {
770 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
771 if (TeamBalanceTeam_IsAllowed(team_ent))
772 {
773 MUTATOR_CALLHOOK(TeamBalance_GetTeamCount, i, ignore);
774 team_ent.m_num_players = M_ARGV(2, float);
775 team_ent.m_num_bots = M_ARGV(3, float);
776 }
777 }
778 }
779 else
780 {
781 float skill_weight = 0, skill_weight_sum = 0;
783 // Manually count all players.
784 FOREACH_CLIENT(true,
785 {
786 if (it.m_skill_mu && it.m_skill_var)
787 {
788 skill_weight = 1 / it.m_skill_var;
789 server_skill_average += it.m_skill_mu * skill_weight;
790 skill_weight_sum += skill_weight;
791 }
792
793 if (it == ignore)
794 continue;
795
796 int team_num = it.killindicator_teamchange > 0 ? it.killindicator_teamchange : it.team;
797 if (team_num <= 0)
798 {
799 int forced_team_index = Player_GetForcedTeamIndex(it);
800 if (forced_team_index > TEAM_FORCE_DEFAULT) // Do we really need this? Probably not.
801 team_num = Team_IndexToTeam(forced_team_index); // reserve the spot
802 else
803 continue;
804 }
805 if (!Team_IsValidTeam(team_num))
806 continue;
807
808 entity team_ent = TeamBalance_GetTeam(balance, team_num);
809 if (!TeamBalanceTeam_IsAllowed(team_ent))
810 continue;
811
812 team_ent.m_num_players_net = ++team_ent.m_num_players;
813 if (IS_BOT_CLIENT(it))
814 ++team_ent.m_num_bots;
815
816 if (it.m_skill_mu && it.m_skill_var)
817 {
818 // skill_weight calculated above
819 team_ent.m_skill_mu += it.m_skill_mu * skill_weight;
820 team_ent.mass += skill_weight;
821 ++team_ent.count;
822 }
823 });
824
825 // Deduct bots that would leave if a player joined their team;
826 // to ensure a player can join we aim to deduct at least one from each team which has some.
827 for (int i = 1, bots_maxperteam = ceil(bots_would_leave / AVAILABLE_TEAMS); i <= AVAILABLE_TEAMS; ++i)
828 {
829 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
830 team_ent.m_num_players_net -= min(team_ent.m_num_bots, bots_maxperteam);
831 }
832
833 // Calculate inverse-variance weighted mean skill ratings,
834 // for each unranked client use a fraction of the mean of all clients' skill ratings.
837 : 1000;
838 float server_skill_average_var = 0; skill_weight = 0; // will only be calculated once, if needed
839 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
840 {
841 entity team_ent = TeamBalance_GetTeamFromIndex(balance, i);
842 int unranked_clients = team_ent.m_num_players - team_ent.count; // noobs and bots
843 if (unranked_clients)
844 {
845 if (!server_skill_average_var)
846 {
847 server_skill_average_var = (server_skill_average * 0.25)**2;
848 skill_weight = 1 / server_skill_average_var;
849 }
850 team_ent.m_skill_mu += (server_skill_average * skill_weight) * unranked_clients;
851 team_ent.mass += skill_weight * unranked_clients;
852 }
853 if (team_ent.mass)
854 {
855 // .mass is storing the arithmetic sum of members' inverse-variance weightings
856 // Hence the team's mean is: team_ent.m_skill_mu / team_ent.mass
857 // Hence the team's variance is: 1 / team_ent.mass
858 team_ent.m_skill_mu /= team_ent.mass;
859 team_ent.m_skill_var = 1 / team_ent.mass;
860 }
861 }
862 }
863
864 // if the player who has a forced team has not joined yet, reserve the spot
867 {
869 if (team_ent.m_num_players == team_ent.m_num_bots)
870 ++team_ent.m_num_players;
871 }
872 balance.m_team_balance_state = TEAM_BALANCE_TEAM_COUNTS_FILLED;
873}
874
876{
877 if (balance == NULL)
878 {
879 LOG_FATAL("Team balance entity is NULL.");
880 }
881 if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
882 {
883 LOG_FATAL("TeamBalance_GetTeamCounts has not been called.");
884 }
885 if (!Team_IsValidIndex(index))
886 {
887 LOG_FATALF("Team index is invalid: %f", index);
888 }
889 return balance.m_team_balance_team[index - 1].m_num_players;
890}
891
892int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player)
893{
894 if (balance == NULL)
895 {
896 LOG_FATAL("Team balance entity is NULL.");
897 }
898 if (balance.m_team_balance_state == TEAM_BALANCE_UNINITIALIZED)
899 {
900 LOG_FATAL("Team balance entity is not initialized.");
901 }
902
903 // count how many players are in each team
904 TeamBalance_GetTeamCounts(balance, ignore_player ? player : NULL);
905
906 int team_bits = TeamBalance_FindBestTeams(balance, player, true);
907 if (team_bits == 0)
908 {
909 LOG_WARNF("BUG or MISCONFIGURATION: No teams available for %s\n", player.netname);
910 return -1; // only spec
911 }
912
913 // don't punish players for UI mistakes by changing teams randomly when already on a best team
914 if (player.team > 0 && (team_bits & Team_TeamToBit(player.team)))
915 return Team_TeamToIndex(player.team);
916
918 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
919 {
920 if (team_bits & Team_IndexToBit(i))
921 {
923 }
924 }
926}
927
928int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
929{
930 if (balance == NULL)
931 LOG_FATAL("Team balance entity is NULL.");
932 if (balance.m_team_balance_state != TEAM_BALANCE_TEAM_COUNTS_FILLED)
933 LOG_FATAL("TeamBalance_GetTeamCounts has not been called.");
934
935 if (MUTATOR_CALLHOOK(TeamBalance_FindBestTeams, player) == true)
936 return M_ARGV(1, float);
937
938 int team_bits = 0;
939 int previous_team = 0;
940 float player_skill_mu = 0, player_skill_var = 0;
941 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
942 {
943 if (!TeamBalance_IsTeamAllowedInternal(balance, i))
944 continue;
945 if (previous_team == 0)
946 {
947 team_bits = Team_IndexToBit(i);
948 previous_team = i;
949 continue;
950 }
951
952 entity team_i = TeamBalance_GetTeamFromIndex(balance, i);
953 entity team_p = TeamBalance_GetTeamFromIndex(balance, previous_team);
954
955 // During warmup the "best" team isn't the smallest or weakest one,
956 // that method puts all the noobs on the same team when players join in the "wrong" order.
957 // This picks the team(s) whose weighted mean skill differs the most from the joining player's skill,
958 // unless they're all close enough in which case it picks smaller team(s).
959 // Warmup-only as it must unbalance team sizes in extreme cases, swaps needed should be minimal.
961 {
962 if (!player_skill_mu)
963 {
964 player_skill_mu = player.m_skill_mu ? player.m_skill_mu : server_skill_average;
965 player_skill_var = player.m_skill_var ? player.m_skill_var : (server_skill_average * 0.25)**2;
966 }
967 // To determine significance, we'll use a z-score of the inverse-variance weightings
968 // with an adjustable threshold
969 float team_i_z = (team_i.m_skill_mu - player_skill_mu) / sqrt(team_i.m_skill_var + player_skill_var);
970 float team_p_z = (team_p.m_skill_mu - player_skill_mu) / sqrt(team_p.m_skill_var + player_skill_var);
971 bool significant = fabs(team_i_z - team_p_z) > autocvar_g_balance_teams_skill_significance_threshold;
972 // Do fabs afterwards, since the player can be in the middle of the teams (one z +ve, other -ve)
973 team_i_z = fabs(team_i_z);
974 team_p_z = fabs(team_p_z);
975
976 // NB: if bots were to use this code they'd need .m_num_players instead of .m_num_players_net
977 if ((!significant && team_i.m_num_players_net < team_p.m_num_players_net)
978 || ((significant || team_i.m_num_players_net == team_p.m_num_players_net) && team_i_z > team_p_z))
979 {
980 team_bits = Team_IndexToBit(i);
981 previous_team = i;
982 }
983 else if (team_i_z == team_p_z // unlikely with humans, normal with all bots
984 && team_i.m_num_players_net == team_p.m_num_players_net)
985 {
986 team_bits |= Team_IndexToBit(i);
987 previous_team = i;
988 }
989 continue;
990 }
991
992 int compare = TeamBalance_CompareTeamsInternal(team_i, team_p, player, use_score);
993 if (compare == TEAMS_COMPARE_LESS)
994 {
995 team_bits = Team_IndexToBit(i);
996 previous_team = i;
997 }
998 else if (compare == TEAMS_COMPARE_EQUAL)
999 {
1000 team_bits |= Team_IndexToBit(i);
1001 previous_team = i;
1002 }
1003 }
1004 return team_bits;
1005}
1006
1008{
1009 // checks disabled because we always want auto-balanced bots
1010 //if (!autocvar_g_balance_teams_prevent_imbalance)
1011 // return;
1012
1013 if (intermission_running) return;
1014
1017 int smallest_team_index = 0;
1018 int smallest_team_player_count = 0;
1019 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1020 {
1021 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1022 if (!TeamBalanceTeam_IsAllowed(team_))
1023 {
1024 continue;
1025 }
1026 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1027 if (smallest_team_index == 0)
1028 {
1029 smallest_team_index = i;
1030 smallest_team_player_count = playercount;
1031 }
1032 else if (playercount < smallest_team_player_count)
1033 {
1034 smallest_team_index = i;
1035 smallest_team_player_count = playercount;
1036 }
1037 }
1038 //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
1039 //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
1040 entity switchable_bot = NULL;
1041 int teams = BITS(AVAILABLE_TEAMS);
1042 while (teams != 0)
1043 {
1044 int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
1045 teams);
1046 if (smallest_team_index == largest_team_index)
1047 {
1048 TeamBalance_Destroy(balance);
1049 return;
1050 }
1051 entity largest_team = TeamBalance_GetTeamFromIndex(balance,
1052 largest_team_index);
1053 int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
1054 largest_team);
1055 if (largest_team_player_count - smallest_team_player_count < 2)
1056 {
1057 TeamBalance_Destroy(balance);
1058 return;
1059 }
1060 //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
1061 //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
1062 switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
1063 smallest_team_index, true);
1064 if (switchable_bot != NULL)
1065 {
1066 break;
1067 }
1068 teams &= ~Team_IndexToBit(largest_team_index);
1069 }
1070 TeamBalance_Destroy(balance);
1071 if (switchable_bot == NULL)
1072 {
1073 //PrintToChatAll("No bot found after searching through all the teams");
1074 return;
1075 }
1076 SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
1077}
1078
1080{
1081 int largest_team_index = 0;
1082 int largest_team_player_count = 0;
1083 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1084 {
1085 if (!(Team_IndexToBit(i) & teams))
1086 {
1087 continue;
1088 }
1089 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1090 if (!TeamBalanceTeam_IsAllowed(team_))
1091 {
1092 continue;
1093 }
1094 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1095 if (largest_team_index == 0)
1096 {
1097 largest_team_index = i;
1098 largest_team_player_count = playercount;
1099 }
1100 else if (playercount > largest_team_player_count)
1101 {
1102 largest_team_index = i;
1103 largest_team_player_count = playercount;
1104 }
1105 }
1106 return largest_team_index;
1107}
1108
1110 int destination_team_index, bool is_bot)
1111{
1113 destination_team_index, is_bot))
1114 {
1115 return M_ARGV(3, entity);
1116 }
1117 entity lowest_player = NULL;
1118 float lowest_score = FLOAT_MAX;
1119 FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
1120 {
1121 if (IS_BOT_CLIENT(it) != is_bot)
1122 {
1123 continue;
1124 }
1125 float temp_score = PlayerScore_Get(it, SP_SCORE);
1126 if (temp_score >= lowest_score)
1127 {
1128 continue;
1129 }
1130 //PrintToChatAll(sprintf(
1131 // "Found %s with lowest score, checking allowed teams", it.netname));
1133 if (TeamBalance_IsTeamAllowed(balance, source_team_index))
1134 {
1135 //PrintToChatAll("Allowed");
1136 lowest_player = it;
1137 lowest_score = temp_score;
1138 }
1139 else
1140 {
1141 //PrintToChatAll("Not allowed");
1142 }
1143 TeamBalance_Destroy(balance);
1144 });
1145 return lowest_player;
1146}
1147
1148void LogTeamChange(float player_id, float team_number, int type)
1149{
1151 {
1152 return;
1153 }
1154 if (player_id < 1)
1155 {
1156 return;
1157 }
1158 GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
1159}
1160
1162{
1163 return index <= AVAILABLE_TEAMS
1164 && balance.m_team_balance_team[index - 1].m_num_players != TEAM_NOT_ALLOWED;
1165}
1166
1167void TeamBalance_BanTeamsExcept(entity balance, int index)
1168{
1169 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1170 {
1171 if (i != index)
1172 {
1173 balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
1174 }
1175 }
1176}
1177
1179{
1180 if (!Team_IsValidIndex(index))
1181 {
1182 LOG_FATALF("Index is invalid: %f", index);
1183 }
1184 return balance.m_team_balance_team[index - 1];
1185}
1186
1187entity TeamBalance_GetTeam(entity balance, int team_num)
1188{
1189 return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
1190}
1191
1193{
1194 return team_ent && team_ent.m_num_players != TEAM_NOT_ALLOWED;
1195}
1196
1198{
1199 return team_ent.m_num_players;
1200}
1201
1203{
1204 return team_ent.m_num_bots;
1205}
1206
1207int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, entity client, bool use_score)
1208{
1209 if (team_a == team_b)
1210 return TEAMS_COMPARE_EQUAL;
1211
1212 int team_a_size = IS_REAL_CLIENT(client) ? team_a.m_num_players_net : team_a.m_num_players;
1213 int team_b_size = IS_REAL_CLIENT(client) ? team_b.m_num_players_net : team_b.m_num_players;
1214
1215 if (team_a_size < team_b_size)
1216 return TEAMS_COMPARE_LESS;
1217 if (team_a_size > team_b_size)
1218 return TEAMS_COMPARE_GREATER;
1219 if (!use_score || !team_a_size)
1220 return TEAMS_COMPARE_EQUAL;
1221
1222// Compare skill and score together as team strength.
1223 // Teams without players have 0 skill_mu but may have a score which must still apply.
1224 bool use_skill = team_a.m_skill_var && team_b.m_skill_var && autocvar_g_balance_teams_skill
1225 // z-score, <= threshold SDs means difference isn't significant, formula squared for perf
1226 && (((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);
1227 float team_a_strength = use_skill ? team_a.m_skill_mu : 1;
1228 float team_b_strength = use_skill ? team_b.m_skill_mu : 1;
1229 // Early in the match scores are ~random due to too few samples,
1230 // as the match progresses scores become more reliable indicators of team strength.
1231 // 0.96875 (31/32): if at halftime one team has score 0 and the other has 2 its skill is multiplied by 1.376,
1232 // < 1 so that a team with 1 point is stronger than one with 0 (but not by much as sample size is small).
1233 // Applying full score ratio to one team is equivalent here to applying half of it to each.
1235 {
1236 float timelimit_sec = autocvar_timelimit > 0 ? autocvar_timelimit * 60 : 20 * 60;
1237 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)
1238 * min(1, (time - game_starttime) / timelimit_sec)**1.5);
1239 }
1240
1241 if (team_a_strength < team_b_strength)
1242 return TEAMS_COMPARE_LESS;
1243 if (team_a_strength > team_b_strength)
1244 return TEAMS_COMPARE_GREATER;
1245 return TEAMS_COMPARE_EQUAL;
1246}
1247
1248void SV_ChangeTeam(entity player, int new_color)
1249{
1250 if (!teamplay)
1251 SetPlayerColors(player, new_color);
1252}
1253
1254bool QueueNeeded(entity client, int team_index)
1255{
1257 return false;
1258
1259 int human_clients = 0;
1261 {
1262 if (++human_clients > 1)
1263 {
1264 if (TeamBalance_AreEqual(client, true))
1265 return true;
1266 if (team_index <= 0)
1267 return false; // auto-select will join a smaller team
1268
1269 entity balance = TeamBalance_CheckAllowedTeams(client);
1270 TeamBalance_GetTeamCounts(balance, client);
1271 if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance, client, false)) == 0)
1272 {
1273 TeamBalance_Destroy(balance);
1274 return true;
1275 }
1276 TeamBalance_Destroy(balance);
1277 return false;
1278 }
1279 });
1280 return false;
1281}
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:2190
const int FRAGS_SPECTATOR
engine expects this value
Definition constants.qh:5
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:484
#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
bool intermission_running
#define SV_ChangeTeam
Definition _all.inc:280
#define LOG_WARNF(...)
Definition log.qh:59
#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
#define INFINITY
Definition mathlib.qh:105
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:698
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:696
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:1049
void Join(entity this, bool queued_join)
it's assumed this isn't called for bots (campaign_bots_may_start, centreprints)
Definition client.qc:2075
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:1192
entity remove_countdown
Definition teamplay.qc:605
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:694
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:1007
void TeamBalance_Destroy(entity balance)
Destroy the team balance entity.
Definition teamplay.qc:528
void TeamBalance_BanTeamsExcept(entity balance, int index)
Bans team change to all teams except the given one.
Definition teamplay.qc:1167
void TeamBalance_RemoveExcessPlayers(entity ignore)
Definition teamplay.qc:631
bool QueueNeeded(entity client, int team_index)
Definition teamplay.qc:1254
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 TeamBalance_JoinBestTeam(entity player)
Assigns the given player to a team that will make the game most balanced.
Definition teamplay.qc:415
bool Player_HasRealForcedTeam(entity player)
Returns whether player has real forced team.
Definition teamplay.qc:340
int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, entity client, bool use_score)
Compares two teams for the purposes of game balance.
Definition teamplay.qc:1207
void LogTeamChange(float player_id, float team_number, int type)
Definition teamplay.qc:1148
int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
Returns the number of players (both humans and bots) in a team.
Definition teamplay.qc:1197
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:875
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:1079
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:1202
int Team_GetNumberOfTeamsWithOwnedItems()
Returns the number of teams that own items.
Definition teamplay.qc:177
void Remove_Countdown(entity this)
Definition teamplay.qc:606
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:758
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:370
bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
Returns whether the team change to the specified team is allowed.
Definition teamplay.qc:1161
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:541
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:424
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:738
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:928
bool TeamBalance_AreEqual(entity ignore, bool would_leave)
Definition teamplay.qc:585
int Team_GetNumberOfAliveTeams()
Returns the number of alive teams.
Definition teamplay.qc:139
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:562
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:1109
void Player_SetForcedTeamIndex(entity player, int team_index)
Sets the index of the forced team of the given player.
Definition teamplay.qc:345
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:892
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:1187
entity TeamBalance_GetTeamFromIndex(entity balance, int index)
Returns the team entity of the team balance entity at the given index.
Definition teamplay.qc:1178
bool MoveToTeam(entity client, int team_index, int type)
Moves player to the specified team.
Definition teamplay.qc:331
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:261
@ TEAMS_COMPARE_LESS
First team is less than the second one.
Definition teamplay.qh:259
@ TEAMS_COMPARE_EQUAL
Both teams are equal.
Definition teamplay.qh:260
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