ItemMessage: use item metadata to create popup messages

Discussion in 'Resources' started by desht, Aug 15, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    desht

    As you're no doubt all aware, changing your held item, or changing the displayName metadata on your held item, causes Minecraft to display the item's name on your screen for a couple of seconds, right above the health/food/armour bars. It occurred to me that this feature can be used to display popup messages; quick notifications where you might not want to clutter up the chat window.

    So I created ItemMessage: https://github.com/desht/dhutils/blob/master/Lib/src/main/java/me/desht/dhutils/ItemMessage.java

    Example usage:
    PHP:
    ItemMessage im = new ItemMessage(yourPlugin);
    // Hello World! will be displayed to the recipient for 5 seconds (at default priority):
    im.sendMessage(player"Hello World!"5);
    // Danger Will Robinson! will be displayed to the recipient for 5 seconds (at raised priority):
    im.sendMessage(playerChatColor.RED "Danger Will Robinson!"510);
    Some notes:
    • You will need ProtocolLib for this. I use ProtocolLib to send SetItemSlot (103) packets to clients to fake the metadata displayName changes. No actual changes are ever made to any items server-side, so this should be extremely safe. Unfortunately this doesn't work in creative mode right now, due to the "interesting" way that item data is handled. But a fix for that is in the works.
    • It may occur to you that this might not work if the player is currently empty-handed. Don't worry, I've accounted for that - the player gets a fake snow layer item while the message is being displayed (I chose snow layer since it's visually pretty unobtrusive).
    • It will continue to work if you change your held item while the message is active - the fake metadata changes just get sent for the newly held item instead.
    • It can display the message for an indefinite length of time by changing it subtly and resending the packet once a second - a different displayName needs to be sent each time, or the client will stop displaying the message. It uses a Bukkit repeating task to do this. However, use good judgement when deciding how long to display a message for - too long could be pretty antisocial. A couple of seconds is fine for most purposes, perhaps a little more for a message you don't want to be missed.
    • By default, the actual message sent alternates between the plain message, and the message with a space added at the beginning and end (giving a static appearance, since the message is centred on the screen), but you can override this with setFormats() - e.g. see https://github.com/desht/ScrollingM...n/commandlets/QuickMessageCommandlet.java#L31, where the message will have a blinking red block in front of it (a changing visual appearance draws attention really well).
    • Sending a new message to the player while a previous one is still active will work; the class uses Bukkit metadata on each Player object to store a queue of pending messages for the player. New messages will wait until the current message has finished showing. Again, a good reason not to use excessive message durations.
    • Messages can also have a priority; the default is 0, but any integer can be used. Higher priority messages will be shown before lower priority messages in the queue (but won't interrupt the current message display, if any).
    • Thanks to MTN for a couple of contributions: customising the packet send interval & allowing more than 2 formats in setFormats().
    The code is free for anyone who wishes to use - I'd only ask that if you make any changes, please make them public. Oh, and change the package name, please :)
     
    BlueMustache, Zupsub, Skyost and 22 others like this.
  2. Offline

    phips99

    Amazing!!!!!
     
  3. Offline

    desht

    I couldn't resist tinkering, since the last point I mentioned (sending a new message when a previous one was active) was bothering me. So I've revamped the class significantly; it now uses Bukkit metadata to store a queue of messages per-player, allowing a couple of nice new features:
    • If a new message is sent while one is active, it'll wait till the previous message is done before being displayed.
    • Added message priorities (a simple integer) - higher priority messages get displayed sooner. The default priority of 0 should be fine for most purposes, but urgent messages could use a higher number.
     
  4. Offline

    hawkfalcon

    Pics please :)
     
  5. Amazing, am so going to use this!
     
  6. Offline

    desht

    Really needs a video to demonstrate, but here's a screenshot:
    [​IMG]

    To see it in action, you can download this dev version of ScrollingMenuSign: http://jenkins.genesis-mc.com/job/ScrollingMenuSign/26/artifact/target/ScrollingMenuSign.jar ScrollingMenuSign v2.3.0 or later and create a menu like this:
    Code:
    /sms create menu test "&4Test Menu"
    /sms add test "Check XP" "QUICKMSG 6 You have &6<EXP>&r exp."
    /sms give map test
    
    ...and click the "Check XP" item on the map you get.
     
    hawkfalcon and bobacadodl like this.
  7. Offline

    phips99

    desht Can you make a tutorial from how you did that with the scrolling menu?
     
  8. Offline

    desht

  9. Offline

    Garris0n

    I've been wondering if anybody would do this for a while now :p Looks great.
     
    lol768 likes this.
  10. Offline

    hawkfalcon

  11. Offline

    chasechocolate

    Yeah :p I did this in a guns-based plugin a little after it was implemented into Minecraft (I used those square characters to make it look like a gun was reloading). Kinda forgot about releasing it though...
     
  12. Offline

    hawkfalcon

    desht It is failing for me now. Gives snow and no message.
     
  13. Offline

    desht

    hawkfalcon I can't reproduce that. If you can reliably, then I'll need full instructions on how to reproduce it.
     
  14. Offline

    hawkfalcon

    It happens every time. I'm calling it when a game starts to say that the game has begun. They only thing I can think of is it's too soon after a teleport, but that doesn't really make sense.
     
  15. Offline

    desht

    No that doesn't seem likely. Can I see your code? And does it only happen if you are empty-handed when the message is sent?
     
  16. Offline

    hawkfalcon

    And yes, the inventory is empty.
     
  17. Offline

    desht

    Was kind of hoping for a bit more context than that :)

    Anyway, I've tried all sorts of combinations - with & without an item in hand, completely empty inventory, calling ItemMessage from a command handler, from a teleport event handler... I honestly can't reproduce your problem, sorry. Only suggestion is for me to see your entire code, but if it's not an open source project, I understand.
     
  18. Offline

    hawkfalcon

    Interesting. :confused: This is odd:(
    Yeah, it's private. I'll play around with it more and let you know if I find anything.
     
  19. Offline

    desht

    Have you tried with no other plugins active? Might be something else interfering...

    Oh, and since you have ProtocolLib installed, try doing packet add server 103 detailed and see if the SetSlot packets are actually getting sent.
     
  20. Offline

    hawkfalcon

    >packet add server 103
    10:23:20 [INFO] Added listener ListeningWhitelist{priority=MONITOR, packets=[103], gamephase=BOTH, options=[]}

    And no other plugins use protocollib :|
    I'll see if it works now.
     
  21. Offline

    desht

    Use the "detailed" flag, so you can see the actual packet payload for each packet being sent. After the sendMessage() call is made, you should see five Packet103SetSlot packets for your player, one per second. Each packet should alternately contain the messages "The game has begun!" and " The game has begun! " (note the leading/trailing spaces in the second message). And there will be a final packet which resets the slot back to its real contents - in your case, nothing.
     
    hawkfalcon likes this.
  22. desht
    Oddly enough, this stops working after 3 reloads. Any idea why? I don't see this as a big problem, but some people might reload their server 3 times (me).
    Show Spoiler
    Code:
    [SEVERE] Could not pass event EntityDamageByEntityEvent to Tester v0.1
    org.bukkit.event.EventException
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java: 427)
    at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java: 62)
    at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java: 477)
    at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java: 462)
    at org.bukkit.craftbukkit.v1_6_R2.event.CraftEventFactory.callEvent(CraftEventFactory.java: 95)
    at org.bukkit.craftbukkit.v1_6_R2.event.CraftEventFactory.callEntityDamageEvent(CraftEventFactory.java: 383)
    at org.bukkit.craftbukkit.v1_6_R2.event.CraftEventFactory.handleEntityDamageEvent(CraftEventFactory.java: 408)
    at net.minecraft.server.v1_6_R2.EntityLiving.damageEntity(EntityLiving.java: 614)
    at net.minecraft.server.v1_6_R2.EntityAnimal.damageEntity(SourceFile: 128)
    at net.minecraft.server.v1_6_R2.EntityHuman.attack(EntityHuman.java: 884)
    at net.minecraft.server.v1_6_R2.PlayerConnection.a(PlayerConnection.java: 1115)
    at net.minecraft.server.v1_6_R2.Packet7UseEntity.handle(SourceFile: 36)
    at net.minecraft.server.v1_6_R2.Packet7UseEntity$$EnhancerByCGLIB$$a6e66499.CGLIB$handle$0( < generated > )
    at net.minecraft.server.v1_6_R2.Packet7UseEntity$$EnhancerByCGLIB$$a6e66499$$FastClassByCGLIB$$fc8be7f5.invoke( < generated > )
    at com.comphenix.net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java: 228)
    at com.comphenix.protocol.injector.packet.ReadPacketModifier.intercept(ReadPacketModifier.java: 116)
    at net.minecraft.server.v1_6_R2.Packet7UseEntity$$EnhancerByCGLIB$$a6e66499.handle( < generated > )
    at net.minecraft.server.v1_6_R2.NetworkManager.b(NetworkManager.java: 296)
    at net.minecraft.server.v1_6_R2.PlayerConnection.e(PlayerConnection.java: 118)
    at net.minecraft.server.v1_6_R2.ServerConnection.b(SourceFile: 37)
    at net.minecraft.server.v1_6_R2.DedicatedServerConnection.b(SourceFile: 30)
    at net.minecraft.server.v1_6_R2.MinecraftServer.t(MinecraftServer.java: 590)
    at net.minecraft.server.v1_6_R2.DedicatedServer.t(DedicatedServer.java: 226)
    at net.minecraft.server.v1_6_R2.MinecraftServer.s(MinecraftServer.java: 486)
    at net.minecraft.server.v1_6_R2.MinecraftServer.run(MinecraftServer.java: 419)
    at net.minecraft.server.v1_6_R2.ThreadServerApplication.run(SourceFile: 582)
    Caused by: java.lang.IndexOutOfBoundsException: Index: 0,
    Size: 0
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at java.util.Collections$UnmodifiableList.get(Unknown Source)
    at me.avastprods.tester.ItemMessage.getNextId(ItemMessage.java: 118)
    at me.avastprods.tester.ItemMessage.sendMessage(ItemMessage.java: 90)
    at me.avastprods.tester.ItemMessage.sendMessage(ItemMessage.java: 63)
    at me.avastprods.tester.Main.popupExperience(Main.java: 48)
    at me.avastprods.tester.Main.onHit(Main.java: 39)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java: 425)
        ...25 more

    Also I'm quite saddened at how pseudo this is.

    I use ProtocolLib to send SetItemSlot (103) packets to clients to fake the metadata displayName changes. No actual changes are ever made to any items server-side, so this should be extremely safe.

    I was kind of excepting the item display name to dissapear, but apparently this isn't the case.
     
  23. Offline

    desht

    Assist I'll have a look into the exception and see if I can reproduce it. Looks like the metadata for the message id is getting invalidated by the reload; should be easy enough to add a little extra validation when I retrieve the id.

    I have no idea what you mean by "pseudo" in this context. I also don't understand your comment about expecting the item name to disappear. Please explain.
     
  24. desht
    I was excepting the display name to go back to as it was, once the popup message dissapears.

    I haven't looked into the code of yours yet, but would it be possible to alter the code in the way it would retrieve the original item display name? I probably could save the display name to a map, then once the scheduler for the message is over, I send another packet which renames the held item back to as it was, but only problem I can think of is it displaying another popup message. Once again, this isn't a big problem, but I'd like the players to be able to see the item's real display name (I have a custom name for nearly every item).

    This is a cool thing, and I'm definitely going to use this, but when I saw this the first time, I thought it was going to be just a popup message on the screen with packets, no tricks to it. Sorry for the misconception.
     
  25. Offline

    desht

    Assist I'm confused. It does revert to the original item display name once the message is finished displaying, so I'm really not sure why it isn't doing that for you. As I mentioned, it never changes the actual item, so once it's finished sending packets with the fake item name (I.e. your message), it just sends one last packet with the actual ItemStack in it, making the client think the original name is back in place.
    That's exactly what it is, which is why your message has me scratching my head.
     
  26. desht
    That's odd. I'll have to do some testing later.
     
  27. Offline

    desht

    Just uploaded a fix to behaviour on server reloading. Basically, if the server was reloaded during a message display, subsequent message displays ceased to function. It's due to the fact that unlike a full stop/start, reloading the server doesn't clear down metadata, which is how I store pending messages for players. Reload also left the player's client showing the last message for the player's held item name (at least until the player moved the item in their inventory, or relogged).

    The updated version stops any message display when it detects a plugin disable event for your plugin, and correctly resets the (client's idea of) the item metadata, also clearing any pending messages from the queue. After reload is complete, subsequent calls to sendMessage() will work as expected.

    The new setup is much better, but not totally perfect, since all pending messages are cleared, not just the message from the current plugin. In most cases that's not an issue, since 99.9% of the time a plugin is being disabled, it's due to a server stop or reload. In the rare case that a single plugin gets disabled while displaying an item message, pending item messages from other plugins will be lost. I will address this corner case in a future update.

    Assist hawkfalcon this may resolve the problems you encountered - please give it a go.
     
    hawkfalcon likes this.
  28. desht
    Just tested it, still after 3 reloads, throws a NullPointerException at:
    Code:
    Line 100: msgQueue.add(new MessageRecord(message, duration, priority, getNextId(player)));
    Line 73: sendMessage(player, message, DEFAULT_DURATION, DEFAULT_PRIORITY);
    Also the other problem I was having seems to be caused by inventory opening - the name stays if I open my inventory while the message is being displayed. Also if I punch them with my hand, and open my inventory, the piece of snow stays.
     
  29. Offline

    desht

    OK. I'll have to look into the NPE more - I can't reproduce it at the moment.

    Opening your inventory while the message is displayed: yes, that's unavoidable. The client has to think that's the name of the item for the duration of the message display, otherwise no message display.
     
  30. Offline

    iZanax

    desht

    It's a perfect alternative to display text to a player!

    The only thing what I miss for my usage is:
    - Disable regular displayname pop-up
    - Send displayname without a queue

    Because if I want to have a displayname always on the person, it getting ruined by the regular pop-up and the queue that makes the displayname flicker.

    I hope u got some solution to this problem,
    Thanks in advance.
     
Thread Status:
Not open for further replies.

Share This Page