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