package openblocks.common;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.block.material.MapColor;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
import openblocks.common.HeightMapData.LayerData;
import openblocks.common.item.ItemEmptyMap;
import openblocks.common.item.ItemHeightMap;
import openmods.utils.BitSet;
public class MapDataBuilder {
private static final int LAYER_TERRAIN = 0;
private static final int LAYER_LIQUIDS = 1;
private static final int LAYER_COUNT = 2;
public final int mapId;
private HeightMapData data;
private static class BlockCount {
public byte groundColor;
public int groundHeight;
public byte liquidColor;
public int liquidHeight;
private static Block getValidBlock(World world, Chunk chunk, int x, int y, int z) {
Block block = chunk.getBlock(x, y, z);
if (block.isAir(world, x, y, z)) return null;
if (block.getMaterial().getMaterialMapColor().colorIndex == 0) return null;
if (MapDataManager.instance.isBlockTransparent(block)) return null;
return block;
}
public void average(World world, Chunk chunk, int startX, int startZ, int size) {
double groundHeightSum = 0;
int[] groundColors = new int[MapColor.mapColorArray.length];
double liquidHeightSum = 0;
int liquidCount = 0;
int[] liquidColors = new int[MapColor.mapColorArray.length];
for (int x = startX; x < startX + size; x++)
for (int z = startZ; z < startZ + size; z++) {
Block blockLiquid = null;
int heightLiquid = 0;
Block blockSolid = null;
int heightSolid = 0;
for (int y = 255; y >= 0; y--) {
Block block = getValidBlock(world, chunk, x, y, z);
if (block == null) continue;
if (block.getMaterial().isLiquid()) {
if (blockLiquid == null) {
blockLiquid = block;
heightLiquid = y;
}
} else {
blockSolid = block;
heightSolid = y;
break;
}
}
if (blockSolid != null) {
int meta = chunk.getBlockMetadata(x, heightSolid, z);
groundHeightSum += heightSolid;
int color = blockSolid.getMapColor(meta).colorIndex;
groundColors[color]++;
}
if (blockLiquid != null) {
int meta = chunk.getBlockMetadata(x, heightLiquid, z);
liquidHeightSum += heightLiquid;
int color = blockLiquid.getMapColor(meta).colorIndex;
liquidColors[color]++;
liquidCount++;
}
}
{
int maxColorCount = -1;
for (int i = 0; i < groundColors.length; i++)
if (groundColors[i] > maxColorCount) {
groundColor = (byte)i;
maxColorCount = groundColors[i];
}
groundHeight = (int)(groundHeightSum / (size * size));
}
if (liquidCount > size * size / 2) {
int maxColorCount = -1;
for (int i = 0; i < liquidColors.length; i++)
if (liquidColors[i] > maxColorCount) {
liquidColor = (byte)i;
maxColorCount = liquidColors[i];
}
liquidHeight = (int)(liquidHeightSum / liquidCount);
}
}
}
public class ChunkJob {
public final ChunkCoordIntPair chunk;
public final int pixelsPerChunk;
public final int mapMinX;
public final int mapMinY;
public final int bitNum;
private ChunkJob(ChunkCoordIntPair chunk, int pixelsPerChunk, int mapMinX, int mapMinY, int bitNum) {
this.chunk = chunk;
this.pixelsPerChunk = pixelsPerChunk;
this.mapMinX = mapMinX;
this.mapMinY = mapMinY;
this.bitNum = bitNum;
}
private void mapChunk(World world, Chunk chunk) {
LayerData ground = data.layers[LAYER_TERRAIN];
LayerData liquid = data.layers[LAYER_LIQUIDS];
final int blocksPerPixel = 16 / pixelsPerChunk;
int blockInChunkX = 0;
for (int mapX = mapMinX; mapX < mapMinX + pixelsPerChunk; mapX++) {
int blockInChunkZ = 0;
for (int mapY = mapMinY; mapY < mapMinY + pixelsPerChunk; mapY++) {
BlockCount count = new BlockCount();
count.average(world, chunk, blockInChunkX, blockInChunkZ, blocksPerPixel);
int index = mapY * 64 + mapX;
ground.colorMap[index] = count.groundColor;
ground.heightMap[index] = (byte)(count.groundHeight);
liquid.colorMap[index] = count.liquidColor;
liquid.heightMap[index] = (byte)(count.liquidHeight);
blockInChunkZ += blocksPerPixel;
}
blockInChunkX += blocksPerPixel;
}
MapDataManager.instance.markDataUpdated(world, mapId);
}
}
public MapDataBuilder(int mapId) {
this.mapId = mapId;
}
public void loadMap(World world) {
this.data = MapDataManager.getMapData(world, mapId);
}
public void resetMap(World world, int x, int z) {
this.data = MapDataManager.getMapData(world, mapId);
data.centerX = ((x >> 4) << 4);
data.centerZ = ((z >> 4) << 4);
data.dimension = world.provider.dimensionId;
if (data.layers == null || data.layers.length != LAYER_COUNT) data.layers = new HeightMapData.LayerData[LAYER_COUNT];
LayerData ground = data.layers[LAYER_TERRAIN];
if (ground == null) {
ground = new LayerData();
data.layers[LAYER_TERRAIN] = ground;
}
ground.alpha = (byte)255;
LayerData liquid = data.layers[LAYER_LIQUIDS];
if (liquid == null) {
liquid = new LayerData();
data.layers[LAYER_LIQUIDS] = liquid;
}
liquid.alpha = (byte)128;
MapDataManager.instance.markDataUpdated(world, mapId);
}
public Set<ChunkJob> createJobs(BitSet finishedChunks) {
Preconditions.checkState(data != null, "Invalid usage, load map first");
Map<ChunkCoordIntPair, ChunkJob> result = Maps.newHashMap();
final int blocksPerPixel = (1 << data.scale);
final int pixelsPerChunk = 16 / blocksPerPixel;
final int chunksPerSide = 64 / pixelsPerChunk;
int middleChunkX = data.centerX >> 4;
int middleChunkZ = data.centerZ >> 4;
int bitNum = 0;
for (int mapX = 0, chunkX = middleChunkX - chunksPerSide / 2; chunkX < middleChunkX + chunksPerSide / 2; mapX += pixelsPerChunk, chunkX++)
for (int mapY = 0, chunkZ = middleChunkZ - chunksPerSide / 2; chunkZ < middleChunkZ + chunksPerSide / 2; mapY += pixelsPerChunk, chunkZ++) {
ChunkCoordIntPair chunk = new ChunkCoordIntPair(chunkX, chunkZ);
if (!finishedChunks.testBit(bitNum)) {
result.put(chunk, new ChunkJob(chunk, pixelsPerChunk, mapX, mapY, bitNum));
}
bitNum++;
}
return Sets.newHashSet(result.values());
}
private static class JobDistance implements Comparable<JobDistance> {
public final double distance;
public final ChunkJob job;
public JobDistance(double distance, ChunkJob job) {
this.distance = distance;
this.job = job;
}
@Override
public int compareTo(JobDistance o) {
return Double.compare(distance, o.distance);
}
}
public static ChunkJob doNextChunk(World world, double x, double z, Collection<ChunkJob> jobs) {
if (jobs.isEmpty()) return null;
PriorityQueue<JobDistance> distances = Queues.newPriorityQueue();
for (ChunkJob job : jobs) {
ChunkCoordIntPair chunk = job.chunk;
double dx = chunk.getCenterXPos() - x;
double dz = chunk.getCenterZPosition() - z;
distances.add(new JobDistance(dx * dx + dz * dz, job));
}
IChunkProvider provider = world.getChunkProvider();
while (!distances.isEmpty()) {
JobDistance dist = distances.poll();
ChunkJob job = dist.job;
ChunkCoordIntPair chunkCoord = job.chunk;
if (provider.chunkExists(chunkCoord.chunkXPos, chunkCoord.chunkZPos)) {
Chunk chunk = provider.loadChunk(chunkCoord.chunkXPos, chunkCoord.chunkZPos);
job.mapChunk(world, chunk);
return job;
}
}
return null;
}
public static ItemStack upgradeToMap(World world, ItemStack stack) {
Item item = stack.getItem();
if (item instanceof ItemHeightMap) return stack;
else if (item instanceof ItemEmptyMap) {
return ItemEmptyMap.upgradeToMap(world, stack);
} else throw new IllegalArgumentException("Invalid item type: " + item);
}
public int size() {
return 4 << data.scale;
}
private int neededBits() {
int line = size();
return line * line;
}
public void resizeIfNeeded(BitSet bitmap) {
int needed = neededBits();
if (!bitmap.checkSize(needed)) bitmap.resize(needed);
}
public void resize(BitSet bitmap) {
bitmap.resize(neededBits());
}
}