Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Use the player's face as server favicon. Uses an asynchronous manager to perform player skin downloads on different worker threads.
package com.comphenix.example;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.concurrent.ConcurrentMap;
import javax.imageio.ImageIO;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
public class FaviconMod extends JavaPlugin implements Listener {
// The number of threads to use for downloading images
private static final int WORKER_THREADS = 4;
// You MUST use a concurrent map here, as we're dealing with asynchronous
// code
private ConcurrentMap<InetAddress, String> playerData = Maps.newConcurrentMap();
public void onEnable() {
// Intercept ping packets
new PacketAdapter(this, ListenerPriority.NORMAL, Arrays
ListenerOptions.ASYNC) {
public void onPacketSending(PacketEvent event) {
handlePingAsync(event, event.getPacket().getServerPings().read(0));
getServer().getPluginManager().registerEvents(this, this);
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Use putIfAbsent if you only want to update the map if the address is missing
playerData.put(getKey(player), player.getName());
* Retrieve the key used to identity a player.
* <p>
* The limitation here is that some players may share the same IP, especially if they're
* playing from the same household. But this can also occur if their ISP decides to reuse the
* same IP for different customers, or even use the same IP at the same time.
* @param player - the player.
* @return The player key.
private InetAddress getKey(Player player) {
return player.getAddress().getAddress();
* Invoked on one of the worker threads when we are ready to download the player skin.
* <p>
* Note that we don't do any caching. You may consider storing the CompressedImage in
* memory, as it's very small in size.
* @param event - the packet event.
* @param ping - the server ping object.
private void handlePingAsync(PacketEvent event, WrappedServerPing ping) {
try {
Player player = event.getPlayer();
String name = playerData.get(getKey(player));
// Use steve for unknown players
if (name == null) {
name = "char";
ping.setMotD("Welcome guest!");
} else {
ping.setMotD("Welcome " + name);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot access image.", e);
* Download a player skin.
* @param userName - the player's user name.
* @return The player skin.
private BufferedImage getIcon(String userName) throws IOException {
URL asset = new URL("" + userName + ".png");
Image img =, 8, 8, 8);
return toBufferedImage(img.getScaledInstance(64, 64, 1));
// Utility method
private BufferedImage toBufferedImage(Image image) {
BufferedImage buffer = new BufferedImage(
Graphics2D g = buffer.createGraphics();
g.drawImage(image, null, null);
return buffer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment