Jump to content

Insane Limits V0.9/R4: Clan Tag Leaderboard (2 teams, not Rush, not CTF)


ImportBot

Recommended Posts

Originally Posted by PapaCharlie9*:

 

Version 0.9/R4: compiled and tested.

 

This example displays a formatted clan tag leaderboard table at the end of every Conquest or TDM round. The leaderboard is displayed up to 3 times at the end of the round: at 20% of tickets left, at 10% and finally at 5%.

 

Clan tags are sorted and first, second and third place determined by average score and average kills. A clan tag is included only if there are at least 2 players with that tag in the round when the table is calculated. All players without a tag are averaged as the "no tag" entry. If there are insufficient players to fill up the 3rd or 2nd place slot, "--" is displayed. By using averages, a clan that only has 3 players in the round has a fighting chance against a clan with 11 players in the round for first place, etc. Solitary players with a tag are excluded, since one hot-shot pro can dominate a mix of players from another clan. Clans are all about team play anyway. :smile:

 

Here are a couple of screenshots:

 

Posted Image

 

Posted Image

 

NOTE: Coders should see the detailed notes at the bottom of the post. There are some re-usable subroutines that you can put in your toolbox.

 

Set limit to evaluate OnKill, set action to None

 

Set first_check to this Expression:

 

Code:

( !Regex.Match(server.Gamemode, @"(_:Squad|Rush)", RegexOptions.IgnoreCase).Success && (server.RemainTicketsPercent(1) < 20 || server.RemainTicketsPercent(2) < 20) )
Set second_check to this Code:

 

Code:

/* Version 0.9/R4 */
/* Check if near end of round */

int level = 2;

try {
    level = Convert.ToInt32(plugin.getPluginVarValue("debug_level"));
} catch (Exception e) {}

if (!limit.RoundData.issetDouble("EOR")) { // EOR: End Of Round
    limit.RoundData.setDouble("EOR", 20); // Threshhold
}

/* First activation is a gimme, check for 2nd and 3rd */

double thresh = limit.RoundData.getDouble("EOR");

if (server.RemainTicketsPercent(1) > thresh && server.RemainTicketsPercent(2) > thresh) {
    if (level >= 5) {
        plugin.ConsoleWrite("^bEOR Debug^n: T1/T2 = " + String.Format("{0:F1}",server.RemainTicketsPercent(1)) +"/"+ String.Format("{0:F1}",server.RemainTicketsPercent(2)));
    }
    return false; // don't do anything;
}

/* Set the next threshhold */

if (thresh == 20) {
    // Set up the 2nd threshhold
    limit.RoundData.setDouble("EOR", 10);
} else if (thresh == 10) {
    // Set up the 3rd threshhold
    limit.RoundData.setDouble("EOR", 5);
} else if (thresh == 5) {
    // Set up the last threshhold
    limit.RoundData.setDouble("EOR", 0);
} else if (thresh == 0) {
    // Stop announcing
    return false;
}

/* Logging */

String map = plugin.FriendlyMapName(server.MapFileName);

if (level >= 4) {
    double t1 = Math.Round(server.RemainTickets(1));
    double t2 = Math.Round(server.RemainTickets(2));
    String t1p = String.Format("{0:F1}",server.RemainTicketsPercent(1));
    String t2p = String.Format("{0:F1}",server.RemainTicketsPercent(2));
    plugin.ConsoleWrite(@"^b[EOR]^n: ^b^4" + map + "^0^n T1/T2 T1%/T2% = " + t1 +"/"+ t2 + " " + t1p + "/" + t2p + ", threshhold=" + thresh);
}

/* Collect clan statistics */

List<PlayerInfoInterface> players = new List<PlayerInfoInterface>();
players.AddRange(team1.players);
players.AddRange(team2.players);
Dictionary<String, double> clanCounts = new Dictionary<String, double>();
Dictionary<String, double> clanScores = new Dictionary<String, double>();
Dictionary<String, double> clanKills = new Dictionary<String, double>();

foreach(PlayerInfoInterface player_info in players)
{
    String tag = player_info.Tag;
    if(tag.Length == 0) {
        // Maybe they are using [_-=]XXX[=-_]PlayerName format
        Match tm = Regex.Match(player_info.Name, @"^[=_\-]_([^=_\-]{2,4})[=_\-]");
        if (tm.Success) {
            tag = tm.Groups[1].Value;
            if (level >= 4 && !clanCounts.ContainsKey(tag)) plugin.ConsoleWrite("^b[EOR]^n extracted [" + tag + "] from " + player_info.Name);
        } else {
            // Use the default "everybody else" tag
            tag = "no tag";
        }
    }   

    if (!clanCounts.ContainsKey(tag)) clanCounts.Add(tag, 0);

    clanCounts[tag] += 1;

    if (!clanScores.ContainsKey(tag)) clanScores.Add(tag, 0);

    clanScores[tag] += player_info.ScoreRound;

    if (!clanKills.ContainsKey(tag)) clanKills.Add(tag, 0);

    clanKills[tag] += player_info.KillsRound;
}

/* Sort all clans by average score */

String[] scoreTags = { "--", "--", "--" };

List<KeyValuePair<String, double>> list = new List<KeyValuePair<String, double>>();

foreach (KeyValuePair<String, double> pair in clanScores) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 0;
            return 0; // equal
        }
    );

    // Example: #1[LGN]/9: 2039.1, #2no tag/27: 1689.3, #3[SOG]/2: 588.5, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            scoreTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg score" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}


/* Sort all clans by average kills */

String[] killTags = { "--", "--", "--" };

list.Clear();

foreach (KeyValuePair<String, double> pair in clanKills) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 1;
            return 0; // equal
        }
    );

    // Example: #1[SOG]/9: 17.3, #2[365]/2: 11.5, #3no tag/40: 6.6, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            killTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg kills" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}

/* Chat character width table for formatting scoreboard */

/* 
This was done entirely by eyeball, so it is just an estimate. It is
normalized to the pixel width of the lower case 'x' character. That is 1.0.
Values less than 1.0, such as 0.8, are narrower than 'x'. Values greater
than 1.0, such as 1.2, are wider than 'x'.

I estimated the width of a tab column to be 4.7. That errs on the
side of being too low, which means it's possible for the next
column to be too far over. The resulting table might look like
this:

Score   Kills
[XXX]       [YYY]

I thought that was less confusing than being too close:

Score   Kills
[XXX][YYY]

Finally, the table is incomplete. It doesn't have all possible
character codes in it. Any character that is not found in
the table is assumed to be 1.0 in the code that uses the table.
*/

Dictionary<Char, double> widths = new Dictionary<Char, double>();

widths.Add('-', 0.8);

widths.Add('0', 1.3); widths.Add('1', 0.7); widths.Add('2', 1.2); widths.Add('3', 1.2); widths.Add('4', 1.2); widths.Add('5', 1.2); widths.Add('6', 1.1); widths.Add('7', 1.0); widths.Add('8', 1.1); widths.Add('9', 1.1);

widths.Add(':', 1.0); widths.Add(';', 1.0); widths.Add('<', 1.0); widths.Add('=', 1.0); widths.Add('>', 1.0); widths.Add('_', 1.2); widths.Add('@', 2.0);

