Package org.spout.vanilla.protocol.codec.world.chunk

Source Code of org.spout.vanilla.protocol.codec.world.chunk.ChunkDataCodec

/*
* This file is part of Vanilla.
*
* Copyright (c) 2011 Spout LLC <http://www.spout.org/>
* Vanilla is licensed under the Spout License Version 1.
*
* Vanilla is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* In addition, 180 days after any changes are published, you can use the
* software, incorporating those changes, under the terms of the MIT license,
* as described in the Spout License Version 1.
*
* Vanilla is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License,
* the MIT license and the Spout License Version 1 along with this program.
* If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
* License and see <http://spout.in/licensev1> for the full license, including
* the MIT license.
*/
package org.spout.vanilla.protocol.codec.world.chunk;

import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import org.spout.api.geo.cuboid.Chunk;
import org.spout.api.protocol.MessageCodec;
import org.spout.api.protocol.Session;
import org.spout.api.protocol.reposition.NullRepositionManager;

import org.spout.vanilla.protocol.VanillaProtocol;
import org.spout.vanilla.protocol.msg.world.chunk.ChunkDataMessage;

public final class ChunkDataCodec extends MessageCodec<ChunkDataMessage> {
  private static final int COMPRESSION_LEVEL = Deflater.BEST_SPEED;
  private static final int MAX_SECTIONS = 16;
  private final byte[] UNLOAD_COMPRESSED = {0x78, (byte) 0x9C, 0x63, 0x64, 0x1C, (byte) 0xD9, 0x00, 0x00, (byte) 0x81, (byte) 0x80, 0x01, 0x01}; //Fake compressed data, client expects this when unloading

  public ChunkDataCodec() {
    super(ChunkDataMessage.class, 0x33);
  }

  @Override
  public ChunkDataMessage decode(ByteBuf buffer) throws IOException {
    int x = buffer.readInt();
    int z = buffer.readInt();
    boolean contiguous = buffer.readByte() == 1;

    short primaryBitMap = buffer.readShort();
    short addBitMap = buffer.readShort();
    int compressedSize = buffer.readInt();
    byte[] compressedData = new byte[compressedSize];
    buffer.readBytes(compressedData);

    boolean[] hasAdditionalData = new boolean[MAX_SECTIONS];
    byte[][] data = new byte[MAX_SECTIONS][];

    int size = 0;
    for (int i = 0; i < MAX_SECTIONS; ++i) {
      if ((primaryBitMap & 1 << i) != 0) { // This chunk exists! Let's initialize the data for it.
        int sectionSize = Chunk.BLOCKS.HALF_VOLUME * 5;
        if ((addBitMap & 1 << i) != 0) {
          hasAdditionalData[i] = true;
          sectionSize += Chunk.BLOCKS.HALF_VOLUME;
        }

        data[i] = new byte[sectionSize];
        size += sectionSize;
      }
    }

    if (contiguous) {
      size += Chunk.BLOCKS.AREA;
    }

    byte[] uncompressedData = new byte[size];

    Inflater inflater = new Inflater();
    inflater.setInput(compressedData);
    inflater.getRemaining();
    int index = 0;
    try {
      while (index < uncompressedData.length) {
        int uncompressed = inflater.inflate(uncompressedData, index, uncompressedData.length - index);
        index += uncompressed;
        if (uncompressed == 0) {
          break;
        }
      }
      if (index != uncompressedData.length) {
        throw new IOException("Not all bytes uncompressed.");
      }
    } catch (DataFormatException e) {
      e.printStackTrace();
      throw new IOException("Bad compressed data.");
    } finally {
      inflater.end();
    }

    size = 0;
    // TODO - fix this total hack
    size = readSectionData(uncompressedData, size, data, 0, 4096);
    size = readSectionData(uncompressedData, size, data, 2 * 2048, 2048);
    size = readSectionData(uncompressedData, size, data, 3 * 2048, 2048);
    size = readSectionData(uncompressedData, size, data, 4 * 2048, 2048);

    /*size = 0;
    for (byte[] sectionData : data) {
      if (sectionData != null && sectionData.length + size < uncompressedData.length) {
        System.arraycopy(uncompressedData, size, sectionData, 0, sectionData.length);
        size += sectionData.length;
      }
    }*/
    byte[] biomeData = new byte[Chunk.BLOCKS.AREA];

    if (contiguous) {
      System.arraycopy(uncompressedData, size, biomeData, 0, biomeData.length);
      size += biomeData.length;
    }

    return new ChunkDataMessage(x, z, contiguous, hasAdditionalData, data, biomeData, null, NullRepositionManager.getInstance());
  }

  @Override
  public ByteBuf encode(ChunkDataMessage message) throws IOException {
    ByteBuf buffer = Unpooled.buffer();

    buffer.writeInt(message.getX());
    buffer.writeInt(message.getZ());
    buffer.writeByte(message.isContiguous() ? 1 : 0);
    if (message.shouldUnload()) {
      buffer.writeShort(0);
      buffer.writeShort(0);
      buffer.writeInt(UNLOAD_COMPRESSED.length);
      buffer.writeBytes(UNLOAD_COMPRESSED);
      return buffer;
    }
    short sectionsSentBitmap = 0;
    short additionalDataBitMap = 0;

    byte[][] data = message.getData();

    int uncompressedSize = 0;
    for (int i = 0; i < MAX_SECTIONS; ++i) {
      if (data[i] != null) { // This chunk exists! Let's initialize the data for it.
        sectionsSentBitmap |= 1 << i;
        if (message.hasAdditionalData()[i]) {
          additionalDataBitMap |= 1 << i;
        }
        uncompressedSize += data[i].length;
      }
    }

    if (message.isContiguous()) {
      uncompressedSize += message.getBiomeData().length;
    }

    buffer.writeShort(sectionsSentBitmap);
    buffer.writeShort(additionalDataBitMap);
    byte[] uncompressedData = new byte[uncompressedSize];
    int index = 0;

    // TODO - fix this total hack
    index = writeSectionData(data, 0, uncompressedData, index, 4096);
    index = writeSectionData(data, 2 * 2048, uncompressedData, index, 2048);
    index = writeSectionData(data, 3 * 2048, uncompressedData, index, 2048);
    index = writeSectionData(data, 4 * 2048, uncompressedData, index, 2048);

    if (message.isContiguous()) {
      System.arraycopy(message.getBiomeData(), 0, uncompressedData, index, message.getBiomeData().length);
      index += message.getBiomeData().length;
    }

    Session session = message.getSession();
    if (session != null) {
      uncompressedData = message.getSession().getDataMap().get(VanillaProtocol.CHUNK_NET_CACHE).handle(uncompressedData);
    }

    byte[] compressedData = new byte[uncompressedSize >> 2];

    Deflater deflater = new Deflater(COMPRESSION_LEVEL);
    deflater.setInput(uncompressedData);
    deflater.finish();

    int compressed;
    try {
      compressed = deflater.deflate(compressedData);
      if (compressed == 0) {
        throw new IOException("No compressed data found");
      } else if (compressed == compressedData.length) {
        boolean done = false;
        while (!done) {
          byte[] newCompressedData = new byte[1 + compressedData.length + (compressedData.length >> 1)];
          System.arraycopy(compressedData, 0, newCompressedData, 0, compressedData.length);
          compressedData = newCompressedData;
          compressed += deflater.deflate(compressedData, compressed, compressedData.length - compressed);
          done = compressed < compressedData.length;
        }
      }
    } finally {
      deflater.end();
    }

    buffer.writeInt(compressed);
    buffer.writeBytes(compressedData, 0, compressed);

    return buffer;
  }

  private int readSectionData(byte[] data, int off, byte[][] target, int targetOff, int len) {
    for (byte[] sectionTarget : target) {
      if (sectionTarget != null) {
        for (int i = targetOff; i < targetOff + len && i < sectionTarget.length; ++i) {
          sectionTarget[i] = data[off++];
        }
      }
    }
    return off;
  }

  private int writeSectionData(byte[][] data, int off, byte[] target, int targetOff, int len) {
    for (byte[] sectionData : data) {
      if (sectionData != null) {
        int j = off;
        for (int i = 0; i < len; i++) {
          target[targetOff++] = sectionData[j++];
        }
      }
    }
    return targetOff;
  }
}
TOP

Related Classes of org.spout.vanilla.protocol.codec.world.chunk.ChunkDataCodec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.