Add server-side generation of faces : fixes face accessory issues, IE8
This commit is contained in:
parent
4a5d8eea38
commit
2ec946eb52
5 changed files with 166 additions and 64 deletions
|
|
@ -44,6 +44,7 @@ import org.bukkit.event.entity.EntityListener;
|
||||||
import org.bukkit.event.player.PlayerChatEvent;
|
import org.bukkit.event.player.PlayerChatEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.event.player.PlayerListener;
|
import org.bukkit.event.player.PlayerListener;
|
||||||
|
import org.bukkit.event.player.PlayerLoginEvent;
|
||||||
import org.bukkit.event.player.PlayerMoveEvent;
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.bukkit.event.world.ChunkLoadEvent;
|
import org.bukkit.event.world.ChunkLoadEvent;
|
||||||
|
|
@ -74,6 +75,7 @@ public class DynmapPlugin extends JavaPlugin {
|
||||||
public HashSet<String> enabledTriggers = new HashSet<String>();
|
public HashSet<String> enabledTriggers = new HashSet<String>();
|
||||||
public PermissionProvider permissions;
|
public PermissionProvider permissions;
|
||||||
public ComponentManager componentManager = new ComponentManager();
|
public ComponentManager componentManager = new ComponentManager();
|
||||||
|
public PlayerFaces playerfacemgr;
|
||||||
public Events events = new Events();
|
public Events events = new Events();
|
||||||
public String deftemplatesuffix = "";
|
public String deftemplatesuffix = "";
|
||||||
/* Flag to let code know that we're doing reload - make sure we don't double-register event handlers */
|
/* 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 = new MapManager(this, configuration);
|
||||||
mapManager.startRendering();
|
mapManager.startRendering();
|
||||||
|
|
||||||
|
playerfacemgr = new PlayerFaces(this);
|
||||||
|
|
||||||
loadWebserver();
|
loadWebserver();
|
||||||
|
|
||||||
enabledTriggers.clear();
|
enabledTriggers.clear();
|
||||||
|
|
@ -342,6 +346,7 @@ public class DynmapPlugin extends JavaPlugin {
|
||||||
List<Listener> ll = event_handlers.get(t);
|
List<Listener> ll = event_handlers.get(t);
|
||||||
ll.clear(); /* Empty list - we use presence of list to remember that we've registered with Bukkit */
|
ll.clear(); /* Empty list - we use presence of list to remember that we've registered with Bukkit */
|
||||||
}
|
}
|
||||||
|
playerfacemgr = null;
|
||||||
|
|
||||||
Debug.clearDebuggers();
|
Debug.clearDebuggers();
|
||||||
}
|
}
|
||||||
|
|
@ -992,6 +997,16 @@ public class DynmapPlugin extends JavaPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public void onPlayerLogin(PlayerLoginEvent event) {
|
||||||
|
/* Call listeners */
|
||||||
|
List<Listener> ll = event_handlers.get(event.getType());
|
||||||
|
if(ll != null) {
|
||||||
|
for(Listener l : ll) {
|
||||||
|
((PlayerListener)l).onPlayerLogin(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerMove(PlayerMoveEvent event) {
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
|
|
||||||
135
src/main/java/org/dynmap/PlayerFaces.java
Normal file
135
src/main/java/org/dynmap/PlayerFaces.java
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
BIN
src/main/resources/char.png
Normal file
BIN
src/main/resources/char.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -41,7 +41,6 @@ DynMap.prototype = {
|
||||||
inittime: new Date().getTime(),
|
inittime: new Date().getTime(),
|
||||||
followingPlayer: '',
|
followingPlayer: '',
|
||||||
missedupdates: 0,
|
missedupdates: 0,
|
||||||
canvassupport: !!document.createElement('canvas').getContext,
|
|
||||||
formatUrl: function(name, options) {
|
formatUrl: function(name, options) {
|
||||||
var url = this.options.url[name];
|
var url = this.options.url[name];
|
||||||
$.each(options, function(n,v) {
|
$.each(options, function(n,v) {
|
||||||
|
|
@ -134,9 +133,6 @@ DynMap.prototype = {
|
||||||
$(me).trigger('zoomchanged');
|
$(me).trigger('zoomchanged');
|
||||||
};
|
};
|
||||||
|
|
||||||
if(me.canvassupport == false)
|
|
||||||
me.options.showplayerfacesinmenu = false;
|
|
||||||
|
|
||||||
/*google.maps.event.addListener(map, 'dragstart', function(mEvent) {
|
/*google.maps.event.addListener(map, 'dragstart', function(mEvent) {
|
||||||
me.followPlayer(null);
|
me.followPlayer(null);
|
||||||
});*/
|
});*/
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,19 @@
|
||||||
var cloneCanvas = function(self) {
|
function createMinecraftHead(player,size,completed,failed) {
|
||||||
var c = document.createElement('canvas');
|
var faceImage = new Image();
|
||||||
c.width = self.width;
|
faceImage.onload = function() {
|
||||||
c.height = self.height;
|
completed(faceImage);
|
||||||
var cxt = c.getContext('2d');
|
|
||||||
cxt.drawImage(self,0,0);
|
|
||||||
return c;
|
|
||||||
};
|
|
||||||
|
|
||||||
function blitImage(ctx, image, sx ,sy, sw, sh, dx, dy, dw, dh) {
|
|
||||||
var x; var y;
|
|
||||||
for (x=0;x<dw;x++) {
|
|
||||||
for (y=0;y<dh;y++) {
|
|
||||||
ctx.drawImage(image,Math.floor(sx+x*(sw/dw)),Math.floor(sy+y*(sw/dw)),1,1,dx+x,dy+y,1,1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMinecraftHead(player,completed,failed) {
|
|
||||||
var skinImage = new Image();
|
|
||||||
skinImage.onload = function() {
|
|
||||||
var headCanvas = document.createElement('canvas');
|
|
||||||
headCanvas.width = 8;
|
|
||||||
headCanvas.height = 8;
|
|
||||||
if(headCanvas.getContext) {
|
|
||||||
var headContext = headCanvas.getContext('2d');
|
|
||||||
blitImage(headContext, skinImage, 8,8,8,8, 0,0,8,8);
|
|
||||||
// Turn off accessory face overlay - causes white faces, and very few skins seem to have them anyway
|
|
||||||
//blitImage(headContext, skinImage, 40,8,8,8, 0,0,8,8);
|
|
||||||
completed(headCanvas);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
skinImage.onerror = function() {
|
faceImage.onerror = function() {
|
||||||
if (skinImage.src == '//www.minecraft.net/img/char.png') {
|
failed();
|
||||||
failed();
|
|
||||||
} else {
|
|
||||||
skinImage.src = '//www.minecraft.net/img/char.png';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
skinImage.src = '//s3.amazonaws.com/MinecraftSkins/' + player + '.png';
|
faceImage.src = dynmap.options.tileUrl + 'faces/' + size + 'x' + size + '/' + player + '.png';
|
||||||
}
|
|
||||||
|
|
||||||
function resizeImage(img,size) {
|
|
||||||
var canvas = document.createElement('canvas');
|
|
||||||
canvas.width = size;
|
|
||||||
canvas.height = size;
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
blitImage(ctx, img, 0,0,img.width,img.height, 0,0,size,size);
|
|
||||||
return canvas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var playerHeads = {};
|
var playerHeads = {};
|
||||||
|
|
||||||
function getMinecraftHead(player,size,completed) {
|
function getMinecraftHead(player,size,completed) {
|
||||||
var head = playerHeads[player];
|
var key = player + '.' + size;
|
||||||
|
var head = playerHeads[key];
|
||||||
// Synchronous
|
// Synchronous
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
return (!head || head.working) ? null : head;
|
return (!head || head.working) ? null : head;
|
||||||
|
|
@ -63,24 +21,22 @@ function getMinecraftHead(player,size,completed) {
|
||||||
|
|
||||||
// Asynchronous
|
// Asynchronous
|
||||||
if (!head) {
|
if (!head) {
|
||||||
playerHeads[player] = { working: true, hooks: [{f:completed,s:size}] };
|
playerHeads[key] = { working: true, hooks: [{f:completed}] };
|
||||||
//console.log('Creating head for ',player,'...');
|
createMinecraftHead(player, size, function(head) {
|
||||||
createMinecraftHead(player, function(head) {
|
hooks = playerHeads[key].hooks;
|
||||||
//console.log('Created head for ',player,': ', head);
|
playerHeads[key] = head;
|
||||||
hooks = playerHeads[player].hooks;
|
|
||||||
playerHeads[player] = head;
|
|
||||||
var i;
|
var i;
|
||||||
for(i=0;i<hooks.length;i++) {
|
for(i=0;i<hooks.length;i++) {
|
||||||
hooks[i].f(resizeImage(head,hooks[i].s));
|
hooks[i].f(head);
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if (head.working) {
|
} else if (head.working) {
|
||||||
//console.log('Other process working on head of ',player,', will add myself to hooks...');
|
//console.log('Other process working on head of ',player,', will add myself to hooks...');
|
||||||
head.hooks[head.hooks.length] = {f:completed,s:size};
|
head.hooks[head.hooks.length] = {f:completed};
|
||||||
} else {
|
} else {
|
||||||
completed(resizeImage(head,size));
|
completed(head);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue