/* ************************************************************************* ArmageTron -- Just another Tron Lightcycle Game in 3D. Copyright (C) 2000 Manuel Moos (manuel@moosnet.de) ************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *************************************************************************** */ #include "eTeam.h" #include "tSysTime.h" #include "rFont.h" #include "nConfig.h" #define TEAMCOLORS 8 static unsigned short se_team_rgb[TEAMCOLORS][3]= { { 4, 8, 15 } , // blue { 15, 15, 4 } , // gold { 15, 4, 4 } , // red { 4, 15, 4 } , // green { 15, 4, 15 } , // violet { 4, 15, 15 } , // ugly green { 15, 15, 15 } , // white { 7, 7, 7 } // black }; static char* se_team_name[TEAMCOLORS]= { "$team_name_blue", "$team_name_gold", "$team_name_red", "$team_name_green", "$team_name_violet", "$team_name_ugly", "$team_name_white", "$team_name_black" }; static const char* ColorString(const eTeam *t) { return ColorString( t->R()/15.0f, t->G()/15.0f, t->B()/15.0f ); } nNOInitialisator eTeam_init(220,"eTeam"); nDescriptor &eTeam::CreatorDescriptor() const{ return eTeam_init; } int eTeam::minTeams=0; // minimum nuber of teams int eTeam::maxTeams=30; // maximum nuber of teams int eTeam::minPlayers=0; // minimum number of players per team int eTeam::maxPlayers=3; // maximum number of players per team int eTeam::maxImbalance=2; // maximum difference of player numbers int eTeam::maxPermImbalance=1; // maximum difference of player numbers bool eTeam::balanceWithAIs=true; // use AI players to balance the teams? bool eTeam::enforceRulesOnQuit=false; // if the quitting of one player unbalances the teams, enforce the rules by redistributing tList eTeam::teams; // list of all teams static bool newTeamAllowed; // is it allowed to create a new team currently? static nSettingItem se_newTeamAllowed("NEW_TEAM_ALLOWED", newTeamAllowed ); static bool se_allowTeamNameColor = true; // allow to name a team after a color static bool se_allowTeamNamePlayer = true; // allow to name a team after the leader // update all internal information void eTeam::UpdateStaticFlags() { bool newTeamAllowedCurrent = teams.Len() >= maxTeams; if ( newTeamAllowedCurrent != newTeamAllowed ) { se_newTeamAllowed.Set( newTeamAllowedCurrent ); for (int i = teams.Len() - 1; i>=0; --i) teams(i)->Update(); } } //update internal properties ( player count ) void eTeam::UpdateProperties() { // bool change = false; if ( nCLIENT != sn_GetNetState() ) { if ( maxPlayersLocal != maxPlayers ) { maxPlayersLocal = maxPlayers; // change = true; } if ( maxImbalanceLocal != maxImbalance ) { maxImbalanceLocal = maxImbalance; // change = true; } // if ( change ) // { // } } numHumans = 0; numAIs = 0; int i; for ( i = players.Len()-1; i>=0; --i ) { if ( players(i)->IsHuman() ) { if ( players(i)->IsActive() ) ++numHumans; } else ++numAIs; } if ( nSERVER == sn_GetNetState() ) RequestSync(); } // update name and color void eTeam::UpdateAppearance() { ePlayerNetID* oldest = OldestHumanPlayer(); if ( !oldest ) { oldest = OldestAIPlayer(); } // vote on team name: color or leader? int voteName = 0; int i; for ( i = players.Len()-1; i>=0; --i ) { if ( players(i)->IsHuman() && ( players(i) != oldest && players(i)->nameTeamAfterMe ) ) voteName++; } bool nameTeamColor = players.Len() > 1 && ( voteName * 2 < players.Len() || !oldest ); if ( !IsHuman() ) nameTeamColor = false; if ( !se_allowTeamNameColor ) nameTeamColor = false; if ( !se_allowTeamNamePlayer ) nameTeamColor = true; nameTeamColor = NameTeamAfterColor ( nameTeamColor ); if ( oldest ) { if ( nameTeamColor ) { // team name determined by color tOutput newname; newname << se_team_name[ colorID ]; name = newname; r = se_team_rgb[colorID][0]; g = se_team_rgb[colorID][1]; b = se_team_rgb[colorID][2]; } else { // let oldest own the team if ( players.Len() > 1 ) { if ( oldest->IsHuman() ) { tOutput newname; newname.SetTemplateParameter( 1, oldest->name ); newname << "$team_owned_by"; name = newname; } else { name = tOutput("$team_ai"); } } else name = oldest->name; r = oldest->r; g = oldest->g; b = oldest->b; } } else { // empty team name = tOutput("$team_empty"); r = g = b = 7; } // make the oldest player spawn in front if ( oldest ) { int max = players.Len()-1; int real = oldest->teamListID; if ( real < max ) { players(max)->teamListID = real; oldest->teamListID = max; players(real) = players(max); players(max) = oldest; } } if ( nSERVER == sn_GetNetState() ) RequestSync(); } //update internal properties ( player count ) void eTeam::Update() { UpdateProperties(); UpdateAppearance(); } void eTeam::AddScore ( int s ) { score += s; if ( nSERVER == sn_GetNetState() ) RequestSync(); } void eTeam::ResetScore ( ) { score = 0; if ( nSERVER == sn_GetNetState() ) RequestSync(); } void eTeam::SetScore ( int s ) { score = s; if ( nSERVER == sn_GetNetState() ) RequestSync(); } void eTeam::AddScore(int points, const tOutput& reasonwin, const tOutput& reasonloose) { if (points==0) return; score += points; tOutput message; message.SetTemplateParameter(1, RemoveColors(name)); message.SetTemplateParameter(2, points > 0 ? points : -points); if (points>0) { if (reasonwin.IsEmpty()) message << "$player_win_default"; else message.Append(reasonwin); } else { if (reasonloose.IsEmpty()) message << "$player_loose_default"; else message.Append(reasonloose); } sn_ConsoleOut(message); RequestSync(true); se_SaveToScoreFile(message); } void eTeam::SwapTeamsNo(int a,int b){ if (0>a || teams.Len()<=a) return; if (0>b || teams.Len()<=b) return; if (a==b) return; eTeam *A=teams(a); eTeam *B=teams(b); teams(b)=A; teams(a)=B; A->listID=b; B->listID=a; } void eTeam::SortByScore(){ // bubble sort (AAARRGGH! but good for lists that change not much) bool inorder=false; while (!inorder){ inorder=true; int i; for(i=teams.Len()-2;i>=0;i--) if (teams(i)->score < teams(i+1)->score){ SwapTeamsNo(i,i+1); inorder=false; } } } tString eTeam::Ranking( int MAX, bool cut ){ SortByScore(); tString ret; if (teams.Len()>0){ ret << ColorString(1,.5,.5); ret << tOutput("$team_scoretable_name"); ret << ColorString(1,1,1); ret.SetPos(40, cut ); ret << tOutput("$team_scoretable_score"); ret << "\n"; int max = teams.Len(); if ( max > MAX && MAX > 0 ) { max = MAX ; } for(int i=0;iName(); //name.RemoveHex(); name.SetPos( 24, cut ); int posDisplacement = 0; for (unsigned int g=0; gscore; ret << line << "\n"; } if ( max < teams.Len() ) { ret << "...\n"; } } else ret << tOutput("$team_scoretable_nobody"); return ret; } // get the number of human players on the team int eTeam::NumHumanPlayers ( ) const { return numHumans; } static int imbalance = 1; // get the number of human players on the team int eTeam::NumAIPlayers ( ) const { return numAIs; } // make sure the limits on team number and such are met void eTeam::EnforceConstraints() { if ( maxImbalance < 2 ) maxImbalance = 2; if ( maxPermImbalance < 1 ) maxImbalance = 1; if ( minTeams > maxTeams ) minTeams = maxTeams; Enforce( minTeams, maxTeams, maxPermImbalance ); } // make sure the limits on team number and such are met void eTeam::EnforceWeakConstraints() { if ( maxImbalance < 2 ) maxImbalance = 2; if ( maxPermImbalance < 1 ) maxImbalance = 1; if ( minTeams > maxTeams ) minTeams = maxTeams; Enforce( minTeams, maxTeams, maxImbalance ); if ( imbalance <= 0 ) EnforceConstraints(); } // make sure the limits on team number and such are met void eTeam::Enforce( int minTeams, int maxTeams, int maxImbalance) { if ( maxTeams < 1 ) maxTeams = 1; if ( maxPlayers * maxTeams < se_PlayerNetIDs.Len() ) { maxPlayers = ( se_PlayerNetIDs.Len()/maxTeams ) + 1; } // nothing to be done on the clients if ( nCLIENT == sn_GetNetState() ) return; if ( maxImbalance < 1 ) maxImbalance = 1; if ( minTeams > maxTeams ) minTeams = maxTeams; bool balance = false; bool ib = false; int giveUp = 10; while ( !balance && giveUp-- > 0 ) { balance = true; // find the max and min number of players per team and the eTeam *max = NULL, *min = NULL; int maxP = minPlayers, minP = 100000; int numTeams = 0; int i; for ( i = teams.Len()-1; i>=0; --i ) { eTeam *t = teams(i); if ( t->BalanceThisTeam() ) { int humans = t->NumHumanPlayers(); numTeams++; if ( humans > maxP ) { maxP = humans; max = t; } if ( humans > 0 && humans < minP ) { minP = humans; min = t; } } } if ( numTeams > maxTeams ) { // too many teams. Destroy the smallest team. // find the second smallest team: // TODO: really find the second smallest... eTeam* second = max; for ( i = min->NumPlayers()-1; i>=0; --i ) { tJUST_CONTROLLED_PTR< ePlayerNetID > pni = min->Player(i); pni->SetTeamForce( second ); pni->UpdateTeamForce(); } // tDESTROY( min ); balance = false; } else if ( numTeams < minTeams ) { // too few teams. Create a new one eTeam *newTeam = tNEW( eTeam ); teams.Add( newTeam, newTeam->listID ); balance = false; } else if ( maxP - maxImbalance > minP || ( maxP > maxPlayers && minP < maxPlayers ) ) { // teams are unbalanced; move one player from the strongest team to the weakest if ( max ) { ePlayerNetID* unluckyOne = max->OldestHumanPlayer(); unluckyOne->SetTeamForce( min ); unluckyOne->UpdateTeamForce(); balance = false; } } else if ( maxP > maxPlayers ) { // teams too large. create a new team and put the leader of the strongest team in eTeam* newTeam = tNEW( eTeam ); if ( max ) { ePlayerNetID* unluckyOne = max->OldestHumanPlayer(); unluckyOne->SetTeamForce( newTeam ); unluckyOne->UpdateTeamForce(); balance = false; } } if ( maxP - eTeam::maxPermImbalance > minP ) ib = true; } if ( ib ) { imbalance--; } else if ( imbalance < 3 ) imbalance++; } static tList se_ColoredTeams; // inquire or set the ability to use a color as a team name bool eTeam::NameTeamAfterColor ( bool wish ) { if ( wish && colorID < 0 && se_ColoredTeams.Len() < TEAMCOLORS ) { se_ColoredTeams.Add( this, colorID ); } if ( !wish && colorID >= 0 ) { eTeam* last = se_ColoredTeams( se_ColoredTeams.Len() - 1 ); se_ColoredTeams.Remove( this, colorID ); if ( last != this ) last->Update(); } return colorID >= 0; } // register a player void eTeam::AddPlayer ( ePlayerNetID* player ) { tJUST_CONTROLLED_PTR< eTeam > keepalive( this ); if ( ! PlayerMayJoin( player ) ) return; if ( player->currentTeam ) { player->currentTeam->players.Remove ( player, player->teamListID ); player->currentTeam->UpdateProperties(); } players.Add( player, player->teamListID ); player->currentTeam = this; player->timeJoinedTeam = tSysTimeFloat(); UpdateProperties(); if ( players.Len() > 1 ) { tOutput message; message.SetTemplateParameter(1, RemoveColors(player->name)); message.SetTemplateParameter(2, RemoveColors(Name()) ); message << "$player_joins_team"; sn_ConsoleOut( message ); } else { UpdateAppearance(); } if ( listID < 0 ) { teams.Add ( this, listID ); } } // register a player the dirty way void eTeam::AddPlayerDirty ( ePlayerNetID* player ) { if ( player->currentTeam ) { player->currentTeam->players.Remove ( player, player->teamListID ); } players.Add( player, player->teamListID ); player->currentTeam = player->nextTeam = this; player->timeJoinedTeam = tSysTimeFloat(); if ( listID < 0 ) { teams.Add ( this, listID ); } } // deregister a player void eTeam::RemovePlayer ( ePlayerNetID* player ) { tCONTROLLED_PTR( eTeam ) safety; safety = this; // avoid premature destruction of this team tASSERT( player->currentTeam == this ); players.Remove ( player, player->teamListID ); player->currentTeam = NULL; if ( players.Len() > 0 ) { tOutput message; message.SetTemplateParameter(1, RemoveColors(player->name)); message.SetTemplateParameter(2, Name() ); message << "$player_leaves_team"; sn_ConsoleOut( message ); } UpdateProperties(); if ( enforceRulesOnQuit && nCLIENT != sn_GetNetState() ) EnforceWeakConstraints(); } // see if the given player may join this team bool eTeam::PlayerMayJoin( const ePlayerNetID* player ) const { int maxInb = maxImbalanceLocal; if ( imbalance <= 0 ) { maxInb = maxImbalance; } int minP = 10000; // minimum number of humans in a team after the player left if ( player->currentTeam ) { minP = player->currentTeam->NumHumanPlayers() - 1; } int i; for ( i = teams.Len()-1; i>=0; --i ) { eTeam *t = teams(i); if ( t->BalanceThisTeam() ) { int humans = t->NumHumanPlayers(); if ( humans < minP ) { minP = humans; } } } int maxPlayers = maxPlayersLocal; if ( maxPlayers * maxTeams < se_PlayerNetIDs.Len() ) { maxPlayers = ( se_PlayerNetIDs.Len()/maxTeams ) + 1; } // we must have room and the joining must not cause huge imbalance return numHumans < maxPlayers && minP + maxInb > numHumans; } // is it allowed to create a new team? bool eTeam::NewTeamAllowed () { return teams.Len() < maxTeams; } // the oldest player ePlayerNetID* eTeam::OldestPlayer ( ) const { ePlayerNetID* ret = NULL; for (int i= players.Len(); i>=0; i--) { ePlayerNetID* p = players(i); if (!ret || ret->timeJoinedTeam > p->timeJoinedTeam ) { ret = p; } } return ret; } // the oldest human player ePlayerNetID* eTeam::OldestHumanPlayer( ) const { ePlayerNetID* ret = NULL; for (int i= players.Len()-1; i>=0; i--) { ePlayerNetID* p = players(i); if ( p->IsHuman() && ( !ret || ret->timeJoinedTeam > p->timeJoinedTeam ) ) { ret = p; } } return ret; } // the oldest AI player ePlayerNetID* eTeam::OldestAIPlayer ( ) const { ePlayerNetID* ret = NULL; for (int i= players.Len()-1; i>=0; i--) { ePlayerNetID* p = players(i); if ( ( !p->IsHuman() ) && ( !ret || ret->timeJoinedTeam > p->timeJoinedTeam ) ) { ret = p; } } return ret; } // is anyone still alive? bool eTeam::Alive ( ) const { for (int i= players.Len()-1; i>=0; --i) { ePlayerNetID* p = players(i); if ( p->Object() && p->Object()->Alive() ) { return true; } } return false; } // print out an understandable name in to s void eTeam::PrintName(tString &s) const { s << "Team " << name; } // we must not transmit an object that contains pointers to non-transmitted objects. // this function is supposed to check that. bool eTeam::ClearToTransmit(int user) const { return true; } // syncronisation functions: // store sync message in m void eTeam::WriteSync(nMessage &m) { m << r; m << g; m << b; m << name; m << maxPlayersLocal; m << maxImbalanceLocal; m << score; } // guess what void eTeam::ReadSync(nMessage &m) { m >> r; m >> g; m >> b; m >> name; m >> maxPlayersLocal; m >> maxImbalanceLocal; m >> score; } // is the message newer than the last accepted sync bool eTeam::SyncIsNew(nMessage &m) { return true; } // the extra information sent on creation: // store sync message in m // the information written by this function should // be read from the message in the "message"- connstructor void eTeam::WriteCreate(nMessage &m) { nNetObject::WriteCreate(m); } // control functions: // receives the control message. the data written to the message created // by *NewControlMessage() can be read directly from m. void eTeam::ReceiveControlNet(nMessage &m) { } // con/desstruction // default constructor eTeam::eTeam() :colorID(-1),listID(-1) { score = 0; Update(); } // remote constructor eTeam::eTeam(nMessage &m) :nNetObject( m ), colorID(-1),listID(-1) { score = 0; Update(); } // destructor eTeam::~eTeam() { if ( listID >= 0 ) teams.Remove( this, listID ); if ( colorID >= 0 ) se_ColoredTeams.Remove( this, colorID ); }