widths.Add('A', 1.3); widths.Add('B', 1.25); widths.Add('C', 1.3); widths.Add('D', 1.3); widths.Add('E', 1.1); widths.Add('F', 0.9); widths.Add('G', 1.3); widths.Add('H', 1.3); widths.Add('I', 0.5); widths.Add('J', 1.2); widths.Add('K', 1.2); widths.Add('L', 1.1); widths.Add('M', 1.4); widths.Add('N', 1.3); widths.Add('O', 1.3); widths.Add('P', 1.2); widths.Add('Q', 1.3); widths.Add('R', 1.25); widths.Add('S', 1.3); widths.Add('T', 1.2); widths.Add('U', 1.3); widths.Add('V', 1.25); widths.Add('W', 1.8); widths.Add('X', 1.25); widths.Add('Y', 1.25); widths.Add('Z', 1.25);


widths.Add('[', 0.65); widths.Add(']', 0.65); widths.Add('^', 1.0); widths.Add('_', 1.0); widths.Add('`', 1.0);

widths.Add('a', 1.05); widths.Add('b', 1.1); widths.Add('c', 1.0); widths.Add('d', 1.1); widths.Add('e', 1.05); widths.Add('f', 0.7); widths.Add('g', 1.1); widths.Add('h', 1.15); widths.Add('i', 0.35); widths.Add('j', 0.35); widths.Add('k', 1.0); widths.Add('l', 0.35); widths.Add('m', 1.6); widths.Add('n', 1.15); widths.Add('o', 1.1); widths.Add('p', 1.1); widths.Add('q', 1.1); widths.Add('r', 0.75); widths.Add('s', 1.1); widths.Add('t', 0.5); widths.Add('u', 1.15); widths.Add('v', 1.1); widths.Add('w', 1.5); widths.Add('x', 1.0); widths.Add('y', 1.05); widths.Add('z', 0.9);

widths.Add('|', 0.2); widths.Add('~', 1.0);

/* Compute number of tabs needed */

double[] tagWidth = {0,0,0};

for (int i = 0; i < 3; ++i) {
    Char[] cs = scoreTags[i].ToCharArray();
    foreach ( Char c in cs ) {
        if (widths.ContainsKey(c)) {
            tagWidth[i] += widths[c];
        } else {
            tagWidth[i] += 1.0;
        }
    }
}

String[] tab = {"\t", "\t", "\t"};
for (int i = 0; i < 3; ++i) {
    if (level >= 4) plugin.ConsoleWrite("^b[EOR]^n widths: " + scoreTags[i] + " " + tagWidth[i]);
    if (tagWidth[i] < 4.7001) { // Use two tabs
        tab[i] += "\t";
    }
}

/* Send clan tag leaderboard to the chat window */

String lb = "Place by:\tScore\tKills   <- Clan Tag Leaderboard\n1st:\t\t"+scoreTags[0]+tab[0]+killTags[0]+"\n2nd:\t\t"+scoreTags[1]+tab[1]+killTags[1]+"\n3rd:\t\t"+scoreTags[2]+tab[2]+killTags[2];
if (level >= 3) plugin.ConsoleWrite("^b[EOR]^n ^4"+map+"^0:\n"+lb);
plugin.SendGlobalMessage(lb);

return false;
Detailed notes for Coders

 

There are several re-usable subroutines in this example that you might find useful. Here's a summary in the order that they appear in the example:

 

* "Near end of round" code for spacing out execution at the end of a round. The code is written for the last 20%, 10% and 5% of tickets remaining, but that condition can be replaced with something else, or the values changed. It uses a RoundData scratchpad variable in conjunction with an event that is expect to happen fairly often: OnKill in this case, but it could be OnInterval instead.

 

* "Sort" Comparison function delegates for sorting a list of KeyValuePair items in descending order by Value.

 

* Chat text character width table for formatting chat into tabular columns. See the comment in the code for a long list of disclaimers -- I created these values by trial-and-error and eyeballing my screen!

 

Change log

 

R4 - updated for End Game

R3 - updated for Scavenger

R2 - updated for all DLC through Aftermath

R1 - original version (0.0.0.7)

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Specialist*:

 

Hi PapaCharlie9,

 

Would it be possible to make/write a code that would show which player had the most knife kills in a round once the server reaches a certain number of tickets remaining like 5%?

 

Sorry, not trying to hijack your thread topic, delete if necessary.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

this looks great, how can i set it up for rush/conquest large maps and is there a way to add top player to this?

All Conquest types, including Large and Assault, work with this limit. Rush does not. You can try it, but because Rush has stages, the "end of round" calculation gets confused. You end up seeing the leaderboard at the end of every stage (up to 3 times x 4 stages, 12 times!), instead of at the end of the "last" stage.

 

If you still want to try it, change the first_check to this:

 

Code:

Regex.Match(server.Gamemode, "(Conquest|Team|Rush)", RegexOptions.IgnoreCase).Success && (server.RemainTicketsPercent(1) < 20 || server.RemainTicketsPercent(2) < 20) )
Top player is available at any time with the TAB key. If you really want it, it can be done with a separate limit. Check out the Examples thread, I think there is an example there. If not, post a request on the Examples thread.
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

PapaCharlie9 I'v add this limit but Procon don't show it's compiled ?

Is that ok ?

 

SCREENSHOT

Just force it to compile. Click on the first_check code then on the drop down button on the right, do not change the code, then click the button again to close it. Or, use the compile_limit command at the top of the Plugin Settings, in the Limit Manager section. Just use the All setting to recompile everything.
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by aduh*:

 

Just force it to compile. Click on the first_check code then on the drop down button on the right, do not change the code, then click the button again to close it. Or, use the compile_limit command at the top of the Plugin Settings, in the Limit Manager section. Just use the All setting to recompile everything.

tried everything - still not compiled ...
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

Hi PapaCharlie9,

 

Would it be possible to make/write a code that would show which player had the most knife kills in a round once the server reaches a certain number of tickets remaining like 5%?

 

Sorry, not trying to hijack your thread topic, delete if necessary.

I replied in a thread with a limit that is closer to what you want:

http://www.phogue.net/forumvb/showth...ll=1#post49514*

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Cire*:

 

awsome i will work on it and see how it looks. thank you, also one more thing, maybe im doing something wrong with the insane limits, but i dont see how or can get the !stats round to work? isnt it set up to do that or is there an expression/code i have to create? thanks again for your help.....

 

just tried code for new setup and im getting unreachable code error for the Clan Leader board? Line 20/40/327... thanks again.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

awsome i will work on it and see how it looks. thank you, also one more thing, maybe im doing something wrong with the insane limits, but i dont see how or can get the !stats round to work? isnt it set up to do that or is there an expression/code i have to create? thanks again for your help.....

 

just tried code for new setup and im getting unreachable code error for the Clan Leader board? Line 20/40/327... thanks again.

Make sure first_check is Expression, not Code. If you are still having problems, post a screenshot.

 

The stats stuff should work if you type in the right command. The format is:

 

_

 

For example, to get my KDR for the round:

 

_PapaCharlieNiner round kdr

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Cire*:

 

its working now papa, thanks the leader board is great! do you have a donate button for your time? i see your a busy man here in the forums. lol take care.

* Restored post. It could be that the author is no longer active.
Link to comment
  • 6 months later...

Originally Posted by PapaCharlie9*:

 

Updated through Aftermath DLC.

 

