[TUTORIAL] Quick Method for More Efficient Block "Disguising"

Discussion in 'Spigot Plugin Development' started by ColonelHedgehog, Nov 11, 2015.

  1. Hi all,

    I'd just like to really quickly show you how to send block change packets (WITHOUT Player#sendBlockChange) in a way that I find to be quite fast. I couldn't find a tutorial that tells you how to do this (or at least one for 1.8) so I decided to make my own!

    I'll just drop the method here.

    Code (Text):

    // Imports included so you can be sure you're importing the right classes.

    import com.colonelhedgehog.mcwarfare.core.MCWarfare;
    import net.minecraft.server.v1_8_R3.Block;
    import net.minecraft.server.v1_8_R3.BlockPosition;
    import net.minecraft.server.v1_8_R3.PacketPlayOutBlockChange;
    import org.bukkit.Location;
    import org.bukkit.Material;
    import org.bukkit.craftbukkit.v1_8_R3.CraftWorld;
    import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import org.bukkit.scheduler.BukkitRunnable;

    /**
    * Created by ColonelHedgehog on 11/11/15.
    */
    public class BlockUtils
    {
        private static Main plugin = Main.myPlugin;

        /**
         * This method will send a block change using packets, asynchronously, without actually changing the block. The block will thus appear differently to the player, without actually changing altogether.
         *
         * @param player The player to whom the packet will be sent.
         * @param location The location where the block should be changed.
         * @param material The NEW material to give the block.
         * @param data The NEW data to give the block.
         */
        public static void maskBlock(Player player, Location location, Material material, byte data)
        {
            // As some have pointed out, it could be unwise to use this as a static. Consider using it as an instance var.
            new BukkitRunnable()
            {
                @Override
                public void run()
                {

                    BlockPosition blockPosition = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
                    PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(((CraftWorld) location.getWorld()).getHandle(), blockPosition);
                    packet.block = Block.getByCombinedId(material.getId() + (data << 12));
                    ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
                }
            }.runTaskAsynchronously(plugin);
        }
    }
     
    You can use this resource as long as you put my name in the header of every project file, in caps, and prefixed by "His majestic honorableness." (Just kidding.)

    Using it should be pretty straightforward. One of the nice things about it is it can be run asynchronously. If you want to run multiple block changes at once, I recommend you schedule a single async task for all of them, rather than scheduling individual ones for each change, like I have done here. In that case, just transfer all the code from the run() void to outside the BukkitRunnable class, and remove the BukkitRunnable class from the method.

    If you need to update your block, just create a PacketPlayOutBlockChange with the same world and location parameters, but this time, don't change its "block" field.

    Hope it works out for you! Let me know if you have any comments about how I did this, how you think it can be done better, and if it's faster than sendBlockChange for you or not.

    ~ CH
     
    #1 ColonelHedgehog, Nov 11, 2015
    Last edited: Nov 11, 2015
    • Like Like x 1
    • Funny Funny x 1
    • Useful Useful x 1
  2. @ColonelHedgehog why not just use sendBlockChange? Also, dat static abuse.

    Note that your optimalization hardly exists (the data structures used can run add operations in O(1)), perhaps it's even slower because of locking mechanisms synchronising access to the packet queue.
     
    • Agree Agree x 2
  3. Well, try using it for yourself! :p Use sendBlockChange, then use this. sendBlockChange(). The reason why I wrote this is because I'm making a plugin where sendBlockChange is slowing my client. I use sendBlockChange and it freezes my client for about a quarter second. Using this makes my client freeze for maybe a tenth.

    I don't really know how sendBlockChange works to be honest. I would assume it does the exact same thing as my code—but if it did, why is mine yielding different results for me?

    Alright, alright, I just pasted my code into a Utilities file and threw in static, you can remove the static modifier and replace it with an instance variable. Sure. I wasn't intending for anyone to just pop it into their project.
     
  4. sendBlockChange() does literally the same thing as you do...
     
    • Like Like x 1
  5. Did you read what I said? I'm very interested in this subject.

     
  6. Nice tutorial. Few months ago I was changing out about 24 blocks at a time using the API method, so I know the client lag you speak of. I ended up doing the packet myself, like you, but using ProtocolLib since pretty much every server already has it installed so that my plugin isn't using NMS. Weird as it sounds to everyone else, I too can confirm sending the packet yourself is faster than using the API method. Placebo effect? Maybe. But I know as I'm flying around now having these packets sent manually doesn't make my client lag/jerk.
     
    • Agree Agree x 1
  7. Yours is probably yielding different results because it's async I assume.
    Btw, afaik, Bukkit and Minecraft aren't thread-safe, so it's probably not worth doing it async anyways.
     
  8. No, not necessarily. I'm running both exactly the same, except I swap out sendBlockChange for my method.

    Yep, I agree. I mean, it could be that the hours I spent researching how to put this together have convinced me it's different, but I just swapped out sendBlockChange for the packet method (while in an async scheduler, mind you, just like the other one), and it was significantly slower.
     
  9. That's why there is Packet34MultiBlockChange or PacketPlayOutMultiBlockChange
     
  10. Last I looked the multi block and chunk update methods weren't implemented in the API. Just stub methods. I didn't think to check for a multi block/chunk packet, though. Oh well, i ended up changing my block changes to particles recently (its just a visual effect for my plugin to see chunks) but still good to know those packets exist. However, the lag can be still be seen using just one block. Its just more lag with more at one time.
     
    • Agree Agree x 1
  11. For reference, this is the sendBlockChange method
    Code (Java):

    public void sendBlockChange(Location loc, int material, byte data)
    {
        if (getHandle().playerConnection == null) {
            return;
        }
        PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(((CraftWorld)loc.getWorld()).getHandle(), new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
        packet.block = CraftMagicNumbers.getBlock(material).fromLegacyData(data);
        getHandle().playerConnection.sendPacket(packet);
    }
    Which hardly differs from you implementation :p. I'm guessing you are just bypassing some locking difficulties by moving it to a different thread (as this would not block the main thread from processing). Then again, you might run into the same issue later on when the load is higher.
     
    • Informative Informative x 2
    • Like Like x 1
  12. Interesting, thanks for sharing. I guess my implementation works best when you're not trying to handle a huge amount of data.