package rtg.world.biome.realistic; import java.util.ArrayList; import java.util.Random; import net.minecraft.block.BlockLeaves; import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.chunk.ChunkPrimer; import rtg.RTG; import rtg.api.RTGAPI; import rtg.api.config.BiomeConfig; import rtg.api.config.RTGConfig; import rtg.api.util.Accessor; import rtg.api.util.noise.CellNoise; import rtg.api.util.noise.OpenSimplexNoise; import rtg.api.util.noise.SimplexCellularNoise; import rtg.api.util.noise.SimplexOctave; import rtg.api.world.RTGWorld; import rtg.util.Logger; import rtg.util.SaplingUtil; import rtg.world.biome.BiomeAnalyzer; import rtg.world.biome.BiomeDecoratorRTG; import rtg.world.biome.IBiomeProviderRTG; import rtg.world.biome.deco.DecoBase; import rtg.world.biome.deco.DecoBaseBiomeDecorations; import rtg.world.biome.deco.collection.DecoCollectionBase; import rtg.world.biome.deco.collection.DecoCollectionDesertRiver; import rtg.world.gen.feature.WorldGenVolcano; import rtg.world.gen.feature.tree.rtg.TreeRTG; import rtg.world.gen.surface.SurfaceBase; import rtg.world.gen.surface.SurfaceGeneric; import rtg.world.gen.surface.SurfaceRiverOasis; import rtg.world.gen.terrain.TerrainBase; @SuppressWarnings({"WeakerAccess", "UnusedParameters", "unused"}) public abstract class RealisticBiomeBase { protected RTGConfig rtgConfig = RTGAPI.config(); private static final RealisticBiomeBase[] arrRealisticBiomeIds = new RealisticBiomeBase[256]; public final Biome baseBiome; public final Biome riverBiome; public final Biome beachBiome; public BiomeConfig config; public String configPath; public TerrainBase terrain; public SurfaceBase surface; public SurfaceBase surfaceRiver; public SurfaceBase surfaceGeneric; public BiomeDecoratorRTG rDecorator; public int waterSurfaceLakeChance; //Lower = more frequent public int lavaSurfaceLakeChance; //Lower = more frequent public int waterUndergroundLakeChance; //Lower = more frequent public int lavaUndergroundLakeChance; //Lower = more frequent public boolean generateVillages; public boolean generatesEmeralds; public boolean generatesSilverfish; public ArrayList<DecoBase> decos; public ArrayList<TreeRTG> rtgTrees; // lake calculations private float lakeInterval = 989.0f; private float lakeShoreLevel = 0.15f; private float lakeWaterLevel = 0.11f;// the lakeStrength below which things should be below water private float lakeDepressionLevel = 0.30f;// the lakeStrength below which land should start to be lowered public boolean noLakes = false; public boolean noWaterFeatures = false; private float largeBendSize = 100; private float mediumBendSize = 40; private float smallBendSize = 15; public boolean disallowStoneBeaches = false; // this is for rugged biomes that should have sand beaches public boolean disallowAllBeaches = false; public RealisticBiomeBase(Biome biome, Biome river) { arrRealisticBiomeIds[Biome.getIdForBiome(biome)] = this; baseBiome = biome; riverBiome = river; this.config = new BiomeConfig(); beachBiome = this.beachBiome(); rDecorator = new BiomeDecoratorRTG(this); waterSurfaceLakeChance = 10; lavaSurfaceLakeChance = 0; // Disabled. waterUndergroundLakeChance = 1; lavaUndergroundLakeChance = 1; generateVillages = true; generatesEmeralds = false; generatesSilverfish = false; decos = new ArrayList<>(); rtgTrees = new ArrayList<>(); /* * Disable base biome decorations by default. * This also needs to be here so that ores get generated. */ DecoBaseBiomeDecorations decoBaseBiomeDecorations = new DecoBaseBiomeDecorations(); decoBaseBiomeDecorations.setAllowed(false); this.addDeco(decoBaseBiomeDecorations); // set the water feature constants with the config changes this.lakeInterval *= rtgConfig.LAKE_FREQUENCY_MULTIPLIER.get(); this.lakeWaterLevel *= rtgConfig.lakeSizeMultiplier(); this.lakeShoreLevel *= rtgConfig.lakeSizeMultiplier(); this.lakeDepressionLevel *= rtgConfig.lakeSizeMultiplier(); this.largeBendSize *= rtgConfig.LAKE_SHORE_BENDINESS_MULTIPLIER.get(); this.mediumBendSize *= rtgConfig.LAKE_SHORE_BENDINESS_MULTIPLIER.get(); this.smallBendSize *= rtgConfig.LAKE_SHORE_BENDINESS_MULTIPLIER.get(); this.init(); } private void init() { initConfig(); this.getConfig().load(this.configPath()); this.adjustBiomeProperties(); this.terrain = initTerrain(); this.surface = initSurface(); this.surfaceRiver = new SurfaceRiverOasis(config); this.surfaceGeneric = new SurfaceGeneric(config, this.surface.getTopBlock(), this.surface.getFillerBlock()); initDecos(); } public abstract void initConfig(); public abstract TerrainBase initTerrain(); public abstract SurfaceBase initSurface(); public abstract void initDecos(); public BiomeConfig getConfig() { return this.config; } public static RealisticBiomeBase getBiome(int id) { return arrRealisticBiomeIds[id]; } public static RealisticBiomeBase[] arr() { return arrRealisticBiomeIds; } /* * Returns the beach biome to use for this biome. * By default, it uses the beach that has been set in the biome config. * If automatic beach detection is enabled (-1), it uses the supplied preferred beach. */ protected Biome beachBiome(Biome preferredBeach) { Biome beach; int configBeachId = this.getConfig().BEACH_BIOME.get(); if (configBeachId > -1 && configBeachId < 256) { beach = Biome.getBiome(configBeachId, preferredBeach); } else { beach = preferredBeach; } return beach; } /* * Returns the beach biome to use for this biome, with a dynamically-calculated preferred beach. */ public Biome beachBiome() { return this.beachBiome(BiomeAnalyzer.getPreferredBeachForBiome(this.baseBiome)); } public void rMapVolcanoes( ChunkPrimer primer, World world, IBiomeProviderRTG cmr, Random mapRand, int baseX, int baseY, int chunkX, int chunkY, OpenSimplexNoise simplex, CellNoise cell, float noise[]) { // Have volcanoes been disabled in the global config? if (!rtgConfig.ENABLE_VOLCANOES.get()) return; // Have volcanoes been disabled in the biome config? int biomeId = Biome.getIdForBiome(cmr.getBiomeGenAt(baseX * 16, baseY * 16)); RealisticBiomeBase realisticBiome = getBiome(biomeId); // Do we need to patch the biome? if (realisticBiome == null) { RealisticBiomePatcher biomePatcher = new RealisticBiomePatcher(); realisticBiome = biomePatcher.getPatchedRealisticBiome( "NULL biome (" + biomeId + ") found when mapping volcanoes."); } if (!realisticBiome.getConfig().ALLOW_VOLCANOES.get()) return; // Have volcanoes been disabled via frequency? // Use the global frequency unless the biome frequency has been explicitly set. int chance = realisticBiome.getConfig().VOLCANO_CHANCE.get() == -1 ? rtgConfig.VOLCANO_CHANCE.get() : realisticBiome.getConfig().VOLCANO_CHANCE.get(); if (chance < 1) return; // If we've made it this far, let's go ahead and generate the volcano. Exciting!!! :D if (baseX % 4 == 0 && baseY % 4 == 0 && mapRand.nextInt(chance) == 0) { float river = cmr.getRiverStrength(baseX * 16, baseY * 16) + 1f; if (river > 0.98f && cmr.isBorderlessAt(baseX * 16, baseY * 16)) { long i1 = mapRand.nextLong() / 2L * 2L + 1L; long j1 = mapRand.nextLong() / 2L * 2L + 1L; mapRand.setSeed((long) chunkX * i1 + (long) chunkY * j1 ^ world.getSeed()); WorldGenVolcano.build(primer, world, mapRand, baseX, baseY, chunkX, chunkY, simplex, cell, noise); } } } public void generateMapGen(ChunkPrimer primer, Long seed, World world, IBiomeProviderRTG cmr, Random mapRand, int chunkX, int chunkY, OpenSimplexNoise simplex, CellNoise cell, float noise[]) { // Have volcanoes been disabled in the global config? if (!rtgConfig.ENABLE_VOLCANOES.get()) return; final int mapGenRadius = 5; final int volcanoGenRadius = 15; mapRand.setSeed(seed); long l = (mapRand.nextLong() / 2L) * 2L + 1L; long l1 = (mapRand.nextLong() / 2L) * 2L + 1L; // Volcanoes generation for (int baseX = chunkX - volcanoGenRadius; baseX <= chunkX + volcanoGenRadius; baseX++) { for (int baseY = chunkY - volcanoGenRadius; baseY <= chunkY + volcanoGenRadius; baseY++) { mapRand.setSeed((long) baseX * l + (long) baseY * l1 ^ seed); rMapVolcanoes(primer, world, cmr, mapRand, baseX, baseY, chunkX, chunkY, simplex, cell, noise); } } } public float rNoise(RTGWorld rtgWorld, int x, int y, float border, float river) { // we now have both lakes and rivers lowering land if (noWaterFeatures) { float borderForRiver = border*2; if (borderForRiver >1f) borderForRiver = 1; river = 1f - (1f-borderForRiver)*(1f-river); return terrain.generateNoise(rtgWorld, x, y, border, river); } float lakeStrength = lakePressure(rtgWorld, x, y, border); float lakeFlattening = lakeFlattening(lakeStrength, lakeShoreLevel, lakeDepressionLevel); // we add some flattening to the rivers. The lakes are pre-flattened. float riverFlattening = river*1.25f-0.25f; if (riverFlattening <0) riverFlattening = 0; if ((river<1)&&(lakeFlattening<1)) { riverFlattening = (1f-riverFlattening)/riverFlattening+(1f-lakeFlattening)/lakeFlattening; riverFlattening = (1f/(riverFlattening+1f)); } else if (lakeFlattening < riverFlattening) riverFlattening = lakeFlattening; // the lakes have to have a little less flattening to avoid the rocky edges lakeFlattening = lakeFlattening(lakeStrength, lakeWaterLevel, lakeDepressionLevel); if ((river<1)&&(lakeFlattening<1)) { river = (1f-river)/river+(1f-lakeFlattening)/lakeFlattening; river = (1f/(river+1f)); } else if (lakeFlattening < river) river = lakeFlattening; // flatten terrain to set up for the water features float terrainNoise = terrain.generateNoise(rtgWorld, x, y, border, riverFlattening); // place water features return this.erodedNoise(rtgWorld, x, y, river, border, terrainNoise, lakeFlattening); } public static final float actualRiverProportion = 300f/1600f; public float erodedNoise(RTGWorld rtgWorld, int x, int y, float river, float border, float biomeHeight, double lakeFlattening) { float r; // put a flat spot in the middle of the river float riverFlattening = river; // moved the flattening to terrain stage if (riverFlattening <0) riverFlattening = 0; // check if rivers need lowering //if (riverFlattening < actualRiverProportion) { r = riverFlattening/actualRiverProportion; //} //if (1>0) return 62f+r*10f; if ((r < 1f && biomeHeight > 57f)) { return (biomeHeight * (r)) + ((57f + rtgWorld.simplex.noise2(x / 12f, y / 12f) * 2f + rtgWorld.simplex.noise2(x / 8f, y / 8f) * 1.5f) * (1f-r)); } else return biomeHeight; } public float lakeFlattening(RTGWorld rtgWorld,int x, int y, float border) { return lakeFlattening(lakePressure(rtgWorld, x, y, border), lakeWaterLevel, lakeDepressionLevel); } public float lakePressure(RTGWorld rtgWorld, int x, int y, float border) { if (noLakes) return 1f; SimplexOctave.Disk jitter = new SimplexOctave.Disk(); rtgWorld.simplex.riverJitter().evaluateNoise((float)x / 240.0, (float)y / 240.0, jitter); double pX = x + jitter.deltax() * largeBendSize; double pY = y + jitter.deltay() * largeBendSize; rtgWorld.simplex.mountain().evaluateNoise((float)x / 80.0, (float)y / 80.0, jitter); pX += jitter.deltax() * mediumBendSize; pY += jitter.deltay() * mediumBendSize; rtgWorld.simplex.octave(4).evaluateNoise((float)x / 30.0, (float)y / 30.0, jitter); pX += jitter.deltax() * smallBendSize; pY += jitter.deltay() * smallBendSize; //double results =simplexCell.river().noise(pX / lakeInterval, pY / lakeInterval,1.0); double [] lakeResults = rtgWorld.cell.river().eval((float)pX/ lakeInterval, (float)pY/ lakeInterval); float results = 1f-(float)((lakeResults[1]-lakeResults[0])/lakeResults[1]); if (results >1.01) throw new RuntimeException("" + lakeResults[0]+ " , "+lakeResults[1]); if (results<-.01) throw new RuntimeException("" + lakeResults[0]+ " , "+lakeResults[1]); //return simplexCell.river().noise((float)x/ lakeInterval, (float)y/ lakeInterval,1.0); return results; } public float lakeFlattening(float pressure, float bottomLevel, float topLevel) { // this number indicates a multiplier to height if (pressure > topLevel) return 1; if (pressure<bottomLevel) return 0; return (float)Math.pow((pressure-bottomLevel)/(topLevel-bottomLevel),1.0); } public void rReplace(ChunkPrimer primer, int i, int j, int x, int y, int depth, RTGWorld rtgWorld, float[] noise, float river, Biome[] base) { float riverRegion = this.noWaterFeatures ? 0f : river; if (rtgConfig.ENABLE_RTG_BIOME_SURFACES.get() && this.getConfig().USE_RTG_SURFACES.get()) { this.surface.paintTerrain(primer, i, j, x, y, depth, rtgWorld, noise, riverRegion, base); } else { this.surfaceGeneric.paintTerrain(primer, i, j, x, y, depth, rtgWorld, noise, riverRegion, base); } } protected void rReplaceWithRiver(ChunkPrimer primer, int i, int j, int x, int y, int depth, RTGWorld rtgWorld, float[] noise, float river, Biome[] base) { float riverRegion = this.noWaterFeatures ? 0f : river; if (rtgConfig.ENABLE_RTG_BIOME_SURFACES.get() && this.getConfig().USE_RTG_SURFACES.get()) { this.surface.paintTerrain(primer, i, j, x, y, depth, rtgWorld, noise, riverRegion, base); if (rtgConfig.ENABLE_LUSH_RIVER_BANK_SURFACES_IN_HOT_BIOMES.get()) { this.surfaceRiver.paintTerrain(primer, i, j, x, y, depth, rtgWorld, noise, riverRegion, base); } } else { this.surfaceGeneric.paintTerrain(primer, i, j, x, y, depth, rtgWorld, noise, riverRegion, base); } } public float r3Dnoise(float z) { return 0f; } public TerrainBase getTerrain() { return this.terrain; } public SurfaceBase getSurface() { return this.surface; } private class ChunkDecoration { ChunkPos chunkLocation; DecoBase decoration; ChunkDecoration(ChunkPos chunkLocation,DecoBase decoration) { this.chunkLocation = chunkLocation; this.decoration = decoration; } } public static ArrayList<ChunkDecoration> decoStack = new ArrayList<>(); public void rDecorate(RTGWorld rtgWorld, Random rand, int worldX, int worldZ, float strength, float river, boolean hasPlacedVillageBlocks) { for (DecoBase deco : this.decos) { decoStack.add(new ChunkDecoration(new ChunkPos(worldX, worldZ), deco)); if (decoStack.size() > 20) { String problem = ""; for (ChunkDecoration inStack : decoStack) { problem += "" + inStack.chunkLocation.toString() + " " + inStack.decoration.getClass().getSimpleName(); } throw new RuntimeException(problem); } if (deco.preGenerate(this, rtgWorld, rand, worldX, worldZ, strength, river, hasPlacedVillageBlocks)) { deco.generate(this, rtgWorld, rand, worldX, worldZ, strength, river, hasPlacedVillageBlocks); } decoStack.remove(decoStack.size() - 1); } } /** * Adds a deco object to the list of biome decos. * The 'allowed' parameter allows us to pass biome config booleans dynamically when configuring the decos in the biome. */ public RealisticBiomeBase addDeco(DecoBase deco, boolean allowed) { if (allowed) { if (!deco.properlyDefined()) throw new RuntimeException(deco.toString()); if (deco instanceof DecoBaseBiomeDecorations) { for (int i = 0; i < this.decos.size(); i++) { if (this.decos.get(i) instanceof DecoBaseBiomeDecorations) { this.decos.remove(i); break; } } } this.decos.add(deco); } return this; } /** * Convenience method for addDeco() where 'allowed' is assumed to be true. */ public RealisticBiomeBase addDeco(DecoBase deco) { if (!deco.properlyDefined()) throw new RuntimeException(deco.toString()); this.addDeco(deco, true); return this; } public void addDecoCollection(DecoCollectionBase decoCollection) { // Don't add the desert river deco collection if the user has disabled it. if (decoCollection instanceof DecoCollectionDesertRiver) { if (!rtgConfig.ENABLE_LUSH_RIVER_BANK_DECORATIONS_IN_HOT_BIOMES.get()) { return; } } // Add this collection's decos to master deco list. if (decoCollection.decos.size() > 0) { for (int i = 0; i < decoCollection.decos.size(); i++) { this.addDeco(decoCollection.decos.get(i)); } } // If there are any tree decos in this collection, then add the individual TreeRTG objects to master tree list. if (decoCollection.rtgTrees.size() > 0) { for (int i = 0; i < decoCollection.rtgTrees.size(); i++) { this.addTree(decoCollection.rtgTrees.get(i)); } } } /** * Adds a tree to the list of RTG trees associated with this biome. * The 'allowed' parameter allows us to pass biome config booleans dynamically when configuring the trees in the biome. * * @param tree * @param allowed */ public void addTree(TreeRTG tree, boolean allowed) { if (allowed) { // Set the sapling data for this tree before we add it to the list. tree.setSaplingBlock(SaplingUtil.getSaplingFromLeaves(tree.getLeavesBlock())); /* * Make sure all leaves delay their decay to prevent insta-despawning of leaves (e.g. Swamp Willow) * The try/catch is a safeguard against trees that use leaves which aren't an instance of BlockLeaves. */ try { IBlockState leaves = tree.getLeavesBlock().withProperty(BlockLeaves.CHECK_DECAY, false); tree.setLeavesBlock(leaves); } catch (Exception e) { // Do nothing. } this.rtgTrees.add(tree); } } /** * Convenience method for addTree() where 'allowed' is assumed to be true. * * @param tree */ public void addTree(TreeRTG tree) { this.addTree(tree, true); } /** * Returns the number of extra blocks of gold ore to generate in this biome. * Defaults to 0, but can be overridden by sub-classed biomes. * Currently only used by vanilla Mesa biome variants. */ public int getExtraGoldGenCount() { return 0; } /** * Returns the minimum Y value at which extra gold ore can generate. * Defaults to 32 (BiomeMesa), but can be overridden by sub-classed biomes. * Currently only used by vanilla Mesa biome variants. * * @see net.minecraft.world.biome.BiomeMesa */ public int getExtraGoldGenMinHeight() { return 32; } /** * Returns the maximum Y value at which extra gold ore can generate. * Defaults to 80 (BiomeMesa), but can be overridden by sub-classed biomes. * * @see net.minecraft.world.biome.BiomeMesa */ public int getExtraGoldGenMaxHeight() { return 80; } public boolean compareTerrain(RTGWorld rtgWorld, TerrainBase oldTerrain) { OpenSimplexNoise simplex = new OpenSimplexNoise(4444); SimplexCellularNoise cell = new SimplexCellularNoise(4444); Random rand = new Random(4444); float oldNoise; TerrainBase newTerrain = this.initTerrain(); float newNoise; for (int x = -64; x <= 64; x++) { for (int z = -64; z <= 64; z++) { oldNoise = oldTerrain.generateNoise(rtgWorld, x, z, 0.5f, 0.5f); newNoise = newTerrain.generateNoise(rtgWorld, x, z, 0.5f, 0.5f); //Logger.info("%s (%d) = oldNoise = %f | newNoise = %f", this.baseBiome.getBiomeName(), Biome.getIdForBiome(this.baseBiome), oldNoise, newNoise); if (oldNoise != newNoise) { throw new RuntimeException( "Terrains do not match in biome ID " + Biome.getIdForBiome(this.baseBiome) + " (" + this.baseBiome.getBiomeName() + ")." ); } } } return true; } private void adjustBiomeProperties() { Biome biome = this.baseBiome; int biomeId = Biome.getIdForBiome(biome); String biomeName = biome.getBiomeName(); // Temperature. String configTemperature = this.getConfig().TEMPERATURE.get(); if (!configTemperature.isEmpty()) { float biomeTemperature = Float.valueOf(configTemperature); if (biomeTemperature > 0.1f && biomeTemperature < 0.2f) { throw new RuntimeException("Invalid biome temperature for " + biomeName + "."); } else if (biomeTemperature < -2f || biomeTemperature > 2f) { throw new RuntimeException("Biome temperature out of range for " + biomeName + "."); } try { Accessor<Biome, Float> biomeTemp = new Accessor<>("temperature", "field_76750_F"); biomeTemp.setField(biome, biomeTemperature); Logger.info("Set biome temperature to %f for %s", biomeTemperature, biomeName); } catch (Exception e) { Logger.warn("Unable to set biome temperature to %f for %s. Reason: %s", biomeTemperature, biomeName, e.getMessage()); } } } public String configPath() { return RTG.configPath + "biomes/" + this.modSlug() + "/" + this.biomeSlug() + ".cfg"; } public String modSlug() { throw new RuntimeException("Realistic biomes need a mod slug."); } public String biomeSlug() { return BiomeConfig.formatSlug(this.baseBiome.getBiomeName()); } }