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