搜索
楼主: 提卡维兰

队伍平衡Ptahhotep's Team Balancer 1.7b8汉化版

[复制链接]
 楼主| 发表于 2004-12-25 19:30:09 | 显示全部楼层 来自 中国–内蒙古–呼和浩特
没有,在我的服务器里一切正常。
回复

使用道具 举报

发表于 2005-11-9 14:00:21 | 显示全部楼层 来自 中国–河南–洛阳

回复: 队伍平衡Ptahhotep's Team Balancer 1.7b8汉化版

/*
*        Ptahhotep's Team Balancer (PTB)
*        Version 1.7 BETA
*
*        DATE: February 9, 2003
*        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 (2)"

// 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 = true // 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]

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       
        engclient_cmd(id,"chooseteam")
        engclient_cmd(id,"menuselect",(playerTeam[id]==TS) ? "2" : "1")
        engclient_cmd(id,"menuselect","5")
        client_cmd(id,"slot1")
}

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]) ) continue
                // Already switched or in PTB_PLAYERFREQ time condition
                if ((lastRoundSwitched[sortedTeams[theTeam]] == roundCounter) ||
                        (roundCounter - lastRoundSwitched[sortedTeams[theTeam]] < PTB_PLAYERFREQ))        continue
                sortedValidTargets[theTeam][n++] = sortedTeams[theTeam]
        }
        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 != 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]])
                                || ( (kills[sortedTeams[theTeam][k]]==kills[sortedTeams[theTeam]])        &&
                                (deaths[sortedTeams[theTeam][k]]>deaths[sortedTeams[theTeam]]))) {
                                // swap
                                swap = sortedTeams[theTeam][k]
                                sortedTeams[theTeam][k] = sortedTeams[theTeam]
                                sortedTeams[theTeam] = 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!=team&&i!=toBeAdded)        || (i==toBeRemoved)        )
                        continue
                sumKD += fdivWorkaround(float(kills), float(deaths))
        }
        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], time[32], mapname[32], name[32], authid[32]
        get_time("%m/%d/%Y - %H:%M:%S",time,31 )
        get_mapname(mapname,31)
        get_user_name(id,name,31)
        get_user_authid(id,authid,31)
        format(text, 255, "%s %s <%s> %s", time, 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...")
                engclient_cmd(id,"chooseteam") // display menu again
                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).", name, wtjCount[id])
                                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 count: %d).", name, wtjCount[id])
                                        doTypesay(text, 5, 0, 50, 255)
                                }
                                else {
                                        format(text, 255, "PTB: The Ts are strong enough, %s (WTJ count: %d).", name, wtjCount[id])
                                        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.")
                        engclient_cmd(id,"chooseteam") // display menu again
                        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.")
                        engclient_cmd(id,"chooseteam") // display menu again
                        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]>teamCounts[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 of %d).", name, wtjCount[id])
                                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 count: %d).", name, wtjCount[id])
                                        engclient_cmd(id,"menuselect","1")
                                        doTypesay(text, 5, 255, 50, 0)
                                        say(text)
                                }
                                else if (PTB_TELLWTJ) {
                                        format(text, 255, "PTB: The CTs are strong enough, %s (WTJ count: %d).", name, wtjCount[id])
                                        doTypesay(text, 5, 0, 50, 255)
                                        say(text)
                                        engclient_cmd(id,"chooseteam") // display menu again
                                }
                        }
                        else {
                                if (wtjCount[id]>=PTB_WTJAUTO) {
                                        manageWtjFile(id)
                                        format(text, 255, "PTB: Forcing %s to the CTs (WTJ count: %d).", name, wtjCount[id])
                                        engclient_cmd(id,"menuselect","2")
                                        doTypesay(text, 5, 0, 50, 255)
                                        say(text)
                                }
                                else if (PTB_TELLWTJ) {
                                        format(text, 255, "PTB: The Ts are strong enough, %s (WTJ count: %d).", name, wtjCount[id])
                                        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 join.")
                        engclient_cmd(id,"chooseteam") // display menu again
                        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.")
                        engclient_cmd(id,"chooseteam") // display menu again
                        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...")
                engclient_cmd(id,"menuselect",(opposingTeam==CTS) ? "2" : "1")
                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???
                        engclient_cmd(id,"chooseteam") // display menu again
                        return PLUGIN_HANDLED
                }
                client_print(id,print_chat,"PTB: You have been auto-assigned...")
                engclient_cmd(id,"menuselect",(iNewTeam == CTS) ? "2" : "1")
                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 ( get_cvar_num("mp_roundtime") * 60 != 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]-- // Unregister from old team
        teamCounts[team]++ // Increase ammount in new team
        playerTeam = 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 = 0
                deaths = 0
                wtjCount = 0
                lastRoundSwitched = -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
                teamKills[team] += kills
                teamDeaths[team] += deaths
        }
       
        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]
       
        return PLUGIN_CONTINUE
}

public plugin_init(){       
        register_plugin("Team Balancer",PTB_VERSION,"Ptahhotep")
        register_cvar("amx_ptb_version",PTB_VERSION,FCVAR_SERVER|FCVAR_EXTDLL|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
}
麻烦问一下 :这些源代码从哪里改,就是不丢武器,不自杀改变组织 我改了一些,测试有点困难...呵呵 :p
回复

使用道具 举报

发表于 2005-11-9 14:25:47 | 显示全部楼层 来自 中国–湖南–怀化

回复: 队伍平衡Ptahhotep's Team Balancer 1.7b8汉化版

队伍平衡功能,原来就有,也许没楼主那么理想,在使用此插件的话功能过于重复。
回复

使用道具 举报

发表于 2005-11-9 14:46:08 | 显示全部楼层 来自 中国–河南–洛阳

回复: 队伍平衡Ptahhotep's Team Balancer 1.7b8汉化版

是的,这个插件,有点损害人气,因为它为了队伍平衡,还得让人自杀,怎么样才能不自杀,然后换组了
olol说要改插件源代码,可是怎么改呢 :burn:
回复

使用道具 举报

发表于 2011-7-28 23:13:23 | 显示全部楼层 来自 中国–广东–潮州
不知道能不能实现把一个厉害的玩家自动平衡到另一方
回复

使用道具 举报

游客
回复
您需要登录后才可以回帖 登录 | 注个册吧

快速回复 返回顶部 返回列表