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