Plugin Help What event is triggered when Magma blocks remove water?

Discussion in 'Plugin Help/Development/Requests' started by Brian_Entei, Oct 11, 2016.

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

    Brian_Entei

    I've been trying to find out how to cancel the default behavior that occurs when there is water or flowing water above a magma block, and the magma block's random tick removes the water with smoke. Does anyone know what block event is fired that I can use to prevent this?
     
  2. @Brian_Entei
    I can't even seem to get the magma block to remove any water when I'm testing, but have you tried the following events?
    • BlockBreakEvent
    • BlockBurnEvent
    • BlockDamageEvent
    • BlockFadeEvent
    • BlockFormEvent
    • BlockIgniteEvent
     
    Brian_Entei likes this.
  3. Offline

    Brian_Entei

    The water has to be flowing/stationary directly above the magma block, that's what causes it for me. It's vanilla minecraft behavior. Unfortunately, I've tried all of those events. I may just end up creating a new blank world with only the magma, water, and surrounding support blocks, then trying to listen to all of the events, and see if any fire in time with the water's dissapearance.

    Edit: Also, any protection plugins like worldguard might be preventing the magma to remove the water(in which case, how, since that's what I want to do!).

    So, BlockFromToEvent fires, but it's related to the water re-forming, not the magma block smoking the water away... ugh, is there really no event for this?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Oct 12, 2016
  4. @Brian_Entei
    I had a look in the source code for you and the magma block directly calls the NMS method World#setAir() which has not event hooks. So no, there is no event being fired. You have a couple options:
    1. Wait for Spigot to add an event
    2. Use a workaround.
    3. Bytecode manipulate the CraftBukkit jar to fire an event when the Magma Block destroys water.
     
    Brian_Entei likes this.
  5. Offline

    I Al Istannen

    @Brian_Entei
    Code:
        @Override
        public void b(final World world, final BlockPosition blockposition, final IBlockData blockData, final Random random) {
            final BlockPosition up = blockposition.up();
            final IBlockData type = world.getType(up);
            if (type.getBlock() == Blocks.WATER || type.getBlock() == Blocks.FLOWING_WATER) {
                world.setAir(up);
                world.a(null, blockposition, SoundEffects.bx, SoundCategory.BLOCKS, 0.5f, 2.6f + (world.random.nextFloat() - world.random.nextFloat()) * 0.8f);
                if (world instanceof WorldServer) {
                    ((WorldServer)world).a(EnumParticle.SMOKE_LARGE, up.getX() + 0.5, up.getY() + 0.25, up.getZ() + 0.5, 8, 0.5, 0.25, 0.5, 0.0, new int[0]);
                }
            }
        }
    Taken straight from the decompiled client code. So no, there is no event for it sadly.


    You could check in the BlockFromToEvent, if it flows over a Magma block and throw a custom event. Would be a working work-around at least.

    EDIT: @AlvinB Really? 3 minutes? Damn ninjas... xD
     
    Brian_Entei likes this.
  6. Offline

    Brian_Entei

    Wow, yeah I had just finished testing all the block events, and you guys are right, since none of them fired. I guess I'll either wait, or find a way to work around it. Thanks guys!
     
  7. Offline

    I Al Istannen

    @Brian_Entei
    No problem. Thanks @AlvinB :) Just wanted to tahg you too :p

    For your problem:
    The best bet is probably BlockFromToEvent. You will just need to check if it flows over a magma block.

    If you really want, you can make your own event, WaterVaporatedEvent ot whatever and let Bukkit throw that for you, so you can listen to that.
    If you want, I could create a small class that does this for you?

    Or you do it yourself, which is a ton of fun too!
     
    Brian_Entei likes this.
  8. Offline

    Brian_Entei

  9. @I Al Istannen
    I have to ask, how are you going to inject it into the class so it gets called when the water evaporates?
     
  10. Offline

    Brian_Entei

    I was going to try compiling a new BlockMagma.java making sure to use the correct packages(v1_10_R1 and the like) and then opening the server.jar and replacing the .class file. If it's got an obfuscated name, then I'm out of luck unless I could find out what the name is.
     
  11. Offline

    I Al Istannen

    @AlvinB
    I interpreted the behaviour of them wrongly. I thought they instantly vaporized it... I am sorry.
    I can't think of an easy workaround. Class transforming is extremly nasty. But you can maybe inject your own class? I will have a look. Will add a dependency on the imports though, and you can't avoid that. So you need one class file for every version you want to support.

    @Brian_Entei
    You can't do it with Bukkit events. But with modifying the class, it is easy. You will need to add a few lines.
    To create a custom event:
    Code:java
    1. /**
    2.   * An event fired if a Magma block vaporizes water.
    3.   */
    4. public static class WaterVaporizeEvent extends BlockEvent implements Cancellable {
    5.  
    6. private static final HandlerList handlerList = new HandlerList();
    7.  
    8. private boolean cancelled;
    9.  
    10. public WaterVaporizeEvent(Block waterBlock) {
    11. super(waterBlock);
    12. }
    13.  
    14. @Override
    15. public boolean isCancelled() {
    16. return cancelled;
    17. }
    18.  
    19. @Override
    20. public void setCancelled(boolean cancelled) {
    21. this.cancelled = cancelled;
    22. }
    23.  
    24. @Override
    25. public HandlerList getHandlers() {
    26. return handlerList;
    27. }
    28.  
    29. /**
    30.   * Returns the HandlerList
    31.   *
    32.   * @return The HandlerList
    33.   */
    34. public static HandlerList getHandlerList() {
    35. return handlerList;
    36. }
    37. }

    Then you can call it with:
    Code:
    WaterVaporizeEvent vaporizeEvent = new WaterVaporizeEvent(block);
    Bukkit.getPluginManager().callEvent(vaporizeEvent);
    if(vaporizeEvent.isCancelled()) {
        // do not vaporize the water!
    }
    You will need to modify the magma block code to do the above and return if it is cancelled.

    This will only work for your server though.
     
    Brian_Entei likes this.
  12. Offline

    Brian_Entei

    So from within BlockMagma.java, how do I get a org.bukkit.block.Block from a minecraft server Block? Is there a bukkit method for that, or do I just cast?

    Edit: Well, I certainly couldn't cast, since the bukkit block isn't derived from the nms block classes...

    Hmm, wait, I may have figured it out, I opened up BlockDoor.java and looked at how it was done there. Does this look right?

    Code:java
    1. @Override
    2. public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
    3. BlockPosition up = blockposition.up();
    4. IBlockData type = world.getType(up);
    5. CraftWorld w = world.getWorld();
    6. org.bukkit.block.Block block = w.getBlockAt(up.getX(), up.getY(), up.getZ());
    7. if(type.getBlock() == Blocks.WATER || type.getBlock() == Blocks.FLOWING_WATER) {
    8. WaterVaporizeEvent vaporizeEvent = new WaterVaporizeEvent(block);
    9. Bukkit.getPluginManager().callEvent(vaporizeEvent);
    10. if(!vaporizeEvent.isCancelled()) {
    11. world.setAir(up);
    12. world.a((EntityHuman) null, blockposition, SoundEffects.bx, SoundCategory.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
    13. if(world instanceof WorldServer) {
    14. ((WorldServer) world).a(EnumParticle.SMOKE_LARGE, up.getX() + 0.5D, up.getY() + 0.25D, up.getZ() + 0.5D, 8, 0.5D, 0.25D, 0.5D, 0.0D, new int[0]);
    15. }
    16. }
    17. }
    18. }


    Edit: Ahh! It works! Yay, thanks for all the help guys! Now I'll just need to remember that I have a customized spigot-1.10.2.jar on my hands.
     
    Last edited: Oct 12, 2016
  13. @I Al Istannen
    When you said Class Transformation, I just had to do it :p

    I basically created a class transformer so the b method calls the BlockBurnEvent, and I added an EventListener which prints "success!" when the event gets called.

    I created a gist for the code here, and as you can see, this incredibly simplistic task takes an insane amount of code to accomplish. It also uses this library which was created using this tutorial.

    Here is the jar (if you for some reason want it):
    https://bringholm.com/downloads/Bytecode Manipulation.jar

    @Brian_Entei
    If you're wondering why the hell I created this mammoth of code to do something which you can just du by manually editing the craftbukkit jar. Well, this code edits the craftbukkit jar upon start, so people without modified craftbukkit jars can use the plugin too.
     
    I Al Istannen and Brian_Entei like this.
  14. Offline

    Brian_Entei

    @AlvinB Wow, so it's sort of automatic, that's cool. I may end up using this if recompiling the BlockMagma and copying over the WaterVaporizeEvent classes into the server .jar becomes a chore, but for now, I'll leave it like it is. Thanks for showing me this! I'm using it to reduce the amount of water being fizzled on my players' islands, since they're a fan of using magma blocks under their water streams to kill mobs instead of using lava blades in their grinders.

    Edit: Woah, looking through the bytecode manipulation, it looks like a ton of work to implement an event, but I still like that it's dynamic. Now if only you could use reflection to dynamically get the server's version instead of hardcoding "v1_10_2" in there...

    Is there any way you could incorporate the following?

    Code:java
    1. /** Finds the first package which contains the NMS
    2.   * version, and returns the version found.
    3.   *
    4.   * @return The current NMS version */
    5. public static final String getNMSVersion() {
    6. for(Package p : Package.getPackages()) {
    7. String name = p.getName();
    8. if(name.startsWith("net.minecraft.server.v")) {
    9. return name.replace("net.minecraft.server.", "");
    10. }
    11. }
    12. return null;
    13. }
    14.  
     
    Last edited: Oct 12, 2016
  15. @Brian_Entei
    Yeah, if you're the only one using the plugin on your server, modifying it won't be a problem, but with others' jars, it won't work.

    Also, if you're interested, I guess I should explain what that actually is. It's called Class Transformation (although I often like to call it "Bytecode Manipulation"). It's basically a way of manually inserting and deleting instructions when java loads the classes (so it doesn't actually change the file, it only changes in memory), which is the way you should go about it when the code is going to be used on other peoples servers (if you modified the actual file, then the changes would stay, even if they removed your plugin!). The big downside of it is, that it is incredibly tedious to do (as you see in the gist I provided) if you use the ASM library like I did, there are however other libraries, like AspectJ which allow you to write in nearly normal java source code.

    EDIT: Also, someone should submit a PullRequest to Spigot about this, more people will probably need this in the future..
     
    Brian_Entei likes this.
  16. Offline

    Brian_Entei

    I agree, there are probably loads of cases where this will be needed. I wonder if the water fizzling in the nether could be considered a candidate for firing the BlockVaporizeEvent(Since that's probably what it would be called anyway)...

    Edit: I have the modified BlockMagma.java and the new event, should I do the pull request? Where do I go?
     
  17. @Brian_Entei
    Here's the instructions for submitting pullrequests for spigot, although you need to be sure to follow some guidlines mentioned in the file (also, I believe you'll need to submit a request to change a .patch file, since that's what they use to change the NMS code):
    https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/README.md

    Also, in response to the edit on your post, yes, that is defiantly doable. Infact, I'll do some cleanups all over the code to make it version independent and I'll upload a new version, so you can use it as a sort of API.
     
    I Al Istannen likes this.
  18. Offline

    Brian_Entei

    Wow, that will be really cool when you get it done.

    Anywho, thanks for the help guys!
     
  19. Offline

    I Al Istannen

    @AlvinB
    You sir are a madman...

    I am amazed I actually understood most of what you did... Seems like learning IJVM in uni was a good thing xD
    Other things like the FrameNode stuff is black wizardry to me, but you seriously tempted me to look into ASM :p
    I stumpled upon it actually a year or so ago, but wasn't even remotely good enough to understand anything.
    However it sounds like it is quite cool and there certainly is a fascination to writing bytecode. It will take ages and is extremly tedious but it is something you can actually be a bit proud of xD Though it will probably be horrible inefficient... ;)

    And I thought before you would need to attach the agent on JVM startup, guess I was wrong :p

    TL;DR: This is amazing. Probably extremly stupid and fragile, but amazing. Well done! This is the cool stuff :)
     
  20. @I Al Istannen
    If you took a uni course in it, then I bet you know 10x more than me! :p Honestly, I have no idea what the instructions do, I just write up a copy of the modified class, compile it and see what instructions I need to edit. If I recall that youtube lecture I watched while half asleep said it had something to do with return values of a method (makes sense, since the "callEvent" method returns a boolean).

    Also, yes you are required to add agents on startup, but there is however a native code injector (IE in operating system code) which will allow it you to do it after startup. Sadly this injector only comes with the JDK, not JRE, so I have to ship it in my jar (And hack java in the process to change the location of it on disk).

    And something to note on ASM: it's horrible. If you actually wanted to do something larger with this, use a higher level API like AspectJ. The advantage of ASM is that it's so small in size (It's also bundled in both the JDK and JRE already), compared to AspectJ. Also, the reason the JAR file weighs like a 100 kb is that I include the native code injectors for each operating system in there.

    I guess I should give a shoutout to @Icyene for making this tutorial, which taught me most of the stuff I needed to make this possible.
     
  21. Offline

    I Al Istannen

    @AlvinB
    Pff, it was barely mentioned. Just one of many topics.

    The native code injector sounds cool, I bet there is a fully blown lib which does all the work for you :p

    I will have a look at ASM and AspectJ, it sounds really cool, thank you for the links!

    Have a nice day and good luck with it ;)
     
  22. I Al Istannen likes this.
  23. @Brian_Entei
    Alright, I have changed the code up to be (hopefully) version-independent! Gist is here. I had to use the BlockBurnEvent because it would not let me fire a custom created one for some unexplainable reason. Download for the plugin is here:
    https://bringholm.com/downloads/MagmaEvent.jar

    Also, some limitations on the bytecode manipulation process used.

    These are the only supported operating systems:
    • Windows 32x & 64x
    • Mac OS 64x
    • Most linux derived operating systems 32x & 64x
    • Solaris 32x & 64x
    It should also be noted that if the Java Environment has limited permissions for programs (IE a ProtectionDomain which doesn't allow class reinstrumentation) it will not work. I'd love it if you tried this out, as I have only ever verified that this process works on my own windows machine.
     
  24. Offline

    Brian_Entei

    I'd love to try it out, is the MagmaEvent.jar loaded as a normal plugin?
     
Thread Status:
Not open for further replies.

Share This Page