Note that this works for Conquest, Conquest Assault, Conquest Domination and Team Deathmatch. Other modes don't work because they don't have exactly two teams with multiple squads and/or don't have normal end of rounds (Rush).

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Apex40000*:

 

Hi PapaCharlie9

 

Many thanks for sharing the Clan Tag Leaderboard :-), Would you be able to point me in the right direction to the code / numbers that I would need to change, if I wanted to change the ticket % of when it shows the leader board.

 

I'm totally knew to all this so if you can make it stupid proof that would be great ;-)

 

Also one other question if I have any other mode than Conq will this cause any problems are will it just not work. e.g. As i tend to use TMDM or scavenger to start the server going before it jumps to CONQ maps.

 

Many thanks

 

Steven

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

Tell how you want it to work differently and I'll change it for you.

 

The way it works now is percentage of tickets. The first with 20% of tickets left, then 10%, then 5%. Sometimes the 5% doesn't show because the round ends within seconds.

 

It works for any mode with two teams except Rush, so it works fine for TDM and Scavenger. Actually, probably doesn't work for Gun Master either.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Apex40000*:

 

Hi PapaCharlie9

 

The only thing I think I would like to change would be the % e.g. lets say 60% 20% 5% not sure that would work reading your notes. But if it can that would be great. Also copy and pasted the current code you have on the first page of this thread. But for some reason it would not compile, so at the moment I have used the code I found on the example page and then just copy and pasted in your map updates and that seem to work as it is compiling, can you confirm that the current code you have up compiles and if there is a major differnce e.g. the 1 I might have is a old version not compatable with AM modes say :-)

 

Sorry to ask some many questions but i'm starting to get the hang of it he heh (not the coding thou that's like straight over my head ;-) ) .

 

Many thanks for helping me with this

 

apex40000

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

I just copy & pasted post #1 and the version below into Procon and it compiled fine. Double check your settings. first_check should be Expression, second_check is Code. Evaluate OnKill.

 

Here is a modified version that shows at 60%, 20% and 5%.

 

first_check

 

Code:

( !Regex.Match(server.Gamemode, @"(_:Squad|Rush)", RegexOptions.IgnoreCase).Success && (server.RemainTicketsPercent(1) <= 60 || server.RemainTicketsPercent(2) <= 60) )
second_check

 

Code:

/* Version 0.9/R3 - Apex mod */
/* Check if near end of round */

int level = 2;

try {
    level = Convert.ToInt32(plugin.getPluginVarValue("debug_level"));
} catch (Exception e) {}

if (!limit.RoundData.issetDouble("EOR")) { // EOR: End Of Round
    limit.RoundData.setDouble("EOR", 60); // Threshhold
}

/* First activation is a gimme, check for 2nd and 3rd */

double thresh = limit.RoundData.getDouble("EOR");

if (server.RemainTicketsPercent(1) > thresh && server.RemainTicketsPercent(2) > thresh) {
    if (level >= 4) {
        plugin.ConsoleWrite("^bEOR Debug^n: T1/T2 = " + String.Format("{0:F1}",server.RemainTicketsPercent(1)) +"/"+ String.Format("{0:F1}",server.RemainTicketsPercent(2)));
    }
    return false; // don't do anything;
}

/* Set the next threshhold */

if (thresh == 60) {
    // Set up the 2nd threshhold
    limit.RoundData.setDouble("EOR", 20);
} else if (thresh == 10) {
    // Set up the 3rd threshhold
    limit.RoundData.setDouble("EOR", 5);
} else if (thresh == 5) {
    // Set up the last threshhold
    limit.RoundData.setDouble("EOR", 0);
} else if (thresh == 0) {
    // Stop announcing
    return false;
}

/* Logging */

Dictionary<String, String> shortMap = new Dictionary<String,String>();
shortMap.Add("MP_001", "Bazaar");
shortMap.Add("MP_003", "Teheran");
shortMap.Add("MP_007", "Caspian");
shortMap.Add("MP_011", "Seine");
shortMap.Add("MP_012", "Firestorm");
shortMap.Add("MP_013", "Damavand");
shortMap.Add("MP_017", "Canals");
shortMap.Add("MP_018", "Kharg");
shortMap.Add("MP_Subway", "Metro");
shortMap.Add("MP_SUBWAY", "Metro");
shortMap.Add("XP1_001", "Karkand");
shortMap.Add("XP1_002", "Oman");
shortMap.Add("XP1_003", "Sharqi");
shortMap.Add("XP1_004", "Wake");
shortMap.Add("XP2_Factory", "Factory");
shortMap.Add("XP2_Skybar", "Skybar");
shortMap.Add("XP2_Palace", "Palace");
shortMap.Add("XP2_Office", "Office");
shortMap.Add("XP3_Desert", "Desert");
shortMap.Add("XP3_Alborz", "Alborz");
shortMap.Add("XP3_Shield", "Shield");
shortMap.Add("XP3_Valley", "Valley");
shortMap.Add("XP4_FD", "Monolith");
shortMap.Add("XP4_Parl", "Azadi");
shortMap.Add("XP4_Quake", "Quake");
shortMap.Add("XP4_Rubble", "Rubble");

String mfname = server.MapFileName.ToUpper();
String map = mfname;
if (shortMap.ContainsKey(mfname)) map = shortMap[mfname];


if (level >= 4) {
    double t1 = Math.Round(server.RemainTickets(1));
    double t2 = Math.Round(server.RemainTickets(2));
    String t1p = String.Format("{0:F1}",server.RemainTicketsPercent(1));
    String t2p = String.Format("{0:F1}",server.RemainTicketsPercent(2));
    plugin.ConsoleWrite(@"^b[EOR]^n: ^b^4" + map + "^0^n T1/T2 T1%/T2% = " + t1 +"/"+ t2 + " " + t1p + "/" + t2p + ", threshhold=" + thresh);
}

/* Collect clan statistics */

List<PlayerInfoInterface> players = new List<PlayerInfoInterface>();
players.AddRange(team1.players);
players.AddRange(team2.players);
Dictionary<String, double> clanCounts = new Dictionary<String, double>();
Dictionary<String, double> clanScores = new Dictionary<String, double>();
Dictionary<String, double> clanKills = new Dictionary<String, double>();

foreach(PlayerInfoInterface player_info in players)
{
    String tag = player_info.Tag;
    if(tag.Length == 0) {
        // Maybe they are using [_-=]XXX[=-_]PlayerName format
        Match tm = Regex.Match(player_info.Name, @"^[=_\-]_([^=_\-]{2,4})[=_\-]");
        if (tm.Success) {
            tag = tm.Groups[1].Value;
            if (level >= 4 && !clanCounts.ContainsKey(tag)) plugin.ConsoleWrite("^b[EOR]^n extracted [" + tag + "] from " + player_info.Name);
        } else {
            // Use the default "everybody else" tag
            tag = "no tag";
        }
    }   

    if (!clanCounts.ContainsKey(tag)) clanCounts.Add(tag, 0);

    clanCounts[tag] += 1;

    if (!clanScores.ContainsKey(tag)) clanScores.Add(tag, 0);

    clanScores[tag] += player_info.ScoreRound;

    if (!clanKills.ContainsKey(tag)) clanKills.Add(tag, 0);

    clanKills[tag] += player_info.KillsRound;
}

/* Sort all clans by average score */

String[] scoreTags = { "--", "--", "--" };

