cn.nukkit.level.generator.Normal Maven / Gradle / Ivy
package cn.nukkit.level.generator;
import cn.nukkit.api.Since;
import cn.nukkit.block.Block;
import cn.nukkit.block.BlockID;
import cn.nukkit.block.BlockStone;
import cn.nukkit.blockstate.BlockState;
import cn.nukkit.level.ChunkManager;
import cn.nukkit.level.biome.Biome;
import cn.nukkit.level.biome.BiomeSelector;
import cn.nukkit.level.biome.EnumBiome;
import cn.nukkit.level.format.generic.BaseFullChunk;
import cn.nukkit.level.generator.noise.vanilla.f.NoiseGeneratorOctavesF;
import cn.nukkit.level.generator.noise.vanilla.f.NoiseGeneratorPerlinF;
import cn.nukkit.level.generator.object.ore.OreType;
import cn.nukkit.level.generator.populator.impl.*;
import cn.nukkit.level.generator.populator.type.Populator;
import cn.nukkit.math.MathHelper;
import cn.nukkit.math.NukkitRandom;
import cn.nukkit.math.Vector3;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Nukkit's terrain generator
* Originally adapted from the PocketMine-MP generator by NycuRO and CreeperFace
* Mostly rewritten by DaPorkchop_
*
* The following classes, and others related to terrain generation are theirs and are intended for NUKKIT USAGE and should not be copied/translated to other server software
* such as BukkitPE, ClearSky, Genisys, PocketMine-MP, or others
*
* Normal.java
* MushroomPopulator.java
* DarkOakTreePopulator.java
* JungleBigTreePopulator.java
* JungleTreePopulaotr.java
* SavannaTreePopulator.java
* SwampTreePopulator.java
* BasicPopulator.java
* TreeGenerator.java
* HugeTreesGenerator.java
* BeachBiome.java
* ColdBeachBiome.java
* DesertBiome.java
* DesertHillsBiome.java
* DesertMBiome.java
* ExtremeHillsBiome.java
* ExtremeHillsEdgeBiome.java
* ExtremeHillsMBiome.java
* ExtremeHillsPlusBiome.java
* ExtremeHillsPlusMBiome.java
* StoneBeachBiome.java
* FlowerForestBiome.java
* ForestBiome.java
* ForestHillsBiome.java
* IcePlainsBiome.java
* IcePlainsSpikesBiome.java
* JungleBiome.java
* JungleEdgeBiome.java
* JungleEdgeMBiome.java
* JungleHillsBiome.java
* JungleMBiome.java
* MesaBiome.java
* MesaBryceBiome.java
* MesaPlateauBiome.java
* MesaPlateauFBiome.java
* MesaPlateauFMBiome.java
* MesaPlateauMBiome.java
* MushroomIslandBiome.java
* MushroomIslandShoreBiome.java
* DeepOceanBiome.java
* FrozenOceanBiome.java
* OceanBiome.java
* PlainsBiome.java
* SunflowerPlainsBiome.java
* FrozenRiverBiome.java
* RiverBiome.java
* RoofedForestBiome.java
* RoofedForestMBiome.java
* SavannaBiome.java
* SavannaMBiome.java
* SavannaPlateauBiome.java
* SavannaPlateauMBiome.java
* SwampBiome.java
* SwamplandMBiome.java
* ColdTaigaBiome.java
* ColdTaigaHillsBiome.java
* ColdTaigaMBiome.java
* MegaSpruceTaigaBiome.java
* MegaTaigaBiome.java
* MegaTagaHillsBiome.java
* TaigaBiome.java
* TaigaHillsBiome.java
* TaigaMBiome.java
* CoveredBiome.java
* GrassyBiome.java
* SandyBiome.java
* WateryBiome.java
* EnumBiomeBiome.java
* PopulatorCount.java
* PopulatorSurfaceBlock.java
* Normal.java
* Nether.java
* End.java
*/
public class Normal extends Generator {
public static final int seaHeight = 64;
private static final float[] biomeWeights = new float[25];
static {
for (int i = -2; i <= 2; ++i) {
for (int j = -2; j <= 2; ++j) {
biomeWeights[i + 2 + (j + 2) * 5] = (float) (10.0F / Math.sqrt((float) (i * i + j * j) + 0.2F));
}
}
}
public NoiseGeneratorOctavesF scaleNoise;
public NoiseGeneratorOctavesF depthNoise;
private List populators = Collections.emptyList();
private List generationPopulators = Collections.emptyList();
private ChunkManager level;
private Random random;
private NukkitRandom nukkitRandom;
private long localSeed1;
private long localSeed2;
private BiomeSelector selector;
private final ThreadLocal biomes = ThreadLocal.withInitial(() -> new Biome[10 * 10]);
private final ThreadLocal depthRegion = ThreadLocal.withInitial(() -> null);
private final ThreadLocal mainNoiseRegion = ThreadLocal.withInitial(() -> null);
private final ThreadLocal minLimitRegion = ThreadLocal.withInitial(() -> null);
private final ThreadLocal maxLimitRegion = ThreadLocal.withInitial(() -> null);
private final ThreadLocal heightMap = ThreadLocal.withInitial(() -> new float[825]);
private NoiseGeneratorOctavesF minLimitPerlinNoise;
private NoiseGeneratorOctavesF maxLimitPerlinNoise;
private NoiseGeneratorOctavesF mainPerlinNoise;
private NoiseGeneratorPerlinF surfaceNoise;
public Normal() {
this(Collections.emptyMap());
}
public Normal(Map options) {
//Nothing here. Just used for future update.
}
@Override
public int getId() {
return TYPE_INFINITE;
}
@Override
public ChunkManager getChunkManager() {
return this.level;
}
@Override
public String getName() {
return "normal";
}
@Override
public Map getSettings() {
return Collections.emptyMap();
}
public Biome pickBiome(int x, int z) {
return this.selector.pickBiome(x, z);
}
@Override
public void init(ChunkManager level, NukkitRandom random) {
this.level = level;
this.nukkitRandom = random;
this.random = new Random();
this.nukkitRandom.setSeed(this.level.getSeed());
this.localSeed1 = this.random.nextLong();
this.localSeed2 = this.random.nextLong();
this.nukkitRandom.setSeed(this.level.getSeed());
this.selector = new BiomeSelector(this.nukkitRandom);
this.minLimitPerlinNoise = new NoiseGeneratorOctavesF(random, 16);
this.maxLimitPerlinNoise = new NoiseGeneratorOctavesF(random, 16);
this.mainPerlinNoise = new NoiseGeneratorOctavesF(random, 8);
this.surfaceNoise = new NoiseGeneratorPerlinF(random, 4);
this.scaleNoise = new NoiseGeneratorOctavesF(random, 10);
this.depthNoise = new NoiseGeneratorOctavesF(random, 16);
//this should run before all other populators so that we don't do things like generate ground cover on bedrock or something
this.generationPopulators = ImmutableList.of(
new PopulatorBedrock(),
new PopulatorGroundCover()
);
this.populators = ImmutableList.of(
new PopulatorOre(STONE, new OreType[]{
new OreType(Block.get(BlockID.COAL_ORE), 20, 17, 0, 128),
new OreType(Block.get(BlockID.IRON_ORE), 20, 9, 0, 64),
new OreType(Block.get(BlockID.REDSTONE_ORE), 8, 8, 0, 16),
new OreType(Block.get(BlockID.LAPIS_ORE), 1, 7, 0, 16),
new OreType(Block.get(BlockID.GOLD_ORE), 2, 9, 0, 32),
new OreType(Block.get(BlockID.DIAMOND_ORE), 1, 8, 0, 16),
new OreType(Block.get(BlockID.DIRT), 10, 33, 0, 128),
new OreType(Block.get(BlockID.GRAVEL), 8, 33, 0, 128),
new OreType(Block.get(BlockID.STONE, BlockStone.GRANITE), 10, 33, 0, 80),
new OreType(Block.get(BlockID.STONE, BlockStone.DIORITE), 10, 33, 0, 80),
new OreType(Block.get(BlockID.STONE, BlockStone.ANDESITE), 10, 33, 0, 80)
}),
new PopulatorCaves(),
//new PopulatorRavines()
new PopulatorSpring(BlockState.of(BlockID.WATER), ImmutableList.of(BlockState.of(BlockID.STONE)), 50, 8, 255),
new PopulatorSpring(BlockState.of(BlockID.LAVA), ImmutableList.of(BlockState.of(BlockID.STONE)), 20, 16, 255)
);
}
@Override
public void generateChunk(final int chunkX, final int chunkZ) {
int baseX = chunkX << 4;
int baseZ = chunkZ << 4;
this.nukkitRandom.setSeed(chunkX * this.localSeed1 ^ chunkZ * this.localSeed2 ^ this.level.getSeed());
BaseFullChunk chunk = this.level.getChunk(chunkX, chunkZ);
//generate base noise values
float[] depthRegion = this.depthNoise.generateNoiseOctaves(this.depthRegion.get(), chunkX * 4, chunkZ * 4, 5, 5, 200f, 200f, 0.5f);
this.depthRegion.set(depthRegion);
float[] mainNoiseRegion = this.mainPerlinNoise.generateNoiseOctaves(this.mainNoiseRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f / 60f, 684.412f / 160f, 684.412f / 60f);
this.mainNoiseRegion.set(mainNoiseRegion);
float[] minLimitRegion = this.minLimitPerlinNoise.generateNoiseOctaves(this.minLimitRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f, 684.412f, 684.412f);
this.minLimitRegion.set(minLimitRegion);
float[] maxLimitRegion = this.maxLimitPerlinNoise.generateNoiseOctaves(this.maxLimitRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f, 684.412f, 684.412f);
this.maxLimitRegion.set(maxLimitRegion);
float[] heightMap = this.heightMap.get();
//generate heightmap and smooth biome heights
int horizCounter = 0;
int vertCounter = 0;
for (int xSeg = 0; xSeg < 5; ++xSeg) {
for (int zSeg = 0; zSeg < 5; ++zSeg) {
float heightVariationSum = 0.0F;
float baseHeightSum = 0.0F;
float biomeWeightSum = 0.0F;
Biome biome = this.pickBiome(baseX + (xSeg * 4), baseZ + (zSeg * 4));
for (int xSmooth = -2; xSmooth <= 2; ++xSmooth) {
for (int zSmooth = -2; zSmooth <= 2; ++zSmooth) {
Biome biome1 = this.pickBiome(baseX + (xSeg * 4) + xSmooth, baseZ + (zSeg * 4) + zSmooth);
float baseHeight = biome1.getBaseHeight();
float heightVariation = biome1.getHeightVariation();
float scaledWeight = biomeWeights[xSmooth + 2 + (zSmooth + 2) * 5] / (baseHeight + 2.0F);
if (biome1.getBaseHeight() > biome.getBaseHeight()) {
scaledWeight /= 2.0F;
}
heightVariationSum += heightVariation * scaledWeight;
baseHeightSum += baseHeight * scaledWeight;
biomeWeightSum += scaledWeight;
}
}
heightVariationSum = heightVariationSum / biomeWeightSum;
baseHeightSum = baseHeightSum / biomeWeightSum;
heightVariationSum = heightVariationSum * 0.9F + 0.1F;
baseHeightSum = (baseHeightSum * 4.0F - 1.0F) / 8.0F;
float depthNoise = depthRegion[vertCounter] / 8000.0f;
if (depthNoise < 0.0f) {
depthNoise = -depthNoise * 0.3f;
}
depthNoise = depthNoise * 3.0f - 2.0f;
if (depthNoise < 0.0f) {
depthNoise = depthNoise / 2.0f;
if (depthNoise < -1.0f) {
depthNoise = -1.0f;
}
depthNoise = depthNoise / 1.4f;
depthNoise = depthNoise / 2.0f;
} else {
if (depthNoise > 1.0f) {
depthNoise = 1.0f;
}
depthNoise = depthNoise / 8.0f;
}
++vertCounter;
float baseHeightClone = baseHeightSum;
float heightVariationClone = heightVariationSum;
baseHeightClone = baseHeightClone + depthNoise * 0.2f;
baseHeightClone = baseHeightClone * 8.5f / 8.0f;
float baseHeightFactor = 8.5f + baseHeightClone * 4.0f;
for (int ySeg = 0; ySeg < 33; ++ySeg) {
float baseScale = ((float) ySeg - baseHeightFactor) * 12f * 128.0f / 256.0f / heightVariationClone;
if (baseScale < 0.0f) {
baseScale *= 4.0f;
}
float minScaled = minLimitRegion[horizCounter] / 512f;
float maxScaled = maxLimitRegion[horizCounter] / 512f;
float noiseScaled = (mainNoiseRegion[horizCounter] / 10.0f + 1.0f) / 2.0f;
float clamp = MathHelper.denormalizeClamp(minScaled, maxScaled, noiseScaled) - baseScale;
if (ySeg > 29) {
float yScaled = ((float) (ySeg - 29) / 3.0F);
clamp = clamp * (1.0f - yScaled) + -10.0f * yScaled;
}
heightMap[horizCounter] = clamp;
++horizCounter;
}
}
}
//place blocks
for (int xSeg = 0; xSeg < 4; ++xSeg) {
int xScale = xSeg * 5;
int xScaleEnd = (xSeg + 1) * 5;
for (int zSeg = 0; zSeg < 4; ++zSeg) {
int zScale1 = (xScale + zSeg) * 33;
int zScaleEnd1 = (xScale + zSeg + 1) * 33;
int zScale2 = (xScaleEnd + zSeg) * 33;
int zScaleEnd2 = (xScaleEnd + zSeg + 1) * 33;
for (int ySeg = 0; ySeg < 32; ++ySeg) {
double height1 = heightMap[zScale1 + ySeg];
double height2 = heightMap[zScaleEnd1 + ySeg];
double height3 = heightMap[zScale2 + ySeg];
double height4 = heightMap[zScaleEnd2 + ySeg];
double height5 = (heightMap[zScale1 + ySeg + 1] - height1) * 0.125f;
double height6 = (heightMap[zScaleEnd1 + ySeg + 1] - height2) * 0.125f;
double height7 = (heightMap[zScale2 + ySeg + 1] - height3) * 0.125f;
double height8 = (heightMap[zScaleEnd2 + ySeg + 1] - height4) * 0.125f;
for (int yIn = 0; yIn < 8; ++yIn) {
double baseIncr = height1;
double baseIncr2 = height2;
double scaleY = (height3 - height1) * 0.25f;
double scaleY2 = (height4 - height2) * 0.25f;
for (int zIn = 0; zIn < 4; ++zIn) {
double scaleZ = (baseIncr2 - baseIncr) * 0.25f;
double scaleZ2 = baseIncr - scaleZ;
for (int xIn = 0; xIn < 4; ++xIn) {
if ((scaleZ2 += scaleZ) > 0.0f) {
chunk.setBlockId(xSeg * 4 + zIn, ySeg * 8 + yIn, zSeg * 4 + xIn, STONE);
} else if (ySeg * 8 + yIn <= seaHeight) {
chunk.setBlockId(xSeg * 4 + zIn, ySeg * 8 + yIn, zSeg * 4 + xIn, STILL_WATER);
}
}
baseIncr += scaleY;
baseIncr2 += scaleY2;
}
height1 += height5;
height2 += height6;
height3 += height7;
height4 += height8;
}
}
}
}
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
Biome biome = this.selector.pickBiome(baseX | x, baseZ | z);
chunk.setBiome(x, z, biome);
}
}
//populate chunk
for (Populator populator : this.generationPopulators) {
populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk);
}
}
@Override
public void populateChunk(int chunkX, int chunkZ) {
BaseFullChunk chunk = this.level.getChunk(chunkX, chunkZ);
this.nukkitRandom.setSeed(0xdeadbeef ^ ((long) chunkX << 8) ^ chunkZ ^ this.level.getSeed());
for (Populator populator : this.populators) {
populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk);
}
@SuppressWarnings("deprecation")
Biome biome = EnumBiome.getBiome(chunk.getBiomeId(7, 7));
biome.populateChunk(this.level, chunkX, chunkZ, this.nukkitRandom);
}
@Since("1.19.21-r2")
@Override
public boolean shouldGenerateStructures() {
return true;
}
@Override
public Vector3 getSpawn() {
return new Vector3(0.5, 256, 0.5);
}
}