From da32c2f0bd9b9cad2e5328d26b0fd7ef25659b01 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Wed, 24 Aug 2011 00:51:54 -0500 Subject: [PATCH] Add server-side generation of faces : fixes face accessory issues, IE8 --- src/main/java/org/dynmap/DynmapPlugin.java | 15 +++ src/main/java/org/dynmap/PlayerFaces.java | 135 +++++++++++++++++++++ src/main/resources/char.png | Bin 0 -> 1832 bytes web/js/map.js | 4 - web/js/minecraft.js | 76 +++--------- 5 files changed, 166 insertions(+), 64 deletions(-) create mode 100644 src/main/java/org/dynmap/PlayerFaces.java create mode 100644 src/main/resources/char.png diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index 7af35cdf..1a6b5d1d 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -44,6 +44,7 @@ import org.bukkit.event.entity.EntityListener; import org.bukkit.event.player.PlayerChatEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.ChunkLoadEvent; @@ -74,6 +75,7 @@ public class DynmapPlugin extends JavaPlugin { public HashSet enabledTriggers = new HashSet(); public PermissionProvider permissions; public ComponentManager componentManager = new ComponentManager(); + public PlayerFaces playerfacemgr; public Events events = new Events(); public String deftemplatesuffix = ""; /* Flag to let code know that we're doing reload - make sure we don't double-register event handlers */ @@ -252,6 +254,8 @@ public class DynmapPlugin extends JavaPlugin { mapManager = new MapManager(this, configuration); mapManager.startRendering(); + playerfacemgr = new PlayerFaces(this); + loadWebserver(); enabledTriggers.clear(); @@ -342,6 +346,7 @@ public class DynmapPlugin extends JavaPlugin { List ll = event_handlers.get(t); ll.clear(); /* Empty list - we use presence of list to remember that we've registered with Bukkit */ } + playerfacemgr = null; Debug.clearDebuggers(); } @@ -992,6 +997,16 @@ public class DynmapPlugin extends JavaPlugin { } } } + @Override + public void onPlayerLogin(PlayerLoginEvent event) { + /* Call listeners */ + List ll = event_handlers.get(event.getType()); + if(ll != null) { + for(Listener l : ll) { + ((PlayerListener)l).onPlayerLogin(event); + } + } + } @Override public void onPlayerMove(PlayerMoveEvent event) { diff --git a/src/main/java/org/dynmap/PlayerFaces.java b/src/main/java/org/dynmap/PlayerFaces.java new file mode 100644 index 00000000..ef66d128 --- /dev/null +++ b/src/main/java/org/dynmap/PlayerFaces.java @@ -0,0 +1,135 @@ +package org.dynmap; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.imageio.ImageIO; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Type; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerLoginEvent; +import org.dynmap.MapType.ImageFormat; +import org.dynmap.debug.Debug; +import org.dynmap.utils.DynmapBufferedImage; +import org.dynmap.utils.FileLockManager; + +/** + * Listen for player logins, and process player faces by fetching skins * + */ +public class PlayerFaces { + private DynmapPlugin plugin; + private File facesdir; + private File faces8x8dir; + private File faces16x16dir; + private File faces32x32dir; + + + private class LoadPlayerImages implements Runnable { + public String playername; + public LoadPlayerImages(String playername) { + this.playername = playername; + } + public void run() { + BufferedImage img = null; + try { + URL url = new URL("http://s3.amazonaws.com/MinecraftSkins/" + playername + ".png"); + 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; + } + 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: first element is transparency color so only ones not matching it */ + for(int i = 0; i < 64; i++) { + if(faceaccessory[i] != faceaccessory[0]) + face8x8.argb_buf[i] = faceaccessory[i]; + } + /* Write 8x8 file */ + File img_8x8 = new File(faces8x8dir, playername + ".png"); + FileLockManager.getWriteLock(img_8x8); + try { + FileLockManager.imageIOWrite(face8x8.buf_img, ImageFormat.FORMAT_PNG, img_8x8); + } catch (IOException iox) { + Log.severe("Cannot write player icon " + img_8x8.getPath()); + } + FileLockManager.releaseWriteLock(img_8x8); + /* 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)]; + } + } + /* Write 16x16 file */ + File img_16x16 = new File(faces16x16dir, playername + ".png"); + FileLockManager.getWriteLock(img_16x16); + try { + FileLockManager.imageIOWrite(face16x16.buf_img, ImageFormat.FORMAT_PNG, img_16x16); + } catch (IOException iox) { + Log.severe("Cannot write player icon " + img_16x16.getPath()); + } + FileLockManager.releaseWriteLock(img_16x16); + DynmapBufferedImage.freeBufferedImage(face16x16); + + /* 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)]; + } + } + /* Write 32x32 file */ + File img_32x32 = new File(faces32x32dir, playername + ".png"); + FileLockManager.getWriteLock(img_32x32); + try { + FileLockManager.imageIOWrite(face32x32.buf_img, ImageFormat.FORMAT_PNG, img_32x32); + } catch (IOException iox) { + Log.severe("Cannot write player icon " + img_32x32.getPath()); + } + FileLockManager.releaseWriteLock(img_32x32); + DynmapBufferedImage.freeBufferedImage(face32x32); + + DynmapBufferedImage.freeBufferedImage(face8x8); + /* TODO: signal update for player icon to client */ + } + } + private class LoginListener extends PlayerListener { + @Override + public void onPlayerLogin(PlayerLoginEvent event) { + MapManager.scheduleDelayedJob(new LoadPlayerImages(event.getPlayer().getName()), 0); + } + } + public PlayerFaces(DynmapPlugin plugin) { + this.plugin = plugin; + plugin.registerEvent(Type.PLAYER_LOGIN, new LoginListener()); + facesdir = new File(plugin.tilesDirectory, "faces"); + facesdir.mkdirs(); /* Make sure directory exists */ + faces8x8dir = new File(facesdir, "8x8"); + faces8x8dir.mkdirs(); + faces16x16dir = new File(facesdir, "16x16"); + faces16x16dir.mkdirs(); + faces32x32dir = new File(facesdir, "32x32"); + faces32x32dir.mkdirs(); + } + + +} diff --git a/src/main/resources/char.png b/src/main/resources/char.png new file mode 100644 index 0000000000000000000000000000000000000000..d02b7180ed7567a3ff8313132874483b838aa335 GIT binary patch literal 1832 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(gFvFf#=B zgt!95B#Zv%k%5V3F<5G>M986D+wAY3mB^iny3m{X^7ZsiE3zQ*xTEyDMlM;6fs0cGbAK1 zWMnYZ*E`8_IV$qFD)BjK2?c4W1sbX*IqT;-YvsG@WH}g=y6cyC=vVm}XJ=t~tt#&e z1_nlkk|4ieU=)vJz!3bMT@@I;5+$w?CBgY=CFO}lsSE*$nRz98ey$-3WyX5OW-FgR z0;*wJ?CIhd;^Dn?N_W_02Z1*3rJCNJPQp40QBuN=j>0Ba&Mx!N5;t`fRC04Y%KiR7 z&#(7+VqLcF9Oq8H^qKT`|J<2#w|v@daCpU|Reh|jE1#$CE8<&m%Z%ab0slWLoJ~dx ztLhmyNwldUf+^n=h)5Ya6O@|@NKe+ z%j(yk*Iv`TJa1jrvWr<~Uzhd@n2M(!k7RV%TvB|{jUuYaGn-~M&vO3%8hI*Vs?2Qg%t=mu?n_G$8;&%ZApK5V(#Z%tbBik%wl z0!EhmHt(+}+V=5f$zz@PJNG5$t7t5o(Ou7AlC?Sd<-OVc_iO&Y{`h%sN%ppD4}7N- z2XbxjD1MtUfn|N1etzD%J#W}gKFC<>sbea>v_VBuh5hBbUyt2Se-96fvx&T4TmIo& zLIeM#^R5%7F#Tvs->}kjqhE6dj#e4X>P>P-CC@b-m|iscwj6v{DvN%+s5;Bi3z z{DUqbH-@?Af|hePKeqc~bADmq{~h=Hm5#(6k?2l7mLkI~fB4-0OIF^~8Dh_u+8$GS z@mOLbw`q@IOWc`99sB=tE0l#vmK;4a!&SuV$hr%U8yS=}SS{AQPKb5+sGKZi923*A zWZek{Uyj6ek1a&9q=LIH2xi?`p1Eu8;cbEi_E(sv2()Q9GA$@%6fimvQy^f-dE%1| zYodt39cDfUrVI;)7mVS|OtS1LOcPR_B}??oCthF0@JEEjo9V|n83nJM8jg%y@g+0m znL=fsA7_}$BC^+leamfEwz*}$HO~~=_6Nl^vM7k3CxS{WPgg&e IbxsLQ0Fp+u!T