package me.ryanhamshire.PopulationDensity;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
public class ScanRegionTask extends Thread
{
private ChunkSnapshot[][] chunks;
private boolean openNewRegions;
private final int CHUNK_SIZE = 16;
public ScanRegionTask(ChunkSnapshot chunks[][], boolean openNewRegions)
{
this.chunks = chunks;
this.openNewRegions = openNewRegions;
}
private class Position
{
public int x;
public int y;
public int z;
public Position(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public String toString()
{
return this.x + " " + this.y + " " + this.z;
}
}
@Override
public void run()
{
ArrayList<String> logEntries = new ArrayList<String>();
//initialize report content
int woodCount = 0;
int coalCount = 0;
int ironCount = 0;
int goldCount = 0;
int redstoneCount = 0;
int diamondCount = 0;
int playerBlocks = 0;
//initialize a new array to track where we've been
int maxHeight = PopulationDensity.ManagedWorld.getMaxHeight();
int x, y, z;
x = y = z = 0;
boolean [][][] examined = new boolean [this.chunks.length * CHUNK_SIZE][maxHeight][this.chunks.length * CHUNK_SIZE];
for(x = 0; x < examined.length; x++)
for(y = 0; y < examined[0].length; y++)
for(z = 0; z < examined[0][0].length; z++)
examined[x][y][z] = false;
//find a reasonable start position
Position currentPosition = null;
for(x = 0; x < examined.length && currentPosition == null; x++)
{
for(z = 0; z < examined[0][0].length && currentPosition == null; z++)
{
Position position = new Position(x, maxHeight - 1, z);
if(this.getMaterialAt(position) == Material.AIR)
{
currentPosition = position;
}
}
}
//set depth boundary
//if a player has to brave cavernous depths, those resources aren't "easily attainable"
int min_y = PopulationDensity.instance.minimumRegionPostY - 20;
//instantiate empty queue
ConcurrentLinkedQueue<Position> unexaminedQueue = new ConcurrentLinkedQueue<Position>();
//mark start position as examined
try
{
examined[currentPosition.x][currentPosition.y][currentPosition.z] = true;
}
catch(ArrayIndexOutOfBoundsException e)
{
logEntries.add("Unexpected Exception: " + e.toString());
}
//enqueue that start position
unexaminedQueue.add(currentPosition);
//as long as there are positions in the queue, keep going
while(!unexaminedQueue.isEmpty())
{
//dequeue a block
currentPosition = unexaminedQueue.remove();
//get material
Material material = this.getMaterialAt(currentPosition);
//material == null indicates the data is out of bounds (not in the snapshots)
//in that case, just move on to the next item in the queue
if(material == null || currentPosition.y < min_y) continue;
//if it's not a pass-through block
if( material != Material.AIR &&
material != Material.WOOD_DOOR &&
material != Material.WOODEN_DOOR &&
material != Material.IRON_DOOR_BLOCK &&
material != Material.TRAP_DOOR &&
material != Material.LADDER
)
{
//if it's a valuable resource, count it
if (material == Material.LOG) woodCount++;
else if (material == Material.LOG_2) woodCount++;
else if (material == Material.COAL_ORE) coalCount++;
else if (material == Material.IRON_ORE) ironCount++;
else if (material == Material.GOLD_ORE) goldCount++;
else if (material == Material.REDSTONE_ORE) redstoneCount++;
else if (material == Material.DIAMOND_ORE) diamondCount++;
//if it's a player block, count it
else if (
material != Material.WATER &&
material != Material.STATIONARY_LAVA &&
material != Material.STATIONARY_WATER &&
material != Material.BROWN_MUSHROOM &&
material != Material.CACTUS &&
material != Material.DEAD_BUSH &&
material != Material.DIRT &&
material != Material.GRAVEL &&
material != Material.GRASS &&
material != Material.HUGE_MUSHROOM_1 &&
material != Material.HUGE_MUSHROOM_2 &&
material != Material.ICE &&
material != Material.LAPIS_ORE &&
material != Material.LAVA &&
material != Material.OBSIDIAN &&
material != Material.RED_MUSHROOM &&
material != Material.RED_ROSE &&
material != Material.LEAVES &&
material != Material.LEAVES_2 &&
material != Material.LOG &&
material != Material.LOG_2 &&
material != Material.LONG_GRASS &&
material != Material.SAND &&
material != Material.SANDSTONE &&
material != Material.SNOW &&
material != Material.STONE &&
material != Material.VINE &&
material != Material.WATER_LILY &&
material != Material.YELLOW_FLOWER &&
material != Material.MOSSY_COBBLESTONE &&
material != Material.CLAY &&
material != Material.STAINED_CLAY &&
material != Material.SUGAR_CANE_BLOCK &&
material != Material.PACKED_ICE &&
material != Material.DOUBLE_PLANT)
{
playerBlocks++;
}
}
//otherwise for pass-through blocks, enqueue the blocks around them for examination
else
{
//make a list of adjacent blocks
ConcurrentLinkedQueue<Position> adjacentPositionQueue = new ConcurrentLinkedQueue<Position>();
//x + 1
adjacentPositionQueue.add(new Position(currentPosition.x + 1, currentPosition.y, currentPosition.z));
//x - 1
adjacentPositionQueue.add(new Position(currentPosition.x - 1, currentPosition.y, currentPosition.z));
//z + 1
adjacentPositionQueue.add(new Position(currentPosition.x, currentPosition.y, currentPosition.z + 1));
//z - 1
adjacentPositionQueue.add(new Position(currentPosition.x, currentPosition.y, currentPosition.z - 1));
//y + 1
adjacentPositionQueue.add(new Position(currentPosition.x, currentPosition.y + 1, currentPosition.z));
//y - 1
adjacentPositionQueue.add(new Position(currentPosition.x, currentPosition.y - 1, currentPosition.z));
//for each adjacent block
while(!adjacentPositionQueue.isEmpty())
{
Position adjacentPosition = adjacentPositionQueue.remove();
try
{
//if it hasn't been examined yet
if(!examined[adjacentPosition.x][adjacentPosition.y][adjacentPosition.z])
{
//mark it as examined
examined[adjacentPosition.x][adjacentPosition.y][adjacentPosition.z] = true;
//shove it in the queue for processing
unexaminedQueue.add(adjacentPosition);
}
}
//ignore any adjacent blocks which are outside the snapshots
catch(ArrayIndexOutOfBoundsException e){ }
}
}
}
//compute a resource score
int resourceScore = coalCount * 2 + ironCount * 3 + goldCount * 3 + redstoneCount * 3 + diamondCount * 4;
//due to a race condition, bukkit might say a chunk is loaded when it really isn't.
//in that case, bukkit will incorrectly report that all of the blocks in the chunk are air
//strategy: if resource score and wood count are flat zero, the result is suspicious, so wait 5 seconds for chunks to load and start over
//to avoid an infinite loop in a resource-bare region, maximum ONE repetition
//deliver report
logEntries.add("");
logEntries.add("Region Scan Results :");
logEntries.add("");
logEntries.add(" Wood :" + woodCount + " (Minimum: " + PopulationDensity.instance.woodMinimum + ")");
logEntries.add(" Coal :" + coalCount);
logEntries.add(" Iron :" + ironCount);
logEntries.add(" Gold :" + goldCount);
logEntries.add(" Redstone :" + redstoneCount);
logEntries.add(" Diamond :" + diamondCount);
logEntries.add("Player Blocks :" + playerBlocks + " (Maximum: " + (PopulationDensity.instance.densityRatio * 40000) + ")");
logEntries.add("");
logEntries.add(" Resource Score : " + resourceScore + " (Minimum: " + PopulationDensity.instance.resourceMinimum + ")");
logEntries.add("");
//if NOT sufficient resources for a good start
if(resourceScore < PopulationDensity.instance.resourceMinimum || woodCount < PopulationDensity.instance.woodMinimum || playerBlocks > 40000 * PopulationDensity.instance.densityRatio)
{
if(resourceScore < PopulationDensity.instance.resourceMinimum || woodCount < PopulationDensity.instance.woodMinimum)
{
logEntries.add("Summary: Insufficient near-surface resources to support new players.");
}
else if(playerBlocks > 40000 * PopulationDensity.instance.densityRatio)
{
logEntries.add("Summary: Region seems overcrowded.");
}
}
//otherwise
else
{
logEntries.add("Summary: Looks good! This region is suitable for new players.");
openNewRegions = false;
}
//now that we're done, notify the main thread
ScanResultsTask resultsTask = new ScanResultsTask(logEntries, openNewRegions);
PopulationDensity.instance.getServer().getScheduler().scheduleSyncDelayedTask(PopulationDensity.instance, resultsTask, 5L);
}
@SuppressWarnings("deprecation")
private Material getMaterialAt(Position position)
{
Material material = null;
int chunkx = position.x / 16;
int chunkz = position.z / 16;
try
{
ChunkSnapshot snapshot = this.chunks[chunkx][chunkz];
int materialID = snapshot.getBlockTypeId(position.x % 16, position.y, position.z % 16);
material = Material.getMaterial(materialID);
}
catch(IndexOutOfBoundsException e) { }
return material;
}
}