package org.dynmap; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.net.URL; import javax.imageio.ImageIO; import org.dynmap.MapType.ImageFormat; import org.dynmap.common.DynmapListenerManager.EventType; import org.dynmap.common.DynmapListenerManager.PlayerEventListener; import org.dynmap.common.DynmapPlayer; import org.dynmap.debug.Debug; import org.dynmap.storage.MapStorage; import org.dynmap.utils.BufferOutputStream; import org.dynmap.utils.DynmapBufferedImage; import org.dynmap.utils.ImageIOManager; /** * Listen for player logins, and process player faces by fetching skins * */ public class PlayerFaces { private boolean fetchskins; private boolean refreshskins; private String skinurl; public MapStorage storage; public enum FaceType { FACE_8X8("8x8", 0), FACE_16X16("16x16", 1), FACE_32X32("32x32", 2), BODY_32X32("body", 3); public final String id; public final int typeID; FaceType(String id, int typeid) { this.id = id; this.typeID = typeid; } public static FaceType byID(String i_d) { for (FaceType ft : values()) { if (ft.id.equals(i_d)) { return ft; } } return null; } public static FaceType byTypeID(int tid) { for (FaceType ft : values()) { if (ft.typeID == tid) { return ft; } } return null; } } private class LoadPlayerImages implements Runnable { public String playername; public LoadPlayerImages(String playername) { this.playername = playername; } public void run() { boolean has_8x8 = storage.hasPlayerFaceImage(playername, FaceType.FACE_8X8); boolean has_16x16 = storage.hasPlayerFaceImage(playername, FaceType.FACE_16X16); boolean has_32x32 = storage.hasPlayerFaceImage(playername, FaceType.FACE_32X32); boolean has_body = storage.hasPlayerFaceImage(playername, FaceType.BODY_32X32); boolean missing_any = !(has_8x8 && has_16x16 && has_32x32 && has_body); BufferedImage img = null; try { if(fetchskins && (refreshskins || missing_any)) { URL url = new URL(skinurl.replace("%player%", playername)); img = ImageIO.read(url); /* Load skin for player */ } } catch (IOException iox) { Debug.debug("Error loading skin for '" + playername + "' - " + iox); } if(img == null) { try { InputStream in = getClass().getResourceAsStream("/char.png"); img = ImageIO.read(in); /* Load generic skin for player */ in.close(); } catch (IOException iox) { Debug.debug("Error loading default skin for '" + playername + "' - " + iox); } } if(img == null) { /* No image to process? Quit */ return; } if((img.getWidth() < 64) || (img.getHeight() < 32)) { img.flush(); return; } int[] faceaccessory = new int[64]; /* 8x8 of face accessory */ /* Get buffered image for face at 8x8 */ DynmapBufferedImage face8x8 = DynmapBufferedImage.allocateBufferedImage(8, 8); img.getRGB(8, 8, 8, 8, face8x8.argb_buf, 0, 8); /* Read face from image */ img.getRGB(40, 8, 8, 8, faceaccessory, 0, 8); /* Read face accessory from image */ /* Apply accessory to face: see if anything is transparent (if so, apply accessory */ boolean transp = false; int v = faceaccessory[0]; for(int i = 0; i < 64; i++) { if((faceaccessory[i] & 0xFF000000) == 0) { transp = true; break; } /* If any different values, render face too */ else if(faceaccessory[i] != v) { transp = true; break; } } if(transp) { for(int i = 0; i < 64; i++) { if((faceaccessory[i] & 0xFF000000) != 0) face8x8.argb_buf[i] = faceaccessory[i]; } } /* Write 8x8 file */ if(refreshskins || (!has_8x8)) { BufferOutputStream bos = ImageIOManager.imageIOEncode(face8x8.buf_img, ImageFormat.FORMAT_PNG); if (bos != null) { storage.setPlayerFaceImage(playername, FaceType.FACE_8X8, bos); } } /* Write 16x16 file */ if(refreshskins || (!has_16x16)) { /* Make 16x16 version */ DynmapBufferedImage face16x16 = DynmapBufferedImage.allocateBufferedImage(16, 16); for(int i = 0; i < 16; i++) { for(int j = 0; j < 16; j++) { face16x16.argb_buf[i*16+j] = face8x8.argb_buf[(i/2)*8 + (j/2)]; } } BufferOutputStream bos = ImageIOManager.imageIOEncode(face16x16.buf_img, ImageFormat.FORMAT_PNG); if (bos != null) { storage.setPlayerFaceImage(playername, FaceType.FACE_16X16, bos); } DynmapBufferedImage.freeBufferedImage(face16x16); } /* Write 32x32 file */ if(refreshskins || (!has_32x32)) { /* Make 32x32 version */ DynmapBufferedImage face32x32 = DynmapBufferedImage.allocateBufferedImage(32, 32); for(int i = 0; i < 32; i++) { for(int j = 0; j < 32; j++) { face32x32.argb_buf[i*32+j] = face8x8.argb_buf[(i/4)*8 + (j/4)]; } } BufferOutputStream bos = ImageIOManager.imageIOEncode(face32x32.buf_img, ImageFormat.FORMAT_PNG); if (bos != null) { storage.setPlayerFaceImage(playername, FaceType.FACE_32X32, bos); } DynmapBufferedImage.freeBufferedImage(face32x32); } /* Write body file */ if(refreshskins || (!has_body)) { /* Make 32x32 version */ DynmapBufferedImage body32x32 = DynmapBufferedImage.allocateBufferedImage(32, 32); /* Copy face at 12,0 to 20,8 (already handled accessory) */ for(int i = 0; i < 8; i++) { for(int j = 0; j < 8; j++) { body32x32.argb_buf[i*32+j+12] = face8x8.argb_buf[i*8 + j]; } } /* Copy body at 12,8 to 20,20 */ img.getRGB(20, 20, 8, 12, body32x32.argb_buf, 8*32+12, 32); /* Read body from image */ /* Copy legs at 12,20 to 16,32 and 16,20 to 20,32 */ img.getRGB(4, 20, 4, 12, body32x32.argb_buf, 20*32+12, 32); /* Read right leg from image */ img.getRGB(4, 20, 4, 12, body32x32.argb_buf, 20*32+16, 32); /* Read left leg from image */ /* Copy arms at 8,8 to 12,20 and 20,8 to 24,20 */ img.getRGB(44, 20, 4, 12, body32x32.argb_buf, 8*32+8, 32); /* Read right leg from image */ img.getRGB(44, 20, 4, 12, body32x32.argb_buf, 8*32+20, 32); /* Read left leg from image */ BufferOutputStream bos = ImageIOManager.imageIOEncode(body32x32.buf_img, ImageFormat.FORMAT_PNG); if (bos != null) { storage.setPlayerFaceImage(playername, FaceType.BODY_32X32, bos); } DynmapBufferedImage.freeBufferedImage(body32x32); } DynmapBufferedImage.freeBufferedImage(face8x8); img.flush(); /* TODO: signal update for player icon to client */ } } public PlayerFaces(DynmapCore core) { fetchskins = core.configuration.getBoolean("fetchskins", true); /* Control whether to fetch skins */ refreshskins = core.configuration.getBoolean("refreshskins", true); /* Control whether to update existing fetched skins or faces */ skinurl = core.configuration.getString("skin-url", "http://skins.minecraft.net/MinecraftSkins/%player%.png"); if (skinurl.equals("http://s3.amazonaws.com/MinecraftSkins/%player%.png")) { // Migrate from legacy URL skinurl = "http://skins.minecraft.net/MinecraftSkins/%player%.png"; } core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() { @Override public void playerEvent(DynmapPlayer p) { Runnable job = new LoadPlayerImages(p.getName()); if(fetchskins) MapManager.scheduleDelayedJob(job, 0); else job.run(); } }); storage = core.getDefaultMapStorage(); } }