Solved Sending several block changes to one client

Discussion in 'Plugin Development' started by sirrus86, Mar 1, 2013.

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

    sirrus86

    I have a plugin called S86 Powers that has an ability called Ore Detector. What it does is it changes all non-ore solid blocks near the player into glass so as to help in finding ore. These changes are of course fake block changes, as only a player with this ability should see the change.

    In my first iteration of the ability I used player.sendBlockChange, and unsurprisingly it lagged terribly since each block required a lighting update. I released my plugin with the ability anyway, and lo and behold several users reported the ability crashing their servers. So I've needed a more efficient method.

    I've decided to go the route of using packets, and through some research have determined that
    Packet52MultiBlockChange is more than likely the best packet to use. Unfortunately the packet requires the block data be compressed into a byte array, and despite my best efforts and attempts at self-education I just can't get the hang of it. The best information I could find was this thread, where Comphenix explains the byte structure needed for the block data, but I'm clueless when it comes to bitwise operators, bit shifts and such.


    Here's the constructor I currently use to create a Packet52MultiBlockChange packet:

    Code:
    public static Packet52MultiBlockChange p52(Chunk chunk, Material material, Block... blocks) {
        Packet52MultiBlockChange packet = new Packet52MultiBlockChange(chunk.getX(), chunk.getZ(), new short[64], blocks.length, ((CraftChunk)chunk).getHandle().world);
        byte[] data = new byte[blocks.length * 4];
        for (int i = 0; i < blocks.length; i ++) {
            int j = i * 4;
            int blockX = blocks[i].getX() - (chunk.getX() * 16);
            int blockY = blocks[i].getY();
            int blockZ = blocks[i].getZ() - (chunk.getZ() * 16);
            int block = material.getId();
            int info = 0;
            data[j] = ?;
            data[j + 1] = ?;
            data[j + 2] = ?;
            data[j + 3] = ?;
        }
        packet.c = data;
        return packet;
    }
    As you can see, I just want to load in some blocks, get their coordinate data, then apply my own material data. Can anyone give me some tips of where to go from here? Or is there a better way?
     
  2. I don't quite understand it.
    You're asking how to apply your own material data on it?

    You already have material.getId();
    So.. you can just change that.
    Code:
    int block = material.getId();
     
    if(block != Material.DIAMOND_ORE.getId()){
      block = Material.GLASS.getId();
    }
    This would only show the diamond ores.
     
  3. Offline

    Netizen

    Ok so according to @Comphenix we need to put the x and z in the first byte, the y in the second byte and the blockid spans the 2nd and 3rd byte, with data on the end...

    I'll take a stab at it but without debugging it myself your mileage may vary.

    // Byte index: | Zero | One |Two| Three |
    // Bit index: | 0 - 3 | 4 - 7 | 8 - 15 | 16 - 27 | 28 - 31 |
    // Content: | x | z | y | block id | data |

    So lets put in the z, then shift it by 4 bits, then add x.
    Code:
     data[j] = ((byte)blockZ << 4) + (byte)blockX; 
    Y is easy, as it takes an entire byte:
    Code:
     data[j+1] = (byte)blockY; 
    ;

    Block id is going to be more tricky... but I think we might be able to cheat a little by using a short (16 bits).
    Code:
    short tempShort = (data<< 12) + block_id; //Shift data over just enough to give room for block id, add block id to it
    //Now get the upper part and lower part of the short using a mask
    data[j+2] = (byte)(tempShort & 0xFF00); //upper part;
    data[j+3] = (byte)(tempShort & 0x00FF); //lower part;
    
    Annnnnnnd that should do it! (with some luck and maybe thinkering). I hope it at least gives you a better idea for were to start....

    Sorry, I think I have to shift the data first, then add block id... edited/fixed above.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 31, 2016
  4. Offline

    sirrus86

    Netizen

    This is a good start, thanks for your help!

    This caused block changes at opposite coordinates (for example, when trying to change a block at x=5, z=7 it'd make the change at x=7, z=5). So I switched them around.

    Code:
    data[j] = (byte) ((blockX << 4) + blockZ);
    Unfortunately now no changes are made :/ Maybe something's being shifted the wrong way?

    Also, when testing to change blocks into glass, it changed them into stone. To further test, I tried changing blocks to diamond ore, and that changed them to dirt. I believe this is related to the hex value for glass and diamond ore:

    Goal: Glass(14) / Diamond Ore(38)
    Result: Stone(1) / Dirt (3)

    I tried messing with the bit-shifting in the tempshort variable, but always got the same result.

    Additional help would be great, but in any case I do appreciate your help!
     
  5. Offline

    Comphenix

    I've added a new helper class to PacketWrapper named BlockChangeArray that should be helpful in this instance.

    With it, constructing multi-block change packets is a breeze:
    Code:java
    1. int blockID = Material.GOLD_BLOCK.getId();
    2.  
    3. // Update a 7x7 square
    4. BlockChangeArray change = new BlockChangeArray(49);
    5. Player player = (Player) sender;
    6.  
    7. for (int i = 0; i < 49; i++) {
    8. change.getBlockChange(i).
    9. setRelativeX(i % 7).
    10. setRelativeZ(i / 7).
    11. setAbsoluteY(player.getLocation().getBlockY()).
    12. setBlockID(blockID);
    13. }
    14.  
    15. Packet34MultiBlockChange packet = new Packet34MultiBlockChange();
    16. packet.setOriginChunkX((player.getLocation().getBlockX() >> 4) + 1);
    17. packet.setOriginChunkZ((player.getLocation().getBlockZ() >> 4) + 1);
    18. packet.setRecordData(change.toByteArray());
    19.  
    20. // Send the packet
    21. try {
    22. manager.sendServerPacket(player, packet.getHandle());
    23. e.printStackTrace();
    24. }
    25. return true;


    I've been thinking about performance, and it did struck me as a bit inefficient to create a new object for each block change in the array, especially if you're sending different packets to different players frequently.

    It probably doesn't matter, but in case you need it, I've changed the getBlockChange() method to also accept a used BlockChange, so that it can be recycled.

    EDIT: Never mind, looks like this is actually slower. Modern JVMs are amazing, and as always - premature optimization is the root of all evil.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 31, 2016
    stirante likes this.
  6. Offline

    sirrus86

    Comphenix

    Awesome, thanks! I'll try it out when I get out of work later tonight. Should it work do I have your permission to add it to my plugin (with credit to you of course)?
     
  7. Offline

    Comphenix

    Of course. :)

    The code itself is licensed under the LGPL, so you can copy the classes directly into your plugin, even if you haven't open sourced it. Though, you will have to be ready to publish any changes you make to the classes themselves.
     
  8. Offline

    sirrus86

    Comphenix

    Just began testing it, but so far it works great!

    Thanks everyone for your help!
     
  9. Offline

    Cryptite

    Bit of a thread necro, but anybody know how the modern 1.8 version of this might could work? Doesn't seem that most of this applies anymore and I'm in need of sending bulk fake-data changes.
     
Thread Status:
Not open for further replies.

Share This Page