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