package rtg.world.biome;
import net.minecraft.init.Biomes;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.BiomeDictionary;
import rtg.api.RTGAPI;
import rtg.api.util.CircularSearchCreator;
import rtg.world.biome.realistic.RealisticBiomeBase;
import rtg.world.biome.realistic.RealisticBiomePatcher;
/**
* @author Zeno410, Modified by srs_bsns 20160914
*/
public class BiomeAnalyzer {
private boolean [] riverBiome;
private boolean [] oceanBiome;
private boolean [] swampBiome;
private boolean [] beachBiome;
private boolean [] landBiome;
private int [] preferredBeach;
private RealisticBiomeBase [] savedJittered;
private RealisticBiomeBase scenicLakeBiome = RealisticBiomeBase.getBiome(RTGAPI.config().SCENIC_LAKE_BIOME_ID.get());
private RealisticBiomeBase scenicFrozenLakeBiome = RealisticBiomeBase.getBiome(RTGAPI.config().SCENIC_FROZEN_LAKE_BIOME_ID.get());
private SmoothingSearchStatus beachSearch;
private SmoothingSearchStatus landSearch;
private SmoothingSearchStatus oceanSearch;
private final static int NO_BIOME = -1;
private RealisticBiomePatcher biomePatcher = new RealisticBiomePatcher();
public BiomeAnalyzer() {
determineRiverBiomes();
determineOceanBiomes();
determineSwampBiomes();
determineBeachBiomes();
determineLandBiomes();
setupBeachesForBiomes();
prepareSearchPattern();
setSearches();
savedJittered = new RealisticBiomeBase[256];
}
public int[] xyinverted() {
int [] result = new int [256];
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
result[i * 16 + j] = j * 16 + i;
}
}
for (int i = 0; i < 256; i++) {
if (result[result[i]] != i) throw new RuntimeException("" + i + " " + result[i] + " " + result[result[i]]);
}
return result;
}
/**
*
* @author Zeno410, Modified by srs_bsns 20160914
*/
private class SmoothingSearchStatus
{
boolean absent = false;
boolean notHunted;
private int size() {return 3;}
private int [] findings = new int [3*3];
// weightings is part of a system to generate some variability in repaired chunks weighting is
// based on how long the search went on (so quasipsuedorandom, based on direction plus distance
private float [] weightings = new float [3*3];
public int [] biomes = new int [256];
private boolean [] desired;
private int arraySize;
private int [] pattern;
private final int upperLeftFinding = 0;
private final int upperRightFinding = 3;
private final int lowerLeftFinding = 1;
private final int lowerRightFinding = 4;
private final int [] quadrantBiome = new int[4];
private final float [] quadrantBiomeWeighting = new float [4];
private int biomeCount;
// private int [] xyinverted = xyinverted(); //no longer used here
SmoothingSearchStatus(boolean[] desired) { this.desired = desired; }
void hunt(int[] biomeNeighborhood) {
// 0,0 in the chunk is 9,9 int the array ; 8,8 is 10,10 and is treated as the center
clear();
int oldArraySize = arraySize;
arraySize = (int)Math.sqrt(biomeNeighborhood.length);
if (arraySize*arraySize != biomeNeighborhood.length)
throw new RuntimeException("non-square array");
if (arraySize != oldArraySize)
pattern = new CircularSearchCreator().pattern(arraySize/2-1, arraySize);
for (int xOffset = -1; xOffset<=1; xOffset++)
for (int zOffset = -1; zOffset<=1; zOffset++)
search(xOffset,zOffset, biomeNeighborhood);
// calling a routine because it gets too indented otherwise
smoothBiomes();
}
private void search(int xOffset, int zOffset, int [] biomeNeighborhood) {
int offset = xOffset*arraySize +zOffset;
int location = (xOffset+1)*size()+zOffset+1;
// set to failed search, which sticks if nothing is found
findings[location] = NO_BIOME;
weightings[location] = 2f;
for (int i = 0; i < pattern.length; i++) {
int biome = biomeNeighborhood[pattern[i]+offset];
if (desired[biome]) {
findings[location]=biome;
weightings[location] = (float)Math.sqrt(pattern.length) -(float)Math.sqrt(i) + 2f ;
break;
}
}
}
private void smoothBiomes() {
// more sophisticated version offsets into findings and biomes upperleft
smoothQuadrant(biomeIndex(0,0),upperLeftFinding);
smoothQuadrant(biomeIndex(8,0),upperRightFinding);
smoothQuadrant(biomeIndex(0,8),lowerLeftFinding);
smoothQuadrant(biomeIndex(8,8),lowerRightFinding);
}
private void smoothQuadrant(int biomesOffset, int findingsOffset) {
int upperLeft = findings[upperLeftFinding+findingsOffset];
int upperRight = findings[upperRightFinding+findingsOffset];
int lowerLeft = findings[lowerLeftFinding+findingsOffset];
int lowerRight = findings[lowerRightFinding+findingsOffset];
// check for uniformity
if ((upperLeft == upperRight)&&(upperLeft == lowerLeft)&&(upperLeft == lowerRight)){
// everythings the same; uniform fill;
for (int x = 0; x<8;x++)
for (int z =0; z<8;z++)
biomes[biomeIndex(x,z)+biomesOffset] = upperLeft;
return;
}
// not all the same; we have to work;
biomeCount = 0;
addBiome(upperLeft);
addBiome(upperRight);
addBiome(lowerLeft);
addBiome(lowerRight);
for (int x = 0; x<8; x++) {
for (int z= 0; z<8; z++) {
addBiome(lowerRight);
for (int i = 0; i < 4; i ++) quadrantBiomeWeighting[i] = 0;
// weighting strategy: weights go down as you move away from the corner.
// they go to 0 on the far edges so only the points on the edge have effects there
// for continuity with the next quadrant
addWeight(upperLeft,weightings[upperLeftFinding+findingsOffset]*(7-x)*(7-z));
addWeight(upperRight,weightings[upperRightFinding+findingsOffset]*x*(7-z));
addWeight(lowerLeft,weightings[lowerLeftFinding+findingsOffset]*(7-x)*z);
addWeight(lowerRight,weightings[lowerRightFinding+findingsOffset]*x*z);
biomes[biomeIndex(x,z)+biomesOffset] = preferredBiome();
}
}
}
private void addBiome(int biome) {
for (int i = 0; i < biomeCount; i++)
if (biome == quadrantBiome[i]) return;
// not there, add
quadrantBiome[biomeCount++] = biome;
}
private void addWeight(int biome, float weight) {
for (int i = 0; i <biomeCount ; i++) {
if (biome == quadrantBiome[i]) {
quadrantBiomeWeighting[i] += weight;
return;
}
}
}
private int preferredBiome() {
float bestWeight = 0;
int result = -2;
for (int i = 0; i <biomeCount ; i++) {
if (quadrantBiomeWeighting[i]>bestWeight) {
bestWeight =quadrantBiomeWeighting[i];
result = quadrantBiome[i];
}
}
return result;
}
private int biomeIndex(int x, int z)
{
return x*16+z;
}
private void clear()
{
for (int i = 0;i <findings.length; i++) findings[i] = -1;
}
}
private void determineRiverBiomes() {
riverBiome = new boolean[256];
for (int i = 0; i < riverBiome.length; i++) {
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
if (biome.getBiomeName().toLowerCase().contains("river")) riverBiome[i] = true;
}
}
private void determineOceanBiomes() {
oceanBiome = new boolean[256];
for (int i = 0; i < oceanBiome.length; i++) {
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
if (biome.getBiomeName().toLowerCase().contains("ocean")) oceanBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("kelp")) oceanBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("coral")) oceanBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("reef")) oceanBiome[i] = true;
}
oceanBiome[Biome.getIdForBiome(Biomes.DEEP_OCEAN)]=true;// not getting set?
}
private void determineSwampBiomes() {
swampBiome = new boolean[256];
for (int i = 0; i < swampBiome.length; i++) {
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
if (biome.getBiomeName().toLowerCase().contains("swamp")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("bayou")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("bog")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("wetland")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("sludge")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("marsh")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("fen")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("moor")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("quagmire")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("ephemeral lake")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("rainforest valley")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("riparian zone")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("ice sheet")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("woodland lake")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().contains("archipelago")) swampBiome[i] = true;
if (biome.getBiomeName().toLowerCase().equals("shield")) swampBiome[i] = true;
if (Biome.getIdForBiome(biome) == Biome.getIdForBiome(Biomes.FROZEN_RIVER)) swampBiome[i] = true;
}
}
private void determineLandBiomes() {
landBiome = new boolean[256];
for (int i = 0; i < landBiome.length; i++) {
if (!oceanBiome[i] && !riverBiome[i] && !beachBiome[i]) {
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
if (!biome.getBiomeName().toLowerCase().equals("lake")) landBiome[i] = true;
}
}
}
private void determineBeachBiomes() {
beachBiome = new boolean[256];
for (int i = 0; i < beachBiome.length; i++) {
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
if (biome.getBiomeName().toLowerCase().contains("beach") ||
biome.getBiomeName().toLowerCase().contains("mangrove")) beachBiome[i] = true;
}
}
private void setupBeachesForBiomes() {
preferredBeach = new int[256];
for (int i = 0; i < preferredBeach.length; i++) {
// We need to work with the realistic biome, so let's try to get it from the base biome, aborting if necessary.
Biome biome = Biome.getBiome(i);
if (biome == null) continue;
RealisticBiomeBase realisticBiome = RealisticBiomeBase.getBiome(i);
if (realisticBiome == null) continue;
preferredBeach[i] = Biome.getIdForBiome(realisticBiome.beachBiome);
// If stone beaches aren't allowed in this biome, then determine the best beach to use based on the biome's temperature.
if (realisticBiome.disallowStoneBeaches) {
if (Biome.getIdForBiome(realisticBiome.beachBiome) == Biome.getIdForBiome(Biomes.STONE_BEACH)) {
preferredBeach[i] = Biome.getIdForBiome((biome.getTemperature() <= 0.05f) ? Biomes.COLD_BEACH : Biomes.BEACH);
}
}
// If beaches aren't allowed in this biome, then use this biome as the beach.
if (realisticBiome.disallowAllBeaches) {
preferredBeach[i] = i;
}
}
}
public static Biome getPreferredBeachForBiome(Biome biome) {
/*
* Some of this code is from Climate Control, and it's still a bit crude. - Zeno
* Some of this code is from Pink's brain, and it's also a bit crude. - Pink
*/
float height = biome.getBaseHeight() + (biome.getHeightVariation() * 2f);
float temp = biome.getTemperature();
// Use a cold beach if the temperature is low enough; otherwise, just use a normal beach.
Biome beach = (temp <= 0.05f) ? Biomes.COLD_BEACH : Biomes.BEACH;
// If this is a mountainous biome or a Taiga variant, then let's use a stone beach.
if ((height > (1.0f + 0.5f) && temp > 0.05f) || isTaigaBiome(biome)) {
beach = Biomes.STONE_BEACH;
}
// Snowy biomes should always use cold beach; otherwise, the transition looks too abrupt.
if (BiomeDictionary.hasType(biome, BiomeDictionary.Type.SNOWY)) {
beach = Biomes.COLD_BEACH;
}
return beach;
}
private static boolean isTaigaBiome(Biome biome) {
return BiomeDictionary.hasType(biome, BiomeDictionary.Type.COLD)
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS)
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.FOREST)
&& !BiomeDictionary.hasType(biome, BiomeDictionary.Type.SNOWY);
}
/* HUNTING
*
*/
public void newRepair(int [] genLayerBiomes, RealisticBiomeBase [] jitteredBiomes, int [] biomeNeighborhood, int neighborhoodSize, float [] noise, float [] riverStrength) {
int sampleSize = 8;
RealisticBiomeBase realisticBiome;
int realisticBiomeId;
if (neighborhoodSize != sampleSize) throw new RuntimeException("mismatch between chunk and analyzer neighborhood sizes");
// currently just stuffs the genLayer into the jitter;
for (int i = 0; i < 256; i++) {
realisticBiome = RealisticBiomeBase.getBiome(genLayerBiomes[i]);
// Do we need to patch the biome?
if (realisticBiome == null) {
realisticBiome = biomePatcher.getPatchedRealisticBiome(
"NULL biome (" + i + ") found when performing new repair.");
}
realisticBiomeId = Biome.getIdForBiome(realisticBiome.baseBiome);
boolean canBeRiver = riverStrength[i] > 0.7;
// save what's there since the jitter keeps changing
savedJittered[i] = jitteredBiomes[i];
//if (savedJittered[i]== null) throw new RuntimeException();
if (noise[i] > 61.5) {
// replace
jitteredBiomes[i] = realisticBiome;
} else {
// check for river
if (canBeRiver && !oceanBiome[realisticBiomeId] && !swampBiome[realisticBiomeId]) {
// make river
int riverBiomeID = Biome.getIdForBiome(realisticBiome.riverBiome);
jitteredBiomes[i] = RealisticBiomeBase.getBiome(riverBiomeID);
} else {
// replace
jitteredBiomes[i] = realisticBiome;
}
}
}
// put beaches on shores
beachSearch.notHunted = true;
beachSearch.absent = false;
float beachTop = 64.5f;
for (int i = 0; i < 256; i++) {
if (beachSearch.absent) break; //no point
float beachBottom = 61.5f;
if (noise[i]< beachBottom ||noise[i]>riverAdjusted(beachTop,riverStrength[i])) continue;// this block isn't beach level
int biomeID = Biome.getIdForBiome(jitteredBiomes[i].baseBiome);
if (swampBiome[biomeID]) continue;// swamps are acceptable at beach level
if (beachSearch.notHunted) {
beachSearch.hunt(biomeNeighborhood);
landSearch.hunt(biomeNeighborhood);
}
int foundBiome = beachSearch.biomes[i];
if (foundBiome != NO_BIOME) {
int nearestLandBiome = landSearch.biomes[i];
if (nearestLandBiome>-1) {
foundBiome = preferredBeach[nearestLandBiome];
}
realisticBiome = RealisticBiomeBase.getBiome(foundBiome);
// Do we need to patch the biome?
if (realisticBiome == null) {
realisticBiome = biomePatcher.getPatchedRealisticBiome(
"NULL biome (" + i + ") found when performing new repair.");
}
jitteredBiomes[i] = realisticBiome;
}
}
// put land higher up;
landSearch.absent = false;
landSearch.notHunted = true;
for (int i = 0; i < 256; i++) {
if (landSearch.absent&&beachSearch.absent) break; //no point
// skip if this block isn't above beach level, adjusted for river effect to prevent abrupt beach stops
if (noise[i] < riverAdjusted(beachTop, riverStrength[i])) continue;
int biomeID = Biome.getIdForBiome(jitteredBiomes[i].baseBiome);
// already land
if (landBiome[biomeID]) continue;
// swamps are acceptable above water
if (swampBiome[biomeID]) continue;
if (landSearch.notHunted) landSearch.hunt(biomeNeighborhood);
int foundBiome = landSearch.biomes[i];
if (foundBiome == NO_BIOME) {
// no land found; try for a beach
if (beachSearch.notHunted) {
beachSearch.hunt(biomeNeighborhood);
}
foundBiome = beachSearch.biomes[i];
}
if (foundBiome != NO_BIOME) {
realisticBiome = RealisticBiomeBase.getBiome(foundBiome);
// Do we need to patch the biome?
if (realisticBiome == null) {
realisticBiome = biomePatcher.getPatchedRealisticBiome(
"NULL biome (" + i + ") found when performing new repair.");
}
jitteredBiomes[i] = realisticBiome;
}
}
// put ocean below sea level
oceanSearch.absent = false;
oceanSearch.notHunted = true;
for (int i = 0; i < 256; i++) {
if (oceanSearch.absent) break; //no point
float oceanTop = 61.5f;
if (noise[i]> oceanTop) continue;// too hight
int biomeID = Biome.getIdForBiome(jitteredBiomes[i].baseBiome);
if (oceanBiome[biomeID]) continue;// obviously ocean is OK
if (swampBiome[biomeID]) continue;// swamps are acceptable
if (riverBiome[biomeID]) continue;// rivers stay rivers
if (oceanSearch.notHunted) oceanSearch.hunt(biomeNeighborhood);
int foundBiome = oceanSearch.biomes[i];
if (foundBiome != NO_BIOME) {
realisticBiome = RealisticBiomeBase.getBiome(foundBiome);
// Do we need to patch the biome?
if (realisticBiome == null) {
realisticBiome = biomePatcher.getPatchedRealisticBiome(
"NULL biome (" + i + ") found when performing new repair.");
}
jitteredBiomes[i] = realisticBiome;
}
}
// convert remainder below sea level to lake biome
for (int i = 0; i < 256; i++) {
int biomeID = Biome.getIdForBiome(jitteredBiomes[i].baseBiome);
if (noise[i]<=61.5&&!riverBiome[biomeID]) {
// check for river
if (!oceanBiome[biomeID] &&
!swampBiome[biomeID] &&
!beachBiome[biomeID]) {
int riverReplacement = Biome.getIdForBiome(jitteredBiomes[i].riverBiome); // make river
if (riverReplacement == Biome.getIdForBiome(Biomes.FROZEN_RIVER))
jitteredBiomes[i] = scenicFrozenLakeBiome;
else jitteredBiomes[i] = scenicLakeBiome;
}
}
}
}
private void prepareSearchPattern() { /*if (searchPattern.length != 256) throw new RuntimeException();*/ }
private void setSearches() {
beachSearch = new SmoothingSearchStatus(this.beachBiome);
landSearch = new SmoothingSearchStatus(this.landBiome);
oceanSearch = new SmoothingSearchStatus(this.oceanBiome);
}
private float riverAdjusted (float top, float river) {
if (river>=1) return top;
float erodedRiver = river/RealisticBiomeBase.actualRiverProportion;
if (erodedRiver <= 1f) top = top*(1-erodedRiver)+62f*erodedRiver;
top = top*(1-river)+62f*river;
return top;
}
}