List<KeyValuePair<String, double>> list = new List<KeyValuePair<String, double>>();

foreach (KeyValuePair<String, double> pair in clanScores) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 0;
            return 0; // equal
        }
    );

    // Example: #1[LGN]/9: 2039.1, #2no tag/27: 1689.3, #3[SOG]/2: 588.5, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            scoreTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg score" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}


/* Sort all clans by average kills */

String[] killTags = { "--", "--", "--" };

list.Clear();

foreach (KeyValuePair<String, double> pair in clanKills) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 1;
            return 0; // equal
        }
    );

    // Example: #1[SOG]/9: 17.3, #2[365]/2: 11.5, #3no tag/40: 6.6, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            killTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg kills" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}

/* Chat character width table for formatting scoreboard */

/* 
This was done entirely by eyeball, so it is just an estimate. It is
normalized to the pixel width of the lower case 'x' character. That is 1.0.
Values less than 1.0, such as 0.8, are narrower than 'x'. Values greater
than 1.0, such as 1.2, are wider than 'x'.

I estimated the width of a tab column to be 4.7. That errs on the
side of being too low, which means it's possible for the next
column to be too far over. The resulting table might look like
this:

Score   Kills
[XXX]       [YYY]

I thought that was less confusing than being too close:

Score   Kills
[XXX][YYY]

Finally, the table is incomplete. It doesn't have all possible
character codes in it. Any character that is not found in
the table is assumed to be 1.0 in the code that uses the table.
*/

Dictionary<Char, double> widths = new Dictionary<Char, double>();

widths.Add('-', 0.8);

widths.Add('0', 1.3); widths.Add('1', 0.7); widths.Add('2', 1.2); widths.Add('3', 1.2); widths.Add('4', 1.2); widths.Add('5', 1.2); widths.Add('6', 1.1); widths.Add('7', 1.0); widths.Add('8', 1.1); widths.Add('9', 1.1);

widths.Add(':', 1.0); widths.Add(';', 1.0); widths.Add('<', 1.0); widths.Add('=', 1.0); widths.Add('>', 1.0); widths.Add('_', 1.2); widths.Add('@', 2.0);

widths.Add('A', 1.3); widths.Add('B', 1.25); widths.Add('C', 1.3); widths.Add('D', 1.3); widths.Add('E', 1.1); widths.Add('F', 0.9); widths.Add('G', 1.3); widths.Add('H', 1.3); widths.Add('I', 0.5); widths.Add('J', 1.2); widths.Add('K', 1.2); widths.Add('L', 1.1); widths.Add('M', 1.4); widths.Add('N', 1.3); widths.Add('O', 1.3); widths.Add('P', 1.2); widths.Add('Q', 1.3); widths.Add('R', 1.25); widths.Add('S', 1.3); widths.Add('T', 1.2); widths.Add('U', 1.3); widths.Add('V', 1.25); widths.Add('W', 1.8); widths.Add('X', 1.25); widths.Add('Y', 1.25); widths.Add('Z', 1.25);


widths.Add('[', 0.65); widths.Add(']', 0.65); widths.Add('^', 1.0); widths.Add('_', 1.0); widths.Add('`', 1.0);

widths.Add('a', 1.05); widths.Add('b', 1.1); widths.Add('c', 1.0); widths.Add('d', 1.1); widths.Add('e', 1.05); widths.Add('f', 0.7); widths.Add('g', 1.1); widths.Add('h', 1.15); widths.Add('i', 0.35); widths.Add('j', 0.35); widths.Add('k', 1.0); widths.Add('l', 0.35); widths.Add('m', 1.6); widths.Add('n', 1.15); widths.Add('o', 1.1); widths.Add('p', 1.1); widths.Add('q', 1.1); widths.Add('r', 0.75); widths.Add('s', 1.1); widths.Add('t', 0.5); widths.Add('u', 1.15); widths.Add('v', 1.1); widths.Add('w', 1.5); widths.Add('x', 1.0); widths.Add('y', 1.05); widths.Add('z', 0.9);

widths.Add('|', 0.2); widths.Add('~', 1.0);

/* Compute number of tabs needed */

double[] tagWidth = {0,0,0};

for (int i = 0; i < 3; ++i) {
    Char[] cs = scoreTags[i].ToCharArray();
    foreach ( Char c in cs ) {
        if (widths.ContainsKey(c)) {
            tagWidth[i] += widths[c];
        } else {
            tagWidth[i] += 1.0;
        }
    }
}

String[] tab = {"\t", "\t", "\t"};
for (int i = 0; i < 3; ++i) {
    if (level >= 4) plugin.ConsoleWrite("^b[EOR]^n widths: " + scoreTags[i] + " " + tagWidth[i]);
    if (tagWidth[i] < 4.7001) { // Use two tabs
        tab[i] += "\t";
    }
}

/* Send clan tag leaderboard to the chat window */

String lb = "Place by:\tScore\tKills   <- Clan Tag Leaderboard\n1st:\t\t"+scoreTags[0]+tab[0]+killTags[0]+"\n2nd:\t\t"+scoreTags[1]+tab[1]+killTags[1]+"\n3rd:\t\t"+scoreTags[2]+tab[2]+killTags[2];
if (level >= 3) plugin.ConsoleWrite("^b[EOR]^n ^4"+map+"^0:\n"+lb);
plugin.SendGlobalMessage(lb);

return false;
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Apex40000*:

 

Hi Papacharlie9

 

I've double check everything that you said to do and I'm getting This Error:

 

[insane Limits] ERROR: 1 error compiling Expression

 

Does this mean anything to you, other than is does not like the Expression coding for some reason.

Im running insane limtes 0.0.9.2 to run the Clan tag leaderboard, Is this the same version as you?

 

Many thanks

 

apex40000

 

 

I just copy & pasted post #1 and the version below into Procon and it compiled fine. Double check your settings. first_check should be Expression, second_check is Code. Evaluate OnKill.

 

Here is a modified version that shows at 60%, 20% and 5%.

 

first_check

 

Code:

( !Regex.Match(server.Gamemode, @"(_:Squad|Rush)", RegexOptions.IgnoreCase).Success && (server.RemainTicketsPercent(1) <= 60 || server.RemainTicketsPercent(2) <= 60) )
second_check

 

Code:

/* Version 0.9/R3 - Apex mod */
/* Check if near end of round */

int level = 2;

try {
    level = Convert.ToInt32(plugin.getPluginVarValue("debug_level"));
} catch (Exception e) {}

if (!limit.RoundData.issetDouble("EOR")) { // EOR: End Of Round
    limit.RoundData.setDouble("EOR", 60); // Threshhold
}

/* First activation is a gimme, check for 2nd and 3rd */

double thresh = limit.RoundData.getDouble("EOR");

if (server.RemainTicketsPercent(1) > thresh && server.RemainTicketsPercent(2) > thresh) {
    if (level >= 4) {
        plugin.ConsoleWrite("^bEOR Debug^n: T1/T2 = " + String.Format("{0:F1}",server.RemainTicketsPercent(1)) +"/"+ String.Format("{0:F1}",server.RemainTicketsPercent(2)));
    }
    return false; // don't do anything;
}

/* Set the next threshhold */

if (thresh == 60) {
    // Set up the 2nd threshhold
    limit.RoundData.setDouble("EOR", 20);
} else if (thresh == 10) {
    // Set up the 3rd threshhold
    limit.RoundData.setDouble("EOR", 5);
} else if (thresh == 5) {
    // Set up the last threshhold
    limit.RoundData.setDouble("EOR", 0);
} else if (thresh == 0) {
    // Stop announcing
    return false;
}

/* Logging */

Dictionary<String, String> shortMap = new Dictionary<String,String>();
shortMap.Add("MP_001", "Bazaar");
shortMap.Add("MP_003", "Teheran");
shortMap.Add("MP_007", "Caspian");
shortMap.Add("MP_011", "Seine");
shortMap.Add("MP_012", "Firestorm");
shortMap.Add("MP_013", "Damavand");
shortMap.Add("MP_017", "Canals");
shortMap.Add("MP_018", "Kharg");
shortMap.Add("MP_Subway", "Metro");
shortMap.Add("MP_SUBWAY", "Metro");
shortMap.Add("XP1_001", "Karkand");
shortMap.Add("XP1_002", "Oman");
shortMap.Add("XP1_003", "Sharqi");
shortMap.Add("XP1_004", "Wake");
shortMap.Add("XP2_Factory", "Factory");
shortMap.Add("XP2_Skybar", "Skybar");
shortMap.Add("XP2_Palace", "Palace");
shortMap.Add("XP2_Office", "Office");
shortMap.Add("XP3_Desert", "Desert");
shortMap.Add("XP3_Alborz", "Alborz");
shortMap.Add("XP3_Shield", "Shield");
shortMap.Add("XP3_Valley", "Valley");
shortMap.Add("XP4_FD", "Monolith");
shortMap.Add("XP4_Parl", "Azadi");
shortMap.Add("XP4_Quake", "Quake");
shortMap.Add("XP4_Rubble", "Rubble");

String mfname = server.MapFileName.ToUpper();
String map = mfname;
if (shortMap.ContainsKey(mfname)) map = shortMap[mfname];


if (level >= 4) {
    double t1 = Math.Round(server.RemainTickets(1));
    double t2 = Math.Round(server.RemainTickets(2));
    String t1p = String.Format("{0:F1}",server.RemainTicketsPercent(1));
    String t2p = String.Format("{0:F1}",server.RemainTicketsPercent(2));
    plugin.ConsoleWrite(@"^b[EOR]^n: ^b^4" + map + "^0^n T1/T2 T1%/T2% = " + t1 +"/"+ t2 + " " + t1p + "/" + t2p + ", threshhold=" + thresh);
}

/* Collect clan statistics */

List<PlayerInfoInterface> players = new List<PlayerInfoInterface>();
players.AddRange(team1.players);
players.AddRange(team2.players);
Dictionary<String, double> clanCounts = new Dictionary<String, double>();
Dictionary<String, double> clanScores = new Dictionary<String, double>();
Dictionary<String, double> clanKills = new Dictionary<String, double>();

foreach(PlayerInfoInterface player_info in players)
{
    String tag = player_info.Tag;
    if(tag.Length == 0) {
        // Maybe they are using [_-=]XXX[=-_]PlayerName format
        Match tm = Regex.Match(player_info.Name, @"^[=_\-]_([^=_\-]{2,4})[=_\-]");
        if (tm.Success) {
            tag = tm.Groups[1].Value;
            if (level >= 4 && !clanCounts.ContainsKey(tag)) plugin.ConsoleWrite("^b[EOR]^n extracted [" + tag + "] from " + player_info.Name);
        } else {
            // Use the default "everybody else" tag
            tag = "no tag";
        }
    }   

    if (!clanCounts.ContainsKey(tag)) clanCounts.Add(tag, 0);

    clanCounts[tag] += 1;

    if (!clanScores.ContainsKey(tag)) clanScores.Add(tag, 0);

    clanScores[tag] += player_info.ScoreRound;

    if (!clanKills.ContainsKey(tag)) clanKills.Add(tag, 0);

    clanKills[tag] += player_info.KillsRound;
}

/* Sort all clans by average score */

String[] scoreTags = { "--", "--", "--" };

List<KeyValuePair<String, double>> list = new List<KeyValuePair<String, double>>();

foreach (KeyValuePair<String, double> pair in clanScores) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 0;
            return 0; // equal
        }
    );

    // Example: #1[LGN]/9: 2039.1, #2no tag/27: 1689.3, #3[SOG]/2: 588.5, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            scoreTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg score" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}


/* Sort all clans by average kills */

String[] killTags = { "--", "--", "--" };

list.Clear();

foreach (KeyValuePair<String, double> pair in clanKills) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 1;
            return 0; // equal
        }
    );

    // Example: #1[SOG]/9: 17.3, #2[365]/2: 11.5, #3no tag/40: 6.6, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            killTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg kills" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}

/* Chat character width table for formatting scoreboard */

/* 
This was done entirely by eyeball, so it is just an estimate. It is
normalized to the pixel width of the lower case 'x' character. That is 1.0.
Values less than 1.0, such as 0.8, are narrower than 'x'. Values greater
than 1.0, such as 1.2, are wider than 'x'.

I estimated the width of a tab column to be 4.7. That errs on the
side of being too low, which means it's possible for the next
column to be too far over. The resulting table might look like
this:

Score   Kills
[XXX]       [YYY]

I thought that was less confusing than being too close:

Score   Kills
[XXX][YYY]

Finally, the table is incomplete. It doesn't have all possible
character codes in it. Any character that is not found in
the table is assumed to be 1.0 in the code that uses the table.
*/

Dictionary<Char, double> widths = new Dictionary<Char, double>();

widths.Add('-', 0.8);

widths.Add('0', 1.3); widths.Add('1', 0.7); widths.Add('2', 1.2); widths.Add('3', 1.2); widths.Add('4', 1.2); widths.Add('5', 1.2); widths.Add('6', 1.1); widths.Add('7', 1.0); widths.Add('8', 1.1); widths.Add('9', 1.1);

widths.Add(':', 1.0); widths.Add(';', 1.0); widths.Add('<', 1.0); widths.Add('=', 1.0); widths.Add('>', 1.0); widths.Add('_', 1.2); widths.Add('@', 2.0);

widths.Add('A', 1.3); widths.Add('B', 1.25); widths.Add('C', 1.3); widths.Add('D', 1.3); widths.Add('E', 1.1); widths.Add('F', 0.9); widths.Add('G', 1.3); widths.Add('H', 1.3); widths.Add('I', 0.5); widths.Add('J', 1.2); widths.Add('K', 1.2); widths.Add('L', 1.1); widths.Add('M', 1.4); widths.Add('N', 1.3); widths.Add('O', 1.3); widths.Add('P', 1.2); widths.Add('Q', 1.3); widths.Add('R', 1.25); widths.Add('S', 1.3); widths.Add('T', 1.2); widths.Add('U', 1.3); widths.Add('V', 1.25); widths.Add('W', 1.8); widths.Add('X', 1.25); widths.Add('Y', 1.25); widths.Add('Z', 1.25);


widths.Add('[', 0.65); widths.Add(']', 0.65); widths.Add('^', 1.0); widths.Add('_', 1.0); widths.Add('`', 1.0);

widths.Add('a', 1.05); widths.Add('b', 1.1); widths.Add('c', 1.0); widths.Add('d', 1.1); widths.Add('e', 1.05); widths.Add('f', 0.7); widths.Add('g', 1.1); widths.Add('h', 1.15); widths.Add('i', 0.35); widths.Add('j', 0.35); widths.Add('k', 1.0); widths.Add('l', 0.35); widths.Add('m', 1.6); widths.Add('n', 1.15); widths.Add('o', 1.1); widths.Add('p', 1.1); widths.Add('q', 1.1); widths.Add('r', 0.75); widths.Add('s', 1.1); widths.Add('t', 0.5); widths.Add('u', 1.15); widths.Add('v', 1.1); widths.Add('w', 1.5); widths.Add('x', 1.0); widths.Add('y', 1.05); widths.Add('z', 0.9);

widths.Add('|', 0.2); widths.Add('~', 1.0);

/* Compute number of tabs needed */

double[] tagWidth = {0,0,0};

for (int i = 0; i < 3; ++i) {
    Char[] cs = scoreTags[i].ToCharArray();
    foreach ( Char c in cs ) {
        if (widths.ContainsKey(c)) {
            tagWidth[i] += widths[c];
        } else {
            tagWidth[i] += 1.0;
        }
    }
}

String[] tab = {"\t", "\t", "\t"};
for (int i = 0; i < 3; ++i) {
    if (level >= 4) plugin.ConsoleWrite("^b[EOR]^n widths: " + scoreTags[i] + " " + tagWidth[i]);
    if (tagWidth[i] < 4.7001) { // Use two tabs
        tab[i] += "\t";
    }
}

/* Send clan tag leaderboard to the chat window */

String lb = "Place by:\tScore\tKills   <- Clan Tag Leaderboard\n1st:\t\t"+scoreTags[0]+tab[0]+killTags[0]+"\n2nd:\t\t"+scoreTags[1]+tab[1]+killTags[1]+"\n3rd:\t\t"+scoreTags[2]+tab[2]+killTags[2];
if (level >= 3) plugin.ConsoleWrite("^b[EOR]^n ^4"+map+"^0:\n"+lb);
plugin.SendGlobalMessage(lb);

return false;
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

Hi Papacharlie9

 

I've double check everything that you said to do and I'm getting This Error:

 

[insane Limits] ERROR: 1 error compiling Expression

 

Does this mean anything to you, other than is does not like the Expression coding for some reason.

Im running insane limtes 0.0.9.2 to run the Clan tag leaderboard, Is this the same version as you?

 

Many thanks

 

apex40000

Apex, that error means you did not copy & paste the code correctly. This happens sometimes if you use Internet Explorer. Try a different browser to copy the code from.

 

And no, I am using version 0.0.9.3. See the thread in Plugins.

* Restored post. It could be that the author is no longer active.
Link to comment
  • 3 months later...

Originally Posted by Apex40000*:

 

Hi Papacharlie9

 

Silly Question

 

If I want to add the new maps to this plug in do I just add them in the same place as here with the correct name and details:

shortMap.Add("XP3_Shield", "Shield");

shortMap.Add("XP3_Valley", "Valley");

 

?

 

Also will this likly crash if I run it with a CTF game mode ?

 

Many thanks

 

apex40000

 

 

 

 

Hi Papacharlie9

 

I've double check everything that you said to do and I'm getting This Error:

 

[insane Limits] ERROR: 1 error compiling Expression

 

Does this mean anything to you, other than is does not like the Expression coding for some reason.

Im running insane limtes 0.0.9.2 to run the Clan tag leaderboard, Is this the same version as you?

 

Many thanks

 

apex40000

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

Saving old version for posterity:

Code:

/* Version 0.9/R3 */
/* Check if near end of round */

int level = 2;

try {
    level = Convert.ToInt32(plugin.getPluginVarValue("debug_level"));
} catch (Exception e) {}

if (!limit.RoundData.issetDouble("EOR")) { // EOR: End Of Round
    limit.RoundData.setDouble("EOR", 20); // Threshhold
}

/* First activation is a gimme, check for 2nd and 3rd */

double thresh = limit.RoundData.getDouble("EOR");

if (server.RemainTicketsPercent(1) > thresh && server.RemainTicketsPercent(2) > thresh) {
    if (level >= 5) {
        plugin.ConsoleWrite("^bEOR Debug^n: T1/T2 = " + String.Format("{0:F1}",server.RemainTicketsPercent(1)) +"/"+ String.Format("{0:F1}",server.RemainTicketsPercent(2)));
    }
    return false; // don't do anything;
}

/* Set the next threshhold */

if (thresh == 20) {
    // Set up the 2nd threshhold
    limit.RoundData.setDouble("EOR", 10);
} else if (thresh == 10) {
    // Set up the 3rd threshhold
    limit.RoundData.setDouble("EOR", 5);
} else if (thresh == 5) {
    // Set up the last threshhold
    limit.RoundData.setDouble("EOR", 0);
} else if (thresh == 0) {
    // Stop announcing
    return false;
}

/* Logging */

Dictionary<String, String> shortMap = new Dictionary<String,String>();
shortMap.Add("MP_001", "Bazaar");
shortMap.Add("MP_003", "Teheran");
shortMap.Add("MP_007", "Caspian");
shortMap.Add("MP_011", "Seine");
shortMap.Add("MP_012", "Firestorm");
shortMap.Add("MP_013", "Damavand");
shortMap.Add("MP_017", "Canals");
shortMap.Add("MP_018", "Kharg");
shortMap.Add("MP_Subway", "Metro");
shortMap.Add("MP_SUBWAY", "Metro");
shortMap.Add("XP1_001", "Karkand");
shortMap.Add("XP1_002", "Oman");
shortMap.Add("XP1_003", "Sharqi");
shortMap.Add("XP1_004", "Wake");
shortMap.Add("XP2_Factory", "Factory");
shortMap.Add("XP2_Skybar", "Skybar");
shortMap.Add("XP2_Palace", "Palace");
shortMap.Add("XP2_Office", "Office");
shortMap.Add("XP3_Desert", "Desert");
shortMap.Add("XP3_Alborz", "Alborz");
shortMap.Add("XP3_Shield", "Shield");
shortMap.Add("XP3_Valley", "Valley");
shortMap.Add("XP4_FD", "Monolith");
shortMap.Add("XP4_Parl", "Azadi");
shortMap.Add("XP4_Quake", "Quake");
shortMap.Add("XP4_Rubble", "Rubble");

String mfname = server.MapFileName.ToUpper();
String map = mfname;
if (shortMap.ContainsKey(mfname)) map = shortMap[mfname];


if (level >= 4) {
    double t1 = Math.Round(server.RemainTickets(1));
    double t2 = Math.Round(server.RemainTickets(2));
    String t1p = String.Format("{0:F1}",server.RemainTicketsPercent(1));
    String t2p = String.Format("{0:F1}",server.RemainTicketsPercent(2));
    plugin.ConsoleWrite(@"^b[EOR]^n: ^b^4" + map + "^0^n T1/T2 T1%/T2% = " + t1 +"/"+ t2 + " " + t1p + "/" + t2p + ", threshhold=" + thresh);
}

/* Collect clan statistics */

List<PlayerInfoInterface> players = new List<PlayerInfoInterface>();
players.AddRange(team1.players);
players.AddRange(team2.players);
Dictionary<String, double> clanCounts = new Dictionary<String, double>();
Dictionary<String, double> clanScores = new Dictionary<String, double>();
Dictionary<String, double> clanKills = new Dictionary<String, double>();

foreach(PlayerInfoInterface player_info in players)
{
    String tag = player_info.Tag;
    if(tag.Length == 0) {
        // Maybe they are using [_-=]XXX[=-_]PlayerName format
        Match tm = Regex.Match(player_info.Name, @"^[=_\-]_([^=_\-]{2,4})[=_\-]");
        if (tm.Success) {
            tag = tm.Groups[1].Value;
            if (level >= 4 && !clanCounts.ContainsKey(tag)) plugin.ConsoleWrite("^b[EOR]^n extracted [" + tag + "] from " + player_info.Name);
        } else {
            // Use the default "everybody else" tag
            tag = "no tag";
        }
    }   

    if (!clanCounts.ContainsKey(tag)) clanCounts.Add(tag, 0);

    clanCounts[tag] += 1;

    if (!clanScores.ContainsKey(tag)) clanScores.Add(tag, 0);

    clanScores[tag] += player_info.ScoreRound;

    if (!clanKills.ContainsKey(tag)) clanKills.Add(tag, 0);

    clanKills[tag] += player_info.KillsRound;
}

/* Sort all clans by average score */

String[] scoreTags = { "--", "--", "--" };

List<KeyValuePair<String, double>> list = new List<KeyValuePair<String, double>>();

foreach (KeyValuePair<String, double> pair in clanScores) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 0;
            return 0; // equal
        }
    );

    // Example: #1[LGN]/9: 2039.1, #2no tag/27: 1689.3, #3[SOG]/2: 588.5, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            scoreTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg score" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}


/* Sort all clans by average kills */

String[] killTags = { "--", "--", "--" };

list.Clear();

foreach (KeyValuePair<String, double> pair in clanKills) {
    list.Add(pair);
}

if (list.Count > 0) {

    list.Sort(
        delegate(KeyValuePair<String, double> firstPair, KeyValuePair<String, double> nextPair) {
            double na = clanCounts[firstPair.Key];
            double nb = clanCounts[nextPair.Key];
            
            if (na != 0 && nb == 0) return -1;
            if (na == 0 && nb != 0) return 1;
            if (na == 0 && nb == 0) return 0;
            
            double a = firstPair.Value/na;
            double b = nextPair.Value/nb;
            
            if (a > B) return -1;
            if (a < B) return 1;
            return 0; // equal
        }
    );

    // Example: #1[SOG]/9: 17.3, #2[365]/2: 11.5, #3no tag/40: 6.6, ...

    int place = 0;
    String message = ": ";
    foreach(KeyValuePair<String, double> pair in list) {
        double n = clanCounts[pair.Key];
        if (n < 2) continue; // Skip solitary players
        String tmp = String.Empty;
        if (!pair.Key.Equals("no tag")) {
            tmp = "[" + pair.Key + "]";
        } else {
            tmp = pair.Key;
        }
        if (place < 3) {
            killTags[place] = tmp;
        }
        place += 1;
        message = message + "#" + place + tmp + "/" + n + ": " + String.Format("{0:F1}",(pair.Value/n)) + ", ";
        if (place >= 6) break; // Limit to top 6
    }
    if (level >= 3 && place > 0) {
        message = "Tags by avg kills" + message + "...";
        plugin.ConsoleWrite("^b[EOR]^n: " + message);
    }
}

/* Chat character width table for formatting scoreboard */

/* 
This was done entirely by eyeball, so it is just an estimate. It is
normalized to the pixel width of the lower case 'x' character. That is 1.0.
Values less than 1.0, such as 0.8, are narrower than 'x'. Values greater
than 1.0, such as 1.2, are wider than 'x'.

I estimated the width of a tab column to be 4.7. That errs on the
side of being too low, which means it's possible for the next
column to be too far over. The resulting table might look like
this:

Score   Kills
[XXX]       [YYY]

I thought that was less confusing than being too close:

Score   Kills
[XXX][YYY]

Finally, the table is incomplete. It doesn't have all possible
character codes in it. Any character that is not found in
the table is assumed to be 1.0 in the code that uses the table.
*/

Dictionary<Char, double> widths = new Dictionary<Char, double>();

widths.Add('-', 0.8);

widths.Add('0', 1.3); widths.Add('1', 0.7); widths.Add('2', 1.2); widths.Add('3', 1.2); widths.Add('4', 1.2); widths.Add('5', 1.2); widths.Add('6', 1.1); widths.Add('7', 1.0); widths.Add('8', 1.1); widths.Add('9', 1.1);

widths.Add(':', 1.0); widths.Add(';', 1.0); widths.Add('<', 1.0); widths.Add('=', 1.0); widths.Add('>', 1.0); widths.Add('_', 1.2); widths.Add('@', 2.0);

widths.Add('A', 1.3); widths.Add('B', 1.25); widths.Add('C', 1.3); widths.Add('D', 1.3); widths.Add('E', 1.1); widths.Add('F', 0.9); widths.Add('G', 1.3); widths.Add('H', 1.3); widths.Add('I', 0.5); widths.Add('J', 1.2); widths.Add('K', 1.2); widths.Add('L', 1.1); widths.Add('M', 1.4); widths.Add('N', 1.3); widths.Add('O', 1.3); widths.Add('P', 1.2); widths.Add('Q', 1.3); widths.Add('R', 1.25); widths.Add('S', 1.3); widths.Add('T', 1.2); widths.Add('U', 1.3); widths.Add('V', 1.25); widths.Add('W', 1.8); widths.Add('X', 1.25); widths.Add('Y', 1.25); widths.Add('Z', 1.25);


widths.Add('[', 0.65); widths.Add(']', 0.65); widths.Add('^', 1.0); widths.Add('_', 1.0); widths.Add('`', 1.0);

widths.Add('a', 1.05); widths.Add('b', 1.1); widths.Add('c', 1.0); widths.Add('d', 1.1); widths.Add('e', 1.05); widths.Add('f', 0.7); widths.Add('g', 1.1); widths.Add('h', 1.15); widths.Add('i', 0.35); widths.Add('j', 0.35); widths.Add('k', 1.0); widths.Add('l', 0.35); widths.Add('m', 1.6); widths.Add('n', 1.15); widths.Add('o', 1.1); widths.Add('p', 1.1); widths.Add('q', 1.1); widths.Add('r', 0.75); widths.Add('s', 1.1); widths.Add('t', 0.5); widths.Add('u', 1.15); widths.Add('v', 1.1); widths.Add('w', 1.5); widths.Add('x', 1.0); widths.Add('y', 1.05); widths.Add('z', 0.9);

widths.Add('|', 0.2); widths.Add('~', 1.0);

/* Compute number of tabs needed */

double[] tagWidth = {0,0,0};

for (int i = 0; i < 3; ++i) {
    Char[] cs = scoreTags[i].ToCharArray();
    foreach ( Char c in cs ) {
        if (widths.ContainsKey(c)) {
            tagWidth[i] += widths[c];
        } else {
            tagWidth[i] += 1.0;
        }
    }
}

String[] tab = {"\t", "\t", "\t"};
for (int i = 0; i < 3; ++i) {
    if (level >= 4) plugin.ConsoleWrite("^b[EOR]^n widths: " + scoreTags[i] + " " + tagWidth[i]);
    if (tagWidth[i] < 4.7001) { // Use two tabs
        tab[i] += "\t";
    }
}

/* Send clan tag leaderboard to the chat window */

String lb = "Place by:\tScore\tKills   <- Clan Tag Leaderboard\n1st:\t\t"+scoreTags[0]+tab[0]+killTags[0]+"\n2nd:\t\t"+scoreTags[1]+tab[1]+killTags[1]+"\n3rd:\t\t"+scoreTags[2]+tab[2]+killTags[2];
if (level >= 3) plugin.ConsoleWrite("^b[EOR]^n ^4"+map+"^0:\n"+lb);
plugin.SendGlobalMessage(lb);

return false;
* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

Hi Papacharlie9

 

Silly Question

 

If I want to add the new maps to this plug in do I just add them in the same place as here with the correct name and details:

shortMap.Add("XP3_Shield", "Shield");

shortMap.Add("XP3_Valley", "Valley");

 

?

 

Also will this likly crash if I run it with a CTF game mode ?

 

Many thanks

 

apex40000

I updated post #1 for End Game, so you can just use it. Make sure you use the latest version of Insane Limits.

 

It won't crash for CTF, it just won't work, will never show the leaderboard.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Apex40000*:

 

Many thanks PapaCharlie9 for all your hard work great plugin :-)

 

 

apex40000

 

 

 

I updated post #1 for End Game, so you can just use it. Make sure you use the latest version of Insane Limits.

 

It won't crash for CTF, it just won't work, will never show the leaderboard.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Tomgun*:

 

running it and it does work, question though, is it supposed to say [no tags] on some of the results which I gather is everyone who hasn't got a tag, as its a clan tag leader board is this not a bit strange that being there?

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by PapaCharlie9*:

 

running it and it does work, question though, is it supposed to say [no tags] on some of the results which I gather is everyone who hasn't got a tag, as its a clan tag leader board is this not a bit strange that being there?

Correct, the [no tags] means all the pubbers with no tags.

 

It's useful to see the leaderboard standings relative to everyone else, the average of pubbers. Four guys with the same tag getting first place above 20 random non-tagged players is something, getting seconds place to 20 random non-tagged players, not so much.

 

Still, you have a good point. The [no tags] group takes a podium slot away from an actual clan. I suppose I could change it to show four places and insure that if there are 3 clans, they will always be shown.

* Restored post. It could be that the author is no longer active.
Link to comment

Originally Posted by Tomgun*:

 

just had a thought, is it possible that to make this work with CTF could it not check the CTF command line set in the start.cfg, see how much it is set to e.g. 20 mins as changing it dose not currently work and say 5 mins before end start to display it.

 

Or just had another thought while typing that just get it to display every 5 mins (user could possible set the time) in any match type as an option. That way it could be used on all game types..

* Restored post. It could be that the author is no longer active.
Link to comment

Archived

This topic is now archived and is closed to further replies.



  • Our picks

    • Game Server Hosting:

      We're happy to announce that EZRCON will branch out into the game server provider scene. This is a big step for us so please having patience if something doesn't go right in this area. Now, what makes us different compared to other providers? Well, we're going with the idea of having a scaleable server hosting and providing more control in how you set up your server. For example, in Minecraft, you have the ability to control how many CPU cores you wish your server to have access to, how much RAM you want to use, how much disk space you want to use. This type of control can't be offered in a single service package so you're able to configure a custom package the way you want it.

      You can see all the available games here. Currently, we have the following games available.

      Valheim (From $1.50 USD)


      Rust (From $3.20 USD)


      Minecraft (Basic) (From $4.00 USD)


      Call of Duty 4X (From $7.00 USD)


      OpenTTD (From $4.00 USD)


      Squad (From $9.00 USD)


      Insurgency: Sandstorm (From $6.40 USD)


      Changes to US-East:

      Starting in January 2022, we will be moving to a different provider that has better support, better infrastructure, and better connectivity. We've noticed that the connection/routes to this location are not ideal and it's been hard getting support to correct this. Our contract for our two servers ends in March/April respectively. If you currently have servers in this location you will be migrated over to the new provider. We'll have more details when the time comes closer to January. The new location for this change will be based out of Atlanta, GA. If you have any questions/concerns please open a ticket and we'll do our best to answer them.
      • 5 replies
    • Hello All,

      I wanted to give an update to how EZRCON is doing. As of today we have 56 active customers using the services offered. I'm glad its doing so well and it hasn't been 1 year yet. To those that have services with EZRCON, I hope the service is doing well and if not please let us know so that we can improve it where possible. We've done quite a few changes behind the scenes to improve the performance hopefully. 

      We'll be launching a new location for hosting procon layers in either Los Angeles, USA or Chicago, IL. Still being decided on where the placement should be but these two locations are not set in stone yet. We would like to get feedback on where we should have a new location for hosting the Procon Layers, which you can do by replying to this topic. A poll will be created where people can vote on which location they would like to see.

      We're also looking for some suggestions on what else you would like to see for hosting provider options. So please let us know your thoughts on this matter.
      • 4 replies
    • Added ability to disable the new API check for player country info


      Updated GeoIP database file


      Removed usage sending stats


      Added EZRCON ad banner



      If you are upgrading then you may need to add these two lines to your existing installation in the file procon.cfg. To enable these options just change False to True.

      procon.private.options.UseGeoIpFileOnly False
      procon.private.options.BlockRssFeedNews False



       
      • 2 replies
    • I wanted I let you know that I am starting to build out the foundation for the hosting services that I talked about here. The pricing model I was originally going for wasn't going to be suitable for how I want to build it. So instead I decided to offer each service as it's own product instead of a package deal. In the future, hopefully, I will be able to do this and offer discounts to those that choose it.

      Here is how the pricing is laid out for each service as well as information about each. This is as of 7/12/2020.

      Single MySQL database (up to 30 GB) is $10 USD per month.



      If you go over the 30 GB usage for the database then each additional gigabyte is charged at $0.10 USD each billing cycle. If you're under 30GB you don't need to worry about this.


      Databases are replicated across 3 zones (regions) for redundancy. One (1) on the east coast of the USA, One (1) in Frankfurt, and One (1) in Singapore. Depending on the demand, this would grow to more regions.


      Databases will also be backed up daily and retained for 7 days.




      Procon Layer will be $2 USD per month.


      Each layer will only allow one (1) game server connection. The reason behind this is for performance.


      Each layer will also come with all available plugins installed by default. This is to help facilitate faster deployments and get you up and running quickly.


      Each layer will automatically restart if Procon crashes. 


      Each layer will also automatically restart daily at midnight to make sure it stays in tip-top shape.


      Custom plugins can be installed by submitting a support ticket.




      Battlefield Admin Control Panel (BFACP) will be $5 USD per month


      As I am still working on building version 3 of the software, I will be installing the last version I did. Once I complete version 3 it will automatically be upgraded for you.





      All these services will be managed by me so you don't have to worry about the technical side of things to get up and going.

      If you would like to see how much it would cost for the services, I made a calculator that you can use. It can be found here https://ezrcon.com/calculator.html

       
      • 11 replies
    • I have pushed out a new minor release which updates the geodata pull (flags in the playerlisting). This should be way more accurate now. As always, please let me know if any problems show up.

       
      • 9 replies
×
×
  • Create New...

Important Information

Please review our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.