/* // misc admin_teambalance - show PTB statistics (ACCESS_ALL) admin_teambalance status - show all current settings (ACCESS_ALL) admin_teambalance list - list of ptb commands (ACCESS_ALL) admin_teambalance help - same as "list" (ACCESS_ALL) admin_teambalance on - enable all options admin_teambalance off - disable all options admin_teambalance save - save all settings to vault.ini admin_teambalance load - load all settings from vault.ini // team selection control admin_teambalance limitjoin on|off (default: on) - team join control admin_teambalance limitafter <value> (default: 0) - rounds after which teams limiting begins admin_teambalance limitmin <value> (default: 0) - minimum players for team limiting admin_teambalance maxsize <value> (default: 10) - maximum team size per team admin_teambalance autorounds <value> (default: 3) - initial rounds without free team choice admin_teambalance maxdiff <value> (default: 2) - maximum accepted team size difference admin_teambalance wtjauto <value> (default: 2) - WTJ tries for auto-join admin_teambalance wtjkick <value> (default: 3) - WTJ tries for kick admin_teambalance kick on|off (default: on) - WTJ kicking admin_teambalance savewtj on|off (default: off) - wtj.log writing // team balancing actions admin_teambalance switch on|off (default: on) - team switching and transfers admin_teambalance switchafter <value> (default: 0) - rounds after which switching begins admin_teambalance switchmin <value> (default: 3) - minimum players on map for switching admin_teambalance switchfreq <value> (default: 1) - maximum team switching frequency admin_teambalance playerfreq <value> (default: 3) - maximum player switching frequency admin_teambalance forceswitch <value> (default: 3) - forced switch delay (switching alive, if neccessary) admin_teambalance deadonly on|off (default: on) - switch dead only or alive, too // messages admin_teambalance tellwtj on|off (default: on) - tell about wtj tries admin_teambalance announce on|off (default: on) - announcements admin_teambalance sayok on|off (default: on) - "no action required" message admin_teambalance typesay on|off (default: on) - globally switch use of typesay // team strength limits admin_teambalance maxstreak <value> (default: 2) - maximum accepted team win streak admin_teambalance maxscore <value> (default: 2) - maximum accepted team score difference admin_teambalance minrating <value> (default: 1.5) - stronger team, if rating >= minrating admin_teambalance maxrating <value> (default: 2.0) - way stronger team, if rating >= maxrating admin_teambalance superrating <value> (default: 3.0) - overwhelming team strength, if rating >= superrating (don't play with these, if you don't fully understand, what they mean) Configuration Using "vault.ini" ------------------------------- PTB options can be set by manipulating the "option_..." variables at the top of the code, or by setting variables in AdminMod's vault.ini file. If you want to do the latter, these are the default settings as they should appear in the file (each option should stand at the beginning of a new line). Look at the comments besides the corresponding "options_..." code to find out what the options do. Atlernatively you can type "admin_teambalance status" in the console of a running CS server to get some info on the settings. // team selection control PTB_LIMITJOIN on PTB_LIMITAFTER 0 PTB_LIMITMIN 0 PTB_MAXSIZE 10 PTB_MAXDIFF 2 PTB_AUTOROUNDS 3 PTB_WTJAUTO 2 PTB WTJKICK 3 PTB_KICK on PTB_SAVEWTJ off // team balancing actions PTB_SWITCH on PTB_SWITCHAFTER 0 PTB_SWITCHMIN 3 PTB_SWITCHFREQ 1 PTB_PLAYERFREQ 3 PTB_FORCESWITCH 3 PTB_DEADONLY on // messages PTB_TELLWTJ on PTB_ANNOUNCE on PTB_SAYOK on PTB_TYPESAY on // team strength limits PTB_MAXSTREAK 2 PTB_MAXSCORE 2 PTB_MINRATING 1.5 PTB_MAXRATING 2.0 PTB_SUPERRATING 3.0 */ #pragma dynamic 32768 #include <core> #include <console> #include <string> #include <admin> #include <adminlib> #include <fixed> new STRING_VERSION[MAX_DATA_LENGTH] = "1.0Z"; #define ACCESS_BALANCE 32 #define ACCESS_CONSOLE 131072 #define TS 1 #define CTS 2 #define AUTO_TEAM 5 // defaults new MIN_RATING[10] = "1.5"; new MAX_RATING[10] = "2.0"; new SUPER_RATING[10] = "3.0"; // options // team selection control new PTB_LIMITJOIN = 1; // set limits on team joining new PTB_LIMITAFTER = 2; // number of rounds after which teams limiting begins new PTB_LIMITMIN = 6; // number of minimum players on map for team limiting new PTB_MAXSIZE = 16; // maximum team size per team new PTB_MAXDIFF = 2; // maximum team size difference new PTB_AUTOROUNDS = 2; // number of 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 PTB_KICK = 1; // kick for wtj counts new PTB_SAVEWTJ = 0; // save wtjs to wtj.txt // team balancing actions new PTB_SWITCH = 1; // switch/transfer players new PTB_SWITCHAFTER = 1; // 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 = 2; // number of tries after which PTB switches alive, if neccessary new PTB_DEADONLY = 1; // switch dead only // messages new PTB_TELLWTJ = 0; // tell about wtj tries new PTB_ANNOUNCE = 0; // announce team status at beginning of round new PTB_SAYOK = 0; // announce team status, if teams are alright new PTB_TYPESAY = 0; // use typesay // team strength limits new PTB_MAXSTREAK = 2; // max allowed team win streak new PTB_MAXSCORE = 2; // max allowed team score difference new fixed:PTB_MINRATING = 1.5; // minimum critical team rating new fixed:PTB_MAXRATING = 2.0; // maximum critical team rating new fixed:PTB_SUPERRATING = 3.0; // super critical team rating new sortedTeams[3][MAX_PLAYERS]; new validTargetCounts[3]; // sorted player indices are 0-based new sortedValidTargets[3][MAX_PLAYERS]; new kills[MAX_PLAYERS] = { 0, ... }; new deaths[MAX_PLAYERS] = { 0, ... }; new teamKills[3] = { 0, ... }; new teamDeaths[3] = { 0, ... }; new teamScores[3] = { 0, ... }; new winStreaks[3] = { 0, ... }; new wtConditions[3]; new winnerTeam = 0; new loserTeam = 0; new roundCounter = 0; new betweenRounds = true; new fixed:ctKD; new fixed:tKD; new fixed:ctStrength; new fixed:tStrength; new fixed:ctRating; new fixed:tRating; // player arrays are 1-based, there is no player 0 new isChoosingTeam[MAX_PLAYERS]; new isBeingTransfered[MAX_PLAYERS]; new playerTeam[MAX_PLAYERS]; new lastRoundSwitched[MAX_PLAYERS]; new wtjCount[MAX_PLAYERS]; new teamCounts[3] = { 0, ... }; new lastSwitchRound = 0; new couldNotSwitchCounter = 0; new lastTeamBalanceCheck[MAX_TEXT_LENGTH]; fixed:fdivWorkaround (fixed:nom, fixed:denom) { // return nom, if denom == 0 if (denom == fixed(0)) return nom; // handle negative result (no idea, if negativ results are reliable ...) if ((nom >= fixed(0)) && (denom < fixed(0))) return fdiv(nom, denom); if ((nom < fixed(0)) && (denom > fixed(0))) return fdiv(nom, denom); // workaround for negative results on dividing 2 positive or 2 negative numbers nom = fabs(nom); denom = fabs(denom); new fixed:result = nom; new MAX_TRIES = 20; new tries = 0; while (tries < MAX_TRIES) { result = fdiv(nom, denom); if (fmul(result, denom) == nom) break; if (result >= fixed(0)) break; ++tries; } return (result); } doTypesay (string[], duration, r, g, b) { if (!PTB_TYPESAY) return; typesay(string, duration, r, g, b); } transferPlayer (player) { //say("transferPlayer"); new Name[MAX_NAME_LENGTH]; playerinfo(player, Name, MAX_NAME_LENGTH); isBeingTransfered[player] = 1; //say("Transferring Player:"); //say(Name); if (playerTeam[player] == TS) { execclient(Name,"chooseteam;wait;wait;wait;wait;menuselect 2;slot2;wait;wait;menuselect 0;slot10"); } else { execclient(Name,"chooseteam;wait;wait;wait;wait;menuselect 1;slot1;wait;wait;menuselect 0;slot10"); } execclient(Name, "wait; wait; wait; slot6, wait; wait; wait; slot6"); execclient(Name, "wait; wait; wait; slot6, wait; wait; wait; slot6"); execclient(Name, "wait; wait; wait; slot6, wait; wait; wait; slot6"); execclient(Name, "wait; wait; wait; slot6, wait; wait; wait; slot6"); execclient(Name, "wait; wait; wait; slot6, wait; wait; wait; slot6"); } actAtEndOfRound () { //say("act"); 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 (playercount() < PTB_SWITCHMIN) return; say("Team balance: Checking teams."); checkTeamBalance(); if (winnerTeam != 0) { sortTeam(CTS); sortTeam(TS); if (teamCounts[winnerTeam] <= teamCounts[loserTeam]) { doSwitch(); } else if (teamCounts[loserTeam] < teamCounts[winnerTeam]) { doTransfer(); } } } createValidTargets (theTeam, deadonly) { new Name[MAX_NAME_LENGTH]; new WonId; new UserId; new team; new dead; //say("createValidTargets"); new n = 0; for (new i = 0; i < teamCounts[theTeam]; ++i) { if (0 == playerinfo(sortedTeams[theTeam][i], Name, MAX_NAME_LENGTH, WonId, UserId, team, dead)) { say("No playerinfo ..."); } //snprintf(Name, MAX_NAME_LENGTH, "Index: %d, sortedTeamIndex: %d, dead: %d", i , sortedTeams[theTeam][i], dead); //say(Name); if (dead == 0 && deadonly) { //say("player alive"); continue; } if ((roundCounter == lastRoundSwitched[sortedTeams[theTeam][i]])){ //say("Player switched this round...") continue; } if ((roundCounter - lastRoundSwitched[sortedTeams[theTeam][i]] > PTB_PLAYERFREQ)) { //say("Player switched not in this round...") sortedValidTargets[theTeam][n] = sortedTeams[theTeam][i]; ++n; continue; } //say("player dead and not switched this round"); sortedValidTargets[theTeam][n] = sortedTeams[theTeam][i]; ++n; } validTargetCounts[theTeam] = n; } sortTeam (theTeam) { //say("sortTeam"); // create list of players new n = 0; for (new i=1; i<MAX_PLAYERS; ++i) { if (playerTeam[i] != theTeam) continue; sortedTeams[theTeam][n] = i; ++n; } // do a selection sort new count = teamCounts[theTeam]; for (new i=count-1; i>0; --i) { for (new k=i-1; k>=0; --k) { // compare players if ((kills[sortedTeams[theTeam][k]] < kills[sortedTeams[theTeam][i]]) || ((kills[sortedTeams[theTeam][k]] == kills[sortedTeams[theTeam][i]]) && (deaths[sortedTeams[theTeam][k]] > deaths[sortedTeams[theTeam][i]]))) { // swap new t; t = sortedTeams[theTeam][k]; sortedTeams[theTeam][k] = sortedTeams[theTeam][i]; sortedTeams[theTeam][i] = t; } } } } fixed:fabs (fixed:n) { if (n >= fixed(0)) return n; return (-n); } fixed:score (team, toBeAdded, toBeRemoved) { new fixed:sumKD = fixed(0); for (new player=1; player<MAX_PLAYERS; ++player) { if (((playerTeam[player] != team) && (player != toBeAdded)) || (player == toBeRemoved)) continue; sumKD += fdivWorkaround(fixed(kills[player]), fixed(deaths[player])); } new fixed:strength = fixed(teamCounts[team]); if (sumKD > fixed(0)) strength = fmul(strength, sumKD); return (strength); } doSwitch () { //say("doSwitch"); new text[MAX_TEXT_LENGTH]; //displayStatistics(true); // don't switch, if at least one team is empty if ((teamCounts[winnerTeam] == 0) || (teamCounts[loserTeam] == 0)) { text = "Team balance: Can't switch players, need players in each team."; doTypesay(text, 5, 10, 255, 10); say(text); return; } // don't switch, if winner is alone (RULER!!!) if (teamCounts[winnerTeam] == 1) { text = "Team balance: Won't switch players, best player makes the winning team."; doTypesay(text, 5, 10, 255, 10); say(text); return; } // don't switch, if both teams are full if ((teamCounts[winnerTeam] == PTB_MAXSIZE) && (teamCounts[loserTeam] == PTB_MAXSIZE)) { new nReserved = getvar("reserve_slots"); if (nReserved == 0) { text = "Team balance: Can't switch players, both teams are full."; doTypesay(text, 5, 10, 255, 10); say(text); return; } } if (!PTB_DEADONLY || (couldNotSwitchCounter > PTB_FORCESWITCH)) { // choose from random top or bottom x createValidTargets(winnerTeam, 0); createValidTargets(loserTeam, 0); if ((validTargetCounts[winnerTeam] == 0) || (validTargetCounts[loserTeam] == 0)) { strcpy(text, "Team balance: Can't switch players, need valid target in each team.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); ++couldNotSwitchCounter; return; } } else { //say("switch dead"); createValidTargets(winnerTeam, 1); createValidTargets(loserTeam, 1); if ((validTargetCounts[winnerTeam] == 0) || (validTargetCounts[loserTeam] == 0)) { ++couldNotSwitchCounter; if (couldNotSwitchCounter > PTB_FORCESWITCH) { say("Team balance: Couldn't switch dead, switching alive."); doSwitch(); return; } strcpy(text, "Team balance: Can't switch players, need valid target in each team.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); return; } } // Now search through the possible 1 to 1 swaps to equalize the strength as much as possible new fixed:closestScore = fabs(score(winnerTeam, -1, -1) - score(loserTeam, -1, -1)); new winner = 0; new loser = 0; for (new w=0; w<validTargetCounts[winnerTeam]; ++w) { new toLoser = sortedValidTargets[winnerTeam][w]; for (new l=0; l<validTargetCounts[loserTeam]; ++l) { new toWinner = sortedValidTargets[loserTeam][l]; new fixed:myScore = fabs(score(winnerTeam, toWinner, toLoser) - score(loserTeam, toLoser, toWinner)); if (myScore < closestScore) { closestScore = myScore; winner = toLoser; loser = toWinner; } } } if (winner == 0 && loser == 0) { strcpy(text, "Team balance: No switch would improve team balancing.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); return; } couldNotSwitchCounter = 0; lastSwitchRound = roundCounter; new winnerName[MAX_NAME_LENGTH]; new loserName[MAX_NAME_LENGTH]; playerinfo(winner, winnerName, MAX_NAME_LENGTH); playerinfo(loser, loserName, MAX_NAME_LENGTH); // if one team is full, first move the the player from the full team ... if (teamCounts[winnerTeam] == PTB_MAXSIZE) { new nReserved = getvar("reserve_slots"); if (nReserved > 0) { new NresBuf[8]; snprintf(NresBuf, 8, "%i", nReserved-1); //execute_command(User, Command, "reserve_slots", NresBuf); set_serverinfo("reserve_slots", NresBuf); transferPlayer(winner); transferPlayer(loser); snprintf(NresBuf, 8, "%i", nReserved); //execute_command(User, Command, "reserve_slots", NresBuf); set_serverinfo("reserve_slots", NresBuf); } else { transferPlayer(winner); transferPlayer(loser); } } else { transferPlayer(loser); transferPlayer(winner); } strcpy(text, "Team balance: Switching ", 80); strcat(text, winnerName, 80); strcat(text, " with ", 80); strcat(text, loserName, 80); strcat(text, ".", 80); doTypesay(text, 5, 10, 255, 10); say(text); } doTransfer () { //say("doTransfer"); new text[MAX_TEXT_LENGTH]; if (teamCounts[winnerTeam] == 0) return; //displayStatistics(true); if (teamCounts[loserTeam] == PTB_MAXSIZE) { strcpy(text, "Team balance: Can't transfer player, losing team is full.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); return; } if (!PTB_DEADONLY || couldNotSwitchCounter > PTB_FORCESWITCH) { createValidTargets(winnerTeam, 0); if (validTargetCounts[winnerTeam] == 0) { strcpy(text, "Team balance: Can't transfer player, no valid target in winning team.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); ++couldNotSwitchCounter; return; } } else { //say("switch dead"); createValidTargets(winnerTeam, 1); if (validTargetCounts[winnerTeam] == 0) { ++couldNotSwitchCounter; if (couldNotSwitchCounter > PTB_FORCESWITCH) { say("Team balance: Couldn't transfer dead, transferring alive."); doTransfer(); return; } strcpy(text, "Team balance: Can't transfer player, no valid target in winning team.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); return; } } new fixed:closestScore = fabs(score(winnerTeam, -1, -1) - score(loserTeam, -1, -1)); new winner = -1; for (new w = 0; w < validTargetCounts[winnerTeam]; ++w) { new toLoser = sortedValidTargets[winnerTeam][w]; new fixed:myScore = fabs(score(winnerTeam, -1, toLoser) - score(loserTeam, toLoser, -1)); if (myScore < closestScore) { closestScore = myScore; winner = toLoser; } } if (winner == -1) { strcpy(text, "Team balance: No transfer would improve team balancing.", MAX_DATA_LENGTH); doTypesay(text, 5, 10, 255, 10); say(text); return; } couldNotSwitchCounter = 0; new winnerName[MAX_NAME_LENGTH]; playerinfo(winner, winnerName, MAX_NAME_LENGTH); transferPlayer(winner); strcpy(text, "Team balance: Transfering ", 80); strcat(text, winnerName, 80); strcat(text, " to the ", 80); if (winnerTeam == CTS) strcat(text, "Ts", 80); else if (winnerTeam == TS) strcat(text, "CTs", 80); strcat(text, ".", 80); doTypesay(text, 5, 10, 255, 10); say(text); } checkTeamBalance () { //say("Checking team balance ..."); servertime(lastTeamBalanceCheck, MAX_TEXT_LENGTH, "none"); calcTeamScores(); //ctStrength = fixed(teamCounts[CTS]); // workaround for match start team balancing // this is making only team size count, // as long as there is no single kill //if (ctKD != fixed(0)) ctStrength = fmul(ctKD, ctStrength); // replacing old strength calculation ctStrength = score(CTS, -1, -1); //tStrength = fixed(teamCounts[TS]); // workaround for match start team balancing // this is making only team size count, // as long as there is no single kill //if (tKD != fixed(0)) tStrength = fmul(tKD, tStrength); // replacing old strength calculation tStrength = score(TS, -1, -1); ctRating = fdivWorkaround(ctStrength, tStrength); tRating = fdivWorkaround(tStrength, ctStrength); wtConditions[TS] = 0; wtConditions[CTS] = 0; //new buffer[10]; // compare scores for unequal rating scores if ((teamScores[TS] - teamScores[CTS] > PTB_MAXSCORE) && (tRating >= PTB_MINRATING)) { wtConditions[TS] = wtConditions[TS] + 1; } if ((teamScores[CTS] - teamScores[TS] > PTB_MAXSCORE) && (ctRating >= PTB_MINRATING)) { wtConditions[CTS] = wtConditions[CTS] + 1; } //say("// check streaks for unequal rating scores"); // check streaks for unequal rating scores if ((winStreaks[TS] > PTB_MAXSTREAK) && (tRating >= PTB_MINRATING)) { wtConditions[TS] = wtConditions[TS] + 1; } if ((winStreaks[CTS] > PTB_MAXSTREAK) && (ctRating >= PTB_MINRATING)) { wtConditions[CTS] = wtConditions[CTS] + 1; } //say("// check ratings"); // check ratings if (tRating >= PTB_MAXRATING) { wtConditions[TS] = wtConditions[TS] + 1; } if (ctRating >= PTB_MAXRATING) { wtConditions[CTS] = wtConditions[CTS] + 1; } //say("// check super ratings"); // check ratings if (tRating >= PTB_SUPERRATING) { wtConditions[TS] = wtConditions[TS] + 1; } if (ctRating >= PTB_SUPERRATING) { wtConditions[CTS] = wtConditions[CTS] + 1; } //say("// check team sizes for unequal ratings"); // check team sizes for unequal ratings if ((teamCounts[TS] > teamCounts[CTS]) && (tRating >= PTB_MINRATING)) { wtConditions[TS] = wtConditions[TS] + 1; } if ((teamCounts[CTS] > teamCounts[TS]) && (ctRating >= PTB_MINRATING)) { wtConditions[CTS] = wtConditions[CTS] + 1; } //say("// check conditions"); // check conditions if (wtConditions[TS] >= 2) { //say("TERRORISTS are the winning team ..."); winnerTeam = TS; loserTeam = CTS; } else if (wtConditions[CTS] >= 2) { //say("COUNTER-TERRORISTS are the winning team ..."); winnerTeam = CTS; loserTeam = TS; } else { winnerTeam = 0; loserTeam = 0; //if (wtConditions[CTS] > 0) say("COUNTER-TERRORIST team advantage ..."); //else if (wtConditions[TS] > 0) say("TERRORIST team advantage ..."); //else say("No winning team, no team advantage ..."); } } public ChoosingTeam (HLCommand, HLData, HLUserName, UserIndex) { //say("ChoosingTeam"); isChoosingTeam[UserIndex] = 1; return PLUGIN_CONTINUE; } public NotChoosingTeam (HLCommand, HLData, HLUserName, UserIndex) { //say("NotChoosingTeam"); isChoosingTeam[UserIndex] = 0; return PLUGIN_CONTINUE; } manageWtjFile (UserIndex) { if (!PTB_SAVEWTJ) return; //log("Trying to write wtj.log ...."); if (wtjCount[UserIndex] != 4) return; //log("wtj.log should be written to now ...."); //new debug[MAX_DATA_LENGTH]; //snprintf(debug, MAX_DATA_LENGTH, "PTB manageWtjFile"); //log(debug); new Time[MAX_DATA_LENGTH]; servertime(Time, MAX_DATA_LENGTH); new Map[MAX_DATA_LENGTH]; currentmap(Map, MAX_DATA_LENGTH); new Name[MAX_NAME_LENGTH]; new UserId; new WonId; playerinfo(UserIndex, Name, MAX_NAME_LENGTH, UserId, WonId); new text[MAX_TEXT_LENGTH]; snprintf(text, MAX_TEXT_LENGTH, "%s %s <%d> %s", Time, Name, WonId, Map); writefile("wtj.log", text); } public MenuSelect (HLCommand, HLData, HLUserName, UserIndex) { //say("MenuSelect"); if (!isChoosingTeam[UserIndex]) return PLUGIN_CONTINUE; isChoosingTeam[UserIndex] = 0; //if (UserIndex < 1) return PLUGIN_CONTINUE; if (!PTB_LIMITJOIN) return PLUGIN_CONTINUE; // skip limiting for a few rounds into the map if (roundCounter <= PTB_LIMITAFTER) return PLUGIN_CONTINUE; // skip limiting for a small number of players if (playercount() < PTB_LIMITMIN) return PLUGIN_CONTINUE; if (isBeingTransfered[UserIndex]) { //say("TRANSFER"); isBeingTransfered[UserIndex] = 0; return PLUGIN_CONTINUE; } else { //say("NO TRANSFER"); } new Data[MAX_DATA_LENGTH]; new iNewTeam; convert_string(HLData, Data, MAX_DATA_LENGTH); iNewTeam = strtonum(Data); // disallow free team choices in the first rounds of a map if (roundCounter <= PTB_AUTOROUNDS) iNewTeam = 5; new iOldTeam = playerTeam[UserIndex]; new Name[MAX_NAME_LENGTH]; playerinfo(UserIndex, Name, MAX_NAME_LENGTH); // prevent unwanted rejoining of the same team ... if (iNewTeam == playerTeam[UserIndex]) { //say("Preventing rejoining of the same team."); return PLUGIN_HANDLED; } checkTeamBalance(); //displayStatistics(true); // check if player is trying to change teams if ((iNewTeam == CTS || iNewTeam == TS) && (iOldTeam == CTS || iOldTeam == TS)) { //say("Changing team directly."); new opposingTeam = iNewTeam == CTS ? TS : CTS; if (teamCounts[iNewTeam] != 0 && teamCounts[opposingTeam] < PTB_MAXSIZE && ((winnerTeam != 0 && iNewTeam == winnerTeam) || (teamCounts[iNewTeam] >= teamCounts[opposingTeam]) ) ) { // player is wtjing new text[MAX_TEXT_LENGTH]; wtjCount[UserIndex] += 1; if (wtjCount[UserIndex] >= PTB_WTJKICK && PTB_KICK) { snprintf(text, 80, "Team balance: Kicking %s for a WTJ count of %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 10, 255, 10); say(text); kick(Name); return PLUGIN_HANDLED; } if (PTB_TELLWTJ) { if (iNewTeam == CTS) { snprintf(text, 80, "Team balance: The CTs are strong enough, %s (WTJ count: %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 10, 10, 255); } else { snprintf(text, 80, "Team balance: The Ts are strong enough, %s (WTJ count: %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 255, 10, 10); } say(text); } return PLUGIN_HANDLED; } // check for maximum team size if (teamCounts[iNewTeam] + 1 > PTB_MAXSIZE) { selfmessage("Team balance: Maximum team size prohibits team change."); return PLUGIN_HANDLED; } // check team size difference limits if ((teamCounts[iNewTeam] + 1) - (teamCounts[opposingTeam] - 1) > PTB_MAXDIFF) { selfmessage("Team balance: Maximum team size difference prohibits team change."); return PLUGIN_HANDLED; } } else if (iNewTeam == CTS || iNewTeam == TS) { //say("Joining team directly."); new opposingTeam = iNewTeam == CTS ? TS : CTS; if (teamCounts[iNewTeam] != 0 && teamCounts[opposingTeam] < PTB_MAXSIZE && ( (winnerTeam != 0 && iNewTeam == winnerTeam) // && teamCounts[winnerTeam] >= teamCounts[opposingTeam]) || (winnerTeam == 0 && teamCounts[iNewTeam] > teamCounts[opposingTeam]) ) ) { new text[MAX_TEXT_LENGTH]; wtjCount[UserIndex] += 1; if (wtjCount[UserIndex] >= PTB_WTJKICK && PTB_KICK) { snprintf(text, 80, "Team balance: Kicking %s for a WTJ count of %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 10, 255, 10); say(text); kick(Name); return PLUGIN_HANDLED; } if (iNewTeam == CTS) { if (wtjCount[UserIndex] > PTB_WTJAUTO) { manageWtjFile(UserIndex); snprintf(text, 80, "Team balance: Forcing %s to the Ts (WTJ count: %d).", Name, wtjCount[UserIndex]); execclient(Name, "menuselect 1"); doTypesay(text, 5, 10, 255, 10); say(text); } else { if (PTB_TELLWTJ) { snprintf(text, 80, "Teambalance: The CTs are strong enough, %s (WTJ count: %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 10, 10, 255); say(text); } } } else { if (wtjCount[UserIndex] >= PTB_WTJAUTO) { manageWtjFile(UserIndex); snprintf(text, 80, "Teambalance: Forcing %s to the CTs (WTJ count: %d).", Name, wtjCount[UserIndex]); execclient(Name, "menuselect 2"); doTypesay(text, 5, 10, 255, 10); say(text); } else { if (PTB_TELLWTJ) { snprintf(text, 80, "Teambalance: The Ts are strong enough, %s (WTJ count: %d).", Name, wtjCount[UserIndex]); doTypesay(text, 5, 255, 10, 10); say(text); } } } return PLUGIN_HANDLED; } // check for maximum team size if (teamCounts[iNewTeam] + 1 > PTB_MAXSIZE) { selfmessage("Teambalance: Maximum team size prohibits team join."); return PLUGIN_HANDLED; } // check team size difference limits if ((teamCounts[iNewTeam] + 1) - (teamCounts[opposingTeam]) > PTB_MAXDIFF) { selfmessage("Teambalance: Maximum team size difference prohibits team join."); return PLUGIN_HANDLED; } } else if (iNewTeam == AUTO_TEAM && (iOldTeam == CTS || iOldTeam == TS)) { //say("Changing team automatically."); new opposingTeam = iOldTeam == CTS ? TS : CTS; if ((teamCounts[opposingTeam] != 0) && ((opposingTeam == PTB_MAXSIZE) || ((loserTeam != 0) && (iOldTeam == loserTeam)) || ((loserTeam == 0) && (teamCounts[iOldTeam] <= teamCounts[opposingTeam])) || ((teamCounts[opposingTeam] + 1) - (teamCounts[iOldTeam] - 1) > PTB_MAXDIFF) ) ) { //say("Aborting automatic team change."); // close menu return PLUGIN_HANDLED; } iNewTeam = opposingTeam; // check for maximum team size if (teamCounts[iNewTeam]+1 > PTB_MAXSIZE) { selfmessage("Teambalance: Maximum team size prohibits team change."); return PLUGIN_HANDLED; } if (iNewTeam == CTS) { execclient(Name, "menuselect 2"); } else { execclient(Name, "menuselect 1"); } return PLUGIN_HANDLED; } else if (iNewTeam == AUTO_TEAM) { //say("Joining team automatically."); // 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] + 1) - (teamCounts[TS]) > PTB_MAXDIFF) iNewTeam = TS; else if ((teamCounts[TS] + 1) - (teamCounts[CTS]) > PTB_MAXDIFF) iNewTeam = CTS; else if (winnerTeam != 0) 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(2) + 1; // check for maximum team size if (teamCounts[iNewTeam] + 1 > PTB_MAXSIZE) { selfmessage("Team balance: Maximum team size prohibits team join."); return PLUGIN_HANDLED; } if (iNewTeam == CTS) { execclient(Name, "menuselect 2"); } else { execclient(Name, "menuselect 1"); } return PLUGIN_HANDLED; } //say("Allow team join ..."); return PLUGIN_CONTINUE; } public ptb_teamscore (HLCommand, HLData, HLUserName, UserIndex) { new params[MAX_DATA_LENGTH]; new team[MAX_DATA_LENGTH]; new myscore[8]; new target[3]; convert_string(HLData, params, MAX_DATA_LENGTH); strsplit(params, " ",target,3,team, MAX_DATA_LENGTH,myscore,8); if (streq(team,"CT")) { teamScores[CTS] = strtonum(myscore); } else if (streq(team,"TERRORIST")) { teamScores[TS] = strtonum(myscore); } } public ptb_teamaction (HLCommand, HLData, HLUserName, UserIndex) { //say("ptb_teamaction"); new params[MAX_DATA_LENGTH]; new type[3]; new action[MAX_DATA_LENGTH]; new target[3]; convert_string(HLData, params, MAX_DATA_LENGTH); strsplit(params, " ",target,3,type, 3,action,MAX_DATA_LENGTH); // count won rounds in a row if (!betweenRounds) { // prevent "bomb explodes after ts win" situations // from producing 2 wins in the same round if (streq(action, "%!MRAD_ctw")) { if (winStreaks[CTS] < 0) { // reset counters winStreaks[CTS] = 1; winStreaks[TS] = -1; } else { winStreaks[CTS] += 1; winStreaks[TS] -= 1; } } else if (streq(action, "%!MRAD_terw")) { if (winStreaks[TS] < 0) { // reset counters winStreaks[CTS] = -1; winStreaks[TS] = 1; } else { winStreaks[CTS] -= 1; winStreaks[TS] += 1; } } actAtEndOfRound(); } // clear flags betweenRounds = true; return PLUGIN_CONTINUE; } public ptb_worldaction (HLCommand, HLData, HLUserName, UserIndex) { //log("Team Balance: ptb_worldaction"); //log(Data); //if (streq(Data, "Round_Start")) { ++roundCounter; betweenRounds = false; //set_timer("announceStatus", 15, 1); announceStatus(); //} return PLUGIN_CONTINUE; } public ptb_teamselection (HLCommand, HLData, HLUserName, UserIndex) { //say("ptb_teamselection"); //displayStatistics(true); new data[MAX_DATA_LENGTH]; new target[3]; new sIndex[4]; new sTeam[MAX_DATA_LENGTH]; new i; new team; convert_string(HLData, data, MAX_DATA_LENGTH); strsplit(data, " ",target, 3 , sIndex, 4, sTeam,MAX_DATA_LENGTH); //log("Team Balance: ptb_teamselection"); //log(data); //strsplit(data, " ", sIndex, MAX_DATA_LENGTH, sTeam, MAX_DATA_LENGTH); //new debug[MAX_DATA_LENGTH]; //snprintf(debug, MAX_DATA_LENGTH, "Team Balance: ptb_teamselection: %s", data); //log(debug); if (streq(sTeam, "TERRORIST")) { team = TS; } else if (streq(sTeam, "CT")) { team = CTS; } else { team = 0; } //if (team == 0) say("Team Balance: ptb_teamselection: Team is 0."); i = strtonum(sIndex); if ((playerTeam[i] == TS) || (playerTeam[i] == CTS)) teamCounts[playerTeam[i]] -= 1; if ((team == TS) || (team == CTS)) teamCounts[team] += 1; playerTeam[i] = team; lastRoundSwitched[i] = roundCounter; //say("ptb_teamselection"); //say(data); //displayStatistics(true); return PLUGIN_CONTINUE; } public ptb_kill (HLCommand, HLData, HLUserName, UserIndex) { //say("ptb_kill"); new iWinner; new iLoser; new idWinner[3]; new idLoser[3]; new target[3]; new data[MAX_DATA_LENGTH]; new name[MAX_NAME_LENGTH]; convert_string(HLData, data, MAX_DATA_LENGTH); //log("Team Balance: ptb_kill"); //log(data); strsplit(data, " ",target,3, idWinner, 3, idLoser, 3); iWinner = strtonum(idWinner); iLoser = strtonum(idLoser); if (iWinner==0) { return PLUGIN_CONTINUE; //Happens on WorldSpawn } if (iLoser==0) { return PLUGIN_CONTINUE; //shouldn*t happen } if (!playerinfo(iWinner, name, MAX_NAME_LENGTH)) { return PLUGIN_CONTINUE; } kills[iWinner] = kills[iWinner] + 1; deaths[iLoser] = deaths[iLoser] + 1; //if (playerTeam[iWinner] != CTS && playerTeam[iWinner] != TS) log("Team Balance: Kill without team!"); //if (playerTeam[iLoser] != CTS && playerTeam[iLoser] != TS) log("Team Balance: Death without team!"); return PLUGIN_CONTINUE; } calcTeamScores () { //say("calcTeamScores"); teamDeaths[CTS] = 0; teamDeaths[TS] = 0; teamKills[CTS] = 0; teamKills[TS] = 0; for (new i=1; i<MAX_PLAYERS; ++i) { new team = playerTeam[i]; if (team == CTS || team == TS) { teamKills[team] += kills[i]; teamDeaths[team] += deaths[i]; } } ctKD = fdivWorkaround(fixed(teamKills[CTS]), fixed(teamDeaths[CTS])); tKD = fdivWorkaround(fixed(teamKills[TS]), fixed(teamDeaths[TS])); //say("calcTeamScores DONE"); } announceStatus () { //say("announceStatus"); if (!PTB_ANNOUNCE) return; checkTeamBalance(); new text[MAX_TEXT_LENGTH]; if (winnerTeam == TS) { snprintf(text, MAX_TEXT_LENGTH, "Team balance: The COUNTER-TERRORIST team could use some support."); doTypesay(text, 5, 10, 10, 255); say("Team balance: The COUNTER-TERRORIST team could use some support."); } else if (winnerTeam == CTS) { snprintf(text, MAX_TEXT_LENGTH, "Team balance: The TERRORIST team could use some support."); doTypesay(text, 5, 255, 10, 10); say("Team balance: The TERRORIST team could use some support."); } else if (wtConditions[TS] > wtConditions[CTS]) { snprintf(text, MAX_TEXT_LENGTH, "Team balance: Observing TERRORIST team advantage."); doTypesay(text, 5, 10, 10, 255); say("Team balance: Observing TERRORIST team advantage."); } else if (wtConditions[CTS] > wtConditions[TS]) { snprintf(text, MAX_TEXT_LENGTH, "Team balance: Observing COUNTER-TERRORIST team advantage."); doTypesay(text, 5, 255, 10, 10); say("Team balance: Observing COUNTER-TERRORIST team advantage."); } else if (PTB_SAYOK) { snprintf(text, MAX_TEXT_LENGTH, "Team balance: Teams look fine, no action required."); doTypesay(text, 5, 255, 255, 255); say("Team balance: Teams look fine, no action required."); } //displayStatistics(); } public admin_teambalance (HLCommand, HLData, HLUserName, UserIndex) { new Data[MAX_DATA_LENGTH]; new Command[MAX_DATA_LENGTH]; new Argument[MAX_DATA_LENGTH]; new s[MAX_TEXT_LENGTH]; convert_string(HLData, Data, MAX_NAME_LENGTH); strinit(Command); strinit(Argument); if (!strlen(Data)) { checkTeamBalance(); displayStatistics(); return PLUGIN_HANDLED; } else if (streq(Data, "on") || streq(Data, "1")) { if (!access(ACCESS_BALANCE, "")) { selfmessage("Team balance: You are not allowed to use this command."); return PLUGIN_HANDLED; } PTB_LIMITJOIN = 1; PTB_SWITCH = 1; PTB_ANNOUNCE = 1; selfmessage("Team Balance: Enabled all PTB actions."); return PLUGIN_HANDLED; } else if (streq(Data, "off") || streq(Data, "0")) { if (!access(ACCESS_BALANCE, "")) { selfmessage("Team balance: You are not allowed to use this command."); return PLUGIN_HANDLED; } PTB_SWITCH = 0; PTB_ANNOUNCE = 0; PTB_LIMITJOIN = 0; selfmessage("Team Balance: Disabled all PTB actions."); return PLUGIN_HANDLED; } else if (streq(Data, "save")) { if (!access(ACCESS_BALANCE, "")) { selfmessage("Team balance: You are not allowed to use this command."); return PLUGIN_HANDLED; } saveSettings(); selfmessage("Team balance: Saved all PTB settings to ^"vault.ini^"."); return PLUGIN_HANDLED; } else if (streq(Data, "load")) { if (!access(ACCESS_BALANCE, "")) { selfmessage("Team balance: You are not allowed to use this command."); return PLUGIN_HANDLED; } loadSettings(); selfmessage("Team balance: Loaded all Team balance settings from ^"vault.ini^"."); return PLUGIN_HANDLED; } else if (streq(Data, "list") || streq(Data, "help")) { selfmessage("Team balance: Available Commands:"); selfmessage("Team balance: Team Join Control: ^"limitjoin^", ^"limitafter^", ^"limitmin^", ^"maxsize^", ^"autorounds^","); selfmessage("Team balance: ^"maxdiff^", ^"wtjauto^", ^"wtjkick^", ^"kick^", ^"savewtj^""); selfmessage("Team balance: Team Balancing Actions: ^"switch^", ^"switchafter^", ^"switchmin^", ^"switchfreq^", ^"playerfreq^","); selfmessage("Team balance: ^"forceswitch^", ^"deadonly^""); selfmessage("Team balance: Team Strength Limits: ^"maxstreak^", ^"maxscore^", ^"minrating^", ^"maxrating^", ^"superrating^""); selfmessage("Team balance: Messages: ^"tellwtj^", ^"announce^", ^"sayok^", ^"typesay^""); selfmessage("Team balance: Misc: ^"^", ^"status^", ^"list^", ^"help^", ^"on^", ^"off^", ^"save^", ^"load^""); selfmessage("Team balance: To view all PTB settings, type ^"admin_teambalance status^"."); selfmessage("Team balance: To view or change a single PTB setting, type ^"admin_teambalance <setting> <^"on^"|^"off^"|value>^"."); selfmessage("Team balance: For PTB statistics, simply type ^"admin_teambalance^"."); return PLUGIN_HANDLED; } else { strsplit(Data, " ", Command, MAX_DATA_LENGTH, Argument, MAX_DATA_LENGTH); if (strlen(Argument) && !access(ACCESS_BALANCE, "")) { selfmessage("Team balance: You are not allowed to use this command."); return PLUGIN_HANDLED; } } // team selection control if (streq(Command, "status")) selfmessage("Team balance: ---------- Team Selection Control ----------"); // PTB_LIMITJOIN if (streq(Command, "limitjoin") && strlen(Argument)) PTB_LIMITJOIN = check_param(Argument); if (streq(Command, "status") || streq(Command, "limitjoin")) { if (PTB_LIMITJOIN) selfmessage("Team balance: (limitjoin) WTJ prevention is ON."); else selfmessage("Team balance: (limitjoin) WTJ prevention is OFF."); } // PTB_LIMITAFTER if (streq(Command, "limitafter") && strlen(Argument)) { PTB_MAXDIFF = strtonum(Argument); if (PTB_LIMITAFTER < 0) PTB_LIMITAFTER = 0; } if (streq(Command, "status") || streq(Command, "limitafter")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (limitafter) Team limiting starts after %d round(s).", PTB_LIMITAFTER); selfmessage(s); } // PTB_LIMITMIN if (streq(Command, "limitmin") && strlen(Argument)) { PTB_LIMITMIN = strtonum(Argument); if (PTB_LIMITMIN < 0) PTB_LIMITMIN = 0; } if (streq(Command, "status") || streq(Command, "limitmin")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (limitmin) Team limiting needs at least %d player(s).", PTB_LIMITMIN); selfmessage(s); } // PTB_MAXSIZE if (streq(Command, "maxsize") && strlen(Argument)) { PTB_MAXSIZE = strtonum(Argument); if (PTB_MAXSIZE < 0) PTB_MAXSIZE = 0; } if (streq(Command, "status") || streq(Command, "maxsize")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (maxsize) Maximum team size is %d player(s).", PTB_MAXSIZE); selfmessage(s); } // PTB_MAXDIFF if (streq(Command, "maxdiff") && strlen(Argument)) { PTB_MAXDIFF = strtonum(Argument); if (PTB_MAXDIFF < 1) PTB_MAXDIFF = 1; } if (streq(Command, "status") || streq(Command, "maxdiff")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (maxdiff) Maximum team size difference is %d.", PTB_MAXDIFF); selfmessage(s); } // PTB_AUTOROUNDS if (streq(Command, "autorounds") && strlen(Argument)) { PTB_AUTOROUNDS = strtonum(Argument); if (PTB_AUTOROUNDS < 0) PTB_AUTOROUNDS = 0; } if (streq(Command, "status") || streq(Command, "autorounds")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (autorounds) First %d rounds no free team choice.", PTB_AUTOROUNDS); selfmessage(s); } // PTB_WTJAUTO if (streq(Command, "wtjauto") && strlen(Argument)) { PTB_WTJAUTO = strtonum(Argument); if (PTB_WTJAUTO < 0) PTB_WTJAUTO = 0; } if (streq(Command, "status") || streq(Command, "wtjauto")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (wtjauto) Auto-joining WTJ after %d tr(y/ies).", PTB_WTJAUTO); selfmessage(s); } // PTB_WTJKICK if (streq(Command, "wtjkick") && strlen(Argument)) { PTB_WTJKICK = strtonum(Argument); if (PTB_WTJKICK < 1) PTB_WTJKICK = 1; } if (streq(Command, "status") || streq(Command, "wtjkick")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (wtjauto) Auto-kicking WTJ after %d tr(y/ies).", PTB_WTJKICK); selfmessage(s); } // PTB_KICK if (streq(Command, "kick") && strlen(Argument)) PTB_KICK = check_param(Argument); if (streq(Command, "status") || streq(Command, "kick")) { if (PTB_KICK) selfmessage("Team balance: (kick) WTJ kicking is ON."); else selfmessage("Team balance: (kick) Kicking is OFF."); } // PTB_SAVEWTJ if (streq(Command, "savewtj") && strlen(Argument)) PTB_SAVEWTJ = check_param(Argument); if (streq(Command, "status") || streq(Command, "savewtj")) { if (PTB_SAVEWTJ) selfmessage("Team balance: (savewtj) Saving to wtj.txt is ON."); else selfmessage("Team balance: (savewtj) Saving to wtj.txt is OFF."); } // team balancing actions if (streq(Command, "status")) selfmessage("Team balance: ---------- Team Balancing Actions ----------"); // PTB_SWITCH if (streq(Command, "switch") && strlen(Argument)) PTB_SWITCH = check_param(Argument); if (streq(Command, "status") || streq(Command, "switch")) { if (PTB_SWITCH) selfmessage("Team balance: (switch) Team switching is ON."); else selfmessage("Team balance: (switch) Team switching is OFF."); } // PTB_SWITCHAFTER if (streq(Command, "switchafter") && strlen(Argument)) { PTB_SWITCHAFTER = strtonum(Argument); if (PTB_SWITCHAFTER < 0) PTB_SWITCHFREQ = 0; } if (streq(Command, "status") || streq(Command, "switchafter")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (switchafter) Switching starts after %d round(s).", PTB_SWITCHAFTER); selfmessage(s); } // PTB_SWITCHMIN if (streq(Command, "switchmin") && strlen(Argument)) { PTB_SWITCHMIN = strtonum(Argument); if (PTB_SWITCHMIN < 0) PTB_SWITCHMIN = 0; } if (streq(Command, "status") || streq(Command, "switchmin")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (switchmin) Switching needs at least %d player(s).", PTB_SWITCHMIN); selfmessage(s); } // PTB_PLAYERFREQ if (streq(Command, "playerfreq") && strlen(Argument)) { PTB_PLAYERFREQ = strtonum(Argument); if (PTB_PLAYERFREQ < 0) PTB_PLAYERFREQ = 0; } if (streq(Command, "status") || streq(Command, "playerfreq")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (playerfreq) Individual players are switched every %d round(s) at maximum.", PTB_PLAYERFREQ); selfmessage(s); } // PTB_SWITCHFREQ if (streq(Command, "switchfreq") && strlen(Argument)) { PTB_SWITCHFREQ = strtonum(Argument); if (PTB_SWITCHFREQ < 1) PTB_SWITCHFREQ = 1; } if (streq(Command, "status") || streq(Command, "switchfreq")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (switchfreq) Switch occurs every %d round(s) at maximum.", PTB_SWITCHFREQ); selfmessage(s); } // PTB_FORCESWITCH if (streq(Command, "forceswitch") && strlen(Argument)) { PTB_FORCESWITCH = strtonum(Argument); if (PTB_FORCESWITCH < 0) PTB_FORCESWITCH = 0; } if (streq(Command, "status") || streq(Command, "forceswitch")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (forceswitch) Forcing switch after %d unsuccessful switch(es).", PTB_FORCESWITCH); selfmessage(s); } // PTB_DEADONLY if (streq(Command, "deadonly") && strlen(Argument)) PTB_DEADONLY = check_param(Argument); if (streq(Command, "status") || streq(Command, "deadonly")) { if (PTB_DEADONLY) selfmessage("Team balance: (deadonly) Switching dead only is ON."); else selfmessage("Team balance: (deadonly) Switching dead only is OFF."); } // messages if (streq(Command, "status")) selfmessage("Team balance: ---------- Messages ----------"); // PTB_TELLWTJ if (streq(Command, "tellwtj") && strlen(Argument)) PTB_TELLWTJ = check_param(Argument); if (streq(Command, "status") || streq(Command, "tellwtj")) { if (PTB_TELLWTJ) selfmessage("Team balance: (tellwtj) Telling about WTJ tries is ON."); else selfmessage("Team balance: (tellwtj) Telling about WTJ tries is OFF."); } // PTB_ANNOUNCE if (streq(Command, "announce") && strlen(Argument)) PTB_ANNOUNCE = check_param(Argument); if (streq(Command, "status") || streq(Command, "announce")) { if (PTB_ANNOUNCE) selfmessage("Team balance: (announce) Announcements are ON."); else selfmessage("Team balance: (announce) Announcements are OFF."); } // PTB_SAYOK if (streq(Command, "sayok") && strlen(Argument)) PTB_SAYOK = check_param(Argument); if (streq(Command, "status") || streq(Command, "sayok")) { if (PTB_SAYOK) selfmessage("Team balance: (sayok) ^"OK^" announcements are ON."); else selfmessage("Team balance: (sayok) ^"OK^" announcements are OFF."); } // PTB_TYPESAY if (streq(Command, "typesay") && strlen(Argument)) PTB_TYPESAY = check_param(Argument); if (streq(Command, "status") || streq(Command, "typesay")) { if (PTB_TYPESAY) selfmessage("Team balance: (typesay) typesay usage is ON."); else selfmessage("Team balance: (typesay) typesay usage is OFF."); } // team strength limits if (streq(Command, "status")) selfmessage("Team balance: ---------- Team Strength Limits ----------"); // PTB_MAXSTREAK if (streq(Command, "maxstreak") && strlen(Argument)) { PTB_MAXSTREAK = strtonum(Argument); if (PTB_MAXSTREAK < 1) PTB_MAXSTREAK = 1; } if (streq(Command, "status") || streq(Command, "maxstreak")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (maxstreak) Maximum accepted win streak is %d.", PTB_MAXSTREAK); selfmessage(s); } // PTB_MAXSTREAK if (streq(Command, "maxscore") && strlen(Argument)) { PTB_MAXSCORE = strtonum(Argument); if (PTB_MAXSCORE < 1) PTB_MAXSCORE = 1; } if (streq(Command, "status") || streq(Command, "maxscore")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (maxscore) Maximum accepted team score difference is %d.", PTB_MAXSCORE); selfmessage(s); } // PTB_MINRATING if (streq(Command, "minrating") && strlen(Argument)) { PTB_MINRATING = fixedstr(Argument); if (PTB_MINRATING < fixed(1)) PTB_MINRATING = fixed(1); } if (streq(Command, "status") || streq(Command, "minrating")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (minrating) Minimum critical strength rating is %d.%d.", fround(PTB_MINRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_MINRATING)), fround_floor) ); selfmessage(s); } // PTB_MAXRATING if (streq(Command, "maxrating") && strlen(Argument)) { PTB_MAXRATING = fixedstr(Argument); if (PTB_MAXRATING < fixed(1)) PTB_MAXRATING = fixed(1); } if (streq(Command, "status") || streq(Command, "maxrating")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (maxrating) Maximum critical strength rating is %d.%d.", fround(PTB_MAXRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_MAXRATING)), fround_floor) ); selfmessage(s); } // PTB_SUPERRATING if (streq(Command, "superrating") && strlen(Argument)) { PTB_SUPERRATING = fixedstr(Argument); if (PTB_SUPERRATING < fixed(1)) PTB_SUPERRATING = fixed(1); } if (streq(Command, "status") || streq(Command, "superrating")) { snprintf(s, MAX_TEXT_LENGTH, "Team balance: (superrating) Super critical strength rating is %d.%d.", fround(PTB_SUPERRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_SUPERRATING)), fround_floor) ); selfmessage(s); } // misc if (streq(Command, "status")) { selfmessage("Team balance: ---------- Misc ----------"); selfmessage("Team balance: To enable or disable PTB, type ^"admin_teambalance <^"on^"|^"1^"|^"off^"|^"0^">^"."); selfmessage("Team balance: To view or change a single PTB setting, type ^"admin_teambalance <setting> <^"on^"|^"off^"|value>^"."); selfmessage("Team balance: To view a brief overview of PTB commands, type ^"admin_teambalance help^" or ^"admin_teambalance list^"."); selfmessage("Team balance: To save all PTB settings to ^"vault.ini^", type ^"admin_teambalance save^"."); selfmessage("Team balance: To load all PTB settings from ^"vault.ini^", type ^"admin_teambalance load^"."); selfmessage("Team balance: For PTB statistics, simply type ^"admin_teambalance^"."); } return PLUGIN_HANDLED; } stock saveSettings () { new VAULTDATA[MAX_DATA_LENGTH]; // team selection control if (PTB_LIMITJOIN) set_vaultdata("PTB_LIMITJOIN", "on"); else set_vaultdata("PTB_LIMITJOIN", "off"); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_LIMITAFTER); set_vaultdata("PTB_LIMITAFTER", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_LIMITMIN); set_vaultdata("PTB_LIMITMIN", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_MAXSIZE); set_vaultdata("PTB_MAXSIZE", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_MAXDIFF); set_vaultdata("PTB_MAXDIFF", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_AUTOROUNDS); set_vaultdata("PTB_AUTOROUNDS", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_WTJAUTO); set_vaultdata("PTB_WTJAUTO", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_WTJKICK); set_vaultdata("PTB_WTJKICK", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_KICK); set_vaultdata("PTB_KICK", VAULTDATA); if (PTB_SAVEWTJ) set_vaultdata("PTB_SAVEWTJ", "on"); else set_vaultdata("PTB_SAVEWTJ", "off"); // team balancing actions if (PTB_SWITCH) set_vaultdata("PTB_SWITCH", "on"); else set_vaultdata("PTB_SWITCH", "off"); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_SWITCHAFTER); set_vaultdata("PTB_SWITCHAFTER", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_SWITCHMIN); set_vaultdata("PTB_SWITCHMIN", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_SWITCHFREQ); set_vaultdata("PTB_SWITCHFREQ", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_PLAYERFREQ); set_vaultdata("PTB_PLAYERFREQ", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_FORCESWITCH); set_vaultdata("PTB_FORCESWITCH", VAULTDATA); if (PTB_DEADONLY) set_vaultdata("PTB_DEADONLY", "on"); else set_vaultdata("PTB_DEADONLY", "off"); // messages if (PTB_TELLWTJ) set_vaultdata("PTB_TELLWTJ", "on"); else set_vaultdata("PTB_TELLWTJ", "off"); if (PTB_ANNOUNCE) set_vaultdata("PTB_ANNOUNCE", "on"); else set_vaultdata("PTB_ANNOUNCE", "off"); if (PTB_SAYOK) set_vaultdata("PTB_SAYOK", "on"); else set_vaultdata("PTB_SAYOK", "off"); if (PTB_TYPESAY) set_vaultdata("PTB_TYPESAY", "on"); else set_vaultdata("PTB_TYPESAY", "off"); // team strength limits snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_MAXSTREAK); set_vaultdata("PTB_MAXSTREAK", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d", PTB_MAXSCORE); set_vaultdata("PTB_MAXSCORE", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d.%d", fround(PTB_MINRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_MINRATING)), fround_floor) ); set_vaultdata("PTB_MINRATING", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d.%d", fround(PTB_MAXRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_MAXRATING)), fround_floor) ); set_vaultdata("PTB_MAXRATING", VAULTDATA); snprintf(VAULTDATA, MAX_DATA_LENGTH, "%d.%d", fround(PTB_SUPERRATING, fround_floor), fround(fmul(fixed(10), ffract(PTB_SUPERRATING)), fround_floor) ); set_vaultdata("PTB_SUPERRATING", VAULTDATA); } stock loadSettings () { new VAULTDATA[MAX_DATA_LENGTH]; // team selection control if (get_vaultdata("PTB_LIMITJOIN", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_LIMITJOIN = check_param(VAULTDATA); if (get_vaultdata("PTB_LIMITAFTER", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_LIMITAFTER = strtonum(VAULTDATA); if (get_vaultdata("PTB_LIMITMIN", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_LIMITMIN = strtonum(VAULTDATA); if (get_vaultdata("PTB_MAXSIZE", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MAXSIZE = strtonum(VAULTDATA); if (get_vaultdata("PTB_MAXDIFF", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MAXDIFF = strtonum(VAULTDATA); if (get_vaultdata("PTB_AUTOROUNDS", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_AUTOROUNDS = strtonum(VAULTDATA); if (get_vaultdata("PTB_WTJAUTO", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_WTJAUTO = strtonum(VAULTDATA); if (get_vaultdata("PTB_WTJKICK", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_WTJKICK = strtonum(VAULTDATA); if (get_vaultdata("PTB_KICK", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_KICK = check_param(VAULTDATA); if (get_vaultdata("PTB_SAVEWTJ", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SAVEWTJ = check_param(VAULTDATA); // team balancing actions if (get_vaultdata("PTB_SWITCH", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SWITCH = check_param(VAULTDATA); if (get_vaultdata("PTB_SWITCHAFTER", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SWITCHAFTER = strtonum(VAULTDATA); if (get_vaultdata("PTB_SWITCHMIN", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SWITCHMIN = strtonum(VAULTDATA); if (get_vaultdata("PTB_SWITCHFREQ", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SWITCHFREQ = strtonum(VAULTDATA); if (get_vaultdata("PTB_PLAYERFREQ", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_PLAYERFREQ = strtonum(VAULTDATA); if (get_vaultdata("PTB_FORCESWITCH", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_FORCESWITCH = strtonum(VAULTDATA); if (get_vaultdata("PTB_DEADONLY", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_DEADONLY = check_param(VAULTDATA); // messages if (get_vaultdata("PTB_TELLWTJ", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_TELLWTJ = check_param(VAULTDATA); if (get_vaultdata("PTB_ANNOUNCE", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_ANNOUNCE = check_param(VAULTDATA); if (get_vaultdata("PTB_SAYOK", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SAYOK = check_param(VAULTDATA); if (get_vaultdata("PTB_TYPESAY", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_TYPESAY = check_param(VAULTDATA); // team strength limits if (get_vaultdata("PTB_MAXSTREAK", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MAXSTREAK = strtonum(VAULTDATA); if (get_vaultdata("PTB_MAXSCORE", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MAXSCORE = strtonum(VAULTDATA); if (get_vaultdata("PTB_MINRATING", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MINRATING = fixedstr(VAULTDATA); if (get_vaultdata("PTB_MAXRATING", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_MAXRATING = fixedstr(VAULTDATA); if (get_vaultdata("PTB_SUPERRATING", VAULTDATA, MAX_DATA_LENGTH ) != 0) PTB_SUPERRATING = fixedstr(VAULTDATA); } stock displayStatistics (toLog = false) { //say("displayStatistics"); new text[MAX_TEXT_LENGTH]; // time snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Statistics generated at: %s", lastTeamBalanceCheck); if (toLog) { log(text); } else { selfmessage(text); } // connected players snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Connected players: %d", playercount()); if (toLog) { log(text); } else { selfmessage(text); } // team sizes snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team sizes: CTs %d, Ts %d", teamCounts[CTS], teamCounts[TS]); if (toLog) { log(text); } else { selfmessage(text); } // team scores snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team scores: CTs %d, Ts %d", teamScores[CTS], teamScores[TS]); if (toLog) { log(text); } else { selfmessage(text); } // Kills:Deaths snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team kills:deaths: CTs %d:%d, Ts %d:%d", teamKills[CTS], teamDeaths[CTS], teamKills[TS], teamDeaths[TS]); if (toLog) { log(text); } else { selfmessage(text); } // Kills/Deaths snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team kills/deaths: CTs %d.%d, Ts %d.%d", fround(ctKD, fround_floor), fround(fmul(fixed(100), ffract(ctKD)), fround_floor), fround(tKD, fround_floor), fround(fmul(fixed(100), ffract(tKD)), fround_floor) ); if (toLog) { log(text); } else { selfmessage(text); } // strength snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team strengths: CTs %d.%d, Ts %d.%d", fround(ctStrength, fround_floor), fround(fmul(fixed(100), ffract(ctStrength)), fround_floor), fround(tStrength, fround_floor), fround(fmul(fixed(100), ffract(tStrength)), fround_floor) ); if (toLog) { log(text); } else { selfmessage(text); } // rating snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Team ratings: CTs %d.%d, Ts %d.%d", fround(ctRating, fround_floor), fround(fmul(fixed(100), ffract(ctRating)), fround_floor), fround(tRating, fround_floor), fround(fmul(fixed(100), ffract(tRating)), fround_floor) ); if (toLog) { log(text); } else { selfmessage(text); } // won rounds if (winStreaks[CTS] > 0) { snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Last %d round(s) won by CTs.", winStreaks[CTS]); if (toLog) { log(text); }else { selfmessage(text); } } else if (winStreaks[TS] > 0) { snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Last %d round(s) won by Ts.", winStreaks[TS]); if (toLog) { log(text); }else { selfmessage(text); } } // winning team if (winnerTeam == CTS) { snprintf(text, MAX_TEXT_LENGTH, "Team Balance: The CTs are the winning team."); } else if (winnerTeam == TS) { snprintf(text, MAX_TEXT_LENGTH, "Team Balance: The Ts are the winning team."); } else { snprintf(text, MAX_TEXT_LENGTH, "Team Balance: Teams are balanced."); } if (toLog) { log(text); } else { selfmessage(text); } snprintf(text, MAX_TEXT_LENGTH, "Team Balance: (These statistics might be already outdated.)"); if (toLog) { log(text); } else { selfmessage(text); } snprintf(text, MAX_TEXT_LENGTH, "Team Balance: To view a brief overview of Team balance commands, type ^"admin_teambalance help^" or ^"admin_teambalance list^"."); if (toLog) { log(text); } else { selfmessage(text); } snprintf(text, MAX_TEXT_LENGTH, "Team Balance: To view all Team balance settings, type ^"admin_teambalance status^"."); if (toLog) { log(text); } else { selfmessage(text); } } public plugin_connect (HLUserName, HLIP, UserIndex) { //say("plugin_connect"); if ((UserIndex >= 1) && (UserIndex < MAX_PLAYERS)) { kills[UserIndex] = 0; deaths[UserIndex] = 0; isChoosingTeam[UserIndex] = 1; isBeingTransfered[UserIndex] = 0; playerTeam[UserIndex] = 0; lastRoundSwitched[UserIndex] = 0; wtjCount[UserIndex] = 0; } else { say("Team Balance: Shouldn't get here, unexpected UserIndex."); } return PLUGIN_CONTINUE; } public plugin_disconnect (HLUserName, UserIndex) { //say("plugin_disconnect"); //new debug[MAX_DATA_LENGTH]; //snprintf(debug, MAX_DATA_LENGTH, "[DEBUG] Team balance plugin_disconnect: %d %d", UserIndex, playerTeam[UserIndex]); //log(debug); if ((UserIndex >= 1) && (UserIndex < MAX_PLAYERS)) { kills[UserIndex] = 0; deaths[UserIndex] = 0; isChoosingTeam[UserIndex] = 0; isBeingTransfered[UserIndex] = 0; if (playerTeam[UserIndex] == TS || playerTeam[UserIndex] == CTS) { teamCounts[playerTeam[UserIndex]] -= 1; } playerTeam[UserIndex] = 0; lastRoundSwitched[UserIndex] = 0; wtjCount[UserIndex] = 0; } else { say("Team Balance: Shouldn't get here, unexpected UserIndex."); } // redundant team size check teamCounts[CTS] = 0; teamCounts[TS] = 0; teamCounts[0] = 0; for (new i=1; i<MAX_PLAYERS; ++i) teamCounts[playerTeam[i]] += 1; //displayStatistics(true); return PLUGIN_CONTINUE; } public plugin_init () { //initVariables(); // must do this here PTB_MINRATING = fixedstr(MIN_RATING); PTB_MAXRATING = fixedstr(MAX_RATING); PTB_SUPERRATING = fixedstr(SUPER_RATING); plugin_registerinfo("Team balance plugin", "Evens the teams by moving/switching players each round.", STRING_VERSION); plugin_registercmd("admin_teambalance", "admin_teambalance", ACCESS_ALL, "admin_teambalance <^"on^" | ^"off^" | ^"status^" | ^"^">: Switch Team balance on or off, get status or statistics."); plugin_registercmd("chooseteam", "ChoosingTeam", ACCESS_ALL); plugin_registercmd("menuselect", "MenuSelect", ACCESS_ALL); plugin_registercmd("radio", "NotChoosingTeam", ACCESS_ALL); plugin_registercmd("buy", "NotChoosingTeam", ACCESS_ALL); plugin_registercmd("buyequip", "NotChoosingTeam", ACCESS_ALL); plugin_registercmd("showbriefing", "NotChoosingTeam", ACCESS_ALL); plugin_registercmd("ptb_teamselection", "ptb_teamselection", ACCESS_CONSOLE); plugin_registercmd("ptb_kill", "ptb_kill", ACCESS_CONSOLE); plugin_registercmd("ptb_teamaction", "ptb_teamaction", ACCESS_CONSOLE); plugin_registercmd("ptb_worldaction", "ptb_worldaction", ACCESS_CONSOLE); plugin_registercmd("ptb_teamscore", "ptb_teamscore", ACCESS_CONSOLE); exec("sm_register TeamInfo ^"admin_command ptb_teamselection^" ad"); exec("sm_register DeathMsg ^"admin_command ptb_kill^" ad"); exec("sm_register SendAudio ^"admin_command ptb_teamaction^" ad ^"2=%!MRAD_terwin^" ^"2=%!MRAD_ctwin^" ^"2=%!MRAD_rounddraw^""); exec("sm_register SM_RoundStart ^"admin_command ptb_worldaction^" bdc"); exec("sm_register TeamScore ^"admin_command ptb_teamscore^" ad"); loadSettings(); PTB_MAXSIZE = maxplayercount()/2; return PLUGIN_CONTINUE; }