Skip to content

Instantly share code, notes, and snippets.

@aadnk
Created February 21, 2014 22:27
Show Gist options
  • Save aadnk/9144914 to your computer and use it in GitHub Desktop.
Save aadnk/9144914 to your computer and use it in GitHub Desktop.
Display a floating image with name tags. Screenshot: http://imgur.com/gF0qyzj
package com.comphenix.example;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.example.nametags.ImageChar;
import com.comphenix.example.nametags.NameTagMessage;
// Screenshot:
// http://imgur.com/gF0qyzj
public class DisplayFloatingImage extends JavaPlugin implements Listener {
private NameTagMessage message;
private BufferedImage image;
@Override
public void onEnable() {
// Must be placed in the data folder
File file = new File(getDataFolder(), "tux.png");
try {
image = ImageIO.read(file);
message = new NameTagMessage(image, 30, ImageChar.BLOCK.getChar());
} catch (IOException e) {
throw new RuntimeException("Cannot read image " + file, e);
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
if (message != null)
message.sendToPlayer(player, player.getLocation().add(0, 5, 0));
else
sender.sendMessage(ChatColor.RED + "No image loaded.");
}
return true;
}
}
package com.comphenix.example.nametags;
/**
* User: bobacadodl
* Date: 1/25/14
* Time: 11:03 PM
*/
public enum ImageChar {
BLOCK('\u2588'),
DARK_SHADE('\u2593'),
MEDIUM_SHADE('\u2592'),
LIGHT_SHADE('\u2591');
private char c;
ImageChar(char c) {
this.c = c;
}
public char getChar() {
return c;
}
}
package com.comphenix.example.nametags;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.util.ChatPaginator;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* User: bobacadodl
* Date: 1/25/14
* Time: 10:28 PM
*/
public class ImageMessage {
private final Color[] colors = {
new Color(0, 0, 0),
new Color(0, 0, 170),
new Color(0, 170, 0),
new Color(0, 170, 170),
new Color(170, 0, 0),
new Color(170, 0, 170),
new Color(255, 170, 0),
new Color(170, 170, 170),
new Color(85, 85, 85),
new Color(85, 85, 255),
new Color(85, 255, 85),
new Color(85, 255, 255),
new Color(255, 85, 85),
new Color(255, 85, 255),
new Color(255, 255, 85),
new Color(255, 255, 255),
};
protected String[] lines;
public ImageMessage(BufferedImage image, int height, char imgChar) {
ChatColor[][] chatColors = toChatColorArray(image, height);
lines = toImgMessage(chatColors, imgChar);
}
public ImageMessage(ChatColor[][] chatColors, char imgChar) {
lines = toImgMessage(chatColors, imgChar);
}
public ImageMessage(String... imgLines) {
lines = imgLines;
}
public ImageMessage appendText(String... text) {
for (int y = 0; y < lines.length; y++) {
if (text.length > y) {
lines[y] += " " + text[y];
}
}
return this;
}
public ImageMessage appendCenteredText(String... text) {
for (int y = 0; y < lines.length; y++) {
if (text.length > y) {
int len = ChatPaginator.AVERAGE_CHAT_PAGE_WIDTH - lines[y].length();
lines[y] = lines[y] + center(text[y], len);
} else {
return this;
}
}
return this;
}
private ChatColor[][] toChatColorArray(BufferedImage image, int height) {
double ratio = (double) image.getHeight() / image.getWidth();
int width = (int) (height / ratio);
if (width > 10) width = 10;
BufferedImage resized = resizeImage(image, (int) (height / ratio), height);
ChatColor[][] chatImg = new ChatColor[resized.getWidth()][resized.getHeight()];
for (int x = 0; x < resized.getWidth(); x++) {
for (int y = 0; y < resized.getHeight(); y++) {
int rgb = resized.getRGB(x, y);
ChatColor closest = getClosestChatColor(new Color(rgb));
chatImg[x][y] = closest;
}
}
return chatImg;
}
private String[] toImgMessage(ChatColor[][] colors, char imgchar) {
String[] lines = new String[colors[0].length];
for (int y = 0; y < colors[0].length; y++) {
String line = "";
for (int x = 0; x < colors.length; x++) {
line += colors[x][y].toString() + imgchar;
}
lines[y] = line + ChatColor.RESET;
}
return lines;
}
private BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
BufferedImage resizedImage = new BufferedImage(width, height, 6);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
return resizedImage;
}
private double getDistance(Color c1, Color c2) {
double rmean = (c1.getRed() + c2.getRed()) / 2.0;
double r = c1.getRed() - c2.getRed();
double g = c1.getGreen() - c2.getGreen();
int b = c1.getBlue() - c2.getBlue();
double weightR = 2 + rmean / 256.0;
double weightG = 4.0;
double weightB = 2 + (255 - rmean) / 256.0;
return weightR * r * r + weightG * g * g + weightB * b * b;
}
private boolean areIdentical(Color c1, Color c2) {
return Math.abs(c1.getRed() - c2.getRed()) <= 5 &&
Math.abs(c1.getGreen() - c2.getGreen()) <= 5 &&
Math.abs(c1.getBlue() - c2.getBlue()) <= 5;
}
private ChatColor getClosestChatColor(Color color) {
if (color.getAlpha() < 128) return ChatColor.BLACK;
int index = 0;
double best = -1;
for (int i = 0; i < colors.length; i++) {
if (areIdentical(colors[i], color)) {
return ChatColor.values()[i];
}
}
for (int i = 0; i < colors.length; i++) {
double distance = getDistance(color, colors[i]);
if (distance < best || best == -1) {
best = distance;
index = i;
}
}
// Minecraft has 15 colors
return ChatColor.values()[index];
}
private String center(String s, int length) {
if (s.length() > length) {
return s.substring(0, length);
} else if (s.length() == length) {
return s;
} else {
int leftPadding = (length - s.length()) / 2;
StringBuilder leftBuilder = new StringBuilder();
for (int i = 0; i < leftPadding; i++) {
leftBuilder.append(" ");
}
return leftBuilder.toString() + s;
}
}
public String[] getLines() {
return lines;
}
public void sendToPlayer(Player player) {
for (String line : lines) {
player.sendMessage(line);
}
}
}
package com.comphenix.example.nametags;
import java.awt.image.BufferedImage;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import com.google.common.base.Preconditions;
public class NameTagMessage extends ImageMessage {
private NameTagSpawner spawner;
private Location location;
private double lineSpacing = 0.25d;
public NameTagMessage(BufferedImage image, int height, char imgChar) {
super(image, height, imgChar);
initialize(height);
}
public NameTagMessage(ChatColor[][] chatColors, char imgChar) {
super(chatColors, imgChar);
this.location = Preconditions.checkNotNull(location, "location cannot be NULL");
initialize(chatColors.length);
}
public NameTagMessage(String... imgLines) {
super(imgLines);
initialize(imgLines.length);
}
private void initialize(int height) {
this.spawner = new NameTagSpawner(height);
}
@Override
public NameTagMessage appendCenteredText(String... text) {
super.appendCenteredText(text);
return this;
}
@Override
public NameTagMessage appendText(String... text) {
super.appendText(text);
return this;
}
public void setLocation(Location location) {
this.location = location;
}
public Location getLocation() {
return location;
}
/**
* Retrieve the default amount of meters in the y-axis between each name tag.
* @return The line spacing.
*/
public double getLineSpacing() {
return lineSpacing;
}
/**
* Set the default amount of meters in the y-axis between each name tag.
* @param lineSpacing - the name spacing.
*/
public void setLineSpacing(double lineSpacing) {
this.lineSpacing = lineSpacing;
}
@Override
public void sendToPlayer(Player player) {
sendToPlayer(player, location != null ? location : player.getLocation());
}
/**
* Send a floating image message to the given player at the specified starting location.
* @param player - the player.
* @param location - the starting location.
*/
public void sendToPlayer(Player player, Location location) {
for (int i = 0; i < lines.length; i++) {
spawner.setNameTag(i, player, location, -i * lineSpacing, lines[i]);
}
}
}
package com.comphenix.example.nametags;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
// These can be found in the following project:
// https://github.com/aadnk/PacketWrapper
import com.comphenix.example.wrapper.WrapperPlayServerAttachEntity;
import com.comphenix.example.wrapper.WrapperPlayServerSpawnEntity;
import com.comphenix.example.wrapper.WrapperPlayServerSpawnEntityLiving;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
/**
* Represents a spawner of name tags.
* @author Kristian
*/
public class NameTagSpawner {
private static final int WITHER_SKULL = 66;
// Shared entity ID allocator
private static int SHARED_ENTITY_ID = Short.MAX_VALUE;
// The starting entity ID
private int startEntityId;
private int nameTagCount;
/**
* Construct a new name tag spawner.
* <p>
* Specify a number of name tags to spawn.
* @param nameTags - the maximum number of name tags we will spawn at any given time.
*/
public NameTagSpawner(int nameTagCount) {
this.startEntityId = SHARED_ENTITY_ID;
this.nameTagCount = nameTagCount;
// We need to reserve two entity IDs per name tag
SHARED_ENTITY_ID += nameTagCount * 2;
}
/**
* Retrieve the maximum number of name tags we can spawn.
* @return The maximum number.
*/
public int getNameTagCount() {
return nameTagCount;
}
/**
* Set the location and message of a name tag.
* @param index - index of the name tag. Cannot exceeed {@link #getNameTagCount()}.
* @param observer - the observing player.
* @param location - the location in the same world as the player.
* @param dY - Y value to add to the final location.
* @param message - the message to display.
*/
public void setNameTag(int index, Player observer, Location location, double dY, String message) {
WrapperPlayServerAttachEntity attach = new WrapperPlayServerAttachEntity();
WrapperPlayServerSpawnEntityLiving horse = createHorsePacket(index, location, dY, message);
WrapperPlayServerSpawnEntity skull = createSkullPacket(index, location, dY);
// The horse is riding on the skull
attach.setEntityId(horse.getEntityID());
attach.setVehicleId(skull.getEntityID());
horse.sendPacket(observer);
skull.sendPacket(observer);
attach.sendPacket(observer);
}
// Construct the invisible horse packet
private WrapperPlayServerSpawnEntityLiving createHorsePacket(int index, Location location, double dY, String message) {
WrapperPlayServerSpawnEntityLiving horse = new WrapperPlayServerSpawnEntityLiving();
horse.setEntityID(startEntityId + index * 2);
horse.setType(EntityType.HORSE);
horse.setX(location.getX());
horse.setY(location.getY() + dY + 55);
horse.setZ(location.getZ());
WrappedDataWatcher wdw = new WrappedDataWatcher();
wdw.setObject(10, message);
wdw.setObject(11, (byte) 1);
wdw.setObject(12, -1700000);
horse.setMetadata(wdw);
return horse;
}
// Construct the wither skull packet
private WrapperPlayServerSpawnEntity createSkullPacket(int index, Location location, double dY) {
WrapperPlayServerSpawnEntity skull = new WrapperPlayServerSpawnEntity();
skull.setEntityID(startEntityId + index * 2 + 1);
skull.setType(WITHER_SKULL);
skull.setX(location.getX());
skull.setY(location.getY() + dY + 55);
skull.setZ(location.getZ());
return skull;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment