package com.bioxx.jmapgen.processing;
import java.util.Vector;
import net.minecraft.util.math.BlockPos;
import com.bioxx.jmapgen.IslandMap;
import com.bioxx.jmapgen.attributes.Attribute;
import com.bioxx.jmapgen.attributes.CaveAttribute;
import com.bioxx.jmapgen.graph.Center;
import com.bioxx.jmapgen.graph.Center.Marker;
import com.bioxx.tfc2.api.Global;
import com.bioxx.tfc2.api.types.StoneType;
public class CaveProcessor
{
IslandMap map;
public CaveProcessor(IslandMap m)
{
map = m;
}
public void generate()
{
Vector<Center> land = getCentersBetween(map.centers, 0.02, 1.0);
Vector<Center> starts = new Vector<Center>();
int majorCavesToGen = 30;
if(land.size() == 0)
return;
Center s;
for(int i = 0; i < majorCavesToGen; i++)
{
s = land.get(map.mapRandom.nextInt(land.size()));
//We don't want any caves to start on the edge of the map or in a lava tile.
if(s.hasAnyMarkersOf(Marker.Border, Marker.Lava) || s.hasAttribute(Attribute.River))
{
i--;
continue;
}
starts.add(s);
}
int caveCount = 0;//This keeps track of the total number of caves that we have generated so far.
if(starts.size() > 0)
for(Center c : starts)
{
gen(c, caveCount);
caveCount++;
}
//After we gen the initial major cave systems, we will attempt to start caves from beach cliffs.
starts.clear();
for(Center c : getBeachesWithCliffs(land))
{
if(map.mapRandom.nextDouble() < 0.6)
starts.add(c);
}
if(starts.size() > 0)
for(Center c : starts)
{
gen(c, caveCount);
caveCount++;
}
//Next we generate sea caves from ocean coastal tiles
starts.clear();
for(Center c : this.getCoastalOcean(map.centers))
{
if(map.mapRandom.nextDouble() < 0.5)
starts.add(c);
}
if(starts.size() > 0)
for(Center c : starts)
{
gen(c, caveCount, true, 5);
caveCount++;
}
}
private void gen(Center start, int caveId)
{
this.gen(start, caveId, false, 50);
}
private void gen(Center start, int caveId, boolean isSeaCave, int maxLength)
{
int minCaveSize = 2;
int maxCaveSize = 6;//This makes a cave have a 2-8 radius
StoneType stone = map.getParams().getSurfaceRock();
if(stone.getSubType() == StoneType.SubType.Sedimentary)
{
maxCaveSize = 2;//This makes a cave have a 2-4 radius
}
else if(stone.getSubType() == StoneType.SubType.Metamorphic)
{
maxCaveSize = 3;//This makes a cave have a 2-5 radius
}
else if(stone.getSubType() == StoneType.SubType.IgneousExtrusive)
{
maxCaveSize = 4;//This makes a cave have a 2-6 radius
}
int curLength = 0;
Center prevCenter = null;
Center center = start;
Center nextCenter = getHighestNonRiverNeighbor(start);
Center sCenter, sNextCenter;
CaveAttrNode sCurNode, sNextNode;
//First we will perform some preliminary validity checks
if(start.hasAttribute(Attribute.River) || (!isSeaCave && start.hasMarker(Marker.Water)))
return;
CaveAttrNode curNode = new CaveAttrNode(caveId);//Start slightly above the ground
CaveAttrNode nextNode = new CaveAttrNode(caveId);
curNode.setOffset(new BlockPos(center.point.x, mcElev(start.getElevation()) + 3, center.point.y));
//First form the mouth of the cave by either going straight down or at an angle.
//If the start hex is next to a cliff then move into that
if(mcElev(nextCenter.getElevation()) - mcElev(start.getElevation()) >= 8)
{
nextNode.setOffset(new BlockPos(nextCenter.point.x, curNode.getOffset().getY(), nextCenter.point.y));
}
else
{
nextNode.setOffset(new BlockPos(nextCenter.point.x, curNode.getOffset().getY()-10, nextCenter.point.y));
}
nextNode.setPrevOffset(getMidpoint(curNode.getOffset(), nextNode.getOffset()));
curNode.setNextOffset(nextNode.getPrevOffset());
if(isSeaCave)
curNode.setSeaCave(true);
int elevOffset = 0;
while(curLength < maxLength)
{
//Cycle the data to start the next iteration
curNode.setNext(nextCenter);
nextNode.setPrev(center);
addNode(center, curNode);
//If the cave breaks into the surface again, we end the cave
if(nextNode.offset.getY() > mcElev(nextCenter.getElevation()))
{
curLength = maxLength;
}
//Otherwise we continue
curNode = nextNode;
prevCenter = center;
center = nextCenter;
//Finished cycling
curNode.setNodeHeight(minCaveSize+map.mapRandom.nextInt(maxCaveSize));
curNode.setNodeWidth(minCaveSize+map.mapRandom.nextInt(maxCaveSize));
if(mcElev(center.getElevation()) - curNode.offset.getY() > 20)
{
if(map.mapRandom.nextDouble() < 0.1)
curNode.setMajorNode(true);
}
//If the cave is long enough, we may want to create little subcave offshoots in random directions
if(curLength > 3)
{
int subCaveCount = map.mapRandom.nextInt(5)+1;
while(subCaveCount > 0)
{
sCenter = center;
if(map.mapRandom.nextDouble() < 0.25)
sNextCenter = sCenter;
else
sNextCenter = sCenter.getRandomNeighbor(map.mapRandom);
sCurNode = new CaveAttrNode(1000+subCaveCount);
sCurNode.setOffset(curNode.getOffset());
sCurNode.setNext(sNextCenter);
sCurNode.setNodeHeight(1+map.mapRandom.nextInt(3));
sCurNode.setNodeWidth(2+map.mapRandom.nextInt(2));
sNextNode = new CaveAttrNode(1000+subCaveCount);
sNextNode.setOffset(new BlockPos(sNextCenter.point.x, sCurNode.getOffset().getY()+map.mapRandom.nextInt(20)-10, sNextCenter.point.y));
sCurNode.setNextOffset(getMidpoint(sCurNode.getOffset(), sNextNode.getOffset()).add(10-map.mapRandom.nextInt(6), 10-map.mapRandom.nextInt(6), 10-map.mapRandom.nextInt(6)));
sNextNode.setPrevOffset(sCurNode.getNextOffset());
sNextNode.setPrev(sCenter);
addNode(sCenter, sCurNode);
addNode(sNextCenter, sNextNode);
subCaveCount--;
}
}
elevOffset = map.mapRandom.nextInt(21)-10;
//Acquire the next hex
nextCenter = center.getRandomNeighborExcept(map.mapRandom, prevCenter);
if(map.mapRandom.nextDouble() < 0.05)
{
nextCenter = center;
elevOffset = map.mapRandom.nextInt(31)-15;
}
//Create our next node
nextNode = new CaveAttrNode(caveId);
if(curNode.isSeaCave)
{
nextCenter = center.getRandomFromGroup(map.mapRandom, center.getOnlyHigherCenters());
if(nextCenter == null)
break;
if(curNode.offset.getY() <= Global.SEALEVEL)
nextNode.setSeaCave(true);
}
else
{
if(nextCenter.hasMarker(Marker.Coast))
break;
}
nextNode.setOffset(new BlockPos(nextCenter.point.x, curNode.getOffset().getY() + elevOffset, nextCenter.point.y));
int midOffsetX = -5+map.mapRandom.nextInt(11);
int midOffsetY = -5+map.mapRandom.nextInt(11);
int midOffsetZ = -5+map.mapRandom.nextInt(11);
//If this cave is moving into a hex that has a surface river then we need to make sure that it doesnt try to peek the surface
if(nextCenter.hasAttribute(Attribute.River) || (!isSeaCave && nextCenter.hasMarker(Marker.Water)))
{
int riverDiff = mcElev(nextCenter.getElevation()) - nextNode.getOffset().getY();
if(riverDiff <= 10)
{
nextNode.getOffset().add(0, -5, 0);
nextNode.setNodeHeight(1);
nextNode.setNodeWidth(1);
midOffsetY -= 10;
}
}
//Setup the midpoint offsets for each node
nextNode.setPrevOffset(getMidpoint(curNode.getOffset(), nextNode.getOffset()).add(midOffsetX, midOffsetY, midOffsetZ));
curNode.setNextOffset(nextNode.getPrevOffset());
curLength++;
}
}
private BlockPos getMidpoint(BlockPos p0, BlockPos p1)
{
int x = (p0.getX() + p1.getX()) / 2;
int y = (p0.getY() + p1.getY()) / 2;
int z = (p0.getZ() + p1.getZ()) / 2;
return new BlockPos(x, y, z);
}
private void addNode(Center c, CaveAttrNode n)
{
CaveAttribute attrib = (CaveAttribute) c.getAttribute(Attribute.Cave);
if(attrib == null)
{
attrib = new CaveAttribute();
c.addAttribute(attrib);
}
attrib.addNode(n);
}
private Center getHighestNonRiverNeighbor(Center c)
{
Center out = null;
for(Center n : c.neighbors)
{
if(n.hasAttribute(Attribute.River))
continue;
if(out == null || n.getElevation() > out.getElevation())
out = n;
}
return out;
}
private int mcElev(double d)
{
return map.convertHeightToMC(d)+Global.SEALEVEL;
}
private Vector<Center> getCentersBetween(Vector<Center> centers, double min, double max)
{
Vector<Center> out = new Vector<Center>();
for(Center c : centers)
{
if (c.elevation > min && c.getElevation() < max)
out.add(c);
}
return out;
}
private Vector<Center> getBeachesWithCliffs(Vector<Center> centers)
{
Vector<Center> out = new Vector<Center>();
for(Center c : centers)
{
if(!c.hasMarker(Marker.Coast))
continue;
Center n = c.getHighestNeighbor();
if(mcElev(n.getElevation()) - mcElev(c.getElevation()) < 8)
continue;
out.add(c);
}
return out;
}
private Vector<Center> getCoastalOcean(Vector<Center> centers)
{
Vector<Center> out = new Vector<Center>();
for(Center c : centers)
{
if(!c.hasMarker(Marker.CoastWater))
continue;
Center n = c.getLowestNeighbor();
if(mcElev(c.getElevation()) - mcElev(n.getElevation()) < 8)
continue;
out.add(n);
}
return out;
}
}