mirror of
https://github.com/GeyserMC/MCProtocolLib.git
synced 2024-12-12 16:51:00 -05:00
Update chunk sending protocol.
This commit is contained in:
parent
ea99836709
commit
93978378f6
17 changed files with 549 additions and 579 deletions
|
@ -1,63 +0,0 @@
|
||||||
package org.spacehq.mc.protocol.data.game;
|
|
||||||
|
|
||||||
public class Chunk {
|
|
||||||
|
|
||||||
private ShortArray3d blocks;
|
|
||||||
private NibbleArray3d blocklight;
|
|
||||||
private NibbleArray3d skylight;
|
|
||||||
|
|
||||||
public Chunk(boolean skylight) {
|
|
||||||
this(new ShortArray3d(4096), new NibbleArray3d(4096), skylight ? new NibbleArray3d(4096) : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk(ShortArray3d blocks, NibbleArray3d blocklight, NibbleArray3d skylight) {
|
|
||||||
this.blocks = blocks;
|
|
||||||
this.blocklight = blocklight;
|
|
||||||
this.skylight = skylight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShortArray3d getBlocks() {
|
|
||||||
return this.blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NibbleArray3d getBlockLight() {
|
|
||||||
return this.blocklight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NibbleArray3d getSkyLight() {
|
|
||||||
return this.skylight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
for(short block : this.blocks.getData()) {
|
|
||||||
if(block != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if(this == o) return true;
|
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Chunk chunk = (Chunk) o;
|
|
||||||
|
|
||||||
if(!blocklight.equals(chunk.blocklight)) return false;
|
|
||||||
if(!blocks.equals(chunk.blocks)) return false;
|
|
||||||
if(skylight != null ? !skylight.equals(chunk.skylight) : chunk.skylight != null) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = blocks.hashCode();
|
|
||||||
result = 31 * result + blocklight.hashCode();
|
|
||||||
result = 31 * result + (skylight != null ? skylight.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ package org.spacehq.mc.protocol.data.game;
|
||||||
import org.spacehq.mc.protocol.data.game.values.entity.MetadataType;
|
import org.spacehq.mc.protocol.data.game.values.entity.MetadataType;
|
||||||
|
|
||||||
public class EntityMetadata {
|
public class EntityMetadata {
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
private MetadataType type;
|
private MetadataType type;
|
||||||
private Object value;
|
private Object value;
|
||||||
|
@ -28,24 +27,14 @@ public class EntityMetadata {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if(this == o) return true;
|
return this == o || (o instanceof EntityMetadata && this.id == ((EntityMetadata) o).id && this.type == ((EntityMetadata) o).type && this.value.equals(((EntityMetadata) o).value));
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
EntityMetadata metadata = (EntityMetadata) o;
|
|
||||||
|
|
||||||
if(id != metadata.id) return false;
|
|
||||||
if(type != metadata.type) return false;
|
|
||||||
if(!value.equals(metadata.value)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = id;
|
int result = this.id;
|
||||||
result = 31 * result + type.hashCode();
|
result = 31 * result + this.type.hashCode();
|
||||||
result = 31 * result + value.hashCode();
|
result = 31 * result + this.value.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package org.spacehq.mc.protocol.data.game;
|
||||||
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
||||||
|
|
||||||
public class ItemStack {
|
public class ItemStack {
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
private int amount;
|
private int amount;
|
||||||
private int data;
|
private int data;
|
||||||
|
@ -46,26 +45,15 @@ public class ItemStack {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if(this == o) return true;
|
return this == o || (o instanceof ItemStack && this.id == ((ItemStack) o).id && this.amount == ((ItemStack) o).amount && this.data == ((ItemStack) o).data && ((this.nbt != null && ((ItemStack) o).nbt != null) || (this.nbt != null && this.nbt.equals(((ItemStack) o).nbt))));
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
ItemStack itemStack = (ItemStack) o;
|
|
||||||
|
|
||||||
if(amount != itemStack.amount) return false;
|
|
||||||
if(data != itemStack.data) return false;
|
|
||||||
if(id != itemStack.id) return false;
|
|
||||||
if(nbt != null ? !nbt.equals(itemStack.nbt) : itemStack.nbt != null) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = id;
|
int result = this.id;
|
||||||
result = 31 * result + amount;
|
result = 31 * result + this.amount;
|
||||||
result = 31 * result + data;
|
result = 31 * result + this.data;
|
||||||
result = 31 * result + (nbt != null ? nbt.hashCode() : 0);
|
result = 31 * result + (this.nbt != null ? this.nbt.hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.spacehq.mc.protocol.data.game;
|
package org.spacehq.mc.protocol.data.game;
|
||||||
|
|
||||||
public class Position {
|
public class Position {
|
||||||
|
|
||||||
private int x;
|
private int x;
|
||||||
private int y;
|
private int y;
|
||||||
private int z;
|
private int z;
|
||||||
|
@ -26,24 +25,14 @@ public class Position {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if(this == o) return true;
|
return this == o || (o instanceof Position && this.x == ((Position) o).x && this.y == ((Position) o).y && this.z == ((Position) o).z);
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Position position = (Position) o;
|
|
||||||
|
|
||||||
if(x != position.x) return false;
|
|
||||||
if(y != position.y) return false;
|
|
||||||
if(z != position.z) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = x;
|
int result = this.x;
|
||||||
result = 31 * result + y;
|
result = 31 * result + this.y;
|
||||||
result = 31 * result + z;
|
result = 31 * result + this.z;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,24 +29,14 @@ public class Rotation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if(this == o) return true;
|
return this == o || (o instanceof Rotation && Float.compare(this.pitch, ((Rotation) o).pitch) == 0 && Float.compare(this.yaw, ((Rotation) o).yaw) == 0 && Float.compare(this.roll, ((Rotation) o).roll) == 0);
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Rotation rotation = (Rotation) o;
|
|
||||||
|
|
||||||
if(Float.compare(rotation.pitch, pitch) != 0) return false;
|
|
||||||
if(Float.compare(rotation.roll, roll) != 0) return false;
|
|
||||||
if(Float.compare(rotation.yaw, yaw) != 0) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = (pitch != +0.0f ? Float.floatToIntBits(pitch) : 0);
|
int result = Float.hashCode(this.pitch);
|
||||||
result = 31 * result + (yaw != +0.0f ? Float.floatToIntBits(yaw) : 0);
|
result = 31 * result + Float.hashCode(this.yaw);
|
||||||
result = 31 * result + (roll != +0.0f ? Float.floatToIntBits(roll) : 0);
|
result = 31 * result + Float.hashCode(this.roll);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package org.spacehq.mc.protocol.data.game;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class ShortArray3d {
|
|
||||||
|
|
||||||
private short[] data;
|
|
||||||
|
|
||||||
public ShortArray3d(int size) {
|
|
||||||
this.data = new short[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShortArray3d(short[] array) {
|
|
||||||
this.data = array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short[] getData() {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get(int x, int y, int z) {
|
|
||||||
return this.data[y << 8 | z << 4 | x] & 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(int x, int y, int z, int val) {
|
|
||||||
this.data[y << 8 | z << 4 | x] = (short) val;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBlock(int x, int y, int z) {
|
|
||||||
return this.get(x, y, z) >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlock(int x, int y, int z, int block) {
|
|
||||||
this.set(x, y, z, block << 4 | this.getData(x, y, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getData(int x, int y, int z) {
|
|
||||||
return this.get(x, y, z) & 0xF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(int x, int y, int z, int data) {
|
|
||||||
this.set(x, y, z, this.getBlock(x, y, z) << 4 | data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlockAndData(int x, int y, int z, int block, int data) {
|
|
||||||
this.set(x, y, z, block << 4 | data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(int val) {
|
|
||||||
Arrays.fill(this.data, (short) val);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if(this == o) return true;
|
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
ShortArray3d that = (ShortArray3d) o;
|
|
||||||
|
|
||||||
if(!Arrays.equals(data, that.data)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BlockStateMap {
|
||||||
|
private static final int[] MAX_ID_PER_BYTES = { 15, 255, 4095 };
|
||||||
|
|
||||||
|
private BlockStorage parent;
|
||||||
|
|
||||||
|
private Map<Integer, Integer> idToState = new HashMap<Integer, Integer>();
|
||||||
|
private Map<Integer, Integer> stateToId = new HashMap<Integer, Integer>();
|
||||||
|
private int stateCount = 0;
|
||||||
|
|
||||||
|
private int currentSize = 0;
|
||||||
|
|
||||||
|
public BlockStateMap(BlockStorage parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
this.idToState.put(0, 0);
|
||||||
|
this.stateToId.put(0, 0);
|
||||||
|
this.stateCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStateMap(BlockStorage parent, NetInput in) throws IOException {
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
this.stateCount = in.readVarInt();
|
||||||
|
for(int i = 0; i < this.stateCount; i++) {
|
||||||
|
int state = in.readVarInt();
|
||||||
|
int id = in.readVarInt();
|
||||||
|
|
||||||
|
this.idToState.put(id, state);
|
||||||
|
this.stateToId.put(state, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(NetOutput out) throws IOException {
|
||||||
|
out.writeVarInt(this.stateCount);
|
||||||
|
for(Map.Entry<Integer, Integer> entry : this.stateToId.entrySet()) {
|
||||||
|
out.writeVarInt(entry.getKey());
|
||||||
|
out.writeVarInt(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Integer> getIdsToStates() {
|
||||||
|
return new HashMap<Integer, Integer>(this.idToState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Integer> getStatesToIds() {
|
||||||
|
return new HashMap<Integer, Integer>(this.stateToId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getState(int id) {
|
||||||
|
return this.idToState.containsKey(id) ? this.idToState.get(id) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId(int state) {
|
||||||
|
if(!this.stateToId.containsKey(state)) {
|
||||||
|
int id = this.stateCount;
|
||||||
|
this.idToState.put(id, state);
|
||||||
|
this.stateToId.put(state, id);
|
||||||
|
|
||||||
|
if(this.currentSize < MAX_ID_PER_BYTES.length - 1 && this.stateCount > MAX_ID_PER_BYTES[this.currentSize]) {
|
||||||
|
this.parent.resize(MAX_ID_PER_BYTES[++this.currentSize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.stateToId.get(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o || (o instanceof BlockStateMap && this.idToState.equals(((BlockStateMap) o).idToState) && this.stateToId.equals(((BlockStateMap) o).stateToId) && this.stateCount == ((BlockStateMap) o).stateCount && this.currentSize == ((BlockStateMap) o).currentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = this.idToState.hashCode();
|
||||||
|
result = 31 * result + this.stateToId.hashCode();
|
||||||
|
result = 31 * result + this.stateCount;
|
||||||
|
result = 31 * result + this.currentSize;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
import org.spacehq.mc.protocol.util.NetUtil;
|
||||||
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class BlockStorage {
|
||||||
|
private BlockStateMap stateMap;
|
||||||
|
private FlexibleStorage storage;
|
||||||
|
|
||||||
|
public BlockStorage() {
|
||||||
|
this.stateMap = new BlockStateMap(this);
|
||||||
|
this.storage = new FlexibleStorage(4, 4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStorage(NetInput in) throws IOException {
|
||||||
|
this.stateMap = new BlockStateMap(this, in);
|
||||||
|
this.storage = new FlexibleStorage(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(NetOutput out) throws IOException {
|
||||||
|
this.stateMap.write(out);
|
||||||
|
this.storage.write(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStateMap getStateMap() {
|
||||||
|
return this.stateMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlexibleStorage getStorage() {
|
||||||
|
return this.storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int x, int y, int z) {
|
||||||
|
return this.stateMap.getState(this.storage.get(index(x, y, z)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int x, int y, int z, int state) {
|
||||||
|
int id = this.stateMap.getId(state);
|
||||||
|
this.storage.set(index(x, y, z), id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for(int index = 0; index < this.storage.getSize(); index++) {
|
||||||
|
if(this.storage.get(index) != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resize(int size) {
|
||||||
|
FlexibleStorage oldStorage = this.storage;
|
||||||
|
this.storage = new FlexibleStorage(NetUtil.nextExponentOfTwo(size), this.storage.getSize());
|
||||||
|
for(int index = 0; index < this.storage.getSize(); index++) {
|
||||||
|
this.storage.set(index, oldStorage.get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int index(int x, int y, int z) {
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o || (o instanceof BlockStorage && this.stateMap.equals(((BlockStorage) o).stateMap) && this.storage.equals(((BlockStorage) o).storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = this.stateMap.hashCode();
|
||||||
|
result = 31 * result + this.storage.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
public class Chunk {
|
||||||
|
private BlockStorage blocks;
|
||||||
|
private NibbleArray3d blocklight;
|
||||||
|
private NibbleArray3d skylight;
|
||||||
|
|
||||||
|
public Chunk(boolean skylight) {
|
||||||
|
this(new BlockStorage(), new NibbleArray3d(4096), skylight ? new NibbleArray3d(4096) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk(BlockStorage blocks, NibbleArray3d blocklight, NibbleArray3d skylight) {
|
||||||
|
this.blocks = blocks;
|
||||||
|
this.blocklight = blocklight;
|
||||||
|
this.skylight = skylight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStorage getBlocks() {
|
||||||
|
return this.blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray3d getBlockLight() {
|
||||||
|
return this.blocklight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray3d getSkyLight() {
|
||||||
|
return this.skylight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.blocks.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o || (o instanceof Chunk && this.blocks.equals(((Chunk) o).blocks) && this.blocklight.equals(((Chunk) o).blocklight) && ((this.skylight == null && (((Chunk) o).skylight == null)) || (this.skylight != null && this.skylight.equals(((Chunk) o).skylight))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = this.blocks.hashCode();
|
||||||
|
result = 31 * result + this.blocklight.hashCode();
|
||||||
|
result = 31 * result + (this.skylight != null ? this.skylight.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
public class Column {
|
||||||
|
private int x;
|
||||||
|
private int z;
|
||||||
|
private Chunk chunks[];
|
||||||
|
private byte biomeData[];
|
||||||
|
|
||||||
|
private boolean skylight;
|
||||||
|
|
||||||
|
public Column(int x, int z, Chunk chunks[]) {
|
||||||
|
this(x, z, chunks, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Column(int x, int z, Chunk chunks[], byte biomeData[]) {
|
||||||
|
if(chunks.length != 16) {
|
||||||
|
throw new IllegalArgumentException("Chunk array length must be 16.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(biomeData != null && biomeData.length != 256) {
|
||||||
|
throw new IllegalArgumentException("Biome data array length must be 256.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.skylight = false;
|
||||||
|
boolean noSkylight = false;
|
||||||
|
for(int index = 0; index < chunks.length; index++) {
|
||||||
|
if(chunks[index] != null) {
|
||||||
|
if(chunks[index].getSkyLight() == null) {
|
||||||
|
noSkylight = true;
|
||||||
|
} else {
|
||||||
|
this.skylight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(noSkylight && this.skylight) {
|
||||||
|
throw new IllegalArgumentException("Either all chunks must have skylight values or none must have them.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
this.chunks = chunks;
|
||||||
|
this.biomeData = biomeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZ() {
|
||||||
|
return this.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk[] getChunks() {
|
||||||
|
return this.chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBiomeData() {
|
||||||
|
return this.biomeData != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBiomeData() {
|
||||||
|
return this.biomeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSkylight() {
|
||||||
|
return this.skylight;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class FlexibleStorage {
|
||||||
|
private final long data[];
|
||||||
|
private final int bitsPerEntry;
|
||||||
|
private final int size;
|
||||||
|
private final long maxEntryValue;
|
||||||
|
|
||||||
|
public FlexibleStorage(int bitsPerEntry, int size) {
|
||||||
|
if(bitsPerEntry < 1 || bitsPerEntry > 32) {
|
||||||
|
throw new IllegalArgumentException("BitsPerEntry cannot be outside of accepted range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bitsPerEntry = bitsPerEntry;
|
||||||
|
this.size = size;
|
||||||
|
|
||||||
|
this.maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||||
|
this.data = new long[roundToNearest(size * bitsPerEntry, 64) / 64];
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlexibleStorage(NetInput in) throws IOException {
|
||||||
|
this.bitsPerEntry = in.readVarInt();
|
||||||
|
if(this.bitsPerEntry < 1 || this.bitsPerEntry > 32) {
|
||||||
|
throw new IllegalArgumentException("BitsPerEntry cannot be outside of accepted range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = in.readLongs(in.readVarInt());
|
||||||
|
this.size = this.data.length * 64 / this.bitsPerEntry;
|
||||||
|
this.maxEntryValue = (1L << this.bitsPerEntry) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(NetOutput out) throws IOException {
|
||||||
|
out.writeVarInt(this.bitsPerEntry);
|
||||||
|
out.writeVarInt(this.data.length);
|
||||||
|
out.writeLongs(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitsPerEntry() {
|
||||||
|
return this.bitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int index) {
|
||||||
|
if(index < 0 || index > this.size - 1) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitIndex = index * this.bitsPerEntry;
|
||||||
|
int startIndex = bitIndex / 64;
|
||||||
|
int endIndex = ((index + 1) * this.bitsPerEntry - 1) / 64;
|
||||||
|
int startBitSubIndex = bitIndex % 64;
|
||||||
|
if(startIndex == endIndex) {
|
||||||
|
return (int) (this.data[startIndex] >>> startBitSubIndex & this.maxEntryValue);
|
||||||
|
} else {
|
||||||
|
int endBitSubIndex = 64 - startBitSubIndex;
|
||||||
|
return (int) ((this.data[startIndex] >>> startBitSubIndex | this.data[endIndex] << endBitSubIndex) & this.maxEntryValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, int value) {
|
||||||
|
if(index < 0 || index > this.size - 1) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value < 0 || value > this.maxEntryValue) {
|
||||||
|
throw new IllegalArgumentException("Value cannot be outside of accepted range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitIndex = index * this.bitsPerEntry;
|
||||||
|
int startIndex = bitIndex / 64;
|
||||||
|
int endIndex = ((index + 1) * this.bitsPerEntry - 1) / 64;
|
||||||
|
int startBitSubIndex = bitIndex % 64;
|
||||||
|
this.data[startIndex] = this.data[startIndex] & ~(this.maxEntryValue << startBitSubIndex) | ((long) value & this.maxEntryValue) << startBitSubIndex;
|
||||||
|
if(startIndex != endIndex) {
|
||||||
|
int endBitSubIndex = 64 - startBitSubIndex;
|
||||||
|
this.data[endIndex] = this.data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & this.maxEntryValue) >> endBitSubIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int roundToNearest(int value, int roundTo) {
|
||||||
|
if(roundTo == 0) {
|
||||||
|
return 0;
|
||||||
|
} else if(value == 0) {
|
||||||
|
return roundTo;
|
||||||
|
} else {
|
||||||
|
if(value < 0) {
|
||||||
|
roundTo *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = value % roundTo;
|
||||||
|
return remainder != 0 ? value + roundTo - remainder : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o || (o instanceof FlexibleStorage && Arrays.equals(this.data, ((FlexibleStorage) o).data) && this.bitsPerEntry == ((FlexibleStorage) o).bitsPerEntry && this.size == ((FlexibleStorage) o).size && this.maxEntryValue == ((FlexibleStorage) o).maxEntryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = Arrays.hashCode(this.data);
|
||||||
|
result = 31 * result + this.bitsPerEntry;
|
||||||
|
result = 31 * result + this.size;
|
||||||
|
result = 31 * result + (int) this.maxEntryValue;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package org.spacehq.mc.protocol.data.game;
|
package org.spacehq.mc.protocol.data.game.chunk;
|
||||||
|
|
||||||
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class NibbleArray3d {
|
public class NibbleArray3d {
|
||||||
|
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
|
|
||||||
public NibbleArray3d(int size) {
|
public NibbleArray3d(int size) {
|
||||||
|
@ -14,6 +17,14 @@ public class NibbleArray3d {
|
||||||
this.data = array;
|
this.data = array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NibbleArray3d(NetInput in, int size) throws IOException {
|
||||||
|
this.data = in.readBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(NetOutput out) throws IOException {
|
||||||
|
out.writeBytes(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getData() {
|
public byte[] getData() {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
@ -50,19 +61,11 @@ public class NibbleArray3d {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if(this == o) return true;
|
return this == o || (o instanceof NibbleArray3d && Arrays.equals(this.data, ((NibbleArray3d) o).data));
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
NibbleArray3d that = (NibbleArray3d) o;
|
|
||||||
|
|
||||||
if(!Arrays.equals(data, that.data)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Arrays.hashCode(data);
|
return Arrays.hashCode(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,21 +1,19 @@
|
||||||
package org.spacehq.mc.protocol.packet.ingame.server.world;
|
package org.spacehq.mc.protocol.packet.ingame.server.world;
|
||||||
|
|
||||||
import org.spacehq.mc.protocol.data.game.Chunk;
|
import org.spacehq.mc.protocol.data.game.chunk.Chunk;
|
||||||
|
import org.spacehq.mc.protocol.data.game.chunk.Column;
|
||||||
import org.spacehq.mc.protocol.util.NetUtil;
|
import org.spacehq.mc.protocol.util.NetUtil;
|
||||||
import org.spacehq.mc.protocol.util.NetworkChunkData;
|
|
||||||
import org.spacehq.mc.protocol.util.ParsedChunkData;
|
|
||||||
import org.spacehq.packetlib.io.NetInput;
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
import org.spacehq.packetlib.io.NetOutput;
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
import org.spacehq.packetlib.io.buffer.ByteBufferNetOutput;
|
||||||
import org.spacehq.packetlib.packet.Packet;
|
import org.spacehq.packetlib.packet.Packet;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class ServerChunkDataPacket implements Packet {
|
public class ServerChunkDataPacket implements Packet {
|
||||||
|
|
||||||
private int x;
|
private Column column;
|
||||||
private int z;
|
|
||||||
private Chunk chunks[];
|
|
||||||
private byte biomeData[];
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private ServerChunkDataPacket() {
|
private ServerChunkDataPacket() {
|
||||||
|
@ -28,103 +26,48 @@ public class ServerChunkDataPacket implements Packet {
|
||||||
* @param z Z of the chunk column.
|
* @param z Z of the chunk column.
|
||||||
*/
|
*/
|
||||||
public ServerChunkDataPacket(int x, int z) {
|
public ServerChunkDataPacket(int x, int z) {
|
||||||
this(x, z, new Chunk[16], new byte[256]);
|
this(new Column(x, z, new Chunk[16], new byte[256]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a ServerChunkDataPacket for updating chunks.
|
* Constructs a ServerChunkDataPacket for updating chunks.
|
||||||
*
|
*
|
||||||
* @param x X of the chunk column.
|
* @param column Column to send.
|
||||||
* @param z Z of the chunk column.
|
|
||||||
* @param chunks Array of chunks in the column. Length must be 16 but can contain null values.
|
|
||||||
* @throws IllegalArgumentException If the chunk array length is not 16 or skylight arrays exist in some but not all chunks.
|
|
||||||
*/
|
*/
|
||||||
public ServerChunkDataPacket(int x, int z, Chunk chunks[]) {
|
public ServerChunkDataPacket(Column column) {
|
||||||
this(x, z, chunks, null);
|
this.column = column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Column getColumn() {
|
||||||
* Constructs a ServerChunkDataPacket for updating a full column of chunks.
|
return this.column;
|
||||||
*
|
|
||||||
* @param x X of the chunk column.
|
|
||||||
* @param z Z of the chunk column.
|
|
||||||
* @param chunks Array of chunks in the column. Length must be 16 but can contain null values.
|
|
||||||
* @param biomeData Array of biome data for the column.
|
|
||||||
* @throws IllegalArgumentException If the chunk array length is not 16 or skylight arrays exist in some but not all chunks.
|
|
||||||
*/
|
|
||||||
public ServerChunkDataPacket(int x, int z, Chunk chunks[], byte biomeData[]) {
|
|
||||||
if(chunks.length != 16) {
|
|
||||||
throw new IllegalArgumentException("Chunks length must be 16.");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean noSkylight = false;
|
|
||||||
boolean skylight = false;
|
|
||||||
for(int index = 0; index < chunks.length; index++) {
|
|
||||||
if(chunks[index] != null) {
|
|
||||||
if(chunks[index].getSkyLight() == null) {
|
|
||||||
noSkylight = true;
|
|
||||||
} else {
|
|
||||||
skylight = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(noSkylight && skylight) {
|
|
||||||
throw new IllegalArgumentException("Either all chunks must have skylight values or none must have them.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.x = x;
|
|
||||||
this.z = z;
|
|
||||||
this.chunks = chunks;
|
|
||||||
this.biomeData = biomeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getX() {
|
|
||||||
return this.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getZ() {
|
|
||||||
return this.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk[] getChunks() {
|
|
||||||
return this.chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBiomeData() {
|
|
||||||
return this.biomeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFullChunk() {
|
|
||||||
return this.biomeData != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(NetInput in) throws IOException {
|
public void read(NetInput in) throws IOException {
|
||||||
this.x = in.readInt();
|
int x = in.readInt();
|
||||||
this.z = in.readInt();
|
int z = in.readInt();
|
||||||
boolean fullChunk = in.readBoolean();
|
boolean fullChunk = in.readBoolean();
|
||||||
int chunkMask = in.readUnsignedShort();
|
int chunkMask = in.readInt();
|
||||||
byte data[] = in.readBytes(in.readVarInt());
|
byte data[] = in.readBytes(in.readVarInt());
|
||||||
ParsedChunkData chunkData = NetUtil.dataToChunks(new NetworkChunkData(chunkMask, fullChunk, false, data), true);
|
|
||||||
this.chunks = chunkData.getChunks();
|
this.column = NetUtil.readColumn(data, x, z, fullChunk, false, chunkMask);
|
||||||
this.biomeData = chunkData.getBiomes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(NetOutput out) throws IOException {
|
public void write(NetOutput out) throws IOException {
|
||||||
NetworkChunkData data = NetUtil.chunksToData(new ParsedChunkData(this.chunks, this.biomeData));
|
ByteBufferNetOutput byteOut = new ByteBufferNetOutput(ByteBuffer.allocate(557312));
|
||||||
out.writeInt(this.x);
|
int mask = NetUtil.writeColumn(byteOut, this.column, this.column.hasBiomeData(), this.column.hasSkylight());
|
||||||
out.writeInt(this.z);
|
|
||||||
out.writeBoolean(data.isFullChunk());
|
out.writeInt(this.column.getX());
|
||||||
out.writeShort(data.getMask());
|
out.writeInt(this.column.getZ());
|
||||||
out.writeVarInt(data.getData().length);
|
out.writeBoolean(this.column.hasBiomeData());
|
||||||
out.writeBytes(data.getData(), data.getData().length);
|
out.writeShort(mask);
|
||||||
|
out.writeVarInt(byteOut.getByteBuffer().arrayOffset());
|
||||||
|
out.writeBytes(byteOut.getByteBuffer().array(), byteOut.getByteBuffer().arrayOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPriority() {
|
public boolean isPriority() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,133 +1,92 @@
|
||||||
package org.spacehq.mc.protocol.packet.ingame.server.world;
|
package org.spacehq.mc.protocol.packet.ingame.server.world;
|
||||||
|
|
||||||
import org.spacehq.mc.protocol.data.game.Chunk;
|
import org.spacehq.mc.protocol.data.game.chunk.Column;
|
||||||
import org.spacehq.mc.protocol.util.NetUtil;
|
import org.spacehq.mc.protocol.util.NetUtil;
|
||||||
import org.spacehq.mc.protocol.util.NetworkChunkData;
|
|
||||||
import org.spacehq.mc.protocol.util.ParsedChunkData;
|
|
||||||
import org.spacehq.packetlib.io.NetInput;
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
import org.spacehq.packetlib.io.NetOutput;
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
import org.spacehq.packetlib.io.buffer.ByteBufferNetOutput;
|
||||||
import org.spacehq.packetlib.packet.Packet;
|
import org.spacehq.packetlib.packet.Packet;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class ServerMultiChunkDataPacket implements Packet {
|
public class ServerMultiChunkDataPacket implements Packet {
|
||||||
|
|
||||||
private int x[];
|
private Column columns[];
|
||||||
private int z[];
|
|
||||||
private Chunk chunks[][];
|
|
||||||
private byte biomeData[][];
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private ServerMultiChunkDataPacket() {
|
private ServerMultiChunkDataPacket() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerMultiChunkDataPacket(int x[], int z[], Chunk chunks[][], byte biomeData[][]) {
|
public ServerMultiChunkDataPacket(Column columns[]) {
|
||||||
if(biomeData == null) {
|
|
||||||
throw new IllegalArgumentException("BiomeData cannot be null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(x.length != chunks.length || z.length != chunks.length) {
|
|
||||||
throw new IllegalArgumentException("X, Z, and Chunk arrays must be equal in length.");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean noSkylight = false;
|
boolean noSkylight = false;
|
||||||
boolean skylight = false;
|
boolean skylight = false;
|
||||||
for(int index = 0; index < chunks.length; index++) {
|
for(Column column : columns) {
|
||||||
Chunk column[] = chunks[index];
|
if(!column.hasSkylight()) {
|
||||||
if(column.length != 16) {
|
|
||||||
throw new IllegalArgumentException("Chunk columns must contain 16 chunks each.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int y = 0; y < column.length; y++) {
|
|
||||||
if(column[y] != null) {
|
|
||||||
if(column[y].getSkyLight() == null) {
|
|
||||||
noSkylight = true;
|
noSkylight = true;
|
||||||
} else {
|
} else {
|
||||||
skylight = true;
|
skylight = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(noSkylight && skylight) {
|
if(noSkylight && skylight) {
|
||||||
throw new IllegalArgumentException("Either all chunks must have skylight values or none must have them.");
|
throw new IllegalArgumentException("Either all columns must have skylight values or none must have them.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.x = x;
|
this.columns = columns;
|
||||||
this.z = z;
|
|
||||||
this.chunks = chunks;
|
|
||||||
this.biomeData = biomeData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColumns() {
|
public Column[] getColumns() {
|
||||||
return this.chunks.length;
|
return this.columns;
|
||||||
}
|
|
||||||
|
|
||||||
public int getX(int column) {
|
|
||||||
return this.x[column];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getZ(int column) {
|
|
||||||
return this.z[column];
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk[] getChunks(int column) {
|
|
||||||
return this.chunks[column];
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBiomeData(int column) {
|
|
||||||
return this.biomeData[column];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(NetInput in) throws IOException {
|
public void read(NetInput in) throws IOException {
|
||||||
boolean skylight = in.readBoolean();
|
boolean skylight = in.readBoolean();
|
||||||
int columns = in.readVarInt();
|
int x[] = in.readInts(in.readVarInt());
|
||||||
this.x = new int[columns];
|
int z[] = in.readInts(in.readVarInt());
|
||||||
this.z = new int[columns];
|
int masks[] = in.readInts(in.readVarInt());
|
||||||
this.chunks = new Chunk[columns][];
|
byte data[] = in.readBytes(in.readVarInt());
|
||||||
this.biomeData = new byte[columns][];
|
|
||||||
NetworkChunkData[] data = new NetworkChunkData[columns];
|
|
||||||
for(int column = 0; column < columns; column++) {
|
|
||||||
this.x[column] = in.readInt();
|
|
||||||
this.z[column] = in.readInt();
|
|
||||||
int mask = in.readUnsignedShort();
|
|
||||||
int chunks = Integer.bitCount(mask);
|
|
||||||
int length = (chunks * ((4096 * 2) + 2048)) + (skylight ? chunks * 2048 : 0) + 256;
|
|
||||||
byte dat[] = new byte[length];
|
|
||||||
data[column] = new NetworkChunkData(mask, true, skylight, dat);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int column = 0; column < columns; column++) {
|
Column columns[] = new Column[x.length];
|
||||||
in.readBytes(data[column].getData());
|
for(int i = 0; i < columns.length; i++) {
|
||||||
ParsedChunkData chunkData = NetUtil.dataToChunks(data[column], false);
|
columns[i] = NetUtil.readColumn(data, x[i], z[i], true, skylight, masks[i]);
|
||||||
this.chunks[column] = chunkData.getChunks();
|
|
||||||
this.biomeData[column] = chunkData.getBiomes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(NetOutput out) throws IOException {
|
public void write(NetOutput out) throws IOException {
|
||||||
|
Column columns[] = new Column[1];
|
||||||
|
|
||||||
boolean skylight = false;
|
boolean skylight = false;
|
||||||
NetworkChunkData data[] = new NetworkChunkData[this.chunks.length];
|
for(Column column : columns) {
|
||||||
for(int column = 0; column < this.chunks.length; column++) {
|
if(column.hasSkylight()) {
|
||||||
data[column] = NetUtil.chunksToData(new ParsedChunkData(this.chunks[column], this.biomeData[column]));
|
|
||||||
if(data[column].hasSkyLight()) {
|
|
||||||
skylight = true;
|
skylight = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ByteBufferNetOutput byteOut = new ByteBufferNetOutput(ByteBuffer.allocate(columns.length * 557312));
|
||||||
|
|
||||||
out.writeBoolean(skylight);
|
out.writeBoolean(skylight);
|
||||||
out.writeVarInt(this.chunks.length);
|
|
||||||
for(int column = 0; column < this.x.length; column++) {
|
out.writeVarInt(columns.length);
|
||||||
out.writeInt(this.x[column]);
|
for(Column column : columns) {
|
||||||
out.writeInt(this.z[column]);
|
out.writeInt(column.getX());
|
||||||
out.writeShort(data[column].getMask());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int column = 0; column < this.x.length; column++) {
|
out.writeVarInt(columns.length);
|
||||||
out.writeBytes(data[column].getData());
|
for(Column column : columns) {
|
||||||
|
out.writeInt(column.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.writeVarInt(columns.length);
|
||||||
|
for(Column column : columns) {
|
||||||
|
out.writeInt(NetUtil.writeColumn(byteOut, column, true, skylight));
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeVarInt(byteOut.getByteBuffer().arrayOffset());
|
||||||
|
out.writeBytes(byteOut.getByteBuffer().array(), byteOut.getByteBuffer().arrayOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package org.spacehq.mc.protocol.util;
|
package org.spacehq.mc.protocol.util;
|
||||||
|
|
||||||
import org.spacehq.mc.protocol.data.game.Chunk;
|
import org.spacehq.mc.protocol.data.game.chunk.BlockStorage;
|
||||||
|
import org.spacehq.mc.protocol.data.game.chunk.Chunk;
|
||||||
|
import org.spacehq.mc.protocol.data.game.chunk.Column;
|
||||||
import org.spacehq.mc.protocol.data.game.EntityMetadata;
|
import org.spacehq.mc.protocol.data.game.EntityMetadata;
|
||||||
import org.spacehq.mc.protocol.data.game.ItemStack;
|
import org.spacehq.mc.protocol.data.game.ItemStack;
|
||||||
import org.spacehq.mc.protocol.data.game.NibbleArray3d;
|
import org.spacehq.mc.protocol.data.game.chunk.NibbleArray3d;
|
||||||
import org.spacehq.mc.protocol.data.game.Position;
|
import org.spacehq.mc.protocol.data.game.Position;
|
||||||
import org.spacehq.mc.protocol.data.game.Rotation;
|
import org.spacehq.mc.protocol.data.game.Rotation;
|
||||||
import org.spacehq.mc.protocol.data.game.ShortArray3d;
|
|
||||||
import org.spacehq.mc.protocol.data.game.values.MagicValues;
|
import org.spacehq.mc.protocol.data.game.values.MagicValues;
|
||||||
import org.spacehq.mc.protocol.data.game.values.entity.MetadataType;
|
import org.spacehq.mc.protocol.data.game.values.entity.MetadataType;
|
||||||
import org.spacehq.mc.protocol.data.game.values.world.block.BlockFace;
|
import org.spacehq.mc.protocol.data.game.values.world.block.BlockFace;
|
||||||
|
@ -15,6 +16,7 @@ import org.spacehq.opennbt.NBTIO;
|
||||||
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
||||||
import org.spacehq.packetlib.io.NetInput;
|
import org.spacehq.packetlib.io.NetInput;
|
||||||
import org.spacehq.packetlib.io.NetOutput;
|
import org.spacehq.packetlib.io.NetOutput;
|
||||||
|
import org.spacehq.packetlib.io.buffer.ByteBufferNetInput;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
|
@ -22,8 +24,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -55,7 +55,7 @@ public class NetUtil {
|
||||||
return i != 0 && (i & i - 1) == 0;
|
return i != 0 && (i & i - 1) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int nextExponentOfTwo(int i) {
|
public static int nextExponentOfTwo(int i) {
|
||||||
int power = isPowerOfTwo(i) ? i : nextPowerOfTwo(i);
|
int power = isPowerOfTwo(i) ? i : nextPowerOfTwo(i);
|
||||||
return EXPONENTS_OF_TWO[(int) (power * 125613361L >> 27) & 31];
|
return EXPONENTS_OF_TWO[(int) (power * 125613361L >> 27) & 31];
|
||||||
}
|
}
|
||||||
|
@ -235,124 +235,61 @@ public class NetUtil {
|
||||||
out.writeByte(255);
|
out.writeByte(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParsedChunkData dataToChunks(NetworkChunkData data, boolean checkForSky) {
|
public static Column readColumn(byte data[], int x, int z, boolean fullChunk, boolean hasSkylight, int mask) throws IOException {
|
||||||
Chunk chunks[] = new Chunk[16];
|
NetInput in = new ByteBufferNetInput(ByteBuffer.wrap(data));
|
||||||
int pos = 0;
|
Exception ex = null;
|
||||||
int expected = 0;
|
Column column = null;
|
||||||
boolean sky = false;
|
try {
|
||||||
ShortBuffer buf = ByteBuffer.wrap(data.getData()).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
Chunk[] chunks = new Chunk[16];
|
||||||
// 0 = Calculate expected length and determine if the packet has skylight.
|
for(int index = 0; index < chunks.length; index++) {
|
||||||
// 1 = Create chunks from mask and get blocks.
|
if((mask & (1 << index)) != 0) {
|
||||||
// 2 = Get block light.
|
BlockStorage blocks = new BlockStorage(in);
|
||||||
// 3 = Get sky light.
|
NibbleArray3d blocklight = new NibbleArray3d(in, 2048);
|
||||||
for(int pass = 0; pass < 4; pass++) {
|
NibbleArray3d skylight = hasSkylight ? new NibbleArray3d(in, 2048) : null;
|
||||||
for(int ind = 0; ind < 16; ind++) {
|
chunks[index] = new Chunk(blocks, blocklight, skylight);
|
||||||
if((data.getMask() & 1 << ind) != 0) {
|
|
||||||
if(pass == 0) {
|
|
||||||
// Block length + Blocklight length
|
|
||||||
expected += (4096 * 2) + 2048;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 1) {
|
|
||||||
chunks[ind] = new Chunk(sky || data.hasSkyLight());
|
|
||||||
ShortArray3d blocks = chunks[ind].getBlocks();
|
|
||||||
buf.position(pos / 2);
|
|
||||||
buf.get(blocks.getData(), 0, blocks.getData().length);
|
|
||||||
pos += blocks.getData().length * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 2) {
|
|
||||||
NibbleArray3d blocklight = chunks[ind].getBlockLight();
|
|
||||||
System.arraycopy(data.getData(), pos, blocklight.getData(), 0, blocklight.getData().length);
|
|
||||||
pos += blocklight.getData().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 3 && (sky || data.hasSkyLight())) {
|
|
||||||
NibbleArray3d skylight = chunks[ind].getSkyLight();
|
|
||||||
System.arraycopy(data.getData(), pos, skylight.getData(), 0, skylight.getData().length);
|
|
||||||
pos += skylight.getData().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 0) {
|
|
||||||
// If we have more data than blocks and blocklight combined, there must be skylight data as well.
|
|
||||||
if(data.getData().length >= expected) {
|
|
||||||
sky = checkForSky;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte biomeData[] = null;
|
byte biomeData[] = null;
|
||||||
if(data.isFullChunk()) {
|
|
||||||
biomeData = new byte[256];
|
|
||||||
System.arraycopy(data.getData(), pos, biomeData, 0, biomeData.length);
|
|
||||||
pos += biomeData.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParsedChunkData(chunks, biomeData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkChunkData chunksToData(ParsedChunkData chunks) {
|
|
||||||
int chunkMask = 0;
|
|
||||||
boolean fullChunk = chunks.getBiomes() != null;
|
|
||||||
boolean sky = false;
|
|
||||||
int length = fullChunk ? chunks.getBiomes().length : 0;
|
|
||||||
byte[] data = null;
|
|
||||||
int pos = 0;
|
|
||||||
ShortBuffer buf = null;
|
|
||||||
// 0 = Determine length and masks.
|
|
||||||
// 1 = Add blocks.
|
|
||||||
// 2 = Add block light.
|
|
||||||
// 3 = Add sky light.
|
|
||||||
for(int pass = 0; pass < 4; pass++) {
|
|
||||||
for(int ind = 0; ind < chunks.getChunks().length; ++ind) {
|
|
||||||
Chunk chunk = chunks.getChunks()[ind];
|
|
||||||
if(chunk != null && (!fullChunk || !chunk.isEmpty())) {
|
|
||||||
if(pass == 0) {
|
|
||||||
chunkMask |= 1 << ind;
|
|
||||||
length += chunk.getBlocks().getData().length * 2;
|
|
||||||
length += chunk.getBlockLight().getData().length;
|
|
||||||
if(chunk.getSkyLight() != null) {
|
|
||||||
length += chunk.getSkyLight().getData().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 1) {
|
|
||||||
short blocks[] = chunk.getBlocks().getData();
|
|
||||||
buf.position(pos / 2);
|
|
||||||
buf.put(blocks, 0, blocks.length);
|
|
||||||
pos += blocks.length * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 2) {
|
|
||||||
byte blocklight[] = chunk.getBlockLight().getData();
|
|
||||||
System.arraycopy(blocklight, 0, data, pos, blocklight.length);
|
|
||||||
pos += blocklight.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 3 && chunk.getSkyLight() != null) {
|
|
||||||
byte skylight[] = chunk.getSkyLight().getData();
|
|
||||||
System.arraycopy(skylight, 0, data, pos, skylight.length);
|
|
||||||
pos += skylight.length;
|
|
||||||
sky = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pass == 0) {
|
|
||||||
data = new byte[length];
|
|
||||||
buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add biomes.
|
|
||||||
if(fullChunk) {
|
if(fullChunk) {
|
||||||
System.arraycopy(chunks.getBiomes(), 0, data, pos, chunks.getBiomes().length);
|
biomeData = in.readBytes(256);
|
||||||
pos += chunks.getBiomes().length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NetworkChunkData(chunkMask, fullChunk, sky, data);
|
column = new Column(x, z, chunks, biomeData);
|
||||||
|
} catch(Exception e) {
|
||||||
|
ex = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfortunately, this is needed to detect whether the chunks contain skylight or not.
|
||||||
|
if((in.available() > 0 || ex != null) && !hasSkylight) {
|
||||||
|
return readColumn(data, x, z, fullChunk, true, mask);
|
||||||
|
} else if(ex != null) {
|
||||||
|
throw new IOException("Failed to read chunk data.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int writeColumn(NetOutput out, Column column, boolean fullChunk, boolean hasSkylight) throws IOException {
|
||||||
|
int mask = 0;
|
||||||
|
Chunk chunks[] = column.getChunks();
|
||||||
|
for(int index = 0; index < chunks.length; index++) {
|
||||||
|
Chunk chunk = chunks[index];
|
||||||
|
if(chunk != null && (!fullChunk || !chunk.isEmpty())) {
|
||||||
|
mask |= 1 << index;
|
||||||
|
chunk.getBlocks().write(out);
|
||||||
|
chunk.getBlockLight().write(out);
|
||||||
|
if(hasSkylight) {
|
||||||
|
chunk.getBlockLight().write(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fullChunk) {
|
||||||
|
out.writeBytes(column.getBiomeData());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NetInputStream extends InputStream {
|
private static class NetInputStream extends InputStream {
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
package org.spacehq.mc.protocol.util;
|
|
||||||
|
|
||||||
public class NetworkChunkData {
|
|
||||||
|
|
||||||
private int mask;
|
|
||||||
private boolean fullChunk;
|
|
||||||
private boolean sky;
|
|
||||||
private byte data[];
|
|
||||||
|
|
||||||
public NetworkChunkData(int mask, boolean fullChunk, boolean sky, byte data[]) {
|
|
||||||
this.mask = mask;
|
|
||||||
this.fullChunk = fullChunk;
|
|
||||||
this.sky = sky;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMask() {
|
|
||||||
return this.mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFullChunk() {
|
|
||||||
return this.fullChunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSkyLight() {
|
|
||||||
return this.sky;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if(this == o) return true;
|
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
NetworkChunkData that = (NetworkChunkData) o;
|
|
||||||
|
|
||||||
if(fullChunk != that.fullChunk) return false;
|
|
||||||
if(mask != that.mask) return false;
|
|
||||||
if(sky != that.sky) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = mask;
|
|
||||||
result = 31 * result + (fullChunk ? 1 : 0);
|
|
||||||
result = 31 * result + (sky ? 1 : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package org.spacehq.mc.protocol.util;
|
|
||||||
|
|
||||||
import org.spacehq.mc.protocol.data.game.Chunk;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class ParsedChunkData {
|
|
||||||
|
|
||||||
private Chunk chunks[];
|
|
||||||
private byte biomes[];
|
|
||||||
|
|
||||||
public ParsedChunkData(Chunk chunks[], byte biomes[]) {
|
|
||||||
this.chunks = chunks;
|
|
||||||
this.biomes = biomes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk[] getChunks() {
|
|
||||||
return this.chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBiomes() {
|
|
||||||
return this.biomes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if(this == o) return true;
|
|
||||||
if(o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
ParsedChunkData that = (ParsedChunkData) o;
|
|
||||||
|
|
||||||
if(!Arrays.equals(biomes, that.biomes)) return false;
|
|
||||||
if(!Arrays.equals(chunks, that.chunks)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = Arrays.hashCode(chunks);
|
|
||||||
result = 31 * result + (biomes != null ? Arrays.hashCode(biomes) : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue