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 - team_ent.count; // noobs and bots
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
1052 int team_i_size = IS_REAL_CLIENT(player) ? team_i.m_num_players_net : team_i.m_num_players;
1053 int team_p_size = IS_REAL_CLIENT(player) ? team_p.m_num_players_net : team_p.m_num_players;
1054
1055 if ((!significant && team_i_size < team_p_size)
1056 || ((significant || team_i_size == team_p_size) && team_i_z > team_p_z))
1057 {
1058 team_bits = Team_IndexToBit(i);
1059 previous_team = i;
1060 }
1061 else if (team_i_z == team_p_z // unlikely with humans, normal with all bots
1062 && team_i_size == team_p_size)
1063 {
1064 team_bits |= Team_IndexToBit(i);
1065 previous_team = i;
1066 }
1067 continue;
1068 }
1069
1070 int compare = TeamBalance_CompareTeamsInternal(team_i, team_p, player, use_score);
1071 if (compare == TEAMS_COMPARE_LESS)
1072 {
1073 team_bits = Team_IndexToBit(i);
1074 previous_team = i;
1075 }
1076 else if (compare == TEAMS_COMPARE_EQUAL)
1077 {
1078 team_bits |= Team_IndexToBit(i);
1079 previous_team = i;
1080 }
1081 }
1082 return team_bits;
1083}
1084
1086{
1087 // checks disabled because we always want auto-balanced bots
1088 //if (!autocvar_g_balance_teams_prevent_imbalance)
1089 // return;
1090
1091 if (intermission_running) return;
1092
1095 int smallest_team_index = 0;
1096 int smallest_team_player_count = 0;
1097 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1098 {
1099 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1100 if (!TeamBalanceTeam_IsAllowed(team_))
1101 {
1102 continue;
1103 }
1104 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1105 if (smallest_team_index == 0)
1106 {
1107 smallest_team_index = i;
1108 smallest_team_player_count = playercount;
1109 }
1110 else if (playercount < smallest_team_player_count)
1111 {
1112 smallest_team_index = i;
1113 smallest_team_player_count = playercount;
1114 }
1115 }
1116 //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
1117 //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
1118 entity switchable_bot = NULL;
1119 int teams = BITS(AVAILABLE_TEAMS);
1120 while (teams != 0)
1121 {
1122 int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
1123 teams);
1124 if (smallest_team_index == largest_team_index)
1125 {
1126 TeamBalance_Destroy(balance);
1127 return;
1128 }
1129 entity largest_team = TeamBalance_GetTeamFromIndex(balance,
1130 largest_team_index);
1131 int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
1132 largest_team);
1133 if (largest_team_player_count - smallest_team_player_count < 2)
1134 {
1135 TeamBalance_Destroy(balance);
1136 return;
1137 }
1138 //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
1139 //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
1140 switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
1141 smallest_team_index, true);
1142 if (switchable_bot != NULL)
1143 {
1144 break;
1145 }
1146 teams &= ~Team_IndexToBit(largest_team_index);
1147 }
1148 TeamBalance_Destroy(balance);
1149 if (switchable_bot == NULL)
1150 {
1151 //PrintToChatAll("No bot found after searching through all the teams");
1152 return;
1153 }
1154 SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
1155}
1156
1158{
1159 int largest_team_index = 0;
1160 int largest_team_player_count = 0;
1161 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1162 {
1163 if (!(Team_IndexToBit(i) & teams))
1164 {
1165 continue;
1166 }
1167 entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
1168 if (!TeamBalanceTeam_IsAllowed(team_))
1169 {
1170 continue;
1171 }
1172 int playercount = TeamBalanceTeam_GetNumberOfPlayers(team_);
1173 if (largest_team_index == 0)
1174 {
1175 largest_team_index = i;
1176 largest_team_player_count = playercount;
1177 }
1178 else if (playercount > largest_team_player_count)
1179 {
1180 largest_team_index = i;
1181 largest_team_player_count = playercount;
1182 }
1183 }
1184 return largest_team_index;
1185}
1186
1188 int destination_team_index, bool is_bot)
1189{
1191 destination_team_index, is_bot))
1192 {
1193 return M_ARGV(3, entity);
1194 }
1195 entity lowest_player = NULL;
1196 float lowest_score = FLOAT_MAX;
1197 FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
1198 {
1199 if (IS_BOT_CLIENT(it) != is_bot)
1200 {
1201 continue;
1202 }
1203 float temp_score = PlayerScore_Get(it, SP_SCORE);
1204 if (temp_score >= lowest_score)
1205 {
1206 continue;
1207 }
1208 //PrintToChatAll(sprintf(
1209 // "Found %s with lowest score, checking allowed teams", it.netname));
1211 if (TeamBalance_IsTeamAllowed(balance, source_team_index))
1212 {
1213 //PrintToChatAll("Allowed");
1214 lowest_player = it;
1215 lowest_score = temp_score;
1216 }
1217 else
1218 {
1219 //PrintToChatAll("Not allowed");
1220 }
1221 TeamBalance_Destroy(balance);
1222 });
1223 return lowest_player;
1224}
1225
1226void LogTeamChange(float player_id, float team_number, int type)
1227{
1229 {
1230 return;
1231 }
1232 if (player_id < 1)
1233 {
1234 return;
1235 }
1236 GameLogEcho(sprintf(":team:%d:%d:%d", player_id, team_number, type));
1237}
1238
1240{
1241 if (IS_DEAD(player))
1242 {
1243 return;
1244 }
1245 if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
1246 {
1247 return;
1248 }
1249 Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
1250 player.origin, '0 0 0');
1251}
1252
1254{
1255 return index <= AVAILABLE_TEAMS
1256 && balance.m_team_balance_team[index - 1].m_num_players != TEAM_NOT_ALLOWED;
1257}
1258
1259void TeamBalance_BanTeamsExcept(entity balance, int index)
1260{
1261 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
1262 {
1263 if (i != index)
1264 {
1265 balance.m_team_balance_team[i - 1].m_num_players = TEAM_NOT_ALLOWED;
1266 }
1267 }
1268}
1269
1271{
1272 if (!Team_IsValidIndex(index))
1273 {
1274 LOG_FATALF("Index is invalid: %f", index);
1275 }
1276 return balance.m_team_balance_team[index - 1];
1277}
1278
1279entity TeamBalance_GetTeam(entity balance, int team_num)
1280{
1281 return TeamBalance_GetTeamFromIndex(balance, Team_TeamToIndex(team_num));
1282}
1283
1285{
1286 return team_ent && team_ent.m_num_players != TEAM_NOT_ALLOWED;
1287}
1288
1290{
1291 return team_ent.m_num_players;
1292}
1293
1295{
1296 return team_ent.m_num_bots;
1297}
1298
1299int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b, entity client, bool use_score)
1300{
1301 if (team_a == team_b)
1302 return TEAMS_COMPARE_EQUAL;
1303
1304 int team_a_size = IS_REAL_CLIENT(client) ? team_a.m_num_players_net : team_a.m_num_players;
1305 int team_b_size = IS_REAL_CLIENT(client) ? team_b.m_num_players_net : team_b.m_num_players;
1306
1307 if (team_a_size < team_b_size)
1308 return TEAMS_COMPARE_LESS;
1309 if (team_a_size > team_b_size)
1310 return TEAMS_COMPARE_GREATER;
1311 if (!use_score)
1312 return TEAMS_COMPARE_EQUAL;
1313
1314// Compare skill and score together as team strength.
1315 // Teams without players have 0 skill_mu but may have a score which must still apply.
1316 bool use_skill = team_a.m_skill_var && team_b.m_skill_var && autocvar_g_balance_teams_skill
1317 // z-score, <= threshold SDs means difference isn't significant, formula squared for perf
1318 && (((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);
1319 float team_a_strength = use_skill ? team_a.m_skill_mu : 1;
1320 float team_b_strength = use_skill ? team_b.m_skill_mu : 1;
1321 // Early in the match scores are ~random due to too few samples,
1322 // as the match progresses scores become more reliable indicators of team strength.
1323 // 0.96875 (31/32): if at halftime one team has score 0 and the other has 2 its skill is multiplied by 1.376,
1324 // < 1 so that a team with 1 point is stronger than one with 0 (but not by much as sample size is small).
1325 // Applying full score ratio to one team is equivalent here to applying half of it to each.
1327 {
1328 float timelimit_sec = autocvar_timelimit > 0 ? autocvar_timelimit * 60 : 20 * 60;
1329 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)
1330 * min(1, (time - game_starttime) / timelimit_sec)**1.5);
1331 }
1332
1333 if (team_a_strength < team_b_strength)
1334 return TEAMS_COMPARE_LESS;
1335 if (team_a_strength > team_b_strength)
1336 return TEAMS_COMPARE_GREATER;
1337 return TEAMS_COMPARE_EQUAL;
1338}
1339
1340void SV_ChangeTeam(entity player, int new_color)
1341{
1342 if (!teamplay)
1343 SetPlayerColors(player, new_color);
1344}
1345
1346bool QueueNeeded(entity client, int team_index)
1347{
1349 return false;
1350
1351 int human_clients = 0;
1353 {
1354 if (++human_clients > 1)
1355 {
1356 if (TeamBalance_AreEqual(client, true))
1357 return true;
1358 if (team_index <= 0)
1359 return false; // auto-select will join a smaller team
1360
1361 entity balance = TeamBalance_CheckAllowedTeams(client);
1362 TeamBalance_GetTeamCounts(balance, client);
1363 if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance, client, false)) == 0)
1364 {
1365 TeamBalance_Destroy(balance);
1366 return true;
1367 }
1368 TeamBalance_Destroy(balance);
1369 return false;
1370 }
1371 });
1372 return false;
1373}
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:1284
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:1085
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:1259
void TeamBalance_RemoveExcessPlayers(entity ignore)
Definition teamplay.qc:702
bool QueueNeeded(entity client, int team_index)
Definition teamplay.qc:1346
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
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:1299
void LogTeamChange(float player_id, float team_number, int type)
Definition teamplay.qc:1226
int TeamBalanceTeam_GetNumberOfPlayers(entity team_ent)
Returns the number of players (both humans and bots) in a team.
Definition teamplay.qc:1289
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:1157
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:1294
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:1253
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:1239
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:1187
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:1279
entity TeamBalance_GetTeamFromIndex(entity balance, int index)
Returns the team entity of the team balance entity at the given index.
Definition teamplay.qc:1270
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