1 /*
2 
3 *************************************************************************
4 
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000  Manuel Moos (manuel@moosnet.de)
7 
8 **************************************************************************
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 ***************************************************************************
25 
26 */
27 
28 #include "eTeam.h"
29 #include "tSysTime.h"
30 #include "rFont.h"
31 #include "nConfig.h"
32 
33 #include <set>
34 #include <climits>
35 
operator <<(tString & s,const eTeam * team)36 tString & operator << ( tString &s, const eTeam * team)
37 {
38     if ( !team )
39         return s << tOutput("$player_spectator_message");
40     else
41         return s << team->GetColoredName();
42 }
operator <<(std::ostream & s,const eTeam * team)43 std::ostream & operator << ( std::ostream &s, const eTeam * team)
44 {
45     if ( !team )
46         return s << tOutput("$player_spectator_message");
47     else
48         return s << team->GetColoredName();
49 }
50 
51 #define TEAMCOLORS 8
52 
53 static unsigned short se_team_rgb[TEAMCOLORS][3]=
54 { {  4,  8, 15 } , // blue
55     { 15, 13,  0 } , // gold
56     { 15,  1,  1 } , // red
57     {  0, 15,  4 } , // green
58     { 15,  4, 15 } , // violet
59     {  4, 15, 15 } , // ugly green
60     { 15, 15, 15 } , // white
61     {  7,  7,  7 }   // black
62 };
63 
64 static tString se_team_name[TEAMCOLORS]=
65 {
66     tString("$team_name_blue"),
67     tString("$team_name_gold"),
68     tString("$team_name_red"),
69     tString("$team_name_green"),
70     tString("$team_name_violet"),
71     tString("$team_name_ugly"),
72     tString("$team_name_white"),
73     tString("$team_name_black")
74 };
75 
76 // static tList<eTeam> se_ColoredTeams;
77 static eTeam * se_ColoredTeams[TEAMCOLORS]={0,0,0,0,0,0,0,0};
78 
79 static int IMPOSSIBLY_LOW_SCORE=INT_MIN;
80 
81 // class that creates config items for one team
82 // TEAM_(NAME|RED|GREEN|BLUE)_X
83 class eTeamColorConfig {
84     typedef tSettingItem<tString> nameConf;
85     typedef tSettingItem<unsigned short int> colorConf;
86     colorConf *m_red, *m_green, *m_blue;
87     nameConf *m_name;
88     static int teamCount;
89 public:
eTeamColorConfig()90     eTeamColorConfig() {
91         std::ostringstream name(""), red(""), green(""), blue("");
92         name  << "TEAM_NAME_"  << teamCount + 1;
93         red   << "TEAM_RED_"   << teamCount + 1;
94         green << "TEAM_GREEN_" << teamCount + 1;
95         blue  << "TEAM_BLUE_"  << teamCount + 1;
96         m_name  = new nameConf (name .str().c_str(), se_team_name[teamCount]);
97         m_red   = new colorConf(red  .str().c_str(), se_team_rgb [teamCount][0]);
98         m_green = new colorConf(green.str().c_str(), se_team_rgb [teamCount][1]);
99         m_blue  = new colorConf(blue .str().c_str(), se_team_rgb [teamCount][2]);
100         ++teamCount;
101     }
~eTeamColorConfig()102     ~eTeamColorConfig() {
103         delete m_name;
104         delete m_red;
105         delete m_green;
106         delete m_blue;
107     }
108 };
109 
110 int eTeamColorConfig::teamCount = 0;
111 
112 static eTeamColorConfig se_team_config[TEAMCOLORS];
113 
114 //! Creates a color string inserter
ColorString(const eTeam * t)115 inline static tColoredStringProxy ColorString( const eTeam * t )
116 {
117     return tColoredStringProxy( t->R()/15.0f, t->G()/15.0f, t->B()/15.0f );
118 }
119 
120 nNOInitialisator<eTeam> eTeam_init(220,"eTeam");
121 
CreatorDescriptor() const122 nDescriptor &eTeam::CreatorDescriptor() const{
123     return eTeam_init;
124 }
125 
126 int  eTeam::minTeams=0;					// minimum nuber of teams
127 int  eTeam::maxTeams=30;    			// maximum nuber of teams
128 int  eTeam::minPlayers=0;   			// minimum number of players per team
129 int  eTeam::maxPlayers=3;   			// maximum number of players per team
130 int  eTeam::maxImbalance=2;			// maximum difference of player numbers
131 bool eTeam::balanceWithAIs=true;		// use AI players to balance the teams?
132 bool eTeam::enforceRulesOnQuit=false;	// if the quitting of one player unbalances the teams, enforce the rules by redistributing
133 
134 tList<eTeam> eTeam::teams;		//  list of all teams
135 
136 static bool newTeamAllowed;		// is it allowed to create a new team currently?
137 
138 static nSettingItem<bool> se_newTeamAllowed("NEW_TEAM_ALLOWED", newTeamAllowed );
139 
140 static bool se_allowTeamNameColor  = true; // allow to name a team after a color
141 static bool se_allowTeamNamePlayer = true; // allow to name a team after the leader
142 
143 static tSettingItem<bool> se_allowTeamNameColorConfig("ALLOW_TEAM_NAME_COLOR", se_allowTeamNameColor );
144 static tSettingItem<bool> se_allowTeamNamePlayerConfig("ALLOW_TEAM_NAME_PLAYER", se_allowTeamNamePlayer );
145 
146 // update all internal information
UpdateStaticFlags()147 void eTeam::UpdateStaticFlags()
148 {
149     bool newTeamAllowedCurrent = teams.Len() >= maxTeams;
150 
151     if ( newTeamAllowedCurrent != newTeamAllowed )
152     {
153         se_newTeamAllowed.Set( newTeamAllowedCurrent );
154 
155         for (int i = teams.Len() - 1; i>=0; --i)
156             teams(i)->Update();
157     }
158 }
159 
160 // number of rounds played (updated right after spawning)
RoundsPlayed() const161 int eTeam::RoundsPlayed() const
162 {
163     return roundsPlayed;
164 }
165 
166 // increase round counter
PlayRound()167 void eTeam::PlayRound()
168 {
169     roundsPlayed++;
170 }
171 
172 //update internal properties ( player count )
UpdateProperties()173 void eTeam::UpdateProperties()
174 {
175     //	bool change = false;
176     if ( nCLIENT != sn_GetNetState() )
177     {
178         //if ( maxPlayersLocal != maxPlayers )
179         //{
180         maxPlayersLocal = maxPlayers;
181         //			change = true;
182         //}
183 
184         //if ( maxImbalanceLocal != maxImbalance )
185         //{
186         maxImbalanceLocal = maxImbalance;
187         //			change = true;
188         //}
189 
190         //		if ( change )
191         //		{
192         //		}
193     }
194 
195     numHumans = 0;
196     numAIs = 0;
197     int i;
198     for ( i = players.Len()-1; i>=0; --i )
199     {
200         ePlayerNetID * player = players(i);
201 
202         // on the client, don't count players who already expressed their wish
203         // to leave a team as active players.
204         if ( sn_GetNetState() != nCLIENT || player->nextTeam == this )
205         {
206             if ( player->IsHuman() )
207             {
208                 if ( player->IsActive() )
209                     ++numHumans;
210             }
211             else
212                 ++numAIs;
213         }
214     }
215 
216     if ( nSERVER == sn_GetNetState() )
217         RequestSync();
218 }
219 
220 // update name and color
UpdateAppearance()221 void eTeam::UpdateAppearance()
222 {
223     unsigned short oldr = r, oldg = g, oldb = b;
224 
225     ePlayerNetID* oldest = OldestHumanPlayer();
226     if ( !oldest )
227     {
228         oldest = OldestAIPlayer();
229     }
230 
231     // vote on team name: color or leader?
232     int voteName = 0;
233 
234     int i;
235     for ( i = players.Len()-1; i>=0; --i )
236     {
237         if ( players(i)->IsHuman() && ( players(i) != oldest && players(i)->nameTeamAfterMe ) )
238             voteName++;
239     }
240 
241     bool nameTeamColor = players.Len() > 1 && ( voteName * 2 < players.Len() || !oldest );
242 
243     if ( !IsHuman() )
244         nameTeamColor = false;
245     if ( !se_allowTeamNameColor )
246         nameTeamColor = false;
247     if ( !se_allowTeamNamePlayer )
248         nameTeamColor = true;
249 
250     nameTeamColor = NameTeamAfterColor ( nameTeamColor );
251 
252     if ( oldest )
253     {
254         if ( nameTeamColor )
255         {
256             // team name determined by color
257             tOutput newname;
258             newname << &se_team_name[ colorID ][0];
259 
260             name = newname;
261 
262             r = se_team_rgb[colorID][0];
263             g = se_team_rgb[colorID][1];
264             b = se_team_rgb[colorID][2];
265         }
266         else
267         {
268             // let oldest player own a human team
269             if ( IsHuman() )
270             {
271                 if ( players.Len() > 1 )
272                 {
273                     if ( sn_GetNetState() != nSERVER )
274                         oldest->UpdateName();
275                     tOutput newname;
276                     newname.SetTemplateParameter( 1, oldest->GetName() );
277                     newname << "$team_owned_by";
278 
279                     name = newname;
280                 }
281                 else
282                 {
283                     name = oldest->GetName();
284                 }
285             }
286             else
287             {
288                 // it's the AI team
289                 name = tOutput("$team_ai");
290             }
291 
292             r = oldest->r;
293             g = oldest->g;
294             b = oldest->b;
295 
296             // update colored player names
297             if ( sn_GetNetState() != nSERVER )
298             {
299                 for ( i = players.Len()-1; i>=0; --i )
300                 {
301                     players(i)->UpdateName();
302                 }
303             }
304         }
305     }
306     else
307     {
308         // empty team
309         name = tOutput("$team_empty");
310         r = g = b = 7;
311     }
312 
313     if ( nSERVER == sn_GetNetState() )
314         RequestSync();
315 
316     // update team members if the color changed
317     if ( oldr != r || oldg != g || oldb != b )
318     {
319         for ( int i = players.Len() - 1; i >= 0; --i )
320         {
321             players(i)->UpdateName();
322         }
323     }
324 }
325 
326 //update internal properties ( player count )
Update()327 void eTeam::Update()
328 {
329     UpdateProperties();
330     UpdateAppearance();
331 }
332 
333 // sets the lock status
SetLocked(bool locked)334 void eTeam::SetLocked( bool locked )
335 {
336     if ( locked && !locked_ )
337     {
338         sn_ConsoleOut( tOutput( "$invite_team_locked", Name() ) );
339     }
340     if ( !locked && locked_ )
341     {
342         sn_ConsoleOut( tOutput( "$invite_team_unlocked", Name() ) );
343     }
344 
345     locked_ = locked;
346 }
347 
348 // returns the lock status
IsLocked() const349 bool eTeam::IsLocked() const
350 {
351     return locked_;
352 }
353 
se_UnlockAllTeams(void)354 static void se_UnlockAllTeams ( void )
355 {
356     for ( int i = eTeam::teams.Len()-1; i>=0; --i )
357     {
358         (eTeam::teams(i))->SetLocked( false );
359     }
360 }
361 
se_UnlockAllTeamsConf(std::istream & s)362 static void se_UnlockAllTeamsConf ( std::istream & s )
363 {
364     if ( se_NeedsServer( "UNLOCK_ALL_TEAMS", s ) )
365     {
366         return;
367     }
368 
369     se_UnlockAllTeams();
370 }
371 
372 static tConfItemFunc se_unlockAllTeamsConf("UNLOCK_ALL_TEAMS",&se_UnlockAllTeamsConf);
373 static tAccessLevelSetter se_unlockAllTemasConfLevel( se_unlockAllTeamsConf, tAccessLevel_Moderator );
374 
375 // invite the player to join
Invite(ePlayerNetID * player)376 void eTeam::Invite( ePlayerNetID * player )
377 {
378     tASSERT( player );
379     if ( !IsInvited( player ) && this->IsLocked() )
380     {
381 	sn_ConsoleOut( tOutput( "$invite_team_can_join", player->GetColoredName(), Name() ) );
382     }
383     else if ( !IsInvited( player ) )
384     {
385         sn_ConsoleOut( tOutput( "$invite_team_invite", player->GetColoredName(), Name() ) );
386     }
387     player->invitations_.insert( this );
388 }
389 
390 // revoke an invitation
UnInvite(ePlayerNetID * player)391 void eTeam::UnInvite( ePlayerNetID * player )
392 {
393     tASSERT( player );
394     if ( player->CurrentTeam() == this && this->IsLocked() )
395     {
396         sn_ConsoleOut( tOutput( "$invite_team_kick", player->GetColoredName(), Name() ) );
397         player->SetTeam(0);
398     }
399     else
400     {
401         sn_ConsoleOut( tOutput( "$invite_team_uninvite", player->GetColoredName(), Name() ) );
402     }
403     player->invitations_.erase( this );
404 }
405 
406 // check if a player is invited
IsInvited(ePlayerNetID const * player) const407 bool eTeam::IsInvited( ePlayerNetID const * player ) const
408 {
409     return player->invitations_.find( const_cast< eTeam * >( this ) ) != player->invitations_.end();
410 }
411 
AddScore(int s)412 void eTeam::AddScore ( int s )
413 {
414     score += s;
415 
416     if ( nSERVER == sn_GetNetState() )
417         RequestSync();
418 }
419 
ResetScore()420 void eTeam::ResetScore ( )
421 {
422     score = 0;
423 
424     if ( nSERVER == sn_GetNetState() )
425         RequestSync();
426 }
427 
SetScore(int s)428 void eTeam::SetScore ( int s )
429 {
430     score = s;
431 
432     if ( nSERVER == sn_GetNetState() )
433         RequestSync();
434 }
435 
AddScore(int points,const tOutput & reasonwin,const tOutput & reasonlose)436 void eTeam::AddScore(int points,
437                      const tOutput& reasonwin,
438                      const tOutput& reasonlose)
439 {
440     if (points==0)
441         return;
442 
443     // delegate to player if this is a one-player team
444     if ( players.Len() == 1 && maxPlayersLocal == 1 )
445     {
446         players[0]->AddScore( points, reasonwin, reasonlose );
447         return;
448     }
449 
450     score += points;
451 
452     tOutput message;
453     message.SetTemplateParameter(1, GetColoredName());
454     message.SetTemplateParameter(2, points > 0 ? points : -points);
455 
456     if (points>0)
457     {
458         if (reasonwin.IsEmpty())
459             message << "$player_win_default";
460         else
461             message.Append(reasonwin);
462     }
463     else
464     {
465         if (reasonlose.IsEmpty())
466             message << "$player_lose_default";
467         else
468             message.Append(reasonlose);
469     }
470 
471     sn_ConsoleOut(message);
472     RequestSync(true);
473 
474     se_SaveToScoreFile(message);
475 }
476 
477 // *******************************************************************************
478 // *
479 // *	ResetScoreDifferences
480 // *
481 // *******************************************************************************
482 //!
483 //!
484 // *******************************************************************************
485 
ResetScoreDifferences(void)486 void eTeam::ResetScoreDifferences( void )
487 {
488     for ( int i = teams.Len()-1; i>=0; --i )
489     {
490         eTeam* t = teams(i);
491         if ( t->IsHuman() )
492             t->lastScore_ = t->score;
493     }
494 }
495 
496 // *******************************************************************************
497 // *
498 // *	LogScoreDifferences
499 // *
500 // *******************************************************************************
501 //!
502 //!
503 // *******************************************************************************
504 
LogScoreDifferences(void)505 void eTeam::LogScoreDifferences( void )
506 {
507     for ( int i = teams.Len()-1; i>=0; --i )
508     {
509         eTeam* t = teams(i);
510         t->LogScoreDifference();
511     }
512 }
513 
514 // *******************************************************************************
515 // *
516 // *	LogScoreDifference
517 // *
518 // *******************************************************************************
519 //!
520 //!
521 // *******************************************************************************
522 
523 static eLadderLogWriter se_roundScoreTeamWriter("ROUND_SCORE_TEAM", true);
524 
LogScoreDifference(void)525 void eTeam::LogScoreDifference( void )
526 {
527     if ( lastScore_ > IMPOSSIBLY_LOW_SCORE && IsHuman() )
528     {
529         tString ret;
530         int scoreDifference = score - lastScore_;
531         lastScore_ = IMPOSSIBLY_LOW_SCORE;
532         se_roundScoreTeamWriter << scoreDifference << ePlayerNetID::FilterName( Name() );
533         se_roundScoreTeamWriter.write();
534     }
535 }
536 
SwapTeamsNo(int a,int b)537 void eTeam::SwapTeamsNo(int a,int b){
538     if (0>a || teams.Len()<=a)
539         return;
540     if (0>b || teams.Len()<=b)
541         return;
542     if (a==b)
543         return;
544 
545     eTeam *A=teams(a);
546     eTeam *B=teams(b);
547 
548     teams(b)=A;
549     teams(a)=B;
550     A->listID=b;
551     B->listID=a;
552 }
553 
SortByScore()554 void eTeam::SortByScore(){
555     // bubble sort (AAARRGGH! but good for lists that change not much)
556 
557     bool inorder=false;
558     while (!inorder){
559         inorder=true;
560         int i;
561         for (i=teams.Len()-2;i>=0;i--)
562             if (teams(i)->score < teams(i+1)->score){
563                 SwapTeamsNo(i,i+1);
564                 inorder=false;
565             }
566     }
567 }
568 
Ranking(int MAX,bool cut)569 tString eTeam::Ranking( int MAX, bool cut ){
570     SortByScore();
571 
572     tColoredString ret;
573 
574     if (teams.Len()>0){
575         ret << tColoredString::ColorString(1,.5,.5);
576         ret << tOutput("$team_scoretable_name");
577         ret << tColoredString::ColorString(1,1,1);
578         ret.SetPos(24, cut );
579         ret << tOutput("$team_scoretable_score");
580         ret << "\n";
581 
582         int max = teams.Len();
583         if ( max > MAX && MAX > 0 )
584         {
585             max = MAX ;
586         }
587         for (int i=0;i<max;i++){
588             tColoredString line;
589             eTeam *t = teams(i);
590             line << ColorString(t);
591             tString name = t->Name();
592             //name.RemoveHex();
593             name.SetPos( 24, cut );
594 
595             line << name;
596             line << tColoredString::ColorString(1,1,1);
597             line.SetPos(24, false );
598             line << t->score;
599             ret << line << "\n";
600         }
601         if ( max < teams.Len() )
602         {
603             ret << "...\n";
604         }
605     }
606     // else
607     //    ret << tOutput("$team_scoretable_nobody");
608     return ret;
609 }
610 
611 
612 // get the number of human players on the team
NumHumanPlayers() const613 int	eTeam::NumHumanPlayers	(		) const
614 {
615     return numHumans;
616 }
617 
618 static int imbalance = 1;
619 
620 // get the number of human players on the team
NumAIPlayers() const621 int	eTeam::NumAIPlayers	(		) const
622 {
623     return numAIs;
624 }
625 
626 // make sure the limits on team number and such are met
EnforceConstraints()627 void eTeam::EnforceConstraints()
628 {
629     if ( maxImbalance < 1 )
630         maxImbalance = 1;
631 
632     if ( minTeams > maxTeams )
633         minTeams = maxTeams;
634 
635     Enforce( minTeams, maxTeams, maxImbalance );
636 
637     // reset imbalance count so players may try to switch teams in the next round
638     if ( imbalance <= 0 )
639         imbalance=0;
640 }
641 
642 enum eTeamEliminationMode
643 {
644     TEAM_ELIMINATION_SIZE  = 0, // eliminate smallest team
645     TEAM_ELIMINATION_COLOR = 1, // eliminate ugliest team
646     TEAM_ELIMINATION_SCORE = 2  // eliminate suckiest team
647 };
648 
649 tCONFIG_ENUM( eTeamEliminationMode );
650 
651 static eTeamEliminationMode se_teamEliminationMode = TEAM_ELIMINATION_SIZE;
652 static tSettingItem<eTeamEliminationMode> se_teamEliminationModeConf("TEAM_ELIMINATION_MODE", se_teamEliminationMode );
653 
654 // make sure the limits on team number and such are met
Enforce(int minTeams,int maxTeams,int maxImbalance)655 void eTeam::Enforce( int minTeams, int maxTeams, int maxImbalance)
656 {
657     if ( maxTeams < 1 )
658         maxTeams = 1;
659 
660     /*
661     // z-man: disabled for new "respect maxTeams and maxPlayers setting" logic
662     if ( maxPlayers * maxTeams < se_PlayerNetIDs.Len() )
663     {
664         maxPlayers = ( se_PlayerNetIDs.Len()/maxTeams ) + 1;
665     }
666     */
667 
668     // nothing to be done on the clients
669     if ( nCLIENT == sn_GetNetState() )
670         return;
671 
672     if ( maxImbalance < 1 )
673         maxImbalance = 1;
674 
675     if ( minTeams > maxTeams )
676         minTeams = maxTeams;
677 
678     if ( minPlayers > maxPlayers )
679         minPlayers = maxPlayers;
680 
681     bool balance = false;
682 
683     int giveUp = 10;
684     while ( !balance && giveUp-- > 0 )
685     {
686         balance = true;
687 
688         // find the max and min number of players per team and the
689         eTeam *max = NULL, *min = NULL, *ai = NULL, *lastColor = NULL, *last = NULL;
690         int    maxP = minPlayers, minP = 100000;
691         int    maxColorID = 0;
692 
693         int numTeams = 0;
694         int numHumanTeams = 0;
695 
696         int i;
697         for ( i = teams.Len()-1; i>=0; --i )
698         {
699             eTeam *t = teams(i);
700 
701             if ( t->BalanceThisTeam() )
702             {
703                 int humans = t->NumHumanPlayers();
704 
705                 numTeams++;
706 
707                 if ( humans > 0 )
708                     numHumanTeams++;
709                 else
710                     ai = t;
711 
712                 if ( humans > maxP )
713                 {
714                     maxP = humans;
715                     max  = t;
716                 }
717 
718                 // mode 0: prefer unlocked teams as elimination victims, and of course smaller teams
719                 if ( ( humans > 0 || t->NumPlayers() == 0 ) && humans < minP && !t->IsLocked() )
720                 {
721                     minP = humans;
722                     min  = t;
723                 }
724 
725                 // mode 1: keep the first teams (Team blue, Team gold, etc)
726                 if ( ( humans > 0 || t->NumPlayers() == 0 ) && t->colorID > maxColorID && t == se_ColoredTeams[t->colorID])
727                 {
728                     maxColorID = t->colorID;
729                     lastColor = t;
730                 }
731 
732                 // mode 2: lowest score goes out
733                 if ( !last )
734                 {
735                     last = t;
736                 }
737             }
738         }
739 
740         eTeam * teamToKill = NULL;
741         if( (int)se_teamEliminationMode > 2 ) se_teamEliminationMode = TEAM_ELIMINATION_SIZE;
742         // let negative values simply "lock" teams like you lock a server with a low MAX_CLIENTS
743         switch ( se_teamEliminationMode )
744         {
745             case TEAM_ELIMINATION_SIZE:
746                 teamToKill = min;
747                 break;
748             case TEAM_ELIMINATION_COLOR:
749                 teamToKill = lastColor;
750                 break;
751             case TEAM_ELIMINATION_SCORE:
752                 teamToKill = last;
753                 break;
754         }
755 
756         if ( ( numTeams > maxTeams && teamToKill ) || ( numTeams > minTeams && ai ) )
757         {
758             // too many teams. Destroy the smallest team.
759             // better: destroy the AI team
760             if ( ai )
761                 teamToKill = ai;
762 
763             for ( i = teamToKill->NumPlayers()-1; i>=0; --i )
764             {
765                 // one player from the dismantled team.
766                 tJUST_CONTROLLED_PTR< ePlayerNetID > pni = teamToKill->Player(i);
767 
768                 // just ignore AIs, they get removed later by the "balance with AIs" code once it notices all humans are gone from this team
769                 if ( !pni->IsHuman() )
770                 {
771                     continue;
772                 }
773 
774                 // find the second smallest team:
775                 eTeam* second = NULL;
776                 int secondMinP = maxPlayers; // the number of humans on that team
777                 for ( int j = teams.Len()-1; j>=0; --j )
778                 {
779                     eTeam *t = teams(j);
780 
781                     if ( t->BalanceThisTeam() )
782                     {
783                         int humans = t->NumHumanPlayers();
784 
785                         if ( humans < secondMinP && t != teamToKill )
786                         {
787                             secondMinP = humans;
788                             second = t;
789                         }
790                     }
791                 }
792 
793                 if ( second )
794                 {
795                     // put the player into the second smallest team, overriding balancing settings (they're likely to be in the way )
796                     int imbBackup = second->maxImbalanceLocal;
797                     second->maxImbalanceLocal = 99999;
798                     pni->SetTeamForce( 0 );
799                     pni->UpdateTeamForce();
800                     pni->SetTeamForce( second );
801                     pni->UpdateTeamForce();
802                     second->maxImbalanceLocal = imbBackup;
803 
804                     balance = false;
805                 }
806                 else
807                 {
808                     // no room, kick the player out
809                     pni->SetTeamForce( NULL );
810                     pni->UpdateTeamForce();
811 
812                     balance = false;
813                 }
814             }
815         }
816         else if ( numTeams < minTeams )
817         {
818             // too few teams. Create a new one
819             eTeam *newTeam = tNEW( eTeam );
820             teams.Add( newTeam, newTeam->listID );
821             newTeam->UpdateProperties();
822 
823             balance = false;
824         }
825         else if ( ( ( maxP - maxImbalance > minP || maxP > maxPlayers ) && minP < maxPlayers ) || ( minP == 0 && maxP > 1 ) )
826         {
827             // teams are unbalanced; move one player from the strongest team to the weakest
828             if ( max )
829             {
830                 ePlayerNetID* unluckyOne = max->YoungestHumanPlayer();
831                 unluckyOne->SetTeamForce( min );
832                 unluckyOne->UpdateTeamForce();
833                 balance = false;
834             }
835         }
836         else if ( maxP > maxPlayers )
837         {
838             // teams too large. create a new team and put the last joiner of the strongest team in
839             eTeam* newTeam = tNEW( eTeam );
840             if ( max )
841             {
842                 ePlayerNetID* unluckyOne = max->YoungestHumanPlayer();
843                 unluckyOne->SetTeamForce( newTeam );
844                 unluckyOne->UpdateTeamForce();
845 
846                 balance = false;
847             }
848         }
849     }
850 }
851 
WritePlayers(eLadderLogWriter & writer,const eTeam * team)852 void eTeam::WritePlayers( eLadderLogWriter & writer, const eTeam *team )
853 {
854     for ( int i = team->players.Len() - 1; i >= 0; --i )
855     {
856         writer << team->players( i )->GetLogName();
857     }
858 }
859 
860 // inquire or set the ability to use a color as a team name
NameTeamAfterColor(bool wish)861 bool eTeam::NameTeamAfterColor ( bool wish )
862 {
863     // reassign colors if colorID >= maxTeams
864     if ( wish && colorID >= maxTeams && se_teamEliminationMode == TEAM_ELIMINATION_COLOR )
865     {
866         NameTeamAfterColor( false );
867     }
868 
869     if ( wish && colorID < 0 )
870     {
871         for ( int i = 0; i < TEAMCOLORS; ++i )
872         {
873             if ( !se_ColoredTeams[i] )
874             {
875                 se_ColoredTeams[i] = this;
876                 colorID = i;
877                 return true;
878             }
879         }
880     }
881 
882     if ( !wish && colorID >= 0 )
883     {
884         se_ColoredTeams[ colorID ] = 0;
885         colorID = -1;
886     }
887 
888     return colorID >= 0;
889 }
890 
891 // register a player
AddPlayer(ePlayerNetID * player)892 void eTeam::AddPlayer    ( ePlayerNetID* player )
893 {
894     tASSERT( player );
895 
896     tJUST_CONTROLLED_PTR< eTeam > keepalive( this );
897 
898     if ( ! PlayerMayJoin( player ) )
899         return;
900 
901     tJUST_CONTROLLED_PTR< eTeam > oldTeam( player->currentTeam );
902     tString oldTeamName("Old Team (BUG)");
903     if ( player->currentTeam )
904     {
905         oldTeamName = oldTeam->Name();
906         player->currentTeam->RemovePlayerDirty( player );
907         oldTeam->UpdateProperties();
908         oldTeam->UpdateAppearance();
909     }
910 
911     players.Add( player, player->teamListID );
912     // bool teamChange = player->currentTeam;
913     player->currentTeam = this;
914     player->timeJoinedTeam = tSysTimeFloat();
915 
916     UpdateProperties();
917 
918     // print the new entry
919     if ( players.Len() <= 1 )
920     {
921         UpdateAppearance();
922 
923         /*
924         // print creation message
925         tOutput message;
926         message.SetTemplateParameter(1, player->GetName() );
927         message.SetTemplateParameter(2, Name() );
928         message << "$player_creates_team";
929 
930         sn_ConsoleOut( message );
931         */
932     }
933 
934     // anounce joining if there are is more than one member now or if the team is color-named
935     if ( sn_GetNetState() != nCLIENT )
936     {
937         // get colored player name
938         tColoredString playerName;
939         playerName << *player << tColoredString::ColorString(.5,1,.5);
940 
941         // tString playerNameNoColor = tColoredString::RemoveColors( player->GetName() );
942 
943         if ( ( players.Len() > 1 || colorID >= 0 ) && IsHuman() )
944         {
945             if ( oldTeam && oldTeam->players.Len() >= 1 )
946             {
947                 sn_ConsoleOut( tOutput( "$player_changes_team",
948                                         playerName,
949                                         Name(),
950                                         oldTeamName ) );
951             }
952             else
953             {
954                 // print join message
955                 sn_ConsoleOut( tOutput( "$player_joins_team_start",
956                                         playerName,
957                                         Name() ) );
958             }
959         }
960         else if ( oldTeam )
961         {
962             // or at least the leaving of the old team
963             if ( oldTeam->players.Len() > 0 )
964                 sn_ConsoleOut( tOutput( "$player_leaves_team",
965                                         playerName,
966                                         oldTeamName ) );
967         }
968         else
969         {
970             // announce a generic join
971             sn_ConsoleOut( tOutput( "$player_entered_game", playerName ) );
972         }
973     }
974 
975     if ( listID < 0 )
976     {
977         teams.Add ( this, listID );
978     }
979 
980     player->UpdateName();
981 }
982 
983 // register a player the dirty way
AddPlayerDirty(ePlayerNetID * player)984 void eTeam::AddPlayerDirty   ( ePlayerNetID* player )
985 {
986     tASSERT( player );
987 
988     if ( player->currentTeam )
989     {
990         player->currentTeam->RemovePlayerDirty ( player );
991     }
992 
993     players.Add( player, player->teamListID );
994     player->currentTeam = player->nextTeam = this;
995     player->timeJoinedTeam = tSysTimeFloat();
996 
997     if ( listID < 0 )
998     {
999         teams.Add ( this, listID );
1000     }
1001 
1002     player->UpdateName();
1003 }
1004 
1005 // deregister a player
RemovePlayerDirty(ePlayerNetID * player)1006 void eTeam::RemovePlayerDirty ( ePlayerNetID* player )
1007 {
1008     tASSERT( player );
1009     tASSERT( player->currentTeam == this );
1010 
1011     // remove player without shuffling the list
1012     for ( int i = players.Len()-2; i >= player->teamListID; --i )
1013     {
1014         ePlayerNetID * shuffle = players(i);
1015         players.Remove( shuffle, shuffle->teamListID );
1016         players.Add   ( shuffle, shuffle->teamListID );
1017     }
1018     tASSERT ( player->teamListID == players.Len()-1 );
1019 
1020     // now player has been shuffled to the back of the list without disturbing
1021     // the order of the other players and can be removed
1022     players.Remove ( player, player->teamListID );
1023     player->currentTeam = NULL;
1024 
1025     // remove team from list
1026     if ( listID >= 0 && players.Len() == 0 )
1027     {
1028         teams.Remove( this, listID );
1029 
1030         // don't forget the colored team list
1031         if ( colorID >= 0 )
1032         {
1033             se_ColoredTeams[ colorID ] = 0;
1034             colorID = -1;
1035         }
1036     }
1037 }
1038 
1039 // deregister a player
RemovePlayer(ePlayerNetID * player)1040 void eTeam::RemovePlayer ( ePlayerNetID* player )
1041 {
1042     tCONTROLLED_PTR( eTeam ) safety;
1043     safety = this; 						// avoid premature destruction of this team
1044 
1045     RemovePlayerDirty( player );
1046 
1047     player->UpdateName();
1048 
1049     // get colored player name
1050     tColoredString playerName;
1051     playerName << *player << tColoredString::ColorString(1,.5,.5);
1052 
1053     if ( sn_GetNetState() != nCLIENT )
1054     {
1055         if ( players.Len() > 0 || colorID >= 0  )
1056         {
1057             sn_ConsoleOut( tOutput( "$player_leaves_team",
1058                                     playerName,
1059                                     Name() ) );
1060         }
1061         else
1062         {
1063             // announce a generic leave
1064             sn_ConsoleOut( tOutput( "$player_leaving_game", playerName ) );
1065         }
1066     }
1067 
1068     UpdateProperties();
1069 
1070     // trigger enforcement of strong constraints on next balancing if the player is quitting
1071     if ( enforceRulesOnQuit && 0 == player->nextTeam && nCLIENT != sn_GetNetState() )
1072         imbalance = -10;
1073 }
1074 
1075 
1076 // see if the given player may join this team
PlayerMayJoin(const ePlayerNetID * player) const1077 bool eTeam::PlayerMayJoin( const ePlayerNetID* player ) const
1078 {
1079     int i;
1080 
1081     // AI players are always allowed to join, the logic that tries to put the AI into
1082     // this team is responsible for checking
1083     if ( !player->IsHuman() )
1084         return true;
1085 
1086     // suspended players cannot join
1087     if ( player->GetSuspended() > 0 )
1088         return false;
1089 
1090     // check for invitations. Not with those shoes!
1091     if ( IsLocked() && !IsInvited( player ) )
1092     {
1093         return false;
1094     }
1095 
1096     int maxInb = maxImbalanceLocal;
1097 
1098     int minP = 10000; // minimum number of humans in a team after the player left
1099     if ( bool(player) && bool(player->currentTeam) )
1100     {
1101         minP = player->currentTeam->NumHumanPlayers() - 1;
1102 
1103         // allow leaving a team if it vanishes and the number of teams does not shrink below the minimum team count
1104         if ( minP == 0 && teams.Len() > minTeams )
1105             minP = 10000;
1106     }
1107 
1108     for ( i = teams.Len()-1; i>=0; --i )
1109     {
1110         eTeam *t = teams(i);
1111 
1112         if ( !t->IsLocked() && t->BalanceThisTeam() )
1113         {
1114             int humans = t->NumHumanPlayers();
1115 
1116             if ( humans < minP )
1117             {
1118                 minP = humans;
1119             }
1120         }
1121     }
1122 
1123     int maxPlayers = maxPlayersLocal;
1124 
1125     // we must have room           and the joining must not cause huge imbalance
1126     if ( numHumans < maxPlayers && ( sn_GetNetState() != nSERVER || minP + maxInb > numHumans ) )
1127         return true;
1128 
1129     // always allow circular swapping of players
1130     {
1131         std::set< eTeam const * > swapTargets; // teams players from this team want to swap into (recursively, if someone wants to swap to B and someone else from B wants to swap to C, C is on the list, too)
1132         swapTargets.insert( this );
1133 
1134         bool goon = true;
1135         while ( goon )
1136         {
1137             goon = false;
1138             for ( std::set< eTeam const * >::iterator iter = swapTargets.begin(); iter != swapTargets.end(); ++iter )
1139             {
1140                 eTeam const * team = *iter;
1141                 for ( i = team->players.Len()-1; i>=0; --i )
1142                 {
1143                     ePlayerNetID * otherPlayer = team->players(i);
1144                     eTeam * swapTeam = otherPlayer->NextTeam();
1145                     if ( swapTeam && swapTeam != otherPlayer->CurrentTeam() && swapTargets.find( swapTeam ) == swapTargets.end() )
1146                     {
1147                         goon = true;
1148                         swapTargets.insert( swapTeam );
1149 
1150                         // early return if we find a closed swap chain
1151                         if ( swapTeam == player->CurrentTeam() )
1152                             return true;
1153                     }
1154                 }
1155             }
1156         }
1157     }
1158 
1159     // sorry, no way
1160     return false;
1161 }
1162 
1163 
1164 // is it allowed to create a new team?
NewTeamAllowed()1165 bool eTeam::NewTeamAllowed	()
1166 {
1167     return teams.Len() < maxTeams;
1168 }
1169 
1170 // if this flag is set, the center player is the boss of a team.
1171 // if it isn't set, the oldest player is boss.
1172 static bool se_centerPlayerIsBoss=true;
1173 static tSettingItem<bool> se_centerPlayerIsBossConf("TEAM_CENTER_IS_BOSS", se_centerPlayerIsBoss );
1174 
1175 // the oldest player
OldestPlayer() const1176 ePlayerNetID*	eTeam::OldestPlayer	(		) const
1177 {
1178     ePlayerNetID* ret = NULL;
1179 
1180     for (int i= players.Len(); i>=0; i--)
1181     {
1182         ePlayerNetID* p = players(i);
1183         if (!ret || ret->timeJoinedTeam > p->timeJoinedTeam || se_centerPlayerIsBoss )
1184         {
1185             ret = p;
1186         }
1187     }
1188 
1189     return ret;
1190 }
1191 
1192 // the oldest human player
OldestHumanPlayer() const1193 ePlayerNetID*	eTeam::OldestHumanPlayer(		) const
1194 {
1195     ePlayerNetID* ret = NULL;
1196 
1197     for (int i= players.Len()-1; i>=0; i--)
1198     {
1199         ePlayerNetID* p = players(i);
1200         if ( p->IsHuman() && ( !ret || ret->timeJoinedTeam > p->timeJoinedTeam || se_centerPlayerIsBoss ) )
1201         {
1202             ret = p;
1203         }
1204     }
1205 
1206     if ( !ret )
1207     {
1208         // nobody? Darn. Look for a player that has this team set as next team.
1209 
1210         for (int i= se_PlayerNetIDs.Len()-1; i>=0; i--)
1211         {
1212             ePlayerNetID* p = se_PlayerNetIDs(i);
1213             if ( p->NextTeam() == this && p->IsHuman() && ( !ret || ret->timeJoinedTeam > p->timeJoinedTeam || se_centerPlayerIsBoss ) )
1214             {
1215                 ret = p;
1216             }
1217         }
1218     }
1219 
1220     return ret;
1221 }
1222 
1223 // the oldest AI player
OldestAIPlayer() const1224 ePlayerNetID*	eTeam::OldestAIPlayer	(		) const
1225 {
1226     ePlayerNetID* ret = NULL;
1227 
1228     for (int i= players.Len()-1; i>=0; i--)
1229     {
1230         ePlayerNetID* p = players(i);
1231         if ( ( !p->IsHuman() ) && ( !ret || ret->timeJoinedTeam > p->timeJoinedTeam || se_centerPlayerIsBoss ) )
1232         {
1233             ret = p;
1234         }
1235     }
1236 
1237     return ret;
1238 }
1239 
1240 // the youngest player
YoungestPlayer() const1241 ePlayerNetID*	eTeam::YoungestPlayer	(		) const
1242 {
1243     ePlayerNetID* ret = NULL;
1244 
1245     for (int i= players.Len(); i>=0; i--)
1246     {
1247         ePlayerNetID* p = players(i);
1248         if (!ret || ret->timeJoinedTeam < p->timeJoinedTeam )
1249         {
1250             ret = p;
1251         }
1252     }
1253 
1254     return ret;
1255 }
1256 
1257 // the youngest human player
YoungestHumanPlayer() const1258 ePlayerNetID*	eTeam::YoungestHumanPlayer(		) const
1259 {
1260     ePlayerNetID* ret = NULL;
1261 
1262     for (int i= players.Len()-1; i>=0; i--)
1263     {
1264         ePlayerNetID* p = players(i);
1265         if ( p->IsHuman() && ( !ret || ret->timeJoinedTeam < p->timeJoinedTeam ) )
1266         {
1267             ret = p;
1268         }
1269     }
1270 
1271     return ret;
1272 }
1273 
1274 // the youngest AI player
YoungestAIPlayer() const1275 ePlayerNetID*	eTeam::YoungestAIPlayer	(		) const
1276 {
1277     ePlayerNetID* ret = NULL;
1278 
1279     for (int i= players.Len()-1; i>=0; i--)
1280     {
1281         ePlayerNetID* p = players(i);
1282         if ( ( !p->IsHuman() ) && ( !ret || ret->timeJoinedTeam < p->timeJoinedTeam ) )
1283         {
1284             ret = p;
1285         }
1286     }
1287 
1288     return ret;
1289 }
1290 
1291 // is anyone still alive?
Alive() const1292 bool eTeam::Alive ( ) const
1293 {
1294     for (int i= players.Len()-1; i>=0; --i)
1295     {
1296         ePlayerNetID* p = players(i);
1297         if ( p->Object() && p->Object()->Alive() )
1298         {
1299             return true;
1300         }
1301     }
1302 
1303     return false;
1304 }
1305 
1306 
1307 // print out an understandable name in to s
PrintName(tString & s) const1308 void eTeam::PrintName(tString &s) const
1309 {
1310     s << "Team " << name;
1311 }
1312 
1313 
1314 
1315 // we must not transmit an object that contains pointers to non-transmitted objects.
1316 // this function is supposed to check that.
ClearToTransmit(int user) const1317 bool eTeam::ClearToTransmit(int user) const
1318 {
1319     return true;
1320 }
1321 
1322 
1323 // syncronisation functions:
1324 
1325 // store sync message in m
WriteSync(nMessage & m)1326 void eTeam::WriteSync(nMessage &m)
1327 {
1328     m << r;
1329     m << g;
1330     m << b;
1331     m << name;
1332     m << maxPlayersLocal;
1333     m << maxImbalanceLocal;
1334     m << score;
1335 }
1336 
1337 
1338 // guess what
ReadSync(nMessage & m)1339 void eTeam::ReadSync(nMessage &m)
1340 {
1341     m >> r;
1342     m >> g;
1343     m >> b;
1344     m >> name;
1345     m >> maxPlayersLocal;
1346     m >> maxImbalanceLocal;
1347     m >> score;
1348 
1349     // update colored player names
1350     if ( sn_GetNetState() != nSERVER )
1351     {
1352         for ( int i = players.Len()-1; i>=0; --i )
1353         {
1354             players(i)->UpdateName();
1355         }
1356     }
1357 }
1358 
1359 
1360 // is the message newer	than the last accepted sync
SyncIsNew(nMessage & m)1361 bool eTeam::SyncIsNew(nMessage &m)
1362 {
1363     return true;
1364 }
1365 
1366 
1367 // the extra information sent on creation:
1368 // store sync message in m
1369 // the information written by this function should
1370 // be read from the message in the "message"- connstructor
WriteCreate(nMessage & m)1371 void eTeam::WriteCreate(nMessage &m)
1372 {
1373     nNetObject::WriteCreate(m);
1374 }
1375 
1376 
1377 // control functions:
1378 // receives the control message. the data written to the message created
1379 // by *NewControlMessage() can be read directly from m.
ReceiveControlNet(nMessage & m)1380 void eTeam::ReceiveControlNet(nMessage &m)
1381 {
1382 }
1383 
1384 
1385 
1386 // con/desstruction
1387 // default constructor
eTeam()1388 eTeam::eTeam()
1389         :colorID(-1),listID(-1), roundsPlayed(0)
1390 {
1391     score = 0;
1392     lastScore_=IMPOSSIBLY_LOW_SCORE;
1393     locked_ = false;
1394     maxPlayersLocal = maxPlayers;
1395     maxImbalanceLocal = maxImbalance;
1396     r = g = b = 32; // initialize color so it will be updated, guaranteed
1397     Update();
1398 }
1399 
1400 
1401 // remote constructor
eTeam(nMessage & m)1402 eTeam::eTeam(nMessage &m)
1403         :nNetObject( m ),
1404         colorID(-1),listID(-1)
1405 {
1406     score = 0;
1407     lastScore_=IMPOSSIBLY_LOW_SCORE;
1408     locked_ = false;
1409     maxPlayersLocal = maxPlayers;
1410     maxImbalanceLocal = maxImbalance;
1411     r = g = b = 32; // initialize color so it will be updated, guaranteed
1412     Update();
1413 }
1414 
1415 // destructor
~eTeam()1416 eTeam::~eTeam()
1417 {
1418     if ( listID >= 0 )
1419         teams.Remove( this, listID );
1420 
1421     if ( colorID >= 0 )
1422     {
1423         se_ColoredTeams[ colorID ] = 0;
1424         colorID = -1;
1425     }
1426 
1427     // revoke all invitations
1428     for ( int i = se_PlayerNetIDs.Len()-1; i >= 0; --i )
1429     {
1430         se_PlayerNetIDs(i)->invitations_.erase( this );
1431     }
1432 }
1433 // *******************************************************************************
1434 // *
1435 // *	Enemies
1436 // *
1437 // *******************************************************************************
1438 //!
1439 //!		@param	team	the team to check
1440 //!		@param	player	the player to check
1441 //!		@return		    true if the player has an enemy in the team
1442 //!
1443 // *******************************************************************************
1444 
Enemies(eTeam const * team,ePlayerNetID const * player)1445 bool eTeam::Enemies( eTeam const * team, ePlayerNetID const * player )
1446 {
1447     // nonexistant parties can't be enemies
1448     if (!player || !team)
1449         return false;
1450 
1451     // check if the player is a team member
1452     if ( player->CurrentTeam() == team )
1453         return false;
1454 
1455     // check if the player has a team
1456     if ( !player->currentTeam )
1457         return false;
1458 
1459     // go through player list; the player is an enemy if he is at least enemy with one of the menbers
1460     for (int i = team->players.Len()-1; i>=0; --i)
1461         if ( ePlayerNetID::Enemies( team->players(i), player ) )
1462             return true;
1463 
1464     return false;
1465 }
1466 
1467 // *******************************************************************************
1468 // *
1469 // *	Enemies
1470 // *
1471 // *******************************************************************************
1472 //!
1473 //!		@param	team1
1474 //!		@param	team2
1475 //!		@return
1476 //!
1477 // *******************************************************************************
1478 
Enemies(eTeam const * team1,eTeam const * team2)1479 bool eTeam::Enemies( eTeam const * team1, eTeam const * team2 )
1480 {
1481     // nonexistant parties can't be enemies
1482     if (!team1 || !team2 || team1 == team2)
1483         return false;
1484 
1485     // go through player list; if one is an enemy, so is the team
1486     for (int i = team2->players.Len()-1; i>=0; --i)
1487         if ( Enemies( team1, team2->players(i) ) )
1488             return true;
1489 
1490     return false;
1491 }
1492 
1493 // *******************************************************************************
1494 // *
1495 // *	SwapPlayers
1496 // *
1497 // *******************************************************************************
1498 //!
1499 //!		@param	player1	first player to swap positions
1500 //!		@param	player2	second player to swap positions
1501 //!
1502 // *******************************************************************************
1503 
SwapPlayers(ePlayerNetID * player1,ePlayerNetID * player2)1504 void eTeam::SwapPlayers( ePlayerNetID * player1, ePlayerNetID * player2 )
1505 {
1506     tASSERT( player1 );
1507     tASSERT( player2 );
1508 
1509     // swap IDs
1510     int id3 = player1->teamListID;
1511     player1->teamListID = player2->teamListID;
1512     player2->teamListID = id3;
1513 
1514     // adjust pointers from teams
1515     eTeam * team2 = player1->CurrentTeam();
1516     eTeam * team1 = player2->CurrentTeam();
1517 
1518     if ( team2 )
1519         team2->players[player2->teamListID] = player2;
1520     if ( team1 )
1521         team1->players[player1->teamListID] = player1;
1522 
1523     // swap teams
1524     player1->currentTeam = team1;
1525     player2->currentTeam = team2;
1526 
1527     // swap next teams (if current teams differ)
1528     team1 = player2->NextTeam();
1529     team2 = player1->NextTeam();
1530     if ( player1->currentTeam != player2->currentTeam )
1531     {
1532         player1->nextTeam = team1;
1533         player2->nextTeam = team2;
1534     }
1535 }
1536 
1537 // *******************************************************************************
1538 // *
1539 // *	Shuffle
1540 // *
1541 // *******************************************************************************
1542 //!
1543 //!		@param	startID	player ID to move around
1544 //!		@param	stopID	player ID to move it to
1545 //!
1546 // *******************************************************************************
1547 
Shuffle(int startID,int stopID)1548 void eTeam::Shuffle( int startID, int stopID )
1549 {
1550     tASSERT( 0 <= startID && startID < players.Len() );
1551     tASSERT( 0 <= stopID && stopID < players.Len() );
1552 
1553     if ( startID == stopID )
1554         return;
1555 
1556     ePlayerNetID *player = players[startID];
1557     eShuffleSpamTester & spam = player->shuffleSpam;
1558 
1559     if ( spam.ShouldAnnounce() )
1560     {
1561         sn_ConsoleOut( player->shuffleSpam.ShuffleMessage( player, startID + 1, stopID + 1 ) );
1562     }
1563 
1564     // simply swap the one player over all the players in between.
1565     while ( startID < stopID )
1566     {
1567         SwapPlayers( players[startID], players[startID+1] );
1568         startID++;
1569     }
1570     while ( startID > stopID )
1571     {
1572         SwapPlayers( players[startID], players[startID-1] );
1573         startID--;
1574     }
1575 
1576     spam.Shuffle();
1577 }
1578 
GetColoredName(void) const1579 tColoredString eTeam::GetColoredName(void) const
1580 {
1581     tColoredString ret;
1582     return ret << tColoredString::ColorString( R() / 15.0, G() / 15.0, B() / 15.0)
1583         << Name()
1584         << tColoredString::ColorString(-1, -1, -1);
1585 }
1586