]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/teamplay.qc
some scores updates; route even more through them; fix team change and connect
[divverent/nexuiz.git] / data / qcsrc / server / teamplay.qc
1 string cache_motd;
2 string cache_mutatormsg;
3 string cache_lastmotd;
4 string cache_lastmutatormsg;
5
6 // client counts for each team
7 float c1, c2, c3, c4;
8 // # of bots on those teams
9 float cb1, cb2, cb3, cb4;
10
11 float audit_teams_time;
12
13 float IsTeamBalanceForced()
14 {
15         if(intermission_running)
16                 return 0; // no rebalancing whatsoever please
17         if(!cvar("teamplay"))
18                 return 0;
19         if(cvar("g_campaign"))
20                 return 0;
21         if(!cvar("g_balance_teams_force"))
22                 return -1;
23         return 1;
24 }
25
26 void TeamchangeFrags(entity e)
27 {
28 #ifndef CSQCSCORES
29         if(g_ctf)
30         {
31                 e.captures = 0;
32         }
33         if(e.classname == "player")
34         {
35                 // reduce frags during a team change
36                 DistributeFragsAmongTeam(e, (e.clientcolors & 0x0F) + 1, 1 - cvar("g_changeteam_fragtransfer") / 100);
37         }
38 #endif
39         PlayerScore_Clear(e);
40 }
41
42 vector TeamColor(float teem)
43 {
44         switch(teem)
45         {
46                 case COLOR_TEAM1:
47                         return '1 0.0625 0.0625';
48                 case COLOR_TEAM2:
49                         return '0.0625 0.0625 1';
50                 case COLOR_TEAM3:
51                         return '1 1 0.0625';
52                 case COLOR_TEAM4:
53                         return '1 0.0625 1';
54                 default:
55                         return '1 1 1';
56         }
57 }
58
59 string TeamName(float t)
60 {
61         return strcat(Team_ColorName(t), " Team");
62 }
63 string ColoredTeamName(float t)
64 {
65         return strcat(Team_ColorCode(t), Team_ColorName(t), " Team^7");
66 }
67 string TeamNoName(float t)
68 {
69         // fixme: Search for team entities and get their .netname's!
70         if(t == 1)
71                 return "Red Team";
72         if(t == 2)
73                 return "Blue Team";
74         if(t == 3)
75                 return "Yellow Team";
76         if(t == 4)
77                 return "Pink Team";
78         return "Neutral Team";
79 }
80
81 void dom_init();
82 void ctf_init();
83 void runematch_init();
84 void tdm_init();
85 void entcs_init();
86
87 void LogTeamchange(entity pl)
88 {
89         string str;
90         if(!cvar("sv_eventlog"))
91                 return;
92         if(pl.playerid < 1)
93                 return;
94         str = strcat(":team:", ftos(pl.playerid), ":");
95         str = strcat(str, ftos(pl.team));
96         GameLogEcho(str, FALSE);
97 }
98
99 void ResetGameCvars()
100 {
101         cvar_set("g_dm", "0");
102         cvar_set("g_tdm", "0");
103         cvar_set("g_domination", "0");
104         cvar_set("g_ctf", "0");
105         cvar_set("g_runematch", "0");
106         cvar_set("g_lms", "0");
107         cvar_set("g_arena", "0");
108         cvar_set("g_keyhunt", "0");
109         cvar_set("g_assault", "0");
110         cvar_set("g_onslaught", "0");
111         cvar_set("teamplay", "0");
112 }
113
114 void ActivateTeamplay()
115 {
116         float teamplay_default;
117         teamplay_default = cvar("teamplay_default");
118
119         if(teamplay_default)
120                 cvar_set("teamplay", ftos(teamplay_default));
121         else
122                 cvar_set("teamplay", "3");
123 }
124
125 void InitGameplayMode()
126 {
127         float fraglimit_override, timelimit_override, capturelimit_override;
128
129         VoteReset();
130
131         game = cvar("gamecfg"); // load game options
132
133         // game cvars get reset before map changes
134         // then map's cfg sets them as desired
135
136         // FIXME: also set a message or game mode name to print to players when the join
137
138         // set both here, gamemode can override it later
139         timelimit_override = cvar("timelimit_override");
140         fraglimit_override = cvar("fraglimit_override");
141         capturelimit_override = cvar("capturelimit_override");
142
143         if(game == GAME_DOMINATION || cvar("g_domination"))
144         {
145                 ResetGameCvars();
146                 game = GAME_DOMINATION;
147                 cvar_set("g_domination", "1");
148
149                 ActivateTeamplay();
150
151                 fraglimit_override = cvar("g_domination_point_limit");
152
153                 gamemode_name = "Domination";
154                 teams_matter = 1;
155         }
156         else if(game == GAME_CTF || cvar("g_ctf"))
157         {
158                 ResetGameCvars();
159                 game = GAME_CTF;
160                 cvar_set("g_ctf", "1");
161
162                 ActivateTeamplay();
163
164                 fraglimit_override = cvar("g_ctf_capture_limit");
165                 //no capture limit override here
166
167                 gamemode_name = "Capture the Flag";
168                 teams_matter = 1;
169         }
170         else if(game == GAME_RUNEMATCH || cvar("g_runematch"))
171         {
172                 game = GAME_RUNEMATCH;
173                 cvar_set("g_runematch", "1");
174                 cvar_set("g_minstagib", "0");
175
176                 if(cvar("deathmatch_force_teamplay"))
177                         ActivateTeamplay();
178
179                 fraglimit_override = cvar("g_runematch_point_limit");
180
181                 gamemode_name = "Rune Match";
182                 if(cvar("teamplay"))
183                         teams_matter = 1;
184                 else
185                         teams_matter = 0;
186         }
187         else if(game == GAME_DEATHMATCH || game == GAME_TEAM_DEATHMATCH || cvar("g_tdm"))
188         {
189                 if(!cvar("deathmatch"))
190                         cvar_set("deathmatch", "1");
191
192
193                 if(game == GAME_TEAM_DEATHMATCH || cvar("g_tdm") || cvar("deathmatch_force_teamplay"))
194                 {
195                         ResetGameCvars();
196                         game = GAME_TEAM_DEATHMATCH;
197                         gamemode_name = "Team Deathmatch";
198                         ActivateTeamplay();
199                         teams_matter = 1;
200                         cvar_set("g_tdm", "1");
201                 }
202                 else
203                 {
204                         ResetGameCvars();
205                         game = GAME_DEATHMATCH;
206                         gamemode_name = "Deathmatch";
207                         teams_matter = 0;
208                 }
209
210                 fraglimit_override = cvar("fraglimit_override");
211         }
212         else if(game == GAME_ASSAULT || cvar("g_assault"))
213         {
214                 ResetGameCvars();
215                 game = GAME_ASSAULT;
216                 gamemode_name = "Assault";
217                 ActivateTeamplay();
218                 teams_matter = 1;
219                 cvar_set("g_assault", "1");
220         }
221         else if(game == GAME_LMS || cvar("g_lms"))
222         {
223                 ResetGameCvars();
224                 game = GAME_LMS;
225                 cvar_set("g_lms", "1");
226                 fraglimit_override = cvar("g_lms_lives_override");
227                 if(fraglimit_override == 0)
228                         fraglimit_override = -1;
229                 gamemode_name = "Last Man Standing";
230                 teams_matter = 0;
231                 lms_lowest_lives = 999;
232         }
233         else if(game == GAME_ARENA || cvar("g_arena"))
234         {
235                 ResetGameCvars();
236                 game = GAME_ARENA;
237                 cvar_set("g_arena", "1");
238                 fraglimit_override = cvar("g_arena_point_limit");
239                 maxspawned = cvar("g_arena_maxspawned");
240                 if(maxspawned < 2)
241                         maxspawned = 2;
242                 arena_roundbased = cvar("g_arena_roundbased");
243                 gamemode_name = "Arena";
244                 teams_matter = 0;
245         }
246         else if(game == GAME_KEYHUNT || cvar("g_keyhunt"))
247         {
248                 ResetGameCvars();
249                 game = GAME_KEYHUNT;
250                 cvar_set("g_keyhunt", "1");
251                 fraglimit_override = cvar("g_keyhunt_point_limit");
252                 ActivateTeamplay();
253                 gamemode_name = "Key Hunt";
254                 teams_matter = 1;
255         }
256         else if(game == GAME_ONSLAUGHT || cvar("g_onslaught"))
257         {
258                 ResetGameCvars();
259                 game = GAME_ONSLAUGHT;
260                 cvar_set("g_onslaught", "1");
261
262                 ActivateTeamplay();
263
264                 gamemode_name = "Onslaught";
265                 teams_matter = 1;
266         }
267         else
268         {
269                 // we can only assume...
270                 ResetGameCvars();
271                 game = GAME_DEATHMATCH;
272                 cvar_set("g_dm", "1");
273                 gamemode_name = "Deathmatch";
274                 teams_matter = 0;
275         }
276
277         // those mutators rule each other out
278         if(cvar("g_minstagib"))
279         {
280                 cvar_set("g_instagib", "0");
281                 cvar_set("g_rocketarena", "0");
282         }
283         if(cvar("g_instagib"))
284         {
285                 cvar_set("g_minstagib", "0");
286                 cvar_set("g_rocketarena", "0");
287         }
288         if(cvar("g_rocketarena"))
289         {
290                 cvar_set("g_instagib", "0");
291                 cvar_set("g_minstagib", "0");
292         }
293
294         g_domination = cvar("g_domination");
295         g_ctf = cvar("g_ctf");
296         g_lms = cvar("g_lms");
297         g_tdm = cvar("g_tdm");
298         g_runematch = cvar("g_runematch");
299         g_keyhunt = cvar("g_keyhunt");
300         g_onslaught = cvar("g_onslaught");
301         g_assault = cvar("g_assault");
302         g_arena = cvar("g_arena");
303
304         cache_mutatormsg = strzone("");
305         cache_motd = strzone("");
306         cache_lastmutatormsg = strzone("");
307         cache_lastmotd = strzone("");
308
309         // enforce the server's universal frag/time limits
310         if(!cvar("g_campaign"))
311         {
312                 if(fraglimit_override >= 0)
313                         cvar_set("fraglimit", ftos(fraglimit_override));
314                 if(timelimit_override >= 0)
315                         cvar_set("timelimit", ftos(timelimit_override));
316                 if(capturelimit_override >= 0)
317                         cvar_set("capturelimit", ftos(capturelimit_override));
318         }
319
320         if (game == GAME_DOMINATION)//cvar("g_domination"))
321                 dom_init();
322         else if (game == GAME_CTF)//cvar("g_ctf"))
323                 ctf_init();
324         else if (game == GAME_RUNEMATCH)//cvar("g_runematch"))
325                 runematch_init();
326         else if (game == GAME_TEAM_DEATHMATCH)//cvar("g_runematch"))
327                 tdm_init();
328         else if (game == GAME_KEYHUNT)//cvar("g_keyhunt"))
329                 kh_init();
330         else if (game == GAME_ONSLAUGHT)
331                 entcs_init();
332 }
333
334 string GetClientVersionMessage(float v) {
335         local string versionmsg;
336         if (v == 1) {
337                 versionmsg = "^1client is too old to get versioninfo.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nUPDATE!!! (http://www.nexuiz.com)^8";
338                 // either that or someone wants to be funny
339         } else if (v != cvar("gameversion")) {
340                 if(v < cvar("gameversion")) {
341                         versionmsg = "^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8";
342                 } else {
343                         versionmsg = "^3This server is using an outdated Nexuiz version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8";
344                 }
345         } else {
346                 versionmsg = "^2client version and server version are compatible.^8";
347         }
348         return versionmsg;
349 }
350
351
352 void PrintWelcomeMessage(entity pl)
353 {
354         string s, mutator, modifications, padding;
355
356         /*if(self.welcomemessage_time > time)
357                 return;
358         self.welcomemessage_time = time + 0.8; */
359
360         if(self.cvar_scr_centertime == 0) return;
361         if( !(timeoutStatus >= 1 || (time < restart_countdown) ) ) { //really print the WelcomeMessage to the player every frame when timeout-seconds are shown or the game is restarted, to make sure that the shown number is accurate
362                 if(self.welcomemessage_time > time) return;
363                 self.welcomemessage_time = time + self.cvar_scr_centertime * 0.6;
364         }
365
366         if(cvar("g_campaign"))
367         {
368                 centerprint(pl, campaign_message);
369                 return;
370         }
371
372         if(!self.BUTTON_INFO)
373         {
374                 if(self.classname == "observer")
375                 {
376                         if(g_lms && self.frags <= 0 && self.frags > -666)
377                                 return centerprint_atprio(self, CENTERPRIO_SPAM, strcat(NEWLINES, "^1You have no more lives left\nwait for next round\n\n\n^7press attack to spectate other players"));
378                         else if(g_lms && self.frags == -666)
379                                 return centerprint_atprio(self, CENTERPRIO_SPAM, strcat(NEWLINES, "^1Match has already begun\nwait for next round\n\n\n^7press attack to spectate other players"));
380                 }
381                 else if(self.classname == "spectator")
382                 {
383                         if ((g_lms && self.frags < 1) || g_arena)
384                                 return centerprint_atprio(self, CENTERPRIO_SPAM, strcat(NEWLINES, "spectating ", self.enemy.netname, "\n\n\n^7press attack for next player\npress attack2 for free fly mode"));
385                         else {
386                                 local string specString;
387                                 specString = strcat(NEWLINES, "spectating ", self.enemy.netname, "\n\n\n^7press jump to play\n^7press attack for next player\npress attack2 for free fly mode");
388                                 
389                                 if(time < restart_countdown) //also show the countdown when being a spectator
390                                         specString = strcat(specString, "\n\n^1Game starts in ", ftos(restartAnnouncer.cnt + 1), " seconds^7");
391                                 else if (timeoutStatus != 0)
392                                         specString = strcat(specString, "\n\n", getTimeoutText(1));
393                                 return centerprint_atprio(self, CENTERPRIO_SPAM, specString);
394                         }
395                 }
396         }
397
398         if(g_minstagib)
399                 mutator = "^2Minstagib ^1";
400         else if(g_instagib)
401                 mutator = "^2Instagib ^1";
402         else if(g_rocketarena)
403                 mutator = "^2Rocketarena ^1";
404         else if(g_nixnex)
405                 mutator = "^2No Items Nexuiz ^1";
406
407         if(g_midair) {
408                 // to protect against unheedingly made changes
409                 if (modifications) {
410                         modifications = strcat(modifications, ", ");
411                 }
412                 modifications = "midair";
413         }
414         if(g_vampire) {
415                 if (modifications) {
416                         modifications = strcat(modifications, ", ");
417                 }
418                 modifications = strcat(modifications, "vampire");
419         }
420         if(g_laserguided_missile) {
421                 if (modifications) {
422                         modifications = strcat(modifications, ", ");
423                 }
424                 modifications = strcat(modifications, "laser-guided-missiles");
425         }
426         if(g_tourney) {
427                 if (modifications) {
428                         modifications = strcat(modifications, ", ");
429                 }
430                 modifications = strcat(modifications, "Tournament");
431         }
432
433         local string versionmessage;
434         versionmessage = GetClientVersionMessage(self.version);
435
436         s = strcat(s, NEWLINES, "This is Nexuiz ", cvar_string("g_nexuizversion"), "\n", versionmessage);
437         s = strcat(s, "^8\n\nmatch type is ^1", mutator, gamemode_name, "^8\n");
438
439         if(modifications != "")
440                 s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
441
442         if(g_tourney) {
443                 if(!tourneyInMatchStage)
444                         s = strcat(s, "\n^8The game is currently in ^3warmup-stage ^8!\n");
445                 else
446                         s = strcat(s, "\n^8The game is currently in ^3match-stage ^8!\n");
447         }
448
449         if(time < restart_countdown)
450                 s = strcat(s, "\n^1Game starts in ", ftos(restartAnnouncer.cnt + 1), " seconds^7");
451
452         if(timeoutStatus != 0)
453                 s = strcat(s, "\n\n", getTimeoutText(1));
454
455         if (g_grappling_hook)
456                 s = strcat(s, "\n\n^8grappling hook is enabled, press 'e' to use it\n");
457
458         if(cache_lastmutatormsg != cvar_string("g_mutatormsg"))
459         {
460                 if(cache_lastmutatormsg)
461                         strunzone(cache_lastmutatormsg);
462                 if(cache_mutatormsg)
463                         strunzone(cache_mutatormsg);
464                 cache_lastmutatormsg = strzone(cvar_string("g_mutatormsg"));
465                 cache_mutatormsg = strzone(wordwrap(cache_lastmutatormsg, 50));
466         }
467
468         if (cache_mutatormsg != "") {
469                 s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
470         }
471         
472         if(cache_lastmotd != cvar_string("sv_motd"))
473         {
474                 if(cache_lastmotd)
475                         strunzone(cache_lastmotd);
476                 if(cache_motd)
477                         strunzone(cache_motd);
478                 cache_lastmotd = strzone(cvar_string("sv_motd"));
479                 cache_motd = strzone(wordwrap(cache_lastmotd, 50));
480         }
481
482         if (cache_motd != "") {
483                 s = strcat(s, "\n\n^8MOTD: ^7", cache_motd);
484         }
485
486         s = strcat(s, "\n");
487         if(cvar("fraglimit"))
488         {
489                 padding = "";
490                 if(cvar("timelimit"))
491                         padding = "        ";
492                         //        " minutes"
493                 s = strcat(s, "\n^8frag limit: ^7", cvar_string("fraglimit"), padding);
494         }
495         if(cvar("timelimit"))
496                 s = strcat(s, "\n^8time limit: ^7", cvar_string("timelimit"), " minutes");
497
498         centerprint(pl, s);
499         //sprint(pl, s);
500 }
501
502
503 void SetPlayerColors(entity pl, float _color)
504 {
505         /*string s;
506         s = ftos(cl);
507         stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
508         pl.team = cl + 1;
509         //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
510         pl.clientcolors = 16*cl + cl;*/
511
512         float pants, shirt;
513         pants = _color & 0x0F;
514         shirt = _color & 0xF0;
515
516
517         if(teamplay) {
518                 setcolor(pl, 16*pants + pants);
519         } else {
520                 setcolor(pl, shirt + pants);
521         }
522 }
523
524 void SetPlayerTeam(entity pl, float t, float s, float noprint)
525 {
526         float _color;
527
528         if(t == 4)
529                 _color = COLOR_TEAM4 - 1;
530         else if(t == 3)
531                 _color = COLOR_TEAM3 - 1;
532         else if(t == 2)
533                 _color = COLOR_TEAM2 - 1;
534         else
535                 _color = COLOR_TEAM1 - 1;
536
537         SetPlayerColors(pl,_color);
538
539         if(!noprint && t != s)
540         {
541                 //bprint(pl.netname, " has changed to ", TeamNoName(t), "\n");
542                 bprint(pl.netname, "^7 has changed from ", TeamNoName(s), " to ", TeamNoName(t), "\n");
543         }
544
545         if(t != s)
546                 LogTeamchange(pl);
547 }
548
549
550
551
552
553
554 // set c1...c4 to show what teams are allowed
555 void CheckAllowedTeams (entity for_whom)
556 {
557         string teament_name;
558         float dm;
559         entity head;
560
561 //      if(!dom && !ctf)
562 //              dm = 1;
563
564         c1 = c2 = c3 = c4 = -1;
565         cb1 = cb2 = cb3 = cb4 = 0;
566
567         // onslaught is special
568         if(g_onslaught)
569         {
570                 head = findchain(classname, "onslaught_generator");
571                 while (head)
572                 {
573                         if (head.team == COLOR_TEAM1) c1 = 0;
574                         if (head.team == COLOR_TEAM2) c2 = 0;
575                         if (head.team == COLOR_TEAM3) c3 = 0;
576                         if (head.team == COLOR_TEAM4) c4 = 0;
577                         head = head.chain;
578                 }
579                 return;
580         }
581
582         if(g_domination)
583                 teament_name = "dom_team";
584         else if(g_ctf)
585                 teament_name = "ctf_team";
586         else if(g_tdm)
587                 teament_name = "tdm_team";
588         else if(g_assault)
589         {
590                 c1 = c2 = 0; // Assault always has 2 teams
591                 return;
592         }
593         else
594         {
595                 // cover anything else by treating it like tdm with no teams spawned
596                 if(g_keyhunt)
597                         dm = kh_teams;
598                 else
599                         dm = cvar("g_tdm_teams");
600                 if(dm < 2)
601                         error("g_tdm_teams < 2, not enough teams to play team deathmatch\n");
602
603                 if(dm >= 4)
604                 {
605                         c1 = c2 = c3 = c4 = 0;
606                 }
607                 else if(dm >= 3)
608                 {
609                         c1 = c2 = c3 = 0;
610                 }
611                 else// if(dm >= 2)
612                 {
613                         c1 = c2 = 0;
614                 }
615                 return;
616         }
617
618         // first find out what teams are allowed
619         head = find(world, classname, teament_name);
620         while(head)
621         {
622                 if(!(g_domination && head.netname == ""))
623                 {
624                         if(head.team == COLOR_TEAM1)
625                         {
626                                 c1 = 0;
627                         }
628                         if(head.team == COLOR_TEAM2)
629                         {
630                                 c2 = 0;
631                         }
632                         if(head.team == COLOR_TEAM3)
633                         {
634                                 c3 = 0;
635                         }
636                         if(head.team == COLOR_TEAM4)
637                         {
638                                 c4 = 0;
639                         }
640                 }
641                 head = find(head, classname, teament_name);
642         }
643
644         if(cvar("bot_vs_human") > 0)
645         {
646                 // bots are all blue
647                 if(clienttype(for_whom) == CLIENTTYPE_BOT)
648                         c1 = c3 = c4 = -1;
649                 else
650                         c2 = -1;
651         }
652         else if(cvar("bot_vs_human") < 0)
653         {
654                 // bots are all red
655                 if(clienttype(for_whom) == CLIENTTYPE_BOT)
656                         c2 = c3 = c4 = -1;
657                 else
658                         c1 = -1;
659         }
660 }
661
662 float PlayerValue(entity p)
663 {
664         if(IsTeamBalanceForced() == 1)
665                 return 1;
666         return 1;
667 }
668
669 // c1...c4 should be set to -1 (not allowed) or 0 (allowed).
670 // teams that are allowed will now have their player counts stored in c1...c4
671 void GetTeamCounts(entity ignore)
672 {
673         entity head;
674         float value, bvalue;
675         // now count how many players are on each team already
676
677         // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
678         // also remember the lowest-scoring player
679
680         FOR_EACH_PLAYER(head)
681         {
682                 if(head != ignore)// && head.netname != "")
683                 {
684                         value = PlayerValue(head);
685                         if(clienttype(head) == CLIENTTYPE_BOT)
686                                 bvalue = value;
687                         else
688                                 bvalue = 0;
689                         if(head.team == COLOR_TEAM1)
690                         {
691                                 if(c1 >= 0)
692                                 {
693                                         c1 = c1 + value;
694                                         cb1 = cb1 + bvalue;
695                                 }
696                         }
697                         if(head.team == COLOR_TEAM2)
698                         {
699                                 if(c2 >= 0)
700                                 {
701                                         c2 = c2 + value;
702                                         cb2 = cb2 + bvalue;
703                                 }
704                         }
705                         if(head.team == COLOR_TEAM3)
706                         {
707                                 if(c3 >= 0)
708                                 {
709                                         c3 = c3 + value;
710                                         cb3 = cb3 + bvalue;
711                                 }
712                         }
713                         if(head.team == COLOR_TEAM4)
714                         {
715                                 if(c4 >= 0)
716                                 {
717                                         c4 = c4 + value;
718                                         cb4 = cb4 + bvalue;
719                                 }
720                         }
721                 }
722         }
723 }
724
725 // returns # of smallest team (1, 2, 3, 4)
726 // NOTE: Assumes CheckAllowedTeams has already been called!
727 float FindSmallestTeam(entity pl, float ignore_pl)
728 {
729         float totalteams, smallestteam, smallestteam_count, smallestteam_score, balance_type;
730         totalteams = 0;
731
732         // find out what teams are available
733         //CheckAllowedTeams();
734
735         // make sure there are at least 2 teams to join
736         if(c1 >= 0)
737                 totalteams = totalteams + 1;
738         if(c2 >= 0)
739                 totalteams = totalteams + 1;
740         if(c3 >= 0)
741                 totalteams = totalteams + 1;
742         if(c4 >= 0)
743                 totalteams = totalteams + 1;
744
745         if(cvar("bot_vs_human"))
746                 totalteams += 1;
747
748         if(totalteams <= 1)
749         {
750                 if(g_domination)
751                         error("Too few teams available for domination\n");
752                 else if(g_ctf)
753                         error("Too few teams available for ctf\n");
754                 else if(g_keyhunt)
755                         error("Too few teams available for key hunt\n");
756                 else
757                         error("Too few teams available for team deathmatch\n");
758         }
759
760         // count how many players are in each team
761         if(ignore_pl)
762                 GetTeamCounts(pl);
763         else
764                 GetTeamCounts(world);
765
766         // c1...c4 now have counts of each team
767         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
768
769         smallestteam = 0;
770         smallestteam_count = 999999999;
771         smallestteam_score = 999999999;
772
773         // 2 gives priority to what team you're already on, 1 goes in order
774         // 2 doesn't seem to work though...
775         balance_type = 1;
776
777         if(bots_would_leave)
778         //if(pl.classname != "player")
779         if(clienttype(pl) != CLIENTTYPE_BOT)
780         {
781                 c1 -= cb1 * 255.0/256;
782                 c2 -= cb2 * 255.0/256;
783                 c3 -= cb3 * 255.0/256;
784                 c4 -= cb4 * 255.0/256;
785         }
786
787         if(balance_type == 1)
788         {
789                 if(c1 >= 0 && (c1 < smallestteam_count || (c1 <= smallestteam_count && team1_score < smallestteam_score)))
790                 {
791                         smallestteam = 1;
792                         smallestteam_count = c1;
793                         smallestteam_score = team1_score;
794                 }
795                 if(c2 >= 0 && (c2 < smallestteam_count || (c2 <= smallestteam_count && team2_score < smallestteam_score)))
796                 {
797                         smallestteam = 2;
798                         smallestteam_count = c2;
799                         smallestteam_score = team2_score;
800                 }
801                 if(c3 >= 0 && (c3 < smallestteam_count || (c3 <= smallestteam_count && team3_score < smallestteam_score)))
802                 {
803                         smallestteam = 3;
804                         smallestteam_count = c3;
805                         smallestteam_score = team3_score;
806                 }
807                 if(c4 >= 0 && (c4 < smallestteam_count || (c4 <= smallestteam_count && team4_score < smallestteam_score)))
808                 {
809                         smallestteam = 4;
810                         smallestteam_count = c4;
811                         smallestteam_score = team4_score;
812                 }
813         }
814         else
815         {
816                 if(c1 >= 0 && (c1 < smallestteam_count ||
817                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
818                 {
819                         smallestteam = 1;
820                         smallestteam_count = c1;
821                 }
822                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
823                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
824                 {
825                         smallestteam = 2;
826                         smallestteam_count = c2;
827                 }
828                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
829                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
830                 {
831                         smallestteam = 3;
832                         smallestteam_count = c3;
833                 }
834                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
835                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
836                 {
837                         smallestteam = 4;
838                         smallestteam_count = c4;
839                 }
840         }
841
842         return smallestteam;
843 }
844
845 float JoinBestTeam(entity pl, float only_return_best, float forcebestteam)
846 {
847         float smallest, selectedteam;
848
849         // don't join a team if we're not playing a team game
850         if(!cvar("teamplay") && !g_domination && !g_ctf && !g_keyhunt)
851                 return 0;
852
853         // find out what teams are available
854         CheckAllowedTeams(pl);
855
856         if(g_domination)
857         {
858                 // <div0> WHY? TODO
859                 if(cvar("g_domination_default_teams") < 3)
860                         c3 = 999999999;
861                 if(cvar("g_domination_default_teams") < 4)
862                         c4 = 999999999;
863         }
864
865         // if we don't care what team he ends up on, put him on whatever team he entered as.
866         // if he's not on a valid team, then let other code put him on the smallest team
867         if(!forcebestteam)
868         {
869                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
870                         selectedteam = pl.team;
871                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
872                         selectedteam = pl.team;
873                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
874                         selectedteam = pl.team;
875                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
876                         selectedteam = pl.team;
877                 else
878                         selectedteam = -1;
879                 if(selectedteam > 0)
880                 {
881                         if(!only_return_best)
882                         {
883                                 SetPlayerColors(pl, selectedteam - 1);
884                                 LogTeamchange(pl);
885                         }
886                         return selectedteam;
887                 }
888                 // otherwise end up on the smallest team (handled below)
889         }
890
891         smallest = FindSmallestTeam(pl, TRUE);
892
893
894         if(!only_return_best)
895         {
896                 TeamchangeFrags(self);
897                 if(smallest == 1)
898                 {
899                         SetPlayerColors(pl, COLOR_TEAM1 - 1);
900                 }
901                 else if(smallest == 2)
902                 {
903                         SetPlayerColors(pl, COLOR_TEAM2 - 1);
904                 }
905                 else if(smallest == 3)
906                 {
907                         SetPlayerColors(pl, COLOR_TEAM3 - 1);
908                 }
909                 else if(smallest == 4)
910                 {
911                         SetPlayerColors(pl, COLOR_TEAM4 - 1);
912                 }
913                 else
914                 {
915                         error("smallest team: invalid team\n");
916                 }
917                 LogTeamchange(pl);
918                 if(pl.deadflag == DEAD_NO)
919                         Damage(pl, pl, pl, 100000, DEATH_TEAMCHANGE, pl.origin, '0 0 0');
920         }
921
922         return smallest;
923 }
924
925 //void() ctf_playerchanged;
926 void SV_ChangeTeam(float _color)
927 {
928         float scolor, dcolor, steam, dteam, dbotcount, scount, dcount;
929
930         // in normal deathmatch we can just apply the color and we're done
931         if(!cvar("teamplay")) {
932                 SetPlayerColors(self, _color);
933                 return;
934         }
935
936         scolor = self.clientcolors & 0x0F;
937         dcolor = _color & 0x0F;
938
939         if(scolor == COLOR_TEAM1 - 1)
940                 steam = 1;
941         else if(scolor == COLOR_TEAM2 - 1)
942                 steam = 2;
943         else if(scolor == COLOR_TEAM3 - 1)
944                 steam = 3;
945         else if(scolor == COLOR_TEAM4 - 1)
946                 steam = 4;
947         if(dcolor == COLOR_TEAM1 - 1)
948                 dteam = 1;
949         else if(dcolor == COLOR_TEAM2 - 1)
950                 dteam = 2;
951         else if(dcolor == COLOR_TEAM3 - 1)
952                 dteam = 3;
953         else if(dcolor == COLOR_TEAM4 - 1)
954                 dteam = 4;
955
956         CheckAllowedTeams(self);
957
958         if(dteam == 1 && c1 < 0) dteam = 4;
959         if(dteam == 4 && c4 < 0) dteam = 3;
960         if(dteam == 3 && c3 < 0) dteam = 2;
961         if(dteam == 2 && c2 < 0) dteam = 1;
962
963         // not changing teams
964         if(scolor == dcolor)
965         {
966                 //bprint("same team change\n");
967                 SetPlayerTeam(self, dteam, steam, TRUE);
968                 return;
969         }
970
971         if(cvar("teamplay"))
972         {
973                 if(cvar("g_campaign") || cvar("g_changeteam_banned"))
974                 {
975                         sprint(self, "Team changes not allowed\n");
976                         return; // changing teams is not allowed
977                 }
978
979                 if(!cvar("g_campaign") && cvar("g_balance_teams_prevent_imbalance"))
980                 {
981                         // only allow changing to a smaller or equal size team
982
983                         // find out what teams are available
984                         //CheckAllowedTeams();
985                         // count how many players on each team
986                         GetTeamCounts(world);
987
988                         // get desired team
989                         if(dteam == 1 && c1 >= 0)//dcolor == COLOR_TEAM1 - 1)
990                         {
991                                 dcount = c1;
992                                 dbotcount = cb1;
993                         }
994                         else if(dteam == 2 && c2 >= 0)//dcolor == COLOR_TEAM2 - 1)
995                         {
996                                 dcount = c2;
997                                 dbotcount = cb2;
998                         }
999                         else if(dteam == 3 && c3 >= 0)//dcolor == COLOR_TEAM3 - 1)
1000                         {
1001                                 dcount = c3;
1002                                 dbotcount = cb3;
1003                         }
1004                         else if(dteam == 4 && c4 >= 0)//dcolor == COLOR_TEAM4 - 1)
1005                         {
1006                                 dcount = c4;
1007                                 dbotcount = cb4;
1008                         }
1009                         else
1010                         {
1011                                 sprint(self, "Cannot change to an invalid team\n");
1012
1013                                 return;
1014                         }
1015
1016                         // get starting team
1017                         if(steam == 1)//scolor == COLOR_TEAM1 - 1)
1018                                 scount = c1;
1019                         else if(steam == 2)//scolor == COLOR_TEAM2 - 1)
1020                                 scount = c2;
1021                         else if(steam == 3)//scolor == COLOR_TEAM3 - 1)
1022                                 scount = c3;
1023                         else if(steam == 4)//scolor == COLOR_TEAM4 - 1)
1024                                 scount = c4;
1025
1026                         if(scount) // started at a valid, nonempty team
1027                         {
1028                                 // check if we're trying to change to a larger team that doens't have bots to swap with
1029                                 if(dcount >= scount && dbotcount <= 0)
1030                                 {
1031                                         sprint(self, "Cannot change to a larger team\n");
1032                                         return; // can't change to a larger team
1033                                 }
1034                         }
1035                 }
1036         }
1037
1038 //      bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
1039
1040         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1041         {
1042                 // reduce frags during a team change
1043                 TeamchangeFrags(self);
1044         }
1045
1046         SetPlayerTeam(self, dteam, steam, FALSE);
1047
1048         if(cvar("teamplay") && self.classname == "player" && steam != dteam)
1049         {
1050                 // kill player when changing teams
1051                 if(self.deadflag == DEAD_NO)
1052                         Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
1053         }
1054         //ctf_playerchanged();
1055 }
1056
1057 void ShufflePlayerOutOfTeam (float source_team)
1058 {
1059         float smallestteam, smallestteam_count, steam;
1060         float lowest_bot_score, lowest_player_score;
1061         entity head, lowest_bot, lowest_player, selected;
1062
1063         smallestteam = 0;
1064         smallestteam_count = 999999999;
1065
1066         if(c1 >= 0 && c1 < smallestteam_count)
1067         {
1068                 smallestteam = 1;
1069                 smallestteam_count = c1;
1070         }
1071         if(c2 >= 0 && c2 < smallestteam_count)
1072         {
1073                 smallestteam = 2;
1074                 smallestteam_count = c2;
1075         }
1076         if(c3 >= 0 && c3 < smallestteam_count)
1077         {
1078                 smallestteam = 3;
1079                 smallestteam_count = c3;
1080         }
1081         if(c4 >= 0 && c4 < smallestteam_count)
1082         {
1083                 smallestteam = 4;
1084                 smallestteam_count = c4;
1085         }
1086
1087         if(!smallestteam)
1088         {
1089                 bprint("warning: no smallest team\n");
1090                 return;
1091         }
1092
1093         if(source_team == 1)
1094                 steam = COLOR_TEAM1;
1095         else if(source_team == 2)
1096                 steam = COLOR_TEAM2;
1097         else if(source_team == 3)
1098                 steam = COLOR_TEAM3;
1099         else if(source_team == 4)
1100                 steam = COLOR_TEAM4;
1101
1102         lowest_bot = world;
1103         lowest_bot_score = 999999999;
1104         lowest_player = world;
1105         lowest_player_score = 999999999;
1106
1107         // find the lowest-scoring player & bot of that team
1108         FOR_EACH_PLAYER(head)
1109         {
1110                 if(head.team == steam)
1111                 {
1112                         if(head.isbot)
1113                         {
1114                                 if(head.frags < lowest_bot_score)
1115                                 {
1116                                         lowest_bot = head;
1117                                         lowest_bot_score = head.frags;
1118                                 }
1119                         }
1120                         else
1121                         {
1122                                 if(head.frags < lowest_player_score)
1123                                 {
1124                                         lowest_player = head;
1125                                         lowest_player_score = head.frags;
1126                                 }
1127                         }
1128                 }
1129         }
1130
1131         // prefers to move a bot...
1132         if(lowest_bot != world)
1133                 selected = lowest_bot;
1134         // but it will move a player if it has to
1135         else
1136                 selected = lowest_player;
1137         // don't do anything if it couldn't find anyone
1138         if(!selected)
1139         {
1140                 bprint("warning: couldn't find a player to move from team\n");
1141                 return;
1142         }
1143
1144         // smallest team gains a member
1145         if(smallestteam == 1)
1146         {
1147                 c1 = c1 + 1;
1148         }
1149         else if(smallestteam == 2)
1150         {
1151                 c2 = c2 + 1;
1152         }
1153         else if(smallestteam == 3)
1154         {
1155                 c3 = c3 + 1;
1156         }
1157         else if(smallestteam == 4)
1158         {
1159                 c4 = c4 + 1;
1160         }
1161         else
1162         {
1163                 bprint("warning: destination team invalid\n");
1164                 return;
1165         }
1166         // source team loses a member
1167         if(source_team == 1)
1168         {
1169                 c1 = c1 + 1;
1170         }
1171         else if(source_team == 2)
1172         {
1173                 c2 = c2 + 2;
1174         }
1175         else if(source_team == 3)
1176         {
1177                 c3 = c3 + 3;
1178         }
1179         else if(source_team == 4)
1180         {
1181                 c4 = c4 + 4;
1182         }
1183         else
1184         {
1185                 bprint("warning: source team invalid\n");
1186                 return;
1187         }
1188
1189         // move the player to the new team
1190         TeamchangeFrags(selected);
1191         SetPlayerTeam(selected, smallestteam, source_team, FALSE);
1192
1193         if(selected.deadflag == DEAD_NO)
1194                 Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE, selected.origin, '0 0 0');
1195         centerprint(selected, strcat("You have been moved into a different team to improve team balance\nYou are now on: ", ColoredTeamName(selected.team)));
1196 }
1197
1198 float lastRebalanceInfo;
1199 void CauseRebalance(float source_team, float howmany_toomany)
1200 {
1201         float steam;
1202         entity head;
1203
1204         if(IsTeamBalanceForced() == 1)
1205         {
1206                 bprint("Rebalancing Teams\n");
1207                 ShufflePlayerOutOfTeam(source_team);
1208         }
1209         else
1210         {
1211                 if(howmany_toomany < cvar("g_balance_teams_complain"))
1212                         return;
1213                 if(time < lastRebalanceInfo + 90)
1214                         return;
1215                 lastRebalanceInfo = time;
1216                 if(source_team == 1)
1217                         steam = COLOR_TEAM1;
1218                 else if(source_team == 2)
1219                         steam = COLOR_TEAM2;
1220                 else if(source_team == 3)
1221                         steam = COLOR_TEAM3;
1222                 else if(source_team == 4)
1223                         steam = COLOR_TEAM4;
1224                 ServerConsoleEcho(strcat("Team ", ftos(source_team), " too large, complaining."), TRUE);
1225                 FOR_EACH_REALPLAYER(head)
1226                 {
1227                         if(head.team == steam)
1228                         {
1229                                 sprint(head, "\{1}\{13}^3SERVER NOTICE:^7 One of you please change teams!\n");
1230                                 centerprint_atprio(head, CENTERPRIO_REBALANCE, "^3SERVER NOTICE:\n\n^7Someone of you please change teams!");
1231                         }
1232                 }
1233         }
1234 }
1235
1236 // part of g_balance_teams_force
1237 // occasionally perform an audit of the teams to make
1238 // sure they're more or less balanced in player count.
1239 void AuditTeams()
1240 {
1241         float numplayers, numteams, smallest, toomany;
1242         float balance;
1243         balance = IsTeamBalanceForced();
1244         if(balance == 0)
1245                 return;
1246
1247         if(audit_teams_time > time)
1248                 return;
1249
1250         audit_teams_time = time + 4 + random();
1251
1252 //      bprint("Auditing teams\n");
1253
1254         CheckAllowedTeams(world);
1255         GetTeamCounts(world);
1256
1257
1258         numteams = numplayers = smallest = 0;
1259         if(c1 >= 0)
1260         {
1261                 numteams = numteams + 1;
1262                 numplayers = numplayers + c1;
1263                 smallest = c1;
1264         }
1265         if(c2 >= 0)
1266         {
1267                 numteams = numteams + 1;
1268                 numplayers = numplayers + c2;
1269                 if(c2 < smallest)
1270                         smallest = c2;
1271         }
1272         if(c3 >= 0)
1273         {
1274                 numteams = numteams + 1;
1275                 numplayers = numplayers + c3;
1276                 if(c3 < smallest)
1277                         smallest = c3;
1278         }
1279         if(c4 >= 0)
1280         {
1281                 numteams = numteams + 1;
1282                 numplayers = numplayers + c4;
1283                 if(c4 < smallest)
1284                         smallest = c4;
1285         }
1286
1287         if(numplayers <= 0)
1288                 return; // no players to move around
1289         if(numteams < 2)
1290                 return; // don't bother shuffling if for some reason there aren't any teams
1291
1292         toomany = smallest + 1;
1293
1294         if(c1 && c1 > toomany)
1295                 CauseRebalance(1, c1 - toomany);
1296         if(c2 && c2 > toomany)
1297                 CauseRebalance(2, c2 - toomany);
1298         if(c3 && c3 > toomany)
1299                 CauseRebalance(3, c3 - toomany);
1300         if(c4 && c4 > toomany)
1301                 CauseRebalance(4, c4 - toomany);
1302
1303         // if teams are still unbalanced, balance them further in the next audit,
1304         // which will happen sooner (keep doing rapid audits until things are in order)
1305         audit_teams_time = time + 0.7 + random()*0.3;
1306 }
1307
1308
1309
1310 /*void UpdateTeamScore(entity e, float first)
1311 {
1312         clientno = e.FIXME;
1313         if(first)
1314         {
1315                 WriteByte (MSG_ALL, SVC_UPDATENAME);
1316                 WriteByte (MSG_ALL, clientno);
1317                 WriteString (MSG_ALL, e.netname);
1318
1319                 WriteByte (MSG_ALL, SVC_UPDATECOLORS);
1320                 WriteByte (MSG_ALL, clientno);
1321                 WriteByte (MSG_ALL, e.b_shirt * 16 + who.b_pants);
1322         }
1323
1324         WriteByte (MSG_ALL, SVC_UPDATEFRAGS);
1325         WriteByte (MSG_ALL, clientno);
1326         WriteShort (MSG_ALL, e.frags + 10000);
1327 };
1328
1329 */
1330
1331
1332 // code from here on is just to support maps that don't have team entities
1333 void tdm_spawnteam (string teamname, float teamcolor)
1334 {
1335         local entity e;
1336         e = spawn();
1337         e.classname = "tdm_team";
1338         e.netname = teamname;
1339         e.cnt = teamcolor;
1340         e.team = e.cnt + 1;
1341 };
1342
1343 // spawn some default teams if the map is not set up for tdm
1344 void tdm_spawnteams()
1345 {
1346         float numteams;
1347
1348         numteams = cvar("g_tdm_teams");
1349
1350         tdm_spawnteam("Red", COLOR_TEAM1-1);
1351         tdm_spawnteam("Blue", COLOR_TEAM2-1);
1352         if(numteams >= 3)
1353                 tdm_spawnteam("Yellow", COLOR_TEAM3-1);
1354         if(numteams >= 4)
1355                 tdm_spawnteam("Pink", COLOR_TEAM4-1);
1356 };
1357
1358 void tdm_delayedinit()
1359 {
1360         self.think = SUB_Remove;
1361         self.nextthink = time;
1362         // if no teams are found, spawn defaults
1363         if (find(world, classname, "tdm_team") == world)
1364                 tdm_spawnteams();
1365 };
1366
1367 void tdm_init()
1368 {
1369         local entity e;
1370         e = spawn();
1371         e.think = tdm_delayedinit;
1372         e.nextthink = time + 0.1;
1373 };
1374
1375