Hello, I have been working on a plugin that is meant to send messages to player's hotbars. Since there is no API methods for doing this yet, nor does there seem to be any threads/utils like this, I have decided to post the class here. Code:java import java.lang.reflect.*;import java.util.logging.Level; import org.bukkit.Bukkit;import org.bukkit.entity.Player; public class HotbarMessager { // These are the Class instances. Needed to get fields or methods for classes. private static Class<?> CRAFTPLAYERCLASS, PACKET_PLAYER_CHAT_CLASS, ICHATCOMP, CHATMESSAGE, PACKET_CLASS, CHAT_MESSAGE_TYPE_CLASS; private static Field PLAYERCONNECTION; private static Method GETHANDLE, SENDPACKET; // These are the constructors for those classes. Need to create new objects. private static Constructor<?> PACKET_PLAYER_CHAT_CONSTRUCTOR, CHATMESSAGE_CONSTRUCTOR; // Used in 1.12+. Bytes are replaced with this enum private static Object CHAT_MESSAGE_TYPE_ENUM_OBJECT; // This is the server version. This is how we know the server version. private static final String SERVER_VERSION; private static boolean useByte = false; static { // This gets the server version. String name = Bukkit.getServer().getClass().getName(); name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length()); name = name.substring(0, name.indexOf(".")); SERVER_VERSION = name; try { // This here sets the class fields. CRAFTPLAYERCLASS = Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + ".entity.CraftPlayer"); PACKET_PLAYER_CHAT_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".PacketPlayOutChat"); PACKET_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".Packet"); ICHATCOMP = Class.forName("net.minecraft.server." + SERVER_VERSION + ".IChatBaseComponent"); GETHANDLE = CRAFTPLAYERCLASS.getMethod("getHandle"); PLAYERCONNECTION = GETHANDLE.getReturnType().getField("playerConnection"); SENDPACKET = PLAYERCONNECTION.getType().getMethod("sendPacket", PACKET_CLASS); try { CHAT_MESSAGE_TYPE_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessageType"); CHAT_MESSAGE_TYPE_ENUM_OBJECT = CHAT_MESSAGE_TYPE_CLASS.getEnumConstants()[2]; PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS.getConstructor(ICHATCOMP, CHAT_MESSAGE_TYPE_CLASS); } catch (NoSuchMethodException e) { PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS.getConstructor(ICHATCOMP, byte.class); useByte = true; } CHATMESSAGE = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessage"); CHATMESSAGE_CONSTRUCTOR = CHATMESSAGE.getConstructor(String.class, Object[].class); } catch (Exception e) { e.printStackTrace(); } } /** * Sends the hotbar message 'message' to the player 'player' * * @param player * @param message * @throws Exception */ public static void sendHotBarMessage(Player player, String message) throws Exception { try { // This creates the IChatComponentBase instance Object icb = CHATMESSAGE_CONSTRUCTOR.newInstance(message, new Object[0]); // This creates the packet Object packet; if (useByte) packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, (byte) 2); else packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, CHAT_MESSAGE_TYPE_ENUM_OBJECT); // This casts the player to a craftplayer Object craftplayerInst = CRAFTPLAYERCLASS.cast(player); // This invokes the method above. Object methodhHandle = GETHANDLE.invoke(craftplayerInst); // This gets the player's connection Object playerConnection = PLAYERCONNECTION.get(methodhHandle); // This sends the packet. SENDPACKET.invoke(playerConnection, packet); } catch (Exception e) { failsafe("sendHotBarMessage"); throw e; } } private static void failsafe(String message) { Bukkit.getLogger().log(Level.WARNING, "[PluginConstructorAPI] HotBarMessager disabled! Something went wrong with: " + message); Bukkit.getLogger().log(Level.WARNING, "[PluginConstructorAPI] Report this to Zombie_Striker"); Bukkit.getLogger().log(Level.WARNING, "[PluginConstructorAPI] Needed Information: " + Bukkit.getName() + ", " + Bukkit.getVersion() + ", " + Bukkit.getBukkitVersion()); Bukkit.getLogger().log(Level.WARNING, "[PluginConstructorAPI] [URL]https://github.com/ZombieStriker/PluginConstructorAPI[/URL]"); }} OLD (Move your mouse to reveal the content) OLD (open) OLD (close) Code:java import java.lang.reflect.*; import org.bukkit.Bukkit;import org.bukkit.entity.Player; public class HotbarMessager { /** * These are the Class instances. Use these to get fields or methods for * classes. */ private static Class<?> CRAFTPLAYERCLASS, PACKET_PLAYER_CHAT_CLASS, ICHATCOMP, CHATMESSAGE, PACKET_CLASS, CHAT_MESSAGE_TYPE_CLASS; private static Field PLAYERCONNECTION; private static Method GETHANDLE, SENDPACKET; /** * These are the constructors for those classes. You need these to create new * objects. */ private static Constructor<?> PACKET_PLAYER_CHAT_CONSTRUCTOR, CHATMESSAGE_CONSTRUCTOR; /** * Used in 1.12+. Bytes are replaced with this enum */ private static Object CHAT_MESSAGE_TYPE_ENUM_OBJECT; /** * This is the server version. This is how we know the server version. */ private static final String SERVER_VERSION; static { // This gets the server version. String name = Bukkit.getServer().getClass().getName(); name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length()); name = name.substring(0, name.indexOf(".")); SERVER_VERSION = name; try { // This here sets the class fields. CRAFTPLAYERCLASS = Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + ".entity.CraftPlayer"); PACKET_PLAYER_CHAT_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".PacketPlayOutChat"); PACKET_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".Packet"); ICHATCOMP = Class.forName("net.minecraft.server." + SERVER_VERSION + ".IChatBaseComponent"); GETHANDLE = CRAFTPLAYERCLASS.getMethod("getHandle"); PLAYERCONNECTION = GETHANDLE.getReturnType().getField("playerConnection"); SENDPACKET = PLAYERCONNECTION.getType().getMethod("sendPacket", PACKET_CLASS); try { PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS.getConstructor(ICHATCOMP, byte.class); } catch (NoSuchMethodException e) { CHAT_MESSAGE_TYPE_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessageType"); CHAT_MESSAGE_TYPE_ENUM_OBJECT = CHAT_MESSAGE_TYPE_CLASS.getEnumConstants()[2]; PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS.getConstructor(ICHATCOMP, CHAT_MESSAGE_TYPE_CLASS); } CHATMESSAGE = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessage"); CHATMESSAGE_CONSTRUCTOR = CHATMESSAGE.getConstructor(String.class, Object[].class); } catch (Exception e) { e.printStackTrace(); } } /** * Sends the hotbar message 'message' to the player 'player' * * @param player * @param message */ public static void sendHotBarMessage(Player player, String message) { try { // This creates the IChatComponentBase instance Object icb = CHATMESSAGE_CONSTRUCTOR.newInstance(message, new Object[0]); // This creates the packet Object packet; try { packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, (byte) 2); } catch (Exception e) { packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, CHAT_MESSAGE_TYPE_ENUM_OBJECT); } // This casts the player to a craftplayer Object craftplayerInst = CRAFTPLAYERCLASS.cast(player); // This invokes the method above. Object methodhHandle = GETHANDLE.invoke(craftplayerInst); // This gets the player's connection Object playerConnection = PLAYERCONNECTION.get(methodhHandle); // This sends the packet. SENDPACKET.invoke(playerConnection, packet); } catch (Exception e) { e.printStackTrace(); } }} This class works on versions 1.8+ and higher. To use this util, create a class called "HotbarMessager" and call this method to send a hotbar message Code: HotbarMessager.sendHotBarMessage(Player, Message); where "Player" is the player instance and "Message" is the message you want to send.
@Zombie_Striker God, you are on a rampage I am just amazed that there still is no API way for it. The first thread I found was in 2014... Or I am too dumb to find it And maybe rename "CRAFTPLAYERCLASS" to "CRAFT_PLAYER_CLASS" and document the parameters Otherwise a nice class, maybe I find some use for it!
I made this a while ago I believe I made something like this https://bukkit.org/threads/action-bars-and-such.339179/ but good job.
@ChipDev Your resource is version dependent. You have to import all those classes, which means it can only work on one version of bukkit. This uses reflection, so it works on all versions of bukkit (that support hotbar messages)
Oooh, you implemented reflection. Well then, mine became obsolete. Nice! I haven't been paying attention to MC versions, I'm stuck at 1.8 ^^
@ChipDev Just out of curiosity, why are so many servers still on 1.8? I collected some statistics for my plugins and it seems that half of all servers with them are 1.8 or lower.
I believe that people think PvP got ruined and some mechanics did too in 1.9+, including FPS management. I don't play anymore, but from what I heard thats why.
@Zombie_Striker I was wondering if you could explain some of your code (All if possible)? Also could I use this as a basis for something I would like to create? I was wondering if you could explain deeper because even though I know how it works, I don't know why it works (Get me? What I'm trying to say is I know how this is working and how to do it but I don't know what it's actually doing). - Thanks in advance, Phantom EDIT: Played around a bit and figured alittle more out! It would be great if you still could explain as I'm dabbling in uncharted territory (I'm charting it little by little )
@PhantomUnicorns I just posed an update to the post. There should now be comments for each line so you can understand it better. If you are still confused by anything, or do not know how to modify the code, I'd recommend making a PluginDev thread (since I do not want this one to go offtopic.)
@Zombie_Striker Thanks, I was wondering what is the difference between Code: Optional.of(Method).get().invoke(); and Code: Method#.invoke(); ? They seem to do the same thing, and when I switch one with the other there was no effect (That was noticeable)
@PhantomUnicorns It's actually the same. If I cared a bit more about the code not throwing an exception, I would check the optional to make sure it contains a value. If the method does not exist, that would just return a null/non-existent value instead of throwing an error. If you know that method exists, you can just use the Method#invoke. If the method name changes between versions/ does not exist in certain updates, use the Optional.
Something seems to have broken. I'm getting a nullpointer at Object icb = CHATMESSAGE_CONSTRUCTOR.newInstance(message,
@genandnic @i3ick I fixed it. Here's the updated version: Code:java import java.lang.reflect.*;import java.util.Optional; import org.bukkit.Bukkit;import org.bukkit.entity.Player; public class HotbarMessager { /** * These are the Class instances. Use these to get fields or methods for classes. */ private static Class<?> CRAFTPLAYERCLASS; private static Class<?> PACKET_PLAYER_CHAT_CLASS; private static Class<?> ICHATCOMP; private static Class<?> CHATMESSAGE; private static Class<?> PACKET_CLASS; /** * These are the constructors for those classes. You need these to create new objects. */ private static Constructor<?> PACKET_PLAYER_CHAT_CONSTRUCTOR; private static Constructor<?> CHATMESSAGE_CONSTRUCTOR; /** * This is the server version. This is how we know the server version. */ private static final String SERVER_VERSION; static { /** * This gets the server version. */ String name = Bukkit.getServer().getClass().getName(); name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length()); name = name.substring(0, name.indexOf(".")); SERVER_VERSION = name; try { /** * This here sets the class fields. */ CRAFTPLAYERCLASS = Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + ".entity.CraftPlayer"); PACKET_PLAYER_CHAT_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".PacketPlayOutChat"); PACKET_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".Packet"); ICHATCOMP = Class.forName("net.minecraft.server." + SERVER_VERSION + ".IChatBaseComponent"); PACKET_PLAYER_CHAT_CONSTRUCTOR = Optional.of( PACKET_PLAYER_CHAT_CLASS.getConstructor(ICHATCOMP, byte.class)).get(); CHATMESSAGE = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessage"); /** * If it cannot find the constructor one way, we try to get the declared constructor. */ try { CHATMESSAGE_CONSTRUCTOR = Optional.of( CHATMESSAGE .getConstructor(String.class, Object[].class)) .get(); } catch (NoSuchMethodException e) { CHATMESSAGE_CONSTRUCTOR = Optional.of( CHATMESSAGE.getDeclaredConstructor(String.class, Object[].class)).get(); } } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } } /** * Sends the hotbar message 'message' to the player 'player' * @param player * @param message */ public static void sendHotBarMessage(Player player, String message) { try { //This creates the IChatComponentBase instance Object icb = CHATMESSAGE_CONSTRUCTOR.newInstance(message, new Object[0]); //This creates the packet Object packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, (byte) 2); //This casts the player to a craftplayer Object craftplayerInst = CRAFTPLAYERCLASS.cast(player); //This get's the method for craftplayer's handle Optional<Method> methodOptional = Optional.of(CRAFTPLAYERCLASS .getMethod("getHandle")); //This invokes the method above. Object methodhHandle = methodOptional.get().invoke(craftplayerInst); //This gets the player's connection Object playerConnection = methodhHandle.getClass() .getField("playerConnection").get(methodhHandle); //This sends the packet. Optional.of( playerConnection.getClass().getMethod("sendPacket", PACKET_CLASS)).get() .invoke(playerConnection, packet); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | NoSuchFieldException e) { e.printStackTrace(); } }} @Zombie_Striker The error was rather simple. When finding the "v11_1_R1" string, the index passed to the substring is calculated depending on the string as the result of the previous call, but being a chained call, the variable hadn't updated yet, so the calculations got messed up. Simply moving the calls to two different lines fixed it. I can't explain why it worked previously though. Also, why don't you use a reflection util? I understand not wanting several classes, but why not make it a subclass? It'd make the code way cleaner, without all the exception-catching and stuff.
@i3ick @genandnic @AlvinB I have implemented your change and updated the main post. I did not use the util because I wanted the code to be as small as possible and, at the time, I was practicing how to use the base-java reflection classes.
@Zombie_Striker I recommend not getting the methods and fields inside the method but staticly like you have done for the enum constant and classes
@Zombie_Striker The latest version was throwing exceptions due to the fact that you were using getDeclaringClass() incorrectly. getDeclaringClass() returns the Class that has the method/field/constructor/whatever, not what type it is. I have corrected the code to use getReturnType()/getType() instead, and this fixed the problem. Also, the constructor in ChatMessage, is public, so no need to use getDeclaredConstructor(). I have corrected this to getConstructor(). Code:java import java.lang.reflect.*; import org.bukkit.Bukkit;import org.bukkit.entity.Player; public class HotbarMessager { /** * These are the Class instances. Use these to get fields or methods for * classes. */ private static Class<?> CRAFTPLAYERCLASS, PACKET_PLAYER_CHAT_CLASS, ICHATCOMP, CHATMESSAGE, PACKET_CLASS, CHAT_MESSAGE_TYPE_CLASS; private static Field PLAYERCONNECTION; private static Method GETHANDLE,SENDPACKET; /** * These are the constructors for those classes. You need these to create * new objects. */ private static Constructor<?> PACKET_PLAYER_CHAT_CONSTRUCTOR, CHATMESSAGE_CONSTRUCTOR; /** * Used in 1.12+. Bytes are replaced with this enum */ private static Object CHAT_MESSAGE_TYPE_ENUM_OBJECT; /** * This is the server version. This is how we know the server version. */ private static final String SERVER_VERSION; static { // This gets the server version. String name = Bukkit.getServer().getClass().getName(); name = name.substring(name.indexOf("craftbukkit.") + "craftbukkit.".length()); name = name.substring(0, name.indexOf(".")); SERVER_VERSION = name; try { // This here sets the class fields. CRAFTPLAYERCLASS = Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + ".entity.CraftPlayer"); PACKET_PLAYER_CHAT_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".PacketPlayOutChat"); PACKET_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".Packet"); ICHATCOMP = Class.forName("net.minecraft.server." + SERVER_VERSION + ".IChatBaseComponent"); GETHANDLE = CRAFTPLAYERCLASS.getMethod("getHandle"); PLAYERCONNECTION = GETHANDLE.getReturnType() .getField("playerConnection"); SENDPACKET = PLAYERCONNECTION.getType().getMethod("sendPacket", PACKET_CLASS); try { PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS .getConstructor(ICHATCOMP, byte.class); } catch (NoSuchMethodException e) { CHAT_MESSAGE_TYPE_CLASS = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessageType"); CHAT_MESSAGE_TYPE_ENUM_OBJECT = CHAT_MESSAGE_TYPE_CLASS .getEnumConstants()[2]; PACKET_PLAYER_CHAT_CONSTRUCTOR = PACKET_PLAYER_CHAT_CLASS .getConstructor(ICHATCOMP, CHAT_MESSAGE_TYPE_CLASS); } CHATMESSAGE = Class.forName("net.minecraft.server." + SERVER_VERSION + ".ChatMessage"); CHATMESSAGE_CONSTRUCTOR = CHATMESSAGE.getConstructor( String.class, Object[].class); } catch (Exception e) { e.printStackTrace(); } } /** * Sends the hotbar message 'message' to the player 'player' * * @param player * @param message */ public static void sendHotBarMessage(Player player, String message) { try { // This creates the IChatComponentBase instance Object icb = CHATMESSAGE_CONSTRUCTOR.newInstance(message, new Object[0]); // This creates the packet Object packet; try { packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, (byte) 2); } catch (Exception e) { packet = PACKET_PLAYER_CHAT_CONSTRUCTOR.newInstance(icb, CHAT_MESSAGE_TYPE_ENUM_OBJECT); } // This casts the player to a craftplayer Object craftplayerInst = CRAFTPLAYERCLASS.cast(player); // This invokes the method above. Object methodhHandle = GETHANDLE.invoke(craftplayerInst); // This gets the player's connection Object playerConnection = PLAYERCONNECTION.get(methodhHandle); // This sends the packet. SENDPACKET .invoke(playerConnection, packet); } catch (Exception e) { e.printStackTrace(); } }}
@MightyOne Yes, that's the point, you can use this so you don't have to understand the messy reflection "under the hood".