
   /*
   *    Ptahhotep's Team Balancer (PTB)
   *    Version 1.7 BETA
   *    AUTHOR: Ptahhotep (ptahhotep@planethalflife.com)
   *
   *  PTB converted for AMX Mod
   */
   #include <amxmod>
   #include <amxmisc>
   #define ACCESS_PTB ADMIN_RCON
   // team ids
   #define UNASSIGNED         0
   #define TS                         1
   #define CTS                         2
   #define AUTO_TEAM         5
   new PTB_VERSION[] = "1.7b (3)"
   //  If you experiance some problems with team switching then uncomment
   line below
   //#define MANUAL_SWITCH
   // team selection control
   new bool:PTB_LIMITJOIN = true // set limits on team joining
   new  PTB_LIMITAFTER = 0 // number of rounds after which teams limiting
   begins
   new  PTB_LIMITMIN  =  0  //  number of minimum players on map for team
   limiting
   new PTB_MAXSIZE = 10 // maximum team size per team
   new PTB_MAXDIFF = 2 // maximum team size difference
   new  PTB_AUTOROUNDS  =  3  // number of first rounds into match, which
   allow autojoin only
   new PTB_WTJAUTO = 3 // wtj tries needed to become autojoined
   new PTB_WTJKICK = 5 // wtj tries needed to become kicked
   new bool:PTB_KICK = true // kick for wtj counts
   new bool:PTB_SAVEWTJ = false // save wtjs to wtj.log
   // team balancing actions
   new bool:PTB_SWITCH = true // switch/transfer players
   new  PTB_SWITCHAFTER  =  0  //  number of rounds after which switching
   begins
   new  PTB_SWITCHMIN  =  3  //  number  of  minimum  players  on map for
   switching
   new PTB_SWITCHFREQ = 1 // relative next possible switch round
   new  PTB_PLAYERFREQ  =  3  //  relative next possible switch round for
   player
   new  PTB_FORCESWITCH  =  3 // number of tries after which PTB switches
   alive, if neccessary
   new bool:PTB_DEADONLY = false // switch dead only
   // messages
   new bool:PTB_TELLWTJ = true // tell about wtj tries
   new  bool:PTB_ANNOUNCE  = true // announce team status at beginning of
   round
   new  bool:PTB_SAYOK  =  true  //  announce  team  status, if teams are
   alright
   new bool:PTB_TYPESAY = true // use typesay
   // team strength limits
   new PTB_MAXSTREAK = 2 // max. allowed team win streak
   new PTB_MAXSCORE = 2 // max. allowed team score difference
   new Float:PTB_MINRATING = 1.5 // minimum critical team rating
   new Float:PTB_MAXRATING = 2.0 // maximum critical team rating
   new Float:PTB_SUPERRATING = 3.0 // super critical team rating
   new  PTB_MAXINCIDENTS  = 50 // maximum kills + deaths before the score
   is divided by PTB_SCALEDOWN
   new   PTB_SCALEDOWN  =  2  //  divisor  for  kills  and  deaths,  when
   PTB_MAXINCIDENTS is reached
   // sorted player indices are 0-based
   new sortedTeams[3][32]
   new sortedValidTargets[3][32]
   new validTargetCounts[3]
   new teamKills[3]
   new teamDeaths[3]
   new teamScores[3]
   new winStreaks[3]
   new wtConditions[3]
   new winnerTeam
   new loserTeam
   new Float:ctKD
   new Float:tKD
   new Float:ctStrength
   new Float:tStrength
   new Float:ctRating
   new Float:tRating
   // player arrays are 1-based, there is no player 0
   new bool:isBeingTransfered[33]
   new playerTeam[33]
   new lastRoundSwitched[33]
   new wtjCount[33]
   new teamCounts[3]
   new kills[33]
   new deaths[33]
   new roundCounter
   new lastSwitchRound
   new couldNotSwitchCounter
   new lastTeamBalanceCheck[32]
   public plugin_init(){
       register_plugin("Team Balancer",PTB_VERSION,"Ptahhotep")
       register_cvar("amx_ptb_version",PTB_VERSION,FCVAR_SERVER|FCVAR_EXT
   DLL|FCVAR_UNLOGGED|FCVAR_SPONLY)
       register_menucmd(register_menuid("Team_Select"),(1<<0)|(1<<1)|(1<<
   4),"menuselect")
       register_menucmd(-2,(1<<0)|(1<<1)|(1<<4),"menuselect")    //  VGUI
   menu
       register_event("SendAudio","round_end","a","2=%!MRAD_terwin","2=%!
   MRAD_ctwin","2=%!MRAD_rounddraw") // Round End
       register_event("TeamScore","team_score","a") // Team Score
       register_event("RoundTime", "new_round", "bc") // Round Time
       register_event("DeathMsg","death_msg","a") // Kill
       register_event("TeamInfo","team_assign","a")   //  Team  Assigment
   (also UNASSIGNED and SPECTATOR)
       register_event("TextMsg","team_join","a","1=1","2&Game_join_te","2
   &Game_join_ct") // Team Joining
       register_event("TextMsg","game_restart","a","1=4","2&#Game_C","2&#
   Game_w") // Game restart
       register_concmd("amx_ptb","admin_ptb",ACCESS_PTB,"-  displays  PTB
   options")
       server_cmd("exec addons/amx/ptb.cfg")
       return PLUGIN_CONTINUE
   }
   Float:fabs(Float:n)
       return ( n < 0.0 ) ? -n : n
   Float:fdivWorkaround(Float:nom, Float:denom){
       if ( denom == 0.0) return nom
       return fabs(nom / denom)
   }
   doTypesay(string[], duration, r, g, b) {
       if (!PTB_TYPESAY) return
       set_hudmessage(r, g, b, 0.05, 0.55, 0, 6.0, float(duration) , 0.5,
   0.15, 2)
       show_hudmessage(0, string )
   }
   say(string[]){
       client_print(0,print_chat,string)
       server_print(string)
   }
   bool:check_param_bool(param[])
       return (equali(param, "on") || equal(param, "1")) ? true : false
   Float:check_param_float(param[],Float:n){
       new Float:a = floatstr(param)
       if (a < n) a = n
       return a
   }

   check_param_num(param[],n){
       new a = strtonum(param)
       if (a < n) a = n
       return a
   }
   transferPlayer(id){
       isBeingTransfered[id] = true

   #if defined MANUAL_SWITCH
       client_cmd(id,"chooseteam;menuselect                 %d;menuselect
   5",(playerTeam[id]==TS) ? 2 : 1)
       client_cmd(id,"wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;w
   ait;wait;slot1")
   #else
       engclient_cmd(id,"chooseteam")
       engclient_cmd(id,"menuselect",(playerTeam[id]==TS) ? "2" : "1")
       engclient_cmd(id,"menuselect","5")
       client_cmd(id,"slot1")
   #endif
   }
   actAtEndOfRound(){
       if (!PTB_SWITCH) return
       // skip switching for the first few rounds
       if (roundCounter <= PTB_SWITCHAFTER) return
       // honor switch frequency setting
       if (roundCounter - lastSwitchRound < PTB_SWITCHFREQ) return
       // skip switching for a small number of players
       if (get_playersnum() < PTB_SWITCHMIN) return
       say("PTB: Round ended, checking teams.")
       checkTeamBalance()
       if (winnerTeam) {
           sortTeam(CTS)
           sortTeam(TS)
           if (teamCounts[winnerTeam] <= teamCounts[loserTeam])
               doSwitch()
           else if (teamCounts[loserTeam] < teamCounts[winnerTeam])
               doTransfer()
       }
   }
   createValidTargets(theTeam, bool:deadonly) {
       new n = 0
       for (new i = 0; i < teamCounts[theTeam]; ++i) {
           // Dead only condition
           if  (  deadonly  &&  is_user_alive(sortedTeams[theTeam][i])  )
   continue
           // Already switched or in PTB_PLAYERFREQ time condition
           if       ((lastRoundSwitched[sortedTeams[theTeam][i]]       ==
   roundCounter) ||
               (roundCounter - lastRoundSwitched[sortedTeams[theTeam][i]]
   < PTB_PLAYERFREQ))    continue
           sortedValidTargets[theTeam][n++] = sortedTeams[theTeam][i]
       }
       validTargetCounts[theTeam] = n
   }
   sortTeam(theTeam) {
       // create list of players
       new n = 0, a = get_maxplayers()
       for (new i = 1; i <= a; ++i) {
           // Get only members of specified team
           if (playerTeam[i] != theTeam) continue
           sortedTeams[theTeam][n++] = i
       }
       // do a selection sort
       new swap, count = n
       for (new i = count-1; i > 0; --i){
           for (new k = i-1; k >= 0; --k){
               //  compare  players  (kills better then other or if equal
   then with less deaths)
               if (
   (kills[sortedTeams[theTeam][k]]<kills[sortedTeams[theTeam][i]])
                   || (
   (kills[sortedTeams[theTeam][k]]==kills[sortedTeams[theTeam][i]])    &&
                   (deaths[sortedTeams[theTeam][k]]>deaths[sortedTeams[th
   eTeam][i]]))) {
                   // swap
                   swap = sortedTeams[theTeam][k]
                   sortedTeams[theTeam][k] = sortedTeams[theTeam][i]
                   sortedTeams[theTeam][i] = swap
               }
           }
       }
   }
   Float:score(team, toBeAdded=0, toBeRemoved=0){
       new Float:sumKD = 0.0
       new a = get_maxplayers()
       for (new i = 1; i <= a; ++i) {
           if         (         (playerTeam[i]!=team&&i!=toBeAdded)    ||
   (i==toBeRemoved)    )
               continue
           sumKD += fdivWorkaround(float(kills[i]), float(deaths[i]))
       }
       new Float:strength = float(teamCounts[team])
       if (sumKD) strength *= sumKD
       return strength
   }
   doSwitch() {
       new text[256]
       //displayStatistics(0,true)
       // don't switch, if at least one team is empty
       if ( teamCounts[winnerTeam] == 0 || teamCounts[loserTeam] == 0 ) {
           copy(text,255,  "PTB:  Can't  switch  players, need players in
   each team.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       // don't switch, if winner is alone (RULER!!!)
       if (teamCounts[winnerTeam] == 1) {
           copy(text,255,  "PTB:  Won't switch players, best player makes
   the winning team.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       // don't switch, if both teams are full
       if (teamCounts[winnerTeam] >= PTB_MAXSIZE && teamCounts[loserTeam]
   >= PTB_MAXSIZE) {
           copy(text,255,  "PTB:  Can't  switch  players,  both teams are
   full.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       if (!PTB_DEADONLY || couldNotSwitchCounter > PTB_FORCESWITCH) {
           // choose from random top or bottom x
           createValidTargets(winnerTeam, false)
           createValidTargets(loserTeam, false)
           if       (validTargetCounts[winnerTeam]      ==      0      ||
   validTargetCounts[loserTeam] == 0) {
               ++couldNotSwitchCounter
               copy(text,255,  "PTB:  Can't  switch  players,  need valid
   target in each team.")
               doTypesay(text, 5, 0, 255, 0)
               say(text)
               return
           }
       }
       else {
           //say("switch dead")
           createValidTargets(winnerTeam, true)
           createValidTargets(loserTeam, true)
           if       (validTargetCounts[winnerTeam]      ==      0      ||
   validTargetCounts[loserTeam] == 0) {
               if (++couldNotSwitchCounter > PTB_FORCESWITCH) {
                   say("PTB: Couldn't switch dead, switching alive.")
                   doSwitch()
                   return
               }
               copy(text,  255,"PTB:  Can't  switch  players,  need valid
   target in each team.")
               doTypesay(text, 5, 0, 255, 0)
               say(text)
               return
           }
       }
       //  Now  search  through the possible 1 to 1 swaps to equalize the
   strength as much as possible
       new      Float:closestScore     =     fabs(score(winnerTeam)     -
   score(loserTeam))
       new Float:myScore, toLoser, toWinner
       new winner = 0
       new loser = 0
       for (new w = 0; w < validTargetCounts[winnerTeam]; ++w) {
           toLoser = sortedValidTargets[winnerTeam][w]
           for (new l = 0; l < validTargetCounts[loserTeam]; ++l) {
               toWinner = sortedValidTargets[loserTeam][l]
               myScore  =  fabs(score(winnerTeam,  toWinner,  toLoser)  -
   score(loserTeam, toLoser, toWinner))
               if (myScore < closestScore) {
                   closestScore = myScore
                   winner = toLoser
                   loser = toWinner
               }
           }
       }
       if (winner == 0 && loser == 0) {
           copy(text, 255,"PTB: No switch would improve team balancing.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       couldNotSwitchCounter = 0
       lastSwitchRound = roundCounter
       new winnerName[32], loserName[32]
       get_user_name(winner,winnerName,31)
       get_user_name(loser,loserName,31)
       //  if  one  team is full, first move the the player from the full
   team ...
       if (teamCounts[winnerTeam] >= PTB_MAXSIZE){
           transferPlayer(winner)
           transferPlayer(loser)
       }
       else {
           transferPlayer(loser)
           transferPlayer(winner)
       }
       format(text,255,"PTB: Switching %s with %s.",winnerName,loserName)
       doTypesay(text, 5, 0, 255, 0)
       say(text)
   }
   doTransfer() {
       //displayStatistics(0,true)
       new text[256]
       if (teamCounts[winnerTeam] == 0) {
               copy(text,255, "PTB: Can't switch players, need players in
   each team.")
               doTypesay(text, 5, 0, 255, 0)
               say(text)
               return
       }
       if (teamCounts[loserTeam] >= PTB_MAXSIZE) {
           copy(text,255,  "PTB:  Can't  transfer  player, losing team is
   full.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       if (!PTB_DEADONLY || couldNotSwitchCounter > PTB_FORCESWITCH) {
           createValidTargets(winnerTeam, false)
           if (validTargetCounts[winnerTeam] == 0) {
               copy(text,255,  "PTB:  Can't  transfer  player,  no  valid
   target in winning team.")
               doTypesay(text, 5, 0, 255, 0)
               say(text)
               ++couldNotSwitchCounter
               return
           }
       }
       else {
           //say("switch dead")
           createValidTargets(winnerTeam, true)
           if (validTargetCounts[winnerTeam] == 0) {
               if (++couldNotSwitchCounter > PTB_FORCESWITCH) {
                   say("PTB:   Couldn't   transfer   dead,   transferring
   alive.")
                   doTransfer()
                   return
               }
               copy(text,255,  "PTB:  Can't  transfer  player,  no  valid
   target in winning team.")
               doTypesay(text, 5, 0, 255, 0)
               say(text)
               return
           }
       }
       new      Float:closestScore     =     fabs(score(winnerTeam)     -
   score(loserTeam))
       new Float:myScore, toLoser
       new winner = 0
       for (new w = 0; w < validTargetCounts[winnerTeam]; ++w) {
           toLoser = sortedValidTargets[winnerTeam][w]
           myScore     =    fabs(score(winnerTeam,    0,    toLoser)    -
   score(loserTeam, toLoser, 0))
           if (myScore < closestScore) {
               closestScore = myScore
               winner = toLoser
           }
       }
       if (winner == 0) {
           copy(text,   255,"PTB:   No   transfer   would   improve  team
   balancing.")
           doTypesay(text, 5, 0, 255, 0)
           say(text)
           return
       }
       couldNotSwitchCounter = 0
       new winnerName[32]
       get_user_name(winner,winnerName,31)
       transferPlayer(winner)
       format(text,255,"PTB:   Transfering   %s  to  the  %s",winnerName,
   (winnerTeam == CTS) ? "Ts" : "CTs")
       doTypesay(text, 5, 0, 255, 0)
       say(text)
   }
   checkTeamBalance() {
       get_time("%m/%d/%Y - %H:%M:%S",lastTeamBalanceCheck,31 )
       calcTeamScores()
       ctStrength = score(CTS)
       tStrength = score(TS)
       ctRating = fdivWorkaround(ctStrength, tStrength)
       tRating = fdivWorkaround(tStrength, ctStrength)
       wtConditions[TS] = 0
       wtConditions[CTS] = 0
       // compare scores for unequal rating scores
       if  (teamScores[TS] - teamScores[CTS] > PTB_MAXSCORE && tRating >=
   PTB_MINRATING)
           wtConditions[TS]++
       if (teamScores[CTS] - teamScores[TS] > PTB_MAXSCORE && ctRating >=
   PTB_MINRATING)
           wtConditions[CTS]++
       // check streaks for unequal rating scores
       if (winStreaks[TS] > PTB_MAXSTREAK && tRating >= PTB_MINRATING)
           wtConditions[TS]++
       if (winStreaks[CTS] > PTB_MAXSTREAK && ctRating >= PTB_MINRATING)
           wtConditions[CTS]++
       // check ratings
       if (tRating >= PTB_MAXRATING)
           wtConditions[TS]++
       if (ctRating >= PTB_MAXRATING)
           wtConditions[CTS]++

       // check ratings
       if (tRating >= PTB_SUPERRATING)
           wtConditions[TS]++
       if (ctRating >= PTB_SUPERRATING)
           wtConditions[CTS]++
       // check team sizes for unequal ratings
       if (teamCounts[TS] > teamCounts[CTS] && tRating >= PTB_MINRATING)
           wtConditions[TS]++
       if (teamCounts[CTS] > teamCounts[TS] && ctRating >= PTB_MINRATING)
           wtConditions[CTS]++
       // check conditions
       if (wtConditions[TS] >= 2) {
           winnerTeam = TS
           loserTeam = CTS
       }
       else if (wtConditions[CTS] >= 2) {
           winnerTeam = CTS
           loserTeam = TS
       }
       else {
           winnerTeam = 0
           loserTeam = 0
       }
   }
   manageWtjFile(id) {
       if (!PTB_SAVEWTJ) return
       //say("Trying to write wtj.log ....")
       if (wtjCount[id] < 4) return
       //say("wtj.log should be written to now ....")
       new text[256], stime[32], mapname[32], name[32], authid[32]
       get_time("%m/%d/%Y - %H:%M:%S",stime,31 )
       get_mapname(mapname,31)
       get_user_name(id,name,31)
       get_user_authid(id,authid,31)
       format(text, 255, "%s %s <%s> %s", stime, name, authid, mapname)
       write_file("addons/amx/wtj.log", text)
   }
   public menuselect(id,key) {
       // don't care where player joins
       if (!PTB_LIMITJOIN) return PLUGIN_CONTINUE
       // players is transfered so don't care with rest
       if (isBeingTransfered[id]) {
           //say("TRANSFER")
           isBeingTransfered[id] = false
           return PLUGIN_CONTINUE
       }
       //say("NO TRANSFER")
       // skip limiting for a few rounds into the map
       if  (PTB_LIMITAFTER  &&  roundCounter  <=  PTB_LIMITAFTER)  return
   PLUGIN_CONTINUE
       // skip limiting for a small number of players
       if (get_playersnum() < PTB_LIMITMIN) return PLUGIN_CONTINUE
       new iNewTeam = key + 1 // which key has been pressed
       new iOldTeam = playerTeam[id]

       // disallow free team choices in the first rounds of a map
       if
   (PTB_AUTOROUNDS&&(iOldTeam==UNASSIGNED)&&roundCounter<=PTB_AUTOROUNDS)
           iNewTeam = AUTO_TEAM

       // prevent unwanted rejoining of the same team ...
       if (iNewTeam == iOldTeam) {
           //say("Preventing rejoining of the same team.")
           client_print(id,print_chat,"PTB:  Joining  to the same team is
   not allowed...")
   #if !defined MANUAL_SWITCH
           engclient_cmd(id,"chooseteam") // display menu again
   #endif
           return PLUGIN_HANDLED
       }

       checkTeamBalance()
       //displayStatistics(0,true)
       //  Player  for sure was in CT or T team and now is joining to the
   opposite team
       if ((iNewTeam==CTS&&iOldTeam==TS)||(iNewTeam==TS&&iOldTeam==CTS)){
           // If someone is in new team and old team weren't full
           // and the winning team is a destination team or in
           // new team is more players than in old then treat it as wtj
           if (
   teamCounts[iNewTeam]&&(teamCounts[iOldTeam]<PTB_MAXSIZE)&&
               ((iNewTeam==winnerTeam)||(teamCounts[iNewTeam]>=teamCounts
   [iOldTeam])) ) {
               // player is wtjing
               new text[256],name[32]
               get_user_name(id,name,31)
               // Kick wtj player if reached set limit
               if (++wtjCount[id] >= PTB_WTJKICK && PTB_KICK) {
                   format(text,  255, "PTB: Kicking %s for a WTJ count %d
   of %d.", name, wtjCount[id],PTB_WTJKICK )
                   doTypesay(text, 5, 0, 255, 0)
                   say(text)
                   server_cmd("kick #%d",get_user_userid(id))
                   return PLUGIN_HANDLED
               }
               // Announce about WTJ
               if (PTB_TELLWTJ) {
                   if (iNewTeam == CTS) {
                       format(text, 255, "PTB: The CTs are strong enough,
   %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
                       doTypesay(text, 5, 0, 50, 255)
                   }
                   else {
                       format(text,  255, "PTB: The Ts are strong enough,
   %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
                       doTypesay(text, 5, 255, 50, 0)
                   }
                   say(text)
               }
               engclient_cmd(id,"chooseteam") // display menu again
               return PLUGIN_HANDLED
           }
           // check for maximum team size
           if (teamCounts[iNewTeam] >= PTB_MAXSIZE) {
               client_print(id,print_chat,"PTB:    Maximum    team   size
   prohibits team change.")
   #if !defined MANUAL_SWITCH
               engclient_cmd(id,"chooseteam") // display menu again
   #endif
               return PLUGIN_HANDLED
           }
           // check team size difference limits
           if     (     teamCounts[iNewTeam]+1-teamCounts[iOldTeam]    >=
   PTB_MAXDIFF ) {
               client_print(id,print_chat,"PTB:    Maximum    team   size
   difference prohibits team change.")
   #if !defined MANUAL_SWITCH
               engclient_cmd(id,"chooseteam") // display menu again
   #endif
               return PLUGIN_HANDLED
           }
           return  PLUGIN_CONTINUE  //  OK!  He  can join to the oppsoite
   team!!!
       }

       // Player is choosing his team for the first time!
       if (iNewTeam==CTS||iNewTeam == TS){
           // Get opposite team
           new opposingTeam = (iNewTeam==CTS)? TS : CTS
           //  Players  is  joinging  to one team but the opposite is not
   full
           // and his team is bettter then opposite or has more players
           if ( teamCounts[iNewTeam] &&
   teamCounts[opposingTeam]<PTB_MAXSIZE    &&
           (iNewTeam==winnerTeam||(!winnerTeam&&teamCounts[iNewTeam]>team
   Counts[opposingTeam]))) {
               new text[256],name[32]
               get_user_name(id,name,31)
               if (++wtjCount[id] >= PTB_WTJKICK && PTB_KICK) {
                   format(text,  255, "PTB: Kicking %s for a WTJ count %d
   of %d.", name, wtjCount[id],PTB_WTJKICK)
                   doTypesay(text, 5, 0, 255, 0)
                   say(text)
                   server_cmd("kick #%d", get_user_userid(id))
                   return PLUGIN_HANDLED
               }
               if (iNewTeam==CTS) {
                   if (wtjCount[id]>=PTB_WTJAUTO) {
                       manageWtjFile(id)
                       format(text, 255, "PTB: Forcing %s to the Ts (WTJ:
   %d/%d).", name, wtjCount[id],PTB_WTJKICK)
   #if defined MANUAL_SWITCH
                       client_cmd(id,"chooseteam;menuselect 1")
   #else
                       engclient_cmd(id,"menuselect","1")
   #endif
                       doTypesay(text, 5, 255, 50, 0)
                       say(text)
                   }
                   else if (PTB_TELLWTJ) {
                       format(text, 255, "PTB: The CTs are strong enough,
   %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
                       doTypesay(text, 5, 0, 50, 255)
                       say(text)
   #if !defined MANUAL_SWITCH
                       engclient_cmd(id,"chooseteam")   //  display  menu
   again
   #endif
                   }
               }
               else {
                   if (wtjCount[id]>=PTB_WTJAUTO) {
                       manageWtjFile(id)
                       format(text,  255,  "PTB:  Forcing  %s  to the CTs
   (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
   #if defined MANUAL_SWITCH
                       client_cmd(id,"chooseteam;menuselect 2")
   #else
                       engclient_cmd(id,"menuselect","2")
   #endif
                       doTypesay(text, 5, 0, 50, 255)
                       say(text)
                   }
                   else if (PTB_TELLWTJ) {
                       format(text,  255, "PTB: The Ts are strong enough,
   %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
                       doTypesay(text, 5, 255, 50, 0)
                       say(text)
   #if !defined MANUAL_SWITCH
                       engclient_cmd(id,"chooseteam")   //  display  menu
   again
   #endif
                   }
               }
               return PLUGIN_HANDLED
           }
           // check for maximum team size
           if (teamCounts[iNewTeam] >= PTB_MAXSIZE) {
               client_print(id,print_chat,"PTB:    Maximum    team   size
   prohibits team join.")
   #if !defined MANUAL_SWITCH
               engclient_cmd(id,"chooseteam") // display menu again
   #endif
               return PLUGIN_HANDLED
           }
           // check team size difference limits
           if    (    teamCounts[iNewTeam]-teamCounts[opposingTeam]    >=
   PTB_MAXDIFF) {
               client_print(id,print_chat,"PTB:    Maximum    team   size
   difference prohibits team join.")
   #if !defined MANUAL_SWITCH
               engclient_cmd(id,"chooseteam") // display menu again
   #endif
               return PLUGIN_HANDLED
           }
           return  PLUGIN_CONTINUE  //  OK!  He  can join to the oppsoite
   team!!!
       }

       //  He  is choosing the AUTO-SELECT but he was already in game (He
   wants to play fair!)
       if (iNewTeam==AUTO_TEAM&&(iOldTeam==CTS||iOldTeam==TS)) {
           //say("Changing team automatically.")
           new opposingTeam = (iOldTeam==CTS) ? TS : CTS
           if (teamCounts[opposingTeam] && (
   (teamCounts[opposingTeam]>=PTB_MAXSIZE)
                   || (iOldTeam==loserTeam) ||
   (!loserTeam&&teamCounts[iOldTeam]<=teamCounts[opposingTeam])
                   ||
   (teamCounts[opposingTeam]+1-teamCounts[iOldTeam]>=PTB_MAXDIFF)) ) {
               client_print(id,print_chat,"PTB:  You  have better stay in
   your current team...")
               return PLUGIN_HANDLED
           }
           client_print(id,print_chat,"PTB:       You      have      been
   auto-assigned...")
   #if defined MANUAL_SWITCH
           client_cmd(id,"chooseteam;menuselect %d",(opposingTeam==CTS) ?
   2 : 1 )
   #else
           engclient_cmd(id,"menuselect",(opposingTeam==CTS) ? "2" : "1")
   #endif
           return PLUGIN_HANDLED
       }
       //  He  is  choosing  the team for the first time with AUTO-SELECT
   (What a nice kid!)
       if (iNewTeam==AUTO_TEAM) {
           /* this is the "always smaller team" version
           if  (teamCounts[CTS]  <  teamCounts[TS]  ||  teamCounts[TS] >=
   PTB_MAXSIZE) iNewTeam = CTS
           else  if  (teamCounts[TS] < teamCounts[CTS] || teamCounts[CTS]
   >= PTB_MAXSIZE) iNewTeam = TS
           // both teams have same size ...
           else   if  (winnerTeam  &&  teamCounts[loserTeam]<PTB_MAXSIZE)
   iNewTeam = loserTeam
           else if (teamCounts[TS] >= PTB_MAXSIZE) iNewTeam = CTS
           else if (teamCounts[CTS] >= PTB_MAXSIZE) iNewTeam = TS
           else iNewTeam = (random_num(0,100) < 50) ? CTS : TS
           */
           //  this  version  prefers  the  losing team (but still honors
   PTB_MAXDIFF)
           if (teamCounts[CTS] >= PTB_MAXSIZE) iNewTeam = TS
           else if (teamCounts[TS] >= PTB_MAXSIZE) iNewTeam = CTS
           else   if   (teamCounts[CTS]-teamCounts[TS]   >=  PTB_MAXDIFF)
   iNewTeam = TS
           else   if   (teamCounts[TS]-teamCounts[CTS]   >=  PTB_MAXDIFF)
   iNewTeam = CTS
           else if (winnerTeam) iNewTeam = loserTeam
           else if (teamCounts[CTS]<teamCounts[TS]) iNewTeam = CTS
           else if (teamCounts[TS]<teamCounts[CTS]) iNewTeam = TS
           // both teams have same size ...
           else iNewTeam = (random_num(0,100) < 50) ? CTS : TS
           // check for maximum team size
           if (teamCounts[iNewTeam]>=PTB_MAXSIZE) {
               client_print(id,print_chat,"PTB:    Maximum    team   size
   prohibits team join.") // ??? - only a spectator???
   #if !defined MANUAL_SWITCH
               engclient_cmd(id,"chooseteam") // display menu again
   #endif
               return PLUGIN_HANDLED
           }
           client_print(id,print_chat,"PTB:       You      have      been
   auto-assigned...")
   #if defined MANUAL_SWITCH
           client_cmd(id,"chooseteam;menuselect %d",(iNewTeam==CTS) ? 2 :
   1 )
   #else
           engclient_cmd(id,"menuselect",(iNewTeam == CTS) ? "2" : "1")
   #endif
           return PLUGIN_HANDLED
       }
       return PLUGIN_CONTINUE
   }
   public team_score(){
       new arg[2]
       read_data(1,arg,1)
       teamScores[ ( arg[0] == 'T' ) ? TS : CTS ] = read_data(2)
   }
   public win_streaks(param[]){
       new winner = param[0]
       new looser = param[1]
       if (winStreaks[winner] < 0) {
           winStreaks[winner] = 1
           winStreaks[looser] = -1
       }
       else {
           winStreaks[winner]++
           winStreaks[looser]--
       }
       actAtEndOfRound()
   }
   public round_end(){
       new param[12]
       read_data(2,param,8)
       if (param[7]=='c') {//%!MRAD_ctwin
           param[0] = CTS
           param[1] = TS
       }
       else if (param[7]=='t') {//%!MRAD_terwin
           param[0] = TS
           param[1] = CTS
       }
       else
           return // %!MRAD_rounddraw (both teams have left the game)
       set_task(1.5,"win_streaks",0,param,2)
   }
   public new_round() {
       if   (   floatround(get_cvar_float("mp_roundtime")   *   60.0)  !=
   read_data(1) ) return
       ++roundCounter
       announceStatus()
   }
   // Happen only at team select (also auto-join)
   public team_join() {
       new arg[32]
       read_data(3,arg,31)
       lastRoundSwitched[ get_user_index(arg) ] = roundCounter
   }
   // Can happen at begin of round or team select
   public team_assign() {
       new arg[2], team
       new i = read_data(1)
       read_data(2,arg,1)
       if ( arg[0] == 'C'  )
           team = CTS
       else if ( arg[0] == 'T' )
           team = TS
       else
           team = UNASSIGNED
       teamCounts[playerTeam[i]]-- // Unregister from old team
       teamCounts[team]++ // Increase ammount in new team
       playerTeam[i] = team // Assign to new
   }
   public game_restart(){
       roundCounter = 0
       lastSwitchRound = 0
       couldNotSwitchCounter = 0
       teamKills[0] = teamKills[1] = teamKills[2] = 0
       teamDeaths[0] = teamDeaths[1] = teamDeaths[2] = 0
       teamScores[0] = teamScores[1] = teamScores[2] = 0
       winStreaks[0]    = winStreaks[1] = winStreaks[2] = 0
       wtConditions[0] = wtConditions[1] = wtConditions[2] = 0
       validTargetCounts[0] = validTargetCounts[1] = validTargetCounts[2]
   = 0
       new a = get_maxplayers()
       for (new i = 1; i <= a; ++i){
           kills[i] = 0
           deaths[i] = 0
           wtjCount[i] = 0
           lastRoundSwitched[i] = -999
       }
   }
   public death_msg(){
       new iWinner = read_data(1)
       new iLoser = read_data(2)
       if ( iWinner < 1 || iWinner > 32 || iLoser < 1 || iLoser > 32 )
           return
       if ( playerTeam[iWinner] == playerTeam[iLoser] )
           return // no TKS!!!
       kills[iWinner]++
       deaths[iLoser]++
       if (PTB_SCALEDOWN <= 1) return
       if (kills[iWinner] + deaths[iWinner] >= PTB_MAXINCIDENTS) {
           kills[iWinner] /= PTB_SCALEDOWN
           deaths[iWinner] /= PTB_SCALEDOWN
       }
       if (kills[iLoser] + deaths[iLoser] >= PTB_MAXINCIDENTS) {
           kills[iLoser] /= PTB_SCALEDOWN
           deaths[iLoser] /= PTB_SCALEDOWN
       }
   }
   calcTeamScores() {
       teamDeaths[UNASSIGNED] = 0
       teamDeaths[CTS] = 0
       teamDeaths[TS] = 0
       teamKills[UNASSIGNED] = 0
       teamKills[CTS] = 0
       teamKills[TS] = 0

       new team, a = get_maxplayers()
       for (new i = 1; i <= a; ++i) {
           team = playerTeam[i]
           teamKills[team] += kills[i]
           teamDeaths[team] += deaths[i]
       }

       ctKD            =            fdivWorkaround(float(teamKills[CTS]),
   float(teamDeaths[CTS]))
       tKD = fdivWorkaround(float(teamKills[TS]), float(teamDeaths[TS]))
   }
   announceStatus() {
       if (!PTB_ANNOUNCE) return
       checkTeamBalance()
       new text[256]
       if (winnerTeam == TS) {
           format(text,  255,  "PTB: The COUNTER-TERRORIST team could use
   some support.")
           doTypesay(text, 5, 0, 50, 255)
           say("PTB: The COUNTER-TERRORIST team could use some support.")
       }
       else if (winnerTeam == CTS) {
           format(text,  255,  "PTB:  The  TERRORIST  team could use some
   support.")
           doTypesay(text, 5, 255, 50, 0)
           say("PTB: The TERRORIST team could use some support.")
       }
       else if (wtConditions[TS] > wtConditions[CTS]) {
           format(text, 255, "PTB: Observing TERRORIST team advantage.")
           doTypesay(text, 5, 255, 50, 0)
           say("PTB: Observing TERRORIST team advantage.")
       }
       else if (wtConditions[CTS] > wtConditions[TS]) {
           format(text,   255,  "PTB:  Observing  COUNTER-TERRORIST  team
   advantage.")
           doTypesay(text, 5, 0, 50, 255)
           say("PTB: Observing COUNTER-TERRORIST team advantage.")
       }
       else if (PTB_SAYOK) {
           format(text, 255, "PTB: Teams look fine, no action required.")
           doTypesay(text, 5, 200, 100, 0)
           say("PTB: Teams look fine, no action required.")
       }
   }
   public admin_ptb(id,level,cid) {
       if (!cmd_access(id,level,cid,1))
           return PLUGIN_HANDLED
       new cmd[32], arg[32], lastcmd
       if ( read_argv(1,cmd,31) == 0 ) { // no command - plain amx_ptb
           console_print(id,"PTB:    Ptahhotep's   Team   Balancer   %s",
   PTB_VERSION)
           console_print(id,"PTB: (ptahhotep@planethalflife.com)")
           checkTeamBalance()
           displayStatistics(id)
           return PLUGIN_HANDLED
       }
       if (equali(cmd, "on") || equal(cmd, "1")) {
           PTB_LIMITJOIN = true
           PTB_SWITCH = true
           PTB_ANNOUNCE = true
           console_print(id,"PTB: Enabled all PTB actions.")
           return PLUGIN_HANDLED
       }
       if (equali(cmd, "off") || equal(cmd, "0")) {
           PTB_SWITCH = false
           PTB_ANNOUNCE = false
           PTB_LIMITJOIN = false
           console_print(id,"PTB: Disabled all PTB actions.")
           return PLUGIN_HANDLED
       }
       if (equal(cmd, "list") || equal(cmd, "help")) {
           console_print(id,"PTB: Available Commands:")
           console_print(id,"PTB:   Team   Join  Control:  ^"limitjoin^",
   ^"limitafter^", ^"limitmin^", ^"maxsize^", ^"autorounds^",")
           console_print(id,"PTB:  ^"maxdiff^", ^"wtjauto^", ^"wtjkick^",
   ^"kick^", ^"savewtj^"")
           console_print(id,"PTB:  Team  Balancing  Actions:  ^"switch^",
   ^"switchafter^", ^"switchmin^", ^"switchfreq^", ^"playerfreq^",")
           console_print(id,"PTB: ^"forceswitch^", ^"deadonly^"")
           console_print(id,"PTB:  Team  Strength  Limits: ^"maxstreak^",
   ^"maxscore^", ^"minrating^", ^"maxrating^", ^"superrating^"")
           console_print(id,"PTB:  Messages:  ^"tellwtj^",  ^"announce^",
   ^"sayok^", ^"typesay^"")
           console_print(id,"PTB:   Misc:   ^"^",  ^"status^",  ^"list^",
   ^"help^", ^"on^", ^"off^", ^"save^", ^"load^"")
           console_print(id,"PTB:   To   view   all  PTB  settings,  type
   ^"amx_ptb status^".")
           console_print(id,"PTB: To view or change a single PTB setting,
   type ^"amx_ptb <setting> <on|off|value>^".")
           console_print(id,"PTB:   For   PTB   statistics,  simply  type
   ^"amx_ptb^".")
           return PLUGIN_HANDLED
       }
       new arglen = read_argv(2,arg,31)
       new status = equal(cmd, "status")
       // team selection control
       if  (  status  )  console_print(id,"PTB: ---------- Team Selection
   Control ----------")

       // PTB_LIMITJOIN
       if ( (lastcmd = equal(cmd, "limitjoin")) && arglen ) PTB_LIMITJOIN
   = check_param_bool(arg)
       if  (  status  ||  lastcmd )    console_print(id,"PTB: (limitjoin)
   WTJ prevention is %s.", PTB_LIMITJOIN ? "ON" : "OFF")
       // PTB_LIMITAFTER
       if    (   (lastcmd   =   equal(cmd,   "limitafter"))   &&   arglen
   )    PTB_LIMITAFTER = check_param_num(arg,0)
       if  (  status  || lastcmd )    console_print(id,"PTB: (limitafter)
   Team limiting starts after %d round(s).", PTB_LIMITAFTER)
       // PTB_LIMITMIN
       if ( (lastcmd = equal(cmd, "limitmin")) && arglen ) PTB_LIMITMIN =
   check_param_num(arg,0)
       if ( status || lastcmd )    console_print(id,"PTB: (limitmin) Team
   limiting needs at least %d player(s).", PTB_LIMITMIN)
       // PTB_MAXSIZE
       if ( (lastcmd = equal(cmd, "maxsize")) && arglen )     PTB_MAXSIZE
   = check_param_num(arg,0)
       if  (  status  ||  lastcmd  )    console_print(id,"PTB:  (maxsize)
   Maximum team size is %d player(s).", PTB_MAXSIZE)
       // PTB_MAXDIFF
       if  (  (lastcmd = equal(cmd, "maxdiff")) && arglen ) PTB_MAXDIFF =
   check_param_num(arg,1)
       if  (  status  ||  lastcmd  )    console_print(id,"PTB:  (maxdiff)
   Maximum team size difference is %d.", PTB_MAXDIFF)
       // PTB_AUTOROUNDS
       if    (   (lastcmd   =   equal(cmd,   "autorounds"))   &&   arglen
   )    PTB_AUTOROUNDS = check_param_num(arg,0)
       if  (  status || lastcmd )    console_print(id, "PTB: (autorounds)
   First %d rounds no free team choice.", PTB_AUTOROUNDS)
       // PTB_WTJAUTO
       if  ( (lastcmd = equal(cmd, "wtjauto")) && arglen )    PTB_WTJAUTO
   = check_param_num(arg,0)
       if  (  status  ||  lastcmd  )    console_print(id,"PTB:  (wtjauto)
   Auto-joining WTJ after %d tr(y/ies).", PTB_WTJAUTO)
       // PTB_WTJKICK
       if  (  (lastcmd = equal(cmd, "wtjkick")) && arglen ) PTB_WTJKICK =
   check_param_num(arg,1)
       if  (  status  ||  lastcmd  )    console_print(id,"PTB:  (wtjauto)
   Auto-kicking WTJ after %d tr(y/ies).", PTB_WTJKICK)
       // PTB_KICK
       if  (  (lastcmd  =  equal(cmd,  "kick"))  &&  arglen  ) PTB_KICK =
   check_param_bool(arg)
       if ( status || lastcmd ) console_print(id,"PTB: (kick) WTJ kicking
   is %s.", PTB_KICK ? "ON" : "OFF" )
       // PTB_SAVEWTJ
       if  (  (lastcmd = equal(cmd, "savewtj")) && arglen ) PTB_SAVEWTJ =
   check_param_bool(arg)
       if  (  status  ||  lastcmd  )     console_print(id,"PTB: (savewtj)
   Saving to wtj.log is %s.", PTB_SAVEWTJ ? "ON" : "OFF");
       // team balancing actions
       if  (  status  )  console_print(id,"PTB: ---------- Team Balancing
   Actions ----------")
       // PTB_SWITCH
       if  (  (lastcmd  =  equal(cmd, "switch")) && arglen ) PTB_SWITCH =
   check_param_bool(arg)
       if  (  status  ||  lastcmd  ) console_print(id,"PTB: (switch) Team
   switching is %s.", PTB_SWITCH ? "ON" : "OFF")
       // PTB_SWITCHAFTER
       if   (   (lastcmd   =   equal(cmd,  "switchafter"))  &&  arglen  )
   PTB_SWITCHAFTER = check_param_num(arg,0)
       if  (  status  ||  lastcmd  ) console_print(id,"PTB: (switchafter)
   Switching starts after %d round(s).", PTB_SWITCHAFTER)
       // PTB_SWITCHMIN
       if ( (lastcmd = equal(cmd, "switchmin")) && arglen ) PTB_SWITCHMIN
   = check_param_num(arg,0)
       if  (  status  ||  lastcmd  )  console_print(id,"PTB:  (switchmin)
   Switching needs at least %d player(s).", PTB_SWITCHMIN)
       // PTB_PLAYERFREQ
       if    (   (lastcmd   =   equal(cmd,   "playerfreq"))   &&   arglen
   )    PTB_PLAYERFREQ = check_param_num(arg,0)
       if  (  status  || lastcmd )    console_print(id,"PTB: (playerfreq)
   Individual  players  are  switched  every  %d  round(s)  at maximum.",
   PTB_PLAYERFREQ)
       // PTB_SWITCHFREQ
       if    (  (lastcmd    =   equal(cmd,   "switchfreq"))   &&   arglen
   )    PTB_SWITCHFREQ = check_param_num(arg,1)
       if  (  status  || lastcmd )    console_print(id,"PTB: (switchfreq)
   Switch occurs every %d round(s) at maximum.", PTB_SWITCHFREQ)
       // PTB_FORCESWITCH
       if   (   (lastcmd   =   equal(cmd,   "forceswitch"))   &&   arglen
   )    PTB_FORCESWITCH = check_param_num(arg,0)
       if  (  status  ||  lastcmd  ) console_print(id,"PTB: (forceswitch)
   Forcing switch after %d unsuccessful switch(es).", PTB_FORCESWITCH)
       // PTB_DEADONLY
       if   (   (lastcmd   =  equal(cmd,   "deadonly"))   &&   arglen   )
   PTB_DEADONLY = check_param_bool(arg)
       if   (  status  ||  lastcmd  )  console_print(id,"PTB:  (deadonly)
   Switching dead only is %s.",PTB_DEADONLY ? "ON" : "OFF" )
       // messages
       if   (   status   )   console_print(id,"PTB:  ----------  Messages
   ----------")
       // PTB_TELLWTJ
       if  ( (lastcmd =  equal(cmd, "tellwtj")) && arglen ) PTB_TELLWTJ =
   check_param_bool(arg)
       if  ( status || lastcmd ) console_print(id,"PTB: (tellwtj) Telling
   about WTJ tries is %s.",PTB_TELLWTJ ? "ON" : "OFF")
       // PTB_ANNOUNCE
       if ( (lastcmd = equal(cmd, "announce")) && arglen ) PTB_ANNOUNCE =
   check_param_bool(arg)
       if   (  status  ||  lastcmd  )  console_print(id,"PTB:  (announce)
   Announcements are %s.",PTB_ANNOUNCE ? "ON" : "OFF")

       // PTB_SAYOK
       if  (  (lastcmd  =  equal(cmd,  "sayok"))  && arglen ) PTB_SAYOK =
   check_param_bool(arg)
       if  (  status  ||  lastcmd ) console_print(id,"PTB: (sayok) ^"OK^"
   announcements are %s.",PTB_SAYOK ? "ON" : "OFF")
       // PTB_TYPESAY
       if  (  (lastcmd = equal(cmd, "typesay")) && arglen ) PTB_TYPESAY =
   check_param_bool(arg)
       if  ( status || lastcmd ) console_print(id,"PTB: (typesay) typesay
   usage is %s.",PTB_TYPESAY ? "ON" : "OFF")

       // team strength limits
       if  (  status  )  console_print(id,"PTB:  ---------- Team Strength
   Limits ----------")
       // PTB_MAXSTREAK
       if ( (lastcmd = equal(cmd, "maxstreak")) && arglen ) PTB_MAXSTREAK
   = check_param_num(arg,1)
       if  (  status  ||  lastcmd )    console_print(id,"PTB: (maxstreak)
   Maximum accepted win streak is %d.", PTB_MAXSTREAK)
       // PTB_MAXSCORE
       if    (    (lastcmd    =   equal(cmd,   "maxscore"))   &&   arglen
   )    PTB_MAXSCORE = check_param_num(arg,1)
       if ( status || lastcmd ) console_print(id,"PTB: (maxscore) Maximum
   accepted team score difference is %d.", PTB_MAXSCORE)
       // PTB_MINRATING
       if ( (lastcmd = equal(cmd, "minrating")) && arglen ) PTB_MINRATING
   = check_param_float(arg,1.0)
       if  (  status  || lastcmd )     console_print(id,"PTB: (minrating)
   Minimum critical strength rating is %.2f.",PTB_MINRATING)
       // PTB_MAXRATING
       if ( (lastcmd = equal(cmd, "maxrating")) && arglen ) PTB_MAXRATING
   = check_param_float(arg,1.0)
       if  (  status  || lastcmd )     console_print(id,"PTB: (maxrating)
   Maximum critical strength rating is %.2f.",PTB_MAXRATING)
       // PTB_SUPERRATING
       if   (   (lastcmd   =   equal(cmd,  "superrating"))  &&  arglen  )
   PTB_SUPERRATING = check_param_float(arg,1.0)
       if  ( status || lastcmd )     console_print(id,"PTB: (superrating)
   Super critical strength rating is %.2f.",PTB_SUPERRATING)
       // PTB_MAXINCIDENTS
       if   (   (lastcmd   =   equal(cmd,   "maxincidents"))   &&  arglen
   )    PTB_MAXINCIDENTS = check_param_num(arg,1)
       if ( status || lastcmd )     console_print(id,"PTB: (maxincidents)
   Maximum  incidents  before  internal  player score scale down is %d.",
   PTB_MAXINCIDENTS)
       // PTB_SCALEDOWN
       if    (    (lastcmd    =  equal(cmd,   "scaledown"))   &&   arglen
   )    PTB_SCALEDOWN = check_param_num(arg,1)
       if  (  status  || lastcmd )     console_print(id,"PTB: (scaledown)
   Integer scale down factor for player scores is %d.", PTB_SCALEDOWN)
       // misc
       if ( status ) {
           console_print(id,"PTB: ---------- Misc ----------")
           console_print(id,"PTB:   To   enable   or  disable  PTB,  type
   ^"admin_ptb <on|1|off|0>^".")
           console_print(id,"PTB: To view or change a single PTB setting,
   type ^"amx_ptb <setting> <on|off|value>^".")
           console_print(id,"PTB:   To  view  a  brief  overview  of  PTB
   commands, type ^"amx_ptb help^" or ^"amx_ptb list^".")
           console_print(id,"PTB:   For   PTB   statistics,  simply  type
   ^"amx_ptb^".")
       }

       return PLUGIN_HANDLED
   }
   stock displayStatistics(id,bool:toLog = false) {
       //say("displayStatistics")
       new text[256]
       // time
       format(text,    255,   "PTB:   Statistics   generated   at:   %s",
   lastTeamBalanceCheck)
       if (toLog) log_message(text)
       console_print(id,text)
       // connected players
       format(text, 255, "PTB: Connected players: %d", get_playersnum())
       if (toLog) log_message(text)
       console_print(id,text)
       // team sizes
       format(text,   255,   "PTB:   Team   sizes:   CTs   %d,   Ts  %d",
   teamCounts[CTS], teamCounts[TS])
       if (toLog) log_message(text)
       console_print(id,text)
       // team scores
       format(text,   255,   "PTB:   Team   scores:   CTs   %d,  Ts  %d",
   teamScores[CTS], teamScores[TS])
       if (toLog) log_message(text)
       console_print(id,text)
       // Kills:Deaths
       format(text,  255,  "PTB: Team kills:deaths: CTs %d:%d, Ts %d:%d",
   teamKills[CTS], teamDeaths[CTS], teamKills[TS], teamDeaths[TS])
       if (toLog) log_message(text)
       console_print(id,text)
       // Kills/Deaths
       format(text,  255,  "PTB:  Team  kills/deaths: CTs %.2f, Ts %.2f",
   ctKD , tKD    )
       if (toLog) log_message(text)
       console_print(id,text)
       // strength
       format(text,   255,   "PTB:   Team   strengths:   CTs   %.2f,   Ts
   %.2f",ctStrength , tStrength    )
       if (toLog) log_message(text)
       console_print(id,text)
       // rating
       format(text,    255,    "PTB:   Team   ratings:   CTs   %.2f,   Ts
   %.2f",ctRating,tRating    )
       if (toLog) log_message(text)
       console_print(id,text)
       // won rounds
       if (winStreaks[CTS] > 0) {
           format(text,  255,  "PTB:  Last  %d  round(s)  won  by  CTs.",
   winStreaks[CTS])
           if (toLog) log_message(text)
           console_print(id,text)
       }
       else if (winStreaks[TS] > 0) {
           format(text,   255,  "PTB:  Last  %d  round(s)  won  by  Ts.",
   winStreaks[TS])
           if (toLog) log_message(text)
           console_print(id,text)
       }

       // winning team
       switch(winnerTeam){
           case  CTS:  format(text,  255,  "PTB:  The CTs are the winning
   team.")
           case  TS:  format(text,  255,  "PTB:  The  Ts  are the winning
   team.")
           default: format(text, 255, "PTB: Teams are balanced.")
       }
       if (toLog) log_message(text)
       console_print(id,text)

       format(text,   255,   "PTB:  These  statistics  might  be  already
   outdated.")
       if (toLog) log_message(text)
       console_print(id,text)
       format(text,  255, "PTB: To view a brief overview of PTB commands,
   type ^"amx_ptb help^" or ^"amx_ptb list^".")
       if (toLog) log_message(text)
       console_print(id,text)
       format(text,  255,  "PTB: To view all PTB settings, type ^"amx_ptb
   status^".")
       if (toLog) log_message(text)
       console_print(id,text)
   }
   public client_connect(id){
       kills[id] = 0
       deaths[id] = 0
       isBeingTransfered[id] = false
       playerTeam[id] = UNASSIGNED
       lastRoundSwitched[id] = -999
       wtjCount[id] = 0
       return PLUGIN_CONTINUE
   }
   public client_disconnect(id) {
       kills[id] = 0
       deaths[id] = 0
       isBeingTransfered[id] = false
       playerTeam[id] = UNASSIGNED
       lastRoundSwitched[id] = -999
       wtjCount[id] = 0
       // redundant team size check
       teamCounts[UNASSIGNED] = 0
       teamCounts[CTS] = 0
       teamCounts[TS] = 0

       new a = get_maxplayers()
       for (new i = 1; i <= a; ++i)
           ++teamCounts[playerTeam[i]]

       return PLUGIN_CONTINUE
   }
