mirror of https://github.com/webbukkit/dynmap.git
1122 lines
31 KiB
Java
1122 lines
31 KiB
Java
package org.dynmap.common.chunk;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
|
|
import org.dynmap.DynmapChunk;
|
|
import org.dynmap.DynmapCore;
|
|
import org.dynmap.DynmapWorld;
|
|
import org.dynmap.Log;
|
|
import org.dynmap.common.BiomeMap;
|
|
import org.dynmap.common.chunk.GenericChunkCache.ChunkCacheRec;
|
|
import org.dynmap.hdmap.HDBlockModels;
|
|
import org.dynmap.renderer.DynmapBlockState;
|
|
import org.dynmap.renderer.RenderPatchFactory;
|
|
import org.dynmap.utils.DynIntHashMap;
|
|
import org.dynmap.utils.MapChunkCache;
|
|
import org.dynmap.utils.MapIterator;
|
|
import org.dynmap.utils.BlockStep;
|
|
import org.dynmap.utils.DataBitsPacked;
|
|
import org.dynmap.utils.VisibilityLimit;
|
|
|
|
/**
|
|
* Abstract container for handling map cache and map iterator, using DynmapChunks
|
|
*/
|
|
public abstract class GenericMapChunkCache extends MapChunkCache {
|
|
private static boolean init = false;
|
|
protected DynmapWorld dw;
|
|
private int nsect;
|
|
private int sectoff; // Offset for sake of negative section indexes
|
|
private List<DynmapChunk> chunks;
|
|
private ListIterator<DynmapChunk> iterator;
|
|
private int x_min, x_max, z_min, z_max;
|
|
private int x_dim;
|
|
private HiddenChunkStyle hidestyle = HiddenChunkStyle.FILL_AIR;
|
|
private List<VisibilityLimit> visible_limits = null;
|
|
private List<VisibilityLimit> hidden_limits = null;
|
|
private boolean isempty = true;
|
|
private int snapcnt;
|
|
private GenericChunk[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */
|
|
private boolean[][] isSectionNotEmpty; /* Indexed by snapshot index, then by section index */
|
|
|
|
private static final BlockStep unstep[] = { BlockStep.X_MINUS, BlockStep.Y_MINUS, BlockStep.Z_MINUS,
|
|
BlockStep.X_PLUS, BlockStep.Y_PLUS, BlockStep.Z_PLUS };
|
|
|
|
/**
|
|
* Iterator for traversing map chunk cache (base is for non-snapshot)
|
|
*/
|
|
public class OurMapIterator implements MapIterator {
|
|
private int x, y, z, chunkindex, bx, bz;
|
|
private GenericChunk snap;
|
|
private BlockStep laststep;
|
|
private DynmapBlockState blk;
|
|
private final int worldheight;
|
|
private final int ymin;
|
|
|
|
OurMapIterator(int x0, int y0, int z0) {
|
|
initialize(x0, y0, z0);
|
|
worldheight = dw.worldheight;
|
|
ymin = dw.minY;
|
|
}
|
|
|
|
@Override
|
|
public final void initialize(int x0, int y0, int z0) {
|
|
this.x = x0;
|
|
this.y = y0;
|
|
this.z = z0;
|
|
this.chunkindex = ((x >> 4) - x_min) + (((z >> 4) - z_min) * x_dim);
|
|
this.bx = x & 0xF;
|
|
this.bz = z & 0xF;
|
|
|
|
if ((chunkindex >= snapcnt) || (chunkindex < 0)) {
|
|
snap = getEmpty();
|
|
} else {
|
|
snap = snaparray[chunkindex];
|
|
}
|
|
|
|
laststep = BlockStep.Y_MINUS;
|
|
|
|
if ((y >= ymin) && (y < worldheight)) {
|
|
blk = null;
|
|
} else {
|
|
blk = DynmapBlockState.AIR;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBlockSkyLight() {
|
|
try {
|
|
return snap.getBlockSkyLight(bx, y, bz);
|
|
} catch (ArrayIndexOutOfBoundsException aioobx) {
|
|
return 15;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final int getBlockEmittedLight() {
|
|
try {
|
|
return snap.getBlockEmittedLight(bx, y, bz);
|
|
} catch (ArrayIndexOutOfBoundsException aioobx) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final BiomeMap getBiome() {
|
|
try {
|
|
return snap.getBiome(bx, y, bz);
|
|
} catch (Exception ex) {
|
|
return BiomeMap.NULL;
|
|
}
|
|
}
|
|
|
|
private final BiomeMap getBiomeRel(int dx, int dz) {
|
|
int nx = x + dx;
|
|
int nz = z + dz;
|
|
int nchunkindex = ((nx >> 4) - x_min) + (((nz >> 4) - z_min) * x_dim);
|
|
if ((nchunkindex >= snapcnt) || (nchunkindex < 0)) {
|
|
return BiomeMap.NULL;
|
|
} else {
|
|
return snaparray[nchunkindex].getBiome(nx, y, nz);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final int getSmoothGrassColorMultiplier(int[] colormap) {
|
|
int mult = 0xFFFFFF;
|
|
|
|
try {
|
|
int raccum = 0;
|
|
int gaccum = 0;
|
|
int baccum = 0;
|
|
int cnt = 0;
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dz = -1; dz <= 1; dz++) {
|
|
BiomeMap bm = getBiomeRel(dx, dz);
|
|
if (bm == BiomeMap.NULL) continue;
|
|
int rmult = bm.getModifiedGrassMultiplier(colormap[bm.biomeLookup()]);
|
|
raccum += (rmult >> 16) & 0xFF;
|
|
gaccum += (rmult >> 8) & 0xFF;
|
|
baccum += rmult & 0xFF;
|
|
cnt++;
|
|
}
|
|
}
|
|
cnt = (cnt > 0) ? cnt : 1;
|
|
mult = ((raccum / cnt) << 16) | ((gaccum / cnt) << 8) | (baccum / cnt);
|
|
} catch (Exception x) {
|
|
//Log.info("getSmoothGrassColorMultiplier() error: " + x);
|
|
mult = 0xFFFFFF;
|
|
}
|
|
|
|
//Log.info(String.format("getSmoothGrassColorMultiplier() at %d, %d = %X", x, z, mult));
|
|
return mult;
|
|
}
|
|
|
|
@Override
|
|
public final int getSmoothFoliageColorMultiplier(int[] colormap) {
|
|
int mult = 0xFFFFFF;
|
|
|
|
try {
|
|
int raccum = 0;
|
|
int gaccum = 0;
|
|
int baccum = 0;
|
|
int cnt = 0;
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dz = -1; dz <= 1; dz++) {
|
|
BiomeMap bm = getBiomeRel(dx, dz);
|
|
if (bm == BiomeMap.NULL) continue;
|
|
int rmult = bm.getModifiedFoliageMultiplier(colormap[bm.biomeLookup()]);
|
|
raccum += (rmult >> 16) & 0xFF;
|
|
gaccum += (rmult >> 8) & 0xFF;
|
|
baccum += rmult & 0xFF;
|
|
cnt++;
|
|
}
|
|
}
|
|
cnt = (cnt > 0) ? cnt : 1;
|
|
mult = ((raccum / cnt) << 16) | ((gaccum / cnt) << 8) | (baccum / cnt);
|
|
} catch (Exception x) {
|
|
//Log.info("getSmoothFoliageColorMultiplier() error: " + x);
|
|
}
|
|
//Log.info(String.format("getSmoothFoliageColorMultiplier() at %d, %d = %X", x, z, mult));
|
|
|
|
return mult;
|
|
}
|
|
|
|
@Override
|
|
public final int getSmoothColorMultiplier(int[] colormap, int[] swampmap) {
|
|
int mult = 0xFFFFFF;
|
|
|
|
try {
|
|
int raccum = 0;
|
|
int gaccum = 0;
|
|
int baccum = 0;
|
|
int cnt = 0;
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dz = -1; dz <= 1; dz++) {
|
|
BiomeMap bm = getBiomeRel(dx, dz);
|
|
if (bm == BiomeMap.NULL) continue;
|
|
int rmult;
|
|
if (bm == BiomeMap.SWAMPLAND) {
|
|
rmult = swampmap[bm.biomeLookup()];
|
|
} else {
|
|
rmult = colormap[bm.biomeLookup()];
|
|
}
|
|
raccum += (rmult >> 16) & 0xFF;
|
|
gaccum += (rmult >> 8) & 0xFF;
|
|
baccum += rmult & 0xFF;
|
|
cnt++;
|
|
}
|
|
}
|
|
cnt = (cnt > 0) ? cnt : 1;
|
|
mult = ((raccum / cnt) << 16) | ((gaccum / cnt) << 8) | (baccum / cnt);
|
|
} catch (Exception x) {
|
|
//Log.info("getSmoothColorMultiplier() error: " + x);
|
|
}
|
|
//Log.info(String.format("getSmoothColorMultiplier() at %d, %d = %X", x, z, mult));
|
|
|
|
return mult;
|
|
}
|
|
|
|
@Override
|
|
public final int getSmoothWaterColorMultiplier() {
|
|
int multv = 0xFFFFFF;
|
|
try {
|
|
int raccum = 0;
|
|
int gaccum = 0;
|
|
int baccum = 0;
|
|
int cnt = 0;
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dz = -1; dz <= 1; dz++) {
|
|
BiomeMap bm = getBiomeRel(dx, dz);
|
|
if (bm == BiomeMap.NULL) continue;
|
|
int rmult = bm.getWaterColorMult();
|
|
raccum += (rmult >> 16) & 0xFF;
|
|
gaccum += (rmult >> 8) & 0xFF;
|
|
baccum += rmult & 0xFF;
|
|
cnt++;
|
|
}
|
|
}
|
|
cnt = (cnt > 0) ? cnt : 1;
|
|
multv = ((raccum / cnt) << 16) | ((gaccum / cnt) << 8) | (baccum / cnt);
|
|
} catch (Exception x) {
|
|
//Log.info("getSmoothWaterColorMultiplier(nomap) error: " + x);
|
|
}
|
|
//Log.info(String.format("getSmoothWaterColorMultiplier(nomap) at %d, %d = %X", x, z, multv));
|
|
|
|
return multv;
|
|
}
|
|
|
|
@Override
|
|
public final int getSmoothWaterColorMultiplier(int[] colormap) {
|
|
int mult = 0xFFFFFF;
|
|
|
|
try {
|
|
int raccum = 0;
|
|
int gaccum = 0;
|
|
int baccum = 0;
|
|
int cnt = 0;
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dz = -1; dz <= 1; dz++) {
|
|
BiomeMap bm = getBiomeRel(dx, dz);
|
|
if (bm == BiomeMap.NULL) continue;
|
|
int rmult = colormap[bm.biomeLookup()];
|
|
raccum += (rmult >> 16) & 0xFF;
|
|
gaccum += (rmult >> 8) & 0xFF;
|
|
baccum += rmult & 0xFF;
|
|
cnt++;
|
|
}
|
|
}
|
|
cnt = (cnt > 0) ? cnt : 1;
|
|
mult = ((raccum / cnt) << 16) | ((gaccum / cnt) << 8) | (baccum / cnt);
|
|
} catch (Exception x) {
|
|
//Log.info("getSmoothWaterColorMultiplier() error: " + x);
|
|
}
|
|
//Log.info(String.format("getSmoothWaterColorMultiplier() at %d, %d = %X", x, z, mult));
|
|
|
|
return mult;
|
|
}
|
|
|
|
/**
|
|
* Step current position in given direction
|
|
*/
|
|
@Override
|
|
public final void stepPosition(BlockStep step) {
|
|
blk = null;
|
|
|
|
switch (step.ordinal()) {
|
|
case 0:
|
|
x++;
|
|
bx++;
|
|
|
|
if (bx == 16) /* Next chunk? */
|
|
{
|
|
bx = 0;
|
|
chunkindex++;
|
|
if ((chunkindex >= snapcnt) || (chunkindex < 0)) {
|
|
snap = getEmpty();
|
|
} else {
|
|
snap = snaparray[chunkindex];
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
y++;
|
|
|
|
if (y >= worldheight) {
|
|
blk = DynmapBlockState.AIR;
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
z++;
|
|
bz++;
|
|
|
|
if (bz == 16) /* Next chunk? */
|
|
{
|
|
bz = 0;
|
|
chunkindex += x_dim;
|
|
if ((chunkindex >= snapcnt) || (chunkindex < 0)) {
|
|
snap = getEmpty();
|
|
} else {
|
|
snap = snaparray[chunkindex];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
x--;
|
|
bx--;
|
|
|
|
if (bx == -1) /* Next chunk? */
|
|
{
|
|
bx = 15;
|
|
chunkindex--;
|
|
if ((chunkindex >= snapcnt) || (chunkindex < 0)) {
|
|
snap = getEmpty();
|
|
} else {
|
|
snap = snaparray[chunkindex];
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
y--;
|
|
|
|
if (y < ymin) {
|
|
blk = DynmapBlockState.AIR;
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
z--;
|
|
bz--;
|
|
|
|
if (bz == -1) /* Next chunk? */
|
|
{
|
|
bz = 15;
|
|
chunkindex -= x_dim;
|
|
if ((chunkindex >= snapcnt) || (chunkindex < 0)) {
|
|
snap = getEmpty();
|
|
} else {
|
|
snap = snaparray[chunkindex];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
laststep = step;
|
|
}
|
|
|
|
/**
|
|
* Unstep current position to previous position
|
|
*/
|
|
@Override
|
|
public final BlockStep unstepPosition() {
|
|
BlockStep ls = laststep;
|
|
stepPosition(unstep[ls.ordinal()]);
|
|
return ls;
|
|
}
|
|
|
|
/**
|
|
* Unstep current position in oppisite director of given step
|
|
*/
|
|
@Override
|
|
public final void unstepPosition(BlockStep s) {
|
|
stepPosition(unstep[s.ordinal()]);
|
|
}
|
|
|
|
@Override
|
|
public final void setY(int y) {
|
|
if (y > this.y) {
|
|
laststep = BlockStep.Y_PLUS;
|
|
} else {
|
|
laststep = BlockStep.Y_MINUS;
|
|
}
|
|
|
|
this.y = y;
|
|
|
|
if ((y < ymin) || (y >= worldheight)) {
|
|
blk = DynmapBlockState.AIR;
|
|
} else {
|
|
blk = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final int getX() {
|
|
return x;
|
|
}
|
|
|
|
@Override
|
|
public final int getY() {
|
|
return y;
|
|
}
|
|
|
|
@Override
|
|
public final int getZ() {
|
|
return z;
|
|
}
|
|
|
|
@Override
|
|
public final DynmapBlockState getBlockTypeAt(BlockStep s) {
|
|
return getBlockTypeAt(s.xoff, s.yoff, s.zoff);
|
|
}
|
|
|
|
@Override
|
|
public final BlockStep getLastStep() {
|
|
return laststep;
|
|
}
|
|
|
|
@Override
|
|
public final int getWorldHeight() {
|
|
return worldheight;
|
|
}
|
|
|
|
@Override
|
|
public final long getBlockKey() {
|
|
return (((chunkindex * (worldheight - ymin)) + (y - ymin)) << 8) | (bx << 4) | bz;
|
|
}
|
|
|
|
@Override
|
|
public final RenderPatchFactory getPatchFactory() {
|
|
return HDBlockModels.getPatchDefinitionFactory();
|
|
}
|
|
|
|
@Override
|
|
public final Object getBlockTileEntityField(String fieldId) {
|
|
// TODO: handle tile entities here
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public final DynmapBlockState getBlockTypeAt(int xoff, int yoff, int zoff) {
|
|
int nx = x + xoff;
|
|
int ny = y + yoff;
|
|
int nz = z + zoff;
|
|
int nchunkindex = ((nx >> 4) - x_min) + (((nz >> 4) - z_min) * x_dim);
|
|
if ((nchunkindex >= snapcnt) || (nchunkindex < 0)) {
|
|
return DynmapBlockState.AIR;
|
|
} else {
|
|
return snaparray[nchunkindex].getBlockType(nx & 0xF, ny, nz & 0xF);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final Object getBlockTileEntityFieldAt(String fieldId, int xoff, int yoff, int zoff) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public final long getInhabitedTicks() {
|
|
try {
|
|
return snap.getInhabitedTicks();
|
|
} catch (Exception x) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final DynmapBlockState getBlockType() {
|
|
if (blk == null) {
|
|
blk = snap.getBlockType(bx, y, bz);
|
|
}
|
|
return blk;
|
|
}
|
|
|
|
@Override
|
|
public int getDataVersion() {
|
|
return (snap != null) ? snap.dataVersion : 0;
|
|
}
|
|
|
|
@Override
|
|
public String getChunkStatus() {
|
|
return (snap != null) ? snap.chunkStatus : null;
|
|
}
|
|
}
|
|
|
|
private class OurEndMapIterator extends OurMapIterator {
|
|
OurEndMapIterator(int x0, int y0, int z0) {
|
|
super(x0, y0, z0);
|
|
}
|
|
|
|
@Override
|
|
public final int getBlockSkyLight() {
|
|
return 15;
|
|
}
|
|
}
|
|
|
|
private static final GenericChunkSection STONESECTION = (new GenericChunkSection.Builder()).singleBiome(BiomeMap.PLAINS).singleBlockState(DynmapBlockState.getBaseStateByName(DynmapBlockState.STONE_BLOCK)).build();
|
|
private static final GenericChunkSection WATERSECTION = (new GenericChunkSection.Builder()).singleBiome(BiomeMap.OCEAN).singleBlockState(DynmapBlockState.getBaseStateByName(DynmapBlockState.WATER_BLOCK)).build();
|
|
|
|
private GenericChunkCache cache;
|
|
|
|
// Lazy generic chunks (tailored to height of world)
|
|
private GenericChunk empty_chunk;
|
|
private GenericChunk stone_chunk;
|
|
private GenericChunk ocean_chunk;
|
|
|
|
private final GenericChunk getEmpty() {
|
|
if (empty_chunk == null) {
|
|
empty_chunk = (new GenericChunk.Builder(dw.minY, dw.worldheight)).build();
|
|
}
|
|
return empty_chunk;
|
|
}
|
|
private final GenericChunk getStone() {
|
|
if (stone_chunk == null) {
|
|
GenericChunk.Builder bld = new GenericChunk.Builder(dw.minY, dw.worldheight);
|
|
for (int sy = -sectoff; sy < 4; sy++) { bld.addSection(sy, STONESECTION); }
|
|
stone_chunk = bld.build();
|
|
}
|
|
return stone_chunk;
|
|
}
|
|
private final GenericChunk getOcean() {
|
|
if (ocean_chunk == null) {
|
|
GenericChunk.Builder bld = new GenericChunk.Builder(dw.minY, dw.worldheight);
|
|
for (int sy = -sectoff; sy < 3; sy++) { bld.addSection(sy, STONESECTION); }
|
|
bld.addSection(3, WATERSECTION); // Put stone with ocean on top - less expensive render
|
|
ocean_chunk = bld.build();
|
|
}
|
|
return ocean_chunk;
|
|
}
|
|
/**
|
|
* Construct empty cache
|
|
*/
|
|
public GenericMapChunkCache(GenericChunkCache c) {
|
|
cache = c; // Save reference to cache
|
|
}
|
|
|
|
public void setChunks(DynmapWorld dw, List<DynmapChunk> chunks) {
|
|
this.dw = dw;
|
|
nsect = (dw.worldheight - dw.minY) >> 4;
|
|
sectoff = (-dw.minY) >> 4;
|
|
this.chunks = chunks;
|
|
|
|
/* Compute range */
|
|
if (chunks.size() == 0) {
|
|
this.x_min = 0;
|
|
this.x_max = 0;
|
|
this.z_min = 0;
|
|
this.z_max = 0;
|
|
x_dim = 1;
|
|
}
|
|
else {
|
|
x_min = x_max = chunks.get(0).x;
|
|
z_min = z_max = chunks.get(0).z;
|
|
|
|
for (DynmapChunk c : chunks) {
|
|
if (c.x > x_max) {
|
|
x_max = c.x;
|
|
}
|
|
|
|
if (c.x < x_min) {
|
|
x_min = c.x;
|
|
}
|
|
|
|
if (c.z > z_max) {
|
|
z_max = c.z;
|
|
}
|
|
|
|
if (c.z < z_min) {
|
|
z_min = c.z;
|
|
}
|
|
}
|
|
|
|
x_dim = x_max - x_min + 1;
|
|
}
|
|
|
|
snapcnt = x_dim * (z_max - z_min + 1);
|
|
snaparray = new GenericChunk[snapcnt];
|
|
isSectionNotEmpty = new boolean[snapcnt][];
|
|
|
|
}
|
|
|
|
private boolean isChunkVisible(DynmapChunk chunk) {
|
|
boolean vis = true;
|
|
if (visible_limits != null) {
|
|
vis = false;
|
|
for (VisibilityLimit limit : visible_limits) {
|
|
if (limit.doIntersectChunk(chunk.x, chunk.z)) {
|
|
vis = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (vis && (hidden_limits != null)) {
|
|
for (VisibilityLimit limit : hidden_limits) {
|
|
if (limit.doIntersectChunk(chunk.x, chunk.z)) {
|
|
vis = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return vis;
|
|
}
|
|
|
|
private boolean tryChunkCache(DynmapChunk chunk, boolean vis) {
|
|
/* Check if cached chunk snapshot found */
|
|
GenericChunk ss = null;
|
|
ChunkCacheRec ssr = cache.getSnapshot(dw.getName(), chunk.x, chunk.z);
|
|
if (ssr != null) {
|
|
ss = ssr.ss;
|
|
if (!vis) {
|
|
if (hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) {
|
|
ss = getStone();
|
|
} else if (hidestyle == HiddenChunkStyle.FILL_OCEAN) {
|
|
ss = getOcean();
|
|
} else {
|
|
ss = getEmpty();;
|
|
}
|
|
}
|
|
int idx = (chunk.x - x_min) + (chunk.z - z_min) * x_dim;
|
|
snaparray[idx] = ss;
|
|
}
|
|
return (ssr != null);
|
|
}
|
|
|
|
// Prep snapshot and add to cache
|
|
private void prepChunkSnapshot(DynmapChunk chunk, GenericChunk ss) {
|
|
DynIntHashMap tileData = new DynIntHashMap();
|
|
|
|
ChunkCacheRec ssr = new ChunkCacheRec();
|
|
ssr.ss = ss;
|
|
ssr.tileData = tileData;
|
|
|
|
cache.putSnapshot(dw.getName(), chunk.x, chunk.z, ssr);
|
|
}
|
|
|
|
// Load generic chunk from existing and already loaded chunk
|
|
protected abstract GenericChunk getLoadedChunk(DynmapChunk ch);
|
|
// Load generic chunk from unloaded chunk
|
|
protected abstract GenericChunk loadChunk(DynmapChunk ch);
|
|
|
|
/**
|
|
* Read NBT data from loaded chunks - needs to be called from server/world
|
|
* thread to be safe
|
|
*
|
|
* @returns number loaded
|
|
*/
|
|
public int getLoadedChunks() {
|
|
int cnt = 0;
|
|
if (!dw.isLoaded()) {
|
|
isempty = true;
|
|
unloadChunks();
|
|
return 0;
|
|
}
|
|
ListIterator<DynmapChunk> iter = chunks.listIterator();
|
|
while (iter.hasNext()) {
|
|
long startTime = System.nanoTime();
|
|
DynmapChunk chunk = iter.next();
|
|
int chunkindex = (chunk.x - x_min) + (chunk.z - z_min) * x_dim;
|
|
if (snaparray[chunkindex] != null)
|
|
continue; // Skip if already processed
|
|
|
|
boolean vis = isChunkVisible(chunk);
|
|
|
|
/* Check if cached chunk snapshot found */
|
|
if (tryChunkCache(chunk, vis)) {
|
|
endChunkLoad(startTime, ChunkStats.CACHED_SNAPSHOT_HIT);
|
|
cnt++;
|
|
}
|
|
// If chunk is loaded and not being unloaded, we're grabbing its NBT data
|
|
else {
|
|
// Get generic chunk from already loaded chunk, if we can
|
|
GenericChunk ss = getLoadedChunk(chunk);
|
|
if (ss != null) {
|
|
if (vis) { // If visible
|
|
prepChunkSnapshot(chunk, ss);
|
|
}
|
|
else {
|
|
if (hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) {
|
|
ss = getStone();
|
|
}
|
|
else if (hidestyle == HiddenChunkStyle.FILL_OCEAN) {
|
|
ss = getOcean();
|
|
}
|
|
else {
|
|
ss = getEmpty();
|
|
}
|
|
}
|
|
snaparray[chunkindex] = ss;
|
|
endChunkLoad(startTime, ChunkStats.LOADED_CHUNKS);
|
|
cnt++;
|
|
}
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
@Override
|
|
public int loadChunks(int max_to_load) {
|
|
return getLoadedChunks() + readChunks(max_to_load);
|
|
|
|
}
|
|
|
|
public int readChunks(int max_to_load) {
|
|
if (!dw.isLoaded()) {
|
|
isempty = true;
|
|
unloadChunks();
|
|
return 0;
|
|
}
|
|
|
|
int cnt = 0;
|
|
|
|
if (iterator == null) {
|
|
iterator = chunks.listIterator();
|
|
}
|
|
|
|
DynmapCore.setIgnoreChunkLoads(true);
|
|
|
|
// Load the required chunks.
|
|
while ((cnt < max_to_load) && iterator.hasNext()) {
|
|
long startTime = System.nanoTime();
|
|
|
|
DynmapChunk chunk = iterator.next();
|
|
|
|
int chunkindex = (chunk.x - x_min) + (chunk.z - z_min) * x_dim;
|
|
|
|
if (snaparray[chunkindex] != null)
|
|
continue; // Skip if already processed
|
|
|
|
boolean vis = isChunkVisible(chunk);
|
|
|
|
/* Check if cached chunk snapshot found */
|
|
if (tryChunkCache(chunk, vis)) {
|
|
endChunkLoad(startTime, ChunkStats.CACHED_SNAPSHOT_HIT);
|
|
}
|
|
else {
|
|
GenericChunk ss = loadChunk(chunk);
|
|
// If read was good
|
|
if (ss != null) {
|
|
// If hidden
|
|
if (!vis) {
|
|
if (hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) {
|
|
ss = getStone();
|
|
}
|
|
else if (hidestyle == HiddenChunkStyle.FILL_OCEAN) {
|
|
ss = getOcean();
|
|
}
|
|
else {
|
|
ss = getEmpty();
|
|
}
|
|
}
|
|
else {
|
|
// Prep snapshot
|
|
prepChunkSnapshot(chunk, ss);
|
|
}
|
|
snaparray[chunkindex] = ss;
|
|
endChunkLoad(startTime, ChunkStats.UNLOADED_CHUNKS);
|
|
}
|
|
else {
|
|
endChunkLoad(startTime, ChunkStats.UNGENERATED_CHUNKS);
|
|
}
|
|
}
|
|
cnt++;
|
|
}
|
|
|
|
DynmapCore.setIgnoreChunkLoads(false);
|
|
|
|
if (iterator.hasNext() == false) { /* If we're done */
|
|
isempty = true;
|
|
|
|
/* Fill missing chunks with empty dummy chunk */
|
|
for (int i = 0; i < snaparray.length; i++) {
|
|
if (snaparray[i] == null) {
|
|
snaparray[i] = getEmpty();
|
|
}
|
|
else if (!snaparray[i].isEmpty) {
|
|
isempty = false;
|
|
}
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
/**
|
|
* Test if done loading
|
|
*/
|
|
public boolean isDoneLoading() {
|
|
if (!dw.isLoaded()) {
|
|
return true;
|
|
}
|
|
if (iterator != null) {
|
|
return !iterator.hasNext();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Test if all empty blocks
|
|
*/
|
|
public boolean isEmpty() {
|
|
return isempty;
|
|
}
|
|
|
|
/**
|
|
* Unload chunks
|
|
*/
|
|
public void unloadChunks() {
|
|
if (snaparray != null) {
|
|
for (int i = 0; i < snaparray.length; i++) {
|
|
snaparray[i] = null;
|
|
}
|
|
|
|
snaparray = null;
|
|
}
|
|
}
|
|
|
|
private void initSectionData(int idx) {
|
|
isSectionNotEmpty[idx] = new boolean[nsect + 1];
|
|
|
|
if (!snaparray[idx].isEmpty) {
|
|
for (int i = 0; i < nsect; i++) {
|
|
if (snaparray[idx].isSectionEmpty(i - sectoff) == false) {
|
|
isSectionNotEmpty[idx][i] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isEmptySection(int sx, int sy, int sz) {
|
|
int idx = (sx - x_min) + (sz - z_min) * x_dim;
|
|
boolean[] flags = isSectionNotEmpty[idx];
|
|
if(flags == null) {
|
|
initSectionData(idx);
|
|
flags = isSectionNotEmpty[idx];
|
|
}
|
|
return !flags[sy + sectoff];
|
|
}
|
|
|
|
/**
|
|
* Get cache iterator
|
|
*/
|
|
public MapIterator getIterator(int x, int y, int z) {
|
|
if (dw.getEnvironment().equals("the_end")) {
|
|
return new OurEndMapIterator(x, y, z);
|
|
}
|
|
return new OurMapIterator(x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Set hidden chunk style (default is FILL_AIR)
|
|
*/
|
|
public void setHiddenFillStyle(HiddenChunkStyle style) {
|
|
this.hidestyle = style;
|
|
}
|
|
|
|
/**
|
|
* Add visible area limit - can be called more than once Needs to be set before
|
|
* chunks are loaded Coordinates are block coordinates
|
|
*/
|
|
public void setVisibleRange(VisibilityLimit lim) {
|
|
if (visible_limits == null)
|
|
visible_limits = new ArrayList<VisibilityLimit>();
|
|
visible_limits.add(lim);
|
|
}
|
|
|
|
/**
|
|
* Add hidden area limit - can be called more than once Needs to be set before
|
|
* chunks are loaded Coordinates are block coordinates
|
|
*/
|
|
public void setHiddenRange(VisibilityLimit lim) {
|
|
if (hidden_limits == null)
|
|
hidden_limits = new ArrayList<VisibilityLimit>();
|
|
hidden_limits.add(lim);
|
|
}
|
|
|
|
@Override
|
|
public DynmapWorld getWorld() {
|
|
return dw;
|
|
}
|
|
|
|
@Override
|
|
public boolean setChunkDataTypes(boolean blockdata, boolean biome, boolean highestblocky, boolean rawbiome) {
|
|
return true;
|
|
}
|
|
|
|
private static final String litStates[] = { "light", "spawn", "heightmaps", "full" };
|
|
|
|
public GenericChunk parseChunkFromNBT(GenericNBTCompound orignbt) {
|
|
GenericNBTCompound nbt = orignbt;
|
|
if ((nbt != null) && nbt.contains("Level", GenericNBTCompound.TAG_COMPOUND)) {
|
|
nbt = nbt.getCompound("Level");
|
|
}
|
|
if (nbt == null) return null;
|
|
String status = nbt.getString("Status");
|
|
int version = orignbt.getInt("DataVersion");
|
|
boolean lit = nbt.getBoolean("isLightOn");
|
|
boolean hasLitState = false;
|
|
if (status != null) {
|
|
for (int i = 0; i < litStates.length; i++) {
|
|
if (status.equals(litStates[i])) { hasLitState = true; }
|
|
}
|
|
}
|
|
boolean hasLight = false; // pessimistic: only has light if we see it, due to WB and other flawed chunk generation hasLitState; // Assume good light in a lit state
|
|
|
|
// Start generic chunk builder
|
|
GenericChunk.Builder bld = new GenericChunk.Builder(dw.minY, dw.worldheight);
|
|
int x = nbt.getInt("xPos");
|
|
int z = nbt.getInt("zPos");
|
|
|
|
// Set chunk info
|
|
bld.coords(x, z).chunkStatus(status).dataVersion(version);
|
|
|
|
if (nbt.contains("InhabitedTime")) {
|
|
bld.inhabitedTicks(nbt.getLong("InhabitedTime"));
|
|
}
|
|
// Check for 2D or old 3D biome data from chunk level: need these when we build old sections
|
|
List<BiomeMap[]> old3d = null; // By section, then YZX list
|
|
BiomeMap[] old2d = null;
|
|
if (nbt.contains("Biomes")) {
|
|
int[] bb = nbt.getIntArray("Biomes");
|
|
if (bb != null) {
|
|
// If v1.15+ format
|
|
if (bb.length > 256) {
|
|
old3d = new ArrayList<BiomeMap[]>();
|
|
// Get 4 x 4 x 4 list for each section
|
|
for (int sect = 0; sect < (bb.length / 64); sect++) {
|
|
BiomeMap smap[] = new BiomeMap[64];
|
|
for (int i = 0; i < 64; i++) {
|
|
smap[i] = BiomeMap.byBiomeID(bb[sect*64 + i]);
|
|
}
|
|
old3d.add(smap);
|
|
}
|
|
}
|
|
else { // Else, older chunks
|
|
old2d = new BiomeMap[256];
|
|
for (int i = 0; i < bb.length; i++) {
|
|
old2d[i] = BiomeMap.byBiomeID(bb[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Start section builder
|
|
GenericChunkSection.Builder sbld = new GenericChunkSection.Builder();
|
|
/* Get sections */
|
|
GenericNBTList sect = nbt.contains("sections") ? nbt.getList("sections", 10) : nbt.getList("Sections", 10);
|
|
// And process sections
|
|
for (int i = 0; i < sect.size(); i++) {
|
|
GenericNBTCompound sec = sect.getCompound(i);
|
|
int secnum = sec.getByte("Y");
|
|
|
|
DynmapBlockState[] palette = null;
|
|
// If we've got palette and block states list, process non-empty section
|
|
if (sec.contains("Palette", 9) && sec.contains("BlockStates", 12)) {
|
|
GenericNBTList plist = sec.getList("Palette", 10);
|
|
long[] statelist = sec.getLongArray("BlockStates");
|
|
palette = new DynmapBlockState[plist.size()];
|
|
for (int pi = 0; pi < plist.size(); pi++) {
|
|
GenericNBTCompound tc = plist.getCompound(pi);
|
|
String pname = tc.getString("Name");
|
|
if (tc.contains("Properties")) {
|
|
StringBuilder statestr = new StringBuilder();
|
|
GenericNBTCompound prop = tc.getCompound("Properties");
|
|
for (String pid : prop.getAllKeys()) {
|
|
if (statestr.length() > 0) statestr.append(',');
|
|
statestr.append(pid).append('=').append(prop.getAsString(pid));
|
|
}
|
|
palette[pi] = DynmapBlockState.getStateByNameAndState(pname, statestr.toString());
|
|
}
|
|
if (palette[pi] == null) {
|
|
palette[pi] = DynmapBlockState.getBaseStateByName(pname);
|
|
}
|
|
if (palette[pi] == null) {
|
|
palette[pi] = DynmapBlockState.AIR;
|
|
}
|
|
}
|
|
int recsperblock = (4096 + statelist.length - 1) / statelist.length;
|
|
int bitsperblock = 64 / recsperblock;
|
|
GenericBitStorage db = null;
|
|
DataBitsPacked dbp = null;
|
|
try {
|
|
db = nbt.makeBitStorage(bitsperblock, 4096, statelist);
|
|
} catch (Exception ex) { // Handle legacy encoded
|
|
bitsperblock = (statelist.length * 64) / 4096;
|
|
dbp = new DataBitsPacked(bitsperblock, 4096, statelist);
|
|
}
|
|
if (bitsperblock > 8) { // Not palette
|
|
for (int j = 0; j < 4096; j++) {
|
|
int v = (dbp != null) ? dbp.getAt(j) : db.get(j);
|
|
sbld.xyzBlockState(j & 0xF, (j & 0xF00) >> 8, (j & 0xF0) >> 4, DynmapBlockState.getStateByGlobalIndex(v));
|
|
}
|
|
}
|
|
else {
|
|
for (int j = 0; j < 4096; j++) {
|
|
int v = (dbp != null) ? dbp.getAt(j) : db.get(j);
|
|
DynmapBlockState bs = (v < palette.length) ? palette[v] : DynmapBlockState.AIR;
|
|
sbld.xyzBlockState(j & 0xF, (j & 0xF00) >> 8, (j & 0xF0) >> 4, bs);
|
|
}
|
|
}
|
|
}
|
|
else if (sec.contains("block_states", GenericNBTCompound.TAG_COMPOUND)) { // 1.18
|
|
GenericNBTCompound block_states = sec.getCompound("block_states");
|
|
// If we've got palette, process non-empty section
|
|
if (block_states.contains("palette", GenericNBTCompound.TAG_LIST)) {
|
|
long[] statelist = block_states.contains("data", GenericNBTCompound.TAG_LONG_ARRAY) ? block_states.getLongArray("data") : new long[4096 / 64]; // Handle zero bit palette (all same)
|
|
GenericNBTList plist = block_states.getList("palette", GenericNBTCompound.TAG_COMPOUND);
|
|
palette = new DynmapBlockState[plist.size()];
|
|
for (int pi = 0; pi < plist.size(); pi++) {
|
|
GenericNBTCompound tc = plist.getCompound(pi);
|
|
String pname = tc.getString("Name");
|
|
if (tc.contains("Properties")) {
|
|
StringBuilder statestr = new StringBuilder();
|
|
GenericNBTCompound prop = tc.getCompound("Properties");
|
|
for (String pid : prop.getAllKeys()) {
|
|
if (statestr.length() > 0) statestr.append(',');
|
|
statestr.append(pid).append('=').append(prop.getAsString(pid));
|
|
}
|
|
palette[pi] = DynmapBlockState.getStateByNameAndState(pname, statestr.toString());
|
|
}
|
|
if (palette[pi] == null) {
|
|
palette[pi] = DynmapBlockState.getBaseStateByName(pname);
|
|
}
|
|
if (palette[pi] == null) {
|
|
palette[pi] = DynmapBlockState.AIR;
|
|
}
|
|
}
|
|
GenericBitStorage db = null;
|
|
DataBitsPacked dbp = null;
|
|
|
|
int bitsperblock = (statelist.length * 64) / 4096;
|
|
int expectedStatelistLength = (4096 + (64 / bitsperblock) - 1) / (64 / bitsperblock);
|
|
if (statelist.length == expectedStatelistLength) {
|
|
db = nbt.makeBitStorage(bitsperblock, 4096, statelist);
|
|
}
|
|
else {
|
|
bitsperblock = (statelist.length * 64) / 4096;
|
|
dbp = new DataBitsPacked(bitsperblock, 4096, statelist);
|
|
}
|
|
if (bitsperblock > 8) { // Not palette
|
|
for (int j = 0; j < 4096; j++) {
|
|
int v = db != null ? db.get(j) : dbp.getAt(j);
|
|
sbld.xyzBlockState(j & 0xF, (j & 0xF00) >> 8, (j & 0xF0) >> 4, DynmapBlockState.getStateByGlobalIndex(v));
|
|
}
|
|
}
|
|
else {
|
|
for (int j = 0; j < 4096; j++) {
|
|
int v = db != null ? db.get(j) : dbp.getAt(j);
|
|
DynmapBlockState bs = (v < palette.length) ? palette[v] : DynmapBlockState.AIR;
|
|
sbld.xyzBlockState(j & 0xF, (j & 0xF00) >> 8, (j & 0xF0) >> 4, bs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (sec.contains("BlockLight")) {
|
|
sbld.emittedLight(sec.getByteArray("BlockLight"));
|
|
}
|
|
if (sec.contains("SkyLight")) {
|
|
sbld.skyLight(sec.getByteArray("SkyLight"));
|
|
hasLight = true;
|
|
}
|
|
// If section biome palette
|
|
if (sec.contains("biomes")) {
|
|
GenericNBTCompound nbtbiomes = sec.getCompound("biomes");
|
|
long[] bdataPacked = nbtbiomes.getLongArray("data");
|
|
GenericNBTList bpalette = nbtbiomes.getList("palette", 8);
|
|
GenericBitStorage bdata = null;
|
|
if (bdataPacked.length > 0)
|
|
bdata = nbt.makeBitStorage(bdataPacked.length, 64, bdataPacked);
|
|
for (int j = 0; j < 64; j++) {
|
|
int b = bdata != null ? bdata.get(j) : 0;
|
|
sbld.xyzBiome(j & 0x3, (j & 0x30) >> 4, (j & 0xC) >> 2, BiomeMap.byBiomeResourceLocation(bpalette.getString(b)));
|
|
}
|
|
}
|
|
else { // Else, apply legacy biomes
|
|
if (old3d != null) {
|
|
BiomeMap m[] = old3d.get((secnum > 0) ? ((secnum < old3d.size()) ? secnum : old3d.size()-1) : 0);
|
|
if (m != null) {
|
|
for (int j = 0; j < 64; j++) {
|
|
sbld.xyzBiome(j & 0x3, (j & 0x30) >> 4, (j & 0xC) >> 2, m[j]);
|
|
}
|
|
}
|
|
}
|
|
else if (old2d != null) {
|
|
for (int j = 0; j < 256; j++) {
|
|
sbld.xzBiome(j & 0xF, (j & 0xF0) >> 4, old2d[j]);
|
|
}
|
|
}
|
|
}
|
|
// Finish and add section
|
|
bld.addSection(secnum, sbld.build());
|
|
sbld.reset();
|
|
}
|
|
// Assume skylight is only trustworthy in a lit state
|
|
if ((!hasLitState) || (!lit)) {
|
|
hasLight = false;
|
|
}
|
|
// If no light, do simple generate
|
|
if (!hasLight) {
|
|
//Log.info(String.format("generateSky(%d,%d)", x, z));
|
|
bld.generateSky();
|
|
}
|
|
return bld.build();
|
|
}
|
|
|
|
}
|