Skip to content

Instantly share code, notes, and snippets.

@aadnk
Created December 25, 2013 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aadnk/8119275 to your computer and use it in GitHub Desktop.
Save aadnk/8119275 to your computer and use it in GitHub Desktop.
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.io.IOException;
import java.net.InetAddress;
import java.net.URL;
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 org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
import com.google.common.collect.Maps;
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();
@Override
public void onEnable() {
// Intercept ping packets
ProtocolLibrary.getProtocolManager().getAsynchronousManager().registerAsyncHandler(
new PacketAdapter(this, ListenerPriority.NORMAL, Arrays
.asList(PacketType.Status.Server.OUT_SERVER_INFO),
ListenerOptions.ASYNC) {
@Override
public void onPacketSending(PacketEvent event) {
handlePingAsync(event, event.getPacket().getServerPings().read(0));
}
}).start(WORKER_THREADS);
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
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);
}
ping.setFavicon(CompressedImage.fromPng(getIcon(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("http://s3.amazonaws.com/MinecraftSkins/" + userName + ".png");
Image img = ImageIO.read(asset).getSubimage(8, 8, 8, 8);
return toBufferedImage(img.getScaledInstance(64, 64, 1));
}
// Utility method
private BufferedImage toBufferedImage(Image image) {
BufferedImage buffer = new BufferedImage(
image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_ARGB
);
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