Minecraft Bukkit Server Ascii Chat Filter

I took on another small Minecraft bukkit plugin project this past Friday night (yes, it was a fun way to spend a Friday night, thanks for asking).  The server I primarily play on, Project Ares, has been getting extremely popular over the past several months, due mostly to how awesome and addicting it is.  There are hundreds of people from around the world divided among the 12 servers at any given time.  This is great, but one downside is that the chat gets very busy, and it's often filled with different languages that most of the users can't speak.  There are tons of chat related bukkit plugins that already exist, many of which have fancy features like multiple channels for different languages.

I decided to create a very simple and lightweight plugin that, when toggled on, filters out all non-ascii characters (so, most foreign languages).  Of course, it would be silly (and kind of a jerk move) if this simply muted all non English speakers server-wide.  It would have to work on a per-person basis, meaning that those with it toggled on see only ascii characters, and those with it toggled off see all characters.  I ended up with this.   You can check out the source there, but I'll briefly go through it here (because it's short, and also because I just got a WordPress plugin with allows you to insert code into posts and it's pretty neat).

 

The plugin is composed of three classes: one that registers the toggle command, one that actually filters the chat message, and one main class that puts the two together.  The main class is quite boring as it only registers the event handler and command executor (lines 6 and 7):

1
2
3
4
5
6
7
8
9
10
11
public class AsciiOnlyChatFilter extends JavaPlugin{
 
	public static ArrayList<Player> enabledPlayers = new ArrayList<Player>();
 
	public void onEnable(){
		getCommand("asciionly").setExecutor(new AsciiOnlyChatCommand());		
		this.getServer().getPluginManager().registerEvents(new AsciiOnlyChatListener(), this);
	}
	public void onDisable(){		
	}	
}

It also creates a list called enabledPlayers (line 3), which is where those who have the filter turned on are stored.  The second class handles the command ("/asciionly") if someone toggles it on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AsciiOnlyChatCommand implements CommandExecutor{
 
	public boolean onCommand(CommandSender sender, Command command,String label, String[] args) {
		if(sender instanceof Player == false){
			sender.sendMessage(ChatColor.RED + "This command can only be used in-game.");
			return true;
		}
		Player p = (Player) sender;
 
		if(command.getName().equalsIgnoreCase("asciionly")){
			if(AsciiOnlyChatFilter.enabledPlayers.contains(p)){
				AsciiOnlyChatFilter.enabledPlayers.remove(p);
				p.sendMessage(ChatColor.GREEN + "AsciiOnly filter deactivated");	
			}else{
				AsciiOnlyChatFilter.enabledPlayers.add(p);
				p.sendMessage(ChatColor.GREEN + "AsciiOnly filter activated");
			}
			return true;
		}
		return false;
	}
}

It basically just does some boring checks and then adds the player to the enabledPlayers list if they are turning the filter on, or removes them if they are turning it off.  The final class actually filters the chat for those players with the filter on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AsciiOnlyChatListener implements Listener {
 
	@EventHandler 
	public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event){
		String msg = event.getMessage();
		Set&lt;Player&gt; msgRecipients =  event.getRecipients();
		CharsetEncoder asciiEncoder = Charset.forName("US-ASCII").newEncoder();
 
		//If chat message is not ascii, do not display it to those with /asciionly activated
		if(!asciiEncoder.canEncode(msg)){
			for(Player enabledPlayer : AsciiOnlyChatFilter.enabledPlayers){
		    	if(msgRecipients.contains(enabledPlayer) && enabledPlayer != event.getPlayer()){
		    		event.getRecipients().remove(enabledPlayer);
		    	}		    	
			}
		}
	}
}

When it intercepts a chat message, it checks to see if it contains only ascii characters (line 11).  After this, it modifies the set that contains the message recipients, removing all of those that are on the enabledPlayers list (lines 12-14).  This seems to do the job pretty well!

Fun with Diffraction Gratings

A laser beam passing through a transmission diffraction grating straight on gives the standard diffraction pattern we all know and love.  It's a bit more interesting, though, if the beam hits the grating at an angle:

grating1

figure 1

Most intro optics books cover this situation, and the result is (equation 1):

a[\sin(\theta_{m}) - \sin(\theta_{i})] = m\lambda

where a is the grating spacing, \theta_{m} is the angle of the mth maxima, \theta_{i} is the incident angle, m is the maxima order, and \lambda is the wavelength. We can rewrite this (homework) in a more useful way as (equation 2):

\theta_{m}=\arcsin[\frac{m\lambda}{a} + \sin(\theta_{i})]

A similar, but slightly more complicated situation happens when you rotate the grating instead of the lazer:grating

figure 2

With a rotated grating (figure 2), the laser is still hitting the grating at an angle as it is in figure 1.  So, starting with equation 2 and using some geometry we get the angles that satisfy the maxima condition:

\theta_{m'}=\arcsin(\frac{m\lambda}{a}) + \sin(\theta_{g}) - \theta_{g}

This seemed liked a fun thing to model in Mathematica, especially since I had never played with any of the graphics features before.



You can view the source here.

You need Wolfram's CDF player, but it's totally worth because then you can look through all of the awesome demonstrations they have online.

Project Ares Beta Tournament: Predictions vs. Outcome

Update and Reflection (October 2015):

WOW! This was almost two years ago. It's pretty funny looking back at these old blog posts. This is probably the first actual "data science" project I ever worked on, and now I get paid to do silly things like this which is pretty cool. So yeah, if you're looking to get into software or data science, you have to find random little projects to work through. It might be embarrassing to look back, but in the end if you're learning stuff that's all that matters. Thanks for letting me scrape your website for all of these years, guys! Cheers!

I've added some additional comments at the bottom of the article.

Original Article:

Project Ares held their first full tournament yesterday.  This included 16 teams playing a variety of maps, and it lasted a whopping 8 hours!

Anyway, I thought it would be fun to compare all of the teams competing using some of the statistical data available on the Project Ares site to see how well of a performance predictor it is.  There are three main statistics: KD, KK and OD.  They are defined as follows:

  • KD = kills/deaths
  • KK = kills/times killed by another player
  • OD = objectives completed / deaths

KD and KK are directly related to fighting skills, where as OD has more to do with completing objectives (capturing wools and leaking cores).

I compared teams by taking the average of each of these three quantities for each team.

Results

Predicted Bracket
Actual Bracket (link now broken, October 2015)

The KD and KK ratios give identical predictions.  The OD ratio, on the other hand, is a very poor indicator of performance.

Using the team's average KD (or KK) alone, I was able to predict 12 out of 15 matches (accuracy of 80%), including the semifinals and tournament winner and most match outcomes (there were only three upsets: two pictured and one when YoloSwag beat Impact in Round II).  Of course, minecraft gameplay is complex and the three statistics alone do not represent all of the skills required to win a match, but it does seem to be a pretty strong indicator of overall performance.

Looking at the numbers, it is quite clear that the winning team, Badlion, is totally stacked.  Their average KD was 3 times that of the overall average.

Other Stuff

I decided this would be a neat idea late last night after the tourney ended, so the way I built the bracket is a bit of a mess (a combination of an extremely inefficient Mathematica script and google docs).  But, I plan on playing with this more and automating it better for future use.

More Update Stuff (Still October 2015)

So what's changed? An accuracy of 80% using only player's KD ratios is pretty crazy. In fact, since the first tournament, it is no longer possible to make such a "good" prediction. I think there are two reasons why KD has lost its predictive power:

  1. Since the first tournament, players have begun boosting their stats to get on teams. This includes playing differently than you would in a tournament (e.g. cautiously farming), making them less reflective of actual ability and more of patience.
  2. People strat the hell out of maps now. Players knew maps during the first tournament, but now teams are in general much more organized and practice a lot more, trying to exploit every little opportunity each map has to offer.

I think point 1 isn't as important, because most good players know that stats aren't necessarily an indicator of skill level. At the end of the day everyone does a little sword fighting, but map-specific knowledge and team organization still plays an enormous role in winning matches.

Now that Overcast Network (which was named "Project Ares" when this post was originally written) has introduced a more sophisticated ranking system, I'm looking forward to squeezing some information out of that data next time around.

 

Budder Mod

Okay, this is probably only amusing to those who play on some awesome minecraft server (it's kind of an inside joke), but I thought I'd post it here anyway.  I'm relatively new to the minecraft plugin scene (I have no idea what I'm doing), so this seemed like an easy project for me to get familiar with the very basics of the bukkit server API.

It's a very simple mod that is as useless as it is amusing (to me at least - if you don't find it amusing, you probably still won't find it useful).

Upon death by a gold sword, the plugin modifies the death message displayed:

and shoots some gold fireworks in the air:

My trolling turned out to be amusing to some, and too subtle for others (which I guess is good trolling?).

Plugin source code