package com.xcompwiz.lookingglass.proxyworld;
import java.util.LinkedList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChunkCoordinates;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
import com.xcompwiz.lookingglass.log.LoggerUtils;
import com.xcompwiz.lookingglass.network.ServerPacketDispatcher;
import com.xcompwiz.lookingglass.network.packet.PacketChunkInfo;
/**
* Finds 16x16x16 chunks exposed to the passed chunk location.
* @author Ken Butler/shadowking97
*/
public class ChunkFinder {
private final IChunkProvider chunkProvider;
private final int rootX;
private final int rootZ;
private final int range;
private final int dimension;
private final EntityPlayer player;
private ChunkData[][] map;
private List<ChunkCoordinates> cc;
private final int d;
private int step;
private int stepRange;
private long startTime;
/**
* Finds exposed chunks. Chunks must be loaded.
* @param root The chunk in chunk coordinates.
* @param dimension The target dimension
* @param chunkProvider The world server that contains the chunks
* @param player The player to send the chunks to
* @param range The radius of the chunkfinder.
* @return Sorted Chunk Data, by range. Prioritizes closest chunks.
*/
public ChunkFinder(ChunkCoordinates root, int dimension, IChunkProvider chunkProvider, EntityPlayer player, int range) {
this.chunkProvider = chunkProvider;
this.range = range;
this.dimension = dimension;
this.player = player;
this.d = (range << 1) + 1;
this.map = new ChunkData[d][d];
this.rootX = root.posX - range;
this.rootZ = root.posZ - range;
this.stepRange = 16 - root.posY;
if (root.posY > stepRange) stepRange = root.posY;
startTime = System.nanoTime();
LoggerUtils.debug("ChunkFinder scan started at nano: " + startTime);
for (int i = 0; i < d; i++) {
for (int j = 0; j < d; j++) {
map[i][j] = new ChunkData(i + rootX, j + rootZ);
int x1 = i - range;
int z1 = j - range;
map[i][j].distance = x1 * x1 + z1 * z1;
}
}
cc = new LinkedList<ChunkCoordinates>();
cc.add(new ChunkCoordinates(range, root.posY, range));
step = 0;
List<ChunkCoordinates> cc2 = new LinkedList<ChunkCoordinates>();
while (step - 1 < stepRange && !cc.isEmpty()) {
while (!cc.isEmpty()) {
cc2.addAll(scan(chunkProvider, map, cc.get(0), range));
cc.remove(0);
}
step++;
cc.addAll(cc2);
cc2.clear();
if (step >= stepRange) {
int range2 = step - stepRange + 1;
range2 *= range2;
int range3 = step - stepRange;
if (range3 < 0) range3 = 0;
range3 *= range3;
int minStep = range - (step - stepRange);
int maxStep = range + (step - stepRange) + 1;
if (minStep < 0) minStep = 0;
if (maxStep > d) maxStep = d;
for (int i = minStep; i < maxStep; i++) {
for (int j = minStep; j < maxStep; j++) {
int dist = map[i][j].distance;
if (map[i][j].doAdd() && dist < range2 && dist >= range3) {
ChunkData data = map[i][j];
Chunk c2 = chunkProvider.provideChunk(data.x, data.z);
if (!c2.isChunkLoaded) c2 = chunkProvider.loadChunk(data.x, data.z);
ServerPacketDispatcher.getInstance().addPacket(player, PacketChunkInfo.createPacket(c2, true, data.levels(), dimension));
}
}
}
}
}
}
public boolean findChunks() {
if (!cc.isEmpty()) {
int tick = 0;
List<ChunkCoordinates> cc2 = new LinkedList<ChunkCoordinates>();
while (!cc.isEmpty() && tick < 15) {
ChunkCoordinates ch = cc.get(0);
cc2.addAll(scan(chunkProvider, map, ch, range));
cc.remove(0);
++tick;
}
if (!cc.isEmpty()) return false;
step++;
cc.addAll(cc2);
cc2.clear();
if (step >= stepRange) {
int range2 = step - stepRange + 1;
range2 *= range2;
int range3 = step - stepRange;
if (range3 < 0) range3 = 0;
range3 *= range3;
int minStep = range - (step - stepRange);
int maxStep = range + (step - stepRange) + 1;
if (minStep < 0) minStep = 0;
if (maxStep > d) maxStep = d;
for (int i = minStep; i < maxStep; i++) {
for (int j = minStep; j < maxStep; j++) {
int dist = map[i][j].distance;
if (map[i][j].doAdd() && dist < range2 && dist >= range3) {
ChunkData data = map[i][j];
Chunk c2 = chunkProvider.provideChunk(data.x, data.z);
if (!c2.isChunkLoaded) c2 = chunkProvider.loadChunk(data.x, data.z);
ServerPacketDispatcher.getInstance().addPacket(player, PacketChunkInfo.createPacket(c2, true, data.levels(), dimension));
}
}
}
}
return false;
}
if (step >= stepRange) {
int range2 = step - stepRange;
range2 *= range2;
for (int i = 0; i < d; i++) {
for (int j = 0; j < d; j++) {
int dist = map[i][j].distance;
if (map[i][j].doAdd() && dist >= range2) {
ChunkData data = map[i][j];
Chunk c2 = chunkProvider.provideChunk(data.x, data.z);
if (!c2.isChunkLoaded) c2 = chunkProvider.loadChunk(data.x, data.z);
ServerPacketDispatcher.getInstance().addPacket(player, PacketChunkInfo.createPacket(c2, true, data.levels(), dimension));
}
}
}
}
LoggerUtils.debug("Scan finished. nanoseconds: " + (System.nanoTime() - startTime));
return true;
}
/**
* Recursive function to find all chunk segments attached to the surface.
*/
private static List<ChunkCoordinates> scan(IChunkProvider chunkProvider, ChunkData[][] map, ChunkCoordinates coord, int range) {
int rangeSqr = range * range;
List<ChunkCoordinates> cc3 = new LinkedList<ChunkCoordinates>();
int x = coord.posX;
int y = coord.posY;
int z = coord.posZ;
ChunkData data = map[x][z];
if (data.isAdded(y) || data.distance > rangeSqr) return cc3;
data.add(y);
Chunk c = chunkProvider.provideChunk(data.x, data.z);
if (!c.isChunkLoaded) {
c = chunkProvider.loadChunk(data.x, data.z);
}
if (c.getAreLevelsEmpty(y << 4, (y << 4) + 15)) {
data.empty(y);
if (x < (range << 1) && !(map[x + 1][z].isAdded(y) || map[x + 1][z].distance > rangeSqr || map[x + 1][z].distance < map[x][z].distance)) cc3.add(new ChunkCoordinates(x + 1, y, z));
if (x > 0 && !(map[x - 1][z].isAdded(y) || map[x - 1][z].distance > rangeSqr || map[x - 1][z].distance < map[x][z].distance)) cc3.add(new ChunkCoordinates(x - 1, y, z));
if (y < 15 && !(map[x][z].isAdded(y + 1) || map[x][z].distance > rangeSqr)) cc3.add(new ChunkCoordinates(x, y + 1, z));
if (y > 0 && !(map[x][z].isAdded(y - 1) || map[x][z].distance > rangeSqr)) cc3.add(new ChunkCoordinates(x, y - 1, z));
;
if (z < (range << 1) && !(map[x][z + 1].isAdded(y) || map[x][z + 1].distance > rangeSqr || map[x][z + 1].distance < map[x][z].distance)) cc3.add(new ChunkCoordinates(x, y, z + 1));
if (z > 0 && !(map[x][z - 1].isAdded(y) || map[x][z - 1].distance > rangeSqr || map[x][z - 1].distance < map[x][z].distance)) cc3.add(new ChunkCoordinates(x, y, z - 1));
} else {
boolean ok = false;
if (z > 0 && !(map[x][z - 1].isAdded(y) || map[x][z - 1].distance > rangeSqr || map[x][z - 1].distance < map[x][z].distance)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, l, (y << 4) + i, 0, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x, y, z - 1));
}
ok = false;
}
if (z < (range << 1) && !(map[x][z + 1].isAdded(y) || map[x][z + 1].distance > rangeSqr || map[x][z + 1].distance < map[x][z].distance)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, l, (y << 4) + i, 15, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x, y, z + 1));
}
ok = false;
}
if (y > 0 && !(map[x][z].isAdded(y - 1) || map[x][z].distance > rangeSqr)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, l, (y << 4), i, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x, y - 1, z));
}
ok = false;
}
if (y < 15 && !(map[x][z].isAdded(y + 1) || map[x][z].distance > rangeSqr)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, l, (y << 4) + 15, i, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x, y + 1, z));
}
ok = false;
}
if (x > 0 && !(map[x - 1][z].isAdded(y) || map[x - 1][z].distance > rangeSqr || map[x - 1][z].distance < map[x][z].distance)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, 0, (y << 4) + l, i, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x - 1, y, z));
}
ok = false;
}
if (x < (range << 1) && !(map[x + 1][z].isAdded(y) || map[x + 1][z].distance > rangeSqr || map[x + 1][z].distance < map[x][z].distance)) {
for (int i = 0; i < 16 && !ok; i++) {
for (int l = 0; l < 16 && !ok; l++) {
if (!isBlockNormalCubeDefault(c, 15, (y << 4) + l, i, false)) ok = true;
}
}
if (ok) {
cc3.add(new ChunkCoordinates(x + 1, y, z));
}
}
}
return cc3;
}
public static boolean isBlockNormalCubeDefault(Chunk chunk, int par1, int par2, int par3, boolean par4) {
if (par1 >= -30000000 && par3 >= -30000000 && par1 < 30000000 && par3 < 30000000) {
if (chunk != null && !chunk.isEmpty()) {
Block block = chunk.getBlock(par1 & 15, par2, par3 & 15);
return block.isNormalCube();
}
}
return par4;
}
public class ChunkData implements Comparable<ChunkData> {
public int x;
public int z;
public int added;
public int empty;
public int distance;
public ChunkData(int x, int z) {
this.x = x;
this.z = z;
added = 0;
}
public boolean isAdded(int level) {
return (added & (1 << level)) != 0;
}
public boolean doAdd() {
return (added ^ empty) != 0;
}
public boolean doAdd(int level) {
return isAdded(level) && !isEmpty(level);
}
public void add(int level) {
added |= 1 << level;
}
public boolean isEmpty(int level) {
return (empty & (1 << level)) == 0;
}
public void empty(int level) {
empty |= 1 << level;
}
public int levels() {
return added ^ empty;
}
@Override
public int compareTo(ChunkData arg0) {
return this.distance - arg0.distance;
}
}
}