change chunk for client only

Discussion in 'Plugin Development' started by jayfella, Jan 11, 2013.

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

    jayfella

    Is it possible to alter the chunk for the client only - and not everybody else? I am aware that you can use the following method:

    Code:
    player.sendBlockChange(visualLocation, Material.GOLD_BLOCK, (byte)0);
    I have also tried using a staggered recurring task using the above method, which is fine, but from hours/days of reading it appears that the "lighting differences that the new blocks create" cause it to lag a lot. What I am trying to do is to display an outline of a pre-determined region. It may be possible that a lot of blocks will be shown to the player and so the lighting calculations are bound to cause lag when multiple players are viewing an outline.

    I could change the chunk for everybody, and store the block changes if they try to "mine" it so they dont get the GOLD_BLOCK, but these region outlines could potentially "get in the way" of people walking, or stop doors from opening, or even replace a door, etc etc. since they are 3 dimensional - which is why I only want the player requesting to view the outline to see it.

    I have played around with "Packet51MapChunk" - but when I send that packet, it fills the chunk with air all the way from Y:255 to Y:16 for some reason. And also, I'm not sure how exactly to change individual blocks in a given location in a "Packet51MapChunk". If I alter the chunk before giving it to the packet, it will of course change the chunk for everybody, so I assume I have to alter the packet, not the chunk itself.

    Code:
    EntityPlayer entityPlayer = getNativePlayer(player);
     
    org.bukkit.Chunk bChunk = scPlayer.getPlayer().getLocation().getChunk();
    Chunk nativeChunk = getNativeChunk(bChunk);
     
    Packet51MapChunk packet = new Packet51MapChunk(nativeChunk, true, 1);
     
    entityPlayer.playerConnection.sendPacket(packet);
    
    I've also looked at ProtocolLib, orebfusticator, VNP, etc, etc, etc.. I have looked high and low and googled various terms and just cannot find anything like what I need.

    Is this possible or am I searching for something that doesnt exist. To summarize, I am trying to send a lot of fake blocks to one individual player without the "lighting calculation" lag that "player.sendBlockChange()" causes - possibly by altering the chunk for that player only, not for everybody on the server. If this requires "packet51" - how do i alter blocks at a specific location - e.g. "new Location(world, 2442, 71, 2332)" ?

    Any help would be greatly appreciated.
     
  2. Offline

    skipperguy12

    Why not loop through the chunk and send the block change?

    Sorry if i'm way off, but this is long! So I skimmed :3
     
  3. Offline

    jayfella

    Because as I said, "player.sendBlockChange()" causes the lighting to get re-calculated after ever since iteration of it. The lag from using "sendBlockChange" isnt from adding tons of blocks, because you can just stagger it, the lag is caused from the lighting re-calculation as a result of adding all the blocks individually. If you alter the chunk, and then give it the chunk, the lighting is only re-calculated once, which is why I want to find out how to alter a chunk instead.

    And the reason it is long is because I want to explain myself properly :p
     
  4. Offline

    skipperguy12

    Hmmm....why not make it kind of have a fade effect? Like a teeny tiny delay for every block it adds. If you do it at just the right time, it shouldn't really lagg, about as much as a player placing blocks fast.
     
  5. Offline

    jayfella

    Because that would just take forever. As I said, its not adding the blocks that is the problem, im completely familiar with staggering heavy loops, its the lighting calculation that comes with it thats the problem. Which is why I want to add blocks to a chunk, and then issue the chunk change instead, which is a million orders of magnitude quicker.

    Shameless bump - Any help would be really appreciated.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jul 8, 2016
  6. Offline

    desht

  7. Offline

    jayfella

    Yeah i tried that:

    https://github.com/Bukkit/CraftBukk...rg/bukkit/craftbukkit/entity/CraftPlayer.java

    Code:java
    1.  
    2. public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data) {
    3. if (getHandle().playerConnection == null) return false;
    4. /*
    5. int x = loc.getBlockX();
    6. int y = loc.getBlockY();
    7. int z = loc.getBlockZ();
    8. int cx = x >> 4;
    9. int cz = z >> 4;
    10. if (sx <= 0 || sy <= 0 || sz <= 0) {
    11. return false;
    12. }
    13. if ((x + sx - 1) >> 4 != cx || (z + sz - 1) >> 4 != cz || y < 0 || y + sy > 128) {
    14. return false;
    15. }
    16. if (data.length != (sx * sy * sz * 5) / 2) {
    17. return false;
    18. }
    19. Packet51MapChunk packet = new Packet51MapChunk(x, y, z, sx, sy, sz, data);
    20. getHandle().playerConnection.sendPacket(packet);
    21. return true;
    22. */
    23. throw new NotImplementedException("Chunk changes do not yet work"); // TODO: Chunk changes.
    24. }
    25.  
     
  8. Wow, seriously, bukkit :confused:
    That method had been existant for like forever, I didn't know it doesn't actually do anything ... Why even have it there in the first place if it's unimplemented anyway?

    Back on topic, what you are trying to do is in fact possible, but needs some hackish messing around within byte arrays. Because you - as you noticed yourself - can't just use the chunk's methods to alter it, you more or less have to directly modify the packet's data, which consists of some byte[] fields.

    However, I think a better packet to use would be 0x34 (Packet52MultiBlockChange), which is basically a bunch of block change packets combined into one. You don't need to resend the whole chunk, just all the changes that happened.
    This is also what happens natively when a bunch of block changes have to be updated to the player (for example after the "//set" command of WorldEdit), so it's pretty much the best option.

    To use it, take a look at the linked wiki page of the protocol, and also at how it's internally being used in net.minecraft.server.PlayerChunk (eclipse reference search for the win!).

    To send the packet, you can either use native code or try out the ProtocolLib that you already mentioned. The principle should stay the same.

    I hope you can work your way through with that, good luck :)
     
    jayfella likes this.
  9. Offline

    jayfella

    a ray of light! I shall check that out right now and let you know how I get on. Many thanks! and even more thanks for the detailed links :)
     
  10. Offline

    jayfella

    It takes a lot of math, head scratching and working things out without documentation, but I got there in the end. This is indeed the information I required. Thank you very much ;)
     
  11. I'm glad I could help and that it worked out!
    For future reference, you could post the relevant part of your solution in here, so when someone stumbles upon this thread in the future, there will be an answer :)
     
  12. Offline

    jayfella

    Sure, why not. here goes.

    Code:java
    1.  
    2. int dirtyCount = 63; // amount of blocks you intend to change
    3.  
    4. // create packet
    5. Packet52MultiBlockChange packet = new Packet52MultiBlockChange(
    6. chunk.getX(),
    7. chunk.getZ(),
    8. new short[64],
    9. dirtyCount,
    10. getNativeWorld(chunk.getWorld()));
    11.  
    12. byte[] dirtyBlocks = new byte[256];
    13.  
    14. dirtyBlocks[0] = HexToByte("FF"); // x + z co-ords of block in chunk - hex
    15. dirtyBlocks[1] = HexToByte("FF"); // y co-ord - hex
    16. dirtyBlocks[2] = HexToByte(Integer.toString(1)); // BlockId
    17. dirtyBlocks[3] = HexToByte(Integer.toString(metadata)); // metadata, rotation, etc..
    18.  
    19. packet.c = dirtyBlocks;
    20.  
    21. nativePlayer.playerConnection.sendPacket(packet);
    22.  


    Ok, so you can send the data using the "new short[]" in the constructor, but I found that really tedious. Instead i modified the bytes of the packet before sending it to the client.

    Each block change is 4 bytes - 64 * 4 = 256 (byte[] length), so each 4th iteration of the byte array is a new block.

    One thing I am having trouble with is that the "blockId" doesnt seem to want to work. If I set if any higher than "09" - either "10" or "0A" or whatever - the client crashes out. I can't work out what format i should send this ID. Any help on that would be appreciated.

    Other than that minor issue, it all works. Do not send more than 63 block changes per packet, else it will refuse and expect a fall-back of sending a Map51ChunkChange instead.
     
  13. Offline

    Comphenix

    The BlockID value is actually stored in the third as well as the last byte (totaling 12 bits). Take a look at how I read and write this packet in BlockPatcher:
    Code:java
    1. // Each updated block is stored sequentially in 4 byte sized blocks.
    2. // The content of these bytes are as follows:
    3. //
    4. // Byte index: | Zero | One | Two | Three |
    5. // Bit index: | 0 - 3 | 4 - 7 | 8 - 15 | 16 - 27 | 28 - 31 |
    6. // Content: | x | z | y | block id | data |
    7. //
    8. for (int i = 0; i < data.length; i += 4) {
    9. int block = ((data[i + 2] << 4) & 0xFFF) |
    10. ((data[i + 3] >> 4) & 0xF);
    11. int info = data[i + 3] & 0xF;
    12. int chunkY = (data[i + 1] & 0xFF) >> 4;
    13.  
    14. if (block >= 0) {
    15. // Translate and write back the result
    16. info = lookup.getDataLookup(block, info, chunkY);
    17. block = lookup.getBlockLookup(block, chunkY);
    18.  
    19. data[i + 2] = (byte) ((block >> 4) & 0xFF);
    20. data[i + 3] = (byte) (((block & 0xF) << 4) | info);
    21. }
    22. }

    Oh, and BlockPatcher can also modify whole chunks for you without lag. I used it to make a "glass chunk" plugin that could turn the chunk the player currently walks on into glass (client-side).
     
  14. Offline

    Jobi

    @Comphenix
    I'm sorry, but I'm still to stupid to use your code. You seem to be translating the data of the packet to block info, but how to I write the block info into the data? Lets say I got an Array with Blocks... how do I create a MultiBlockChangePacket from it?
     
Thread Status:
Not open for further replies.

Share This Page