Dimensions-1.9

This more advanced tutorial shows how you can make your own custom dimension. Note that this tutorial depends on the tutorial to add a custom command (to be able to teleport to your dimension) and also the tutorial on the configuration section.

Implementing your own dimension is fun and there really is a lot you can do to tweak your dimension. In this tutorial we will only show you the very basic dimension. There is a lot more that you can do. A good reference for doing more with dimensions is the vanilla 'ChunkProviderOverworld' class. Our dimension only implements a subset of the vanilla overworld.

The first thing you need is a WorldProvider implementation. In many cases every dimension has its own WorldProvider but it is actually possible to reuse the same world provider for multiple dimensions (RFTools Dimensions does that). Also note that there are lot more functions in WorldProvider that you can override to provide all kinds of interesting results. You can affect the time of the dimension, the sky, the weather, and much much more here.

Now we need the actual code that generates our landscape. This is a modified copy of ChunkProviderOverworld except that we separated the actual terrain generator in a separate class for clarity. The terrain we generate here is basically only doing terrain and caves. There are no structures, no ravines and so on. Feel free to check the vanilla code to see how to implement those. We also make our custom mob spawn here as the only creature. You can of course also change that:

publicclassTestChunkGeneratorimplementsIChunkGenerator{privatefinalWorldworldObj;privateRandomrandom;privateBiome[]biomesForGeneration;privateList<Biome.SpawnListEntry>mobs=Lists.newArrayList(newBiome.SpawnListEntry(EntityWeirdZombie.class,100,2,2));privateMapGenBasecaveGenerator=newMapGenCaves();privateNormalTerrainGeneratorterraingen=newNormalTerrainGenerator();publicTestChunkGenerator(WorldworldObj){this.worldObj=worldObj;longseed=worldObj.getSeed();this.random=newRandom((seed+516)*314);terraingen.setup(worldObj,random);caveGenerator=TerrainGen.getModdedMapGen(caveGenerator,CAVE);}@OverridepublicChunkprovideChunk(intx,intz){ChunkPrimerchunkprimer=newChunkPrimer();// Setup biomes for terraingenthis.biomesForGeneration=this.worldObj.getBiomeProvider().getBiomesForGeneration(this.biomesForGeneration,x*4-2,z*4-2,10,10);terraingen.setBiomesForGeneration(biomesForGeneration);terraingen.generate(x,z,chunkprimer);// Setup biomes again for actual biome decorationthis.biomesForGeneration=this.worldObj.getBiomeProvider().getBiomes(this.biomesForGeneration,x*16,z*16,16,16);// This will replace stone with the biome specific stonesterraingen.replaceBiomeBlocks(x,z,chunkprimer,this,biomesForGeneration);// Generate cavesthis.caveGenerator.generate(this.worldObj,x,z,chunkprimer);Chunkchunk=newChunk(this.worldObj,chunkprimer,x,z);byte[]biomeArray=chunk.getBiomeArray();for(inti=0;i<biomeArray.length;++i){biomeArray[i]=(byte)Biome.getIdForBiome(this.biomesForGeneration[i]);}chunk.generateSkylightMap();returnchunk;}@Overridepublicvoidpopulate(intx,intz){inti=x*16;intj=z*16;BlockPosblockpos=newBlockPos(i,0,j);Biomebiome=this.worldObj.getBiome(blockpos.add(16,0,16));// Add biome decorations (like flowers, grass, trees, ...)biome.decorate(this.worldObj,this.random,blockpos);// Make sure animals appropriate to the biome spawn here when the chunk is generatedWorldEntitySpawner.performWorldGenSpawning(this.worldObj,biome,i+8,j+8,16,16,this.random);}@OverridepublicbooleangenerateStructures(ChunkchunkIn,intx,intz){returnfalse;}@OverridepublicList<Biome.SpawnListEntry>getPossibleCreatures(EnumCreatureTypecreatureType,BlockPospos){// If you want normal creatures appropriate for this biome then uncomment the// following two lines:// Biome biome = this.worldObj.getBiome(pos);// return biome.getSpawnableList(creatureType);if(creatureType==EnumCreatureType.MONSTER){returnmobs;}returnImmutableList.of();}@Nullable@OverridepublicBlockPosgetStrongholdGen(WorldworldIn,StringstructureName,BlockPosposition){returnnull;}@OverridepublicvoidrecreateStructures(ChunkchunkIn,intx,intz){}}

Now we need the actual terrain generator. This is pretty complicated code that involves a lot of math (perlin noise functions and so on). We will not explain those in detail. I suggest you try to tweak the values here and see what effects that all gives. The basic purpose of this code is to fill a heightmap and then generate a chunk based on that. Chunks are generated in the ChunkPrimer class. If you just want to create a flat terrain then that would be really easy to do with the primer:

publicclassNormalTerrainGenerator{privateWorldworld;privateRandomrandom;privatefinaldouble[]heightMap;privatedouble[]mainNoiseRegion;privatedouble[]minLimitRegion;privatedouble[]maxLimitRegion;privatedouble[]depthRegion;privateNoiseGeneratorOctavesminLimitPerlinNoise;privateNoiseGeneratorOctavesmaxLimitPerlinNoise;privateNoiseGeneratorOctavesmainPerlinNoise;privateNoiseGeneratorPerlinsurfaceNoise;// A NoiseGeneratorOctaves used in generating terrainprivateNoiseGeneratorOctavesdepthNoise;privatefinalfloat[]biomeWeights;privatedouble[]depthBuffer=newdouble[256];privateBiome[]biomesForGeneration;publicNormalTerrainGenerator(){this.heightMap=newdouble[825];this.biomeWeights=newfloat[25];for(intj=-2;j<=2;++j){for(intk=-2;k<=2;++k){floatf=10.0F/MathHelper.sqrt_float((j*j+k*k)+0.2F);this.biomeWeights[j+2+(k+2)*5]=f;}}}publicvoidsetBiomesForGeneration(Biome[]biomesForGeneration){this.biomesForGeneration=biomesForGeneration;}publicvoidsetup(Worldworld,Randomrand){this.world=world;this.random=rand;this.minLimitPerlinNoise=newNoiseGeneratorOctaves(rand,16);this.maxLimitPerlinNoise=newNoiseGeneratorOctaves(rand,16);this.mainPerlinNoise=newNoiseGeneratorOctaves(rand,8);this.surfaceNoise=newNoiseGeneratorPerlin(rand,4);NoiseGeneratorOctavesnoiseGen5=newNoiseGeneratorOctaves(rand,10);this.depthNoise=newNoiseGeneratorOctaves(rand,16);NoiseGeneratorOctavesmobSpawnerNoise=newNoiseGeneratorOctaves(rand,8);net.minecraftforge.event.terraingen.InitNoiseGensEvent.ContextOverworldctx=newnet.minecraftforge.event.terraingen.InitNoiseGensEvent.ContextOverworld(minLimitPerlinNoise,maxLimitPerlinNoise,mainPerlinNoise,surfaceNoise,noiseGen5,depthNoise,mobSpawnerNoise);ctx=net.minecraftforge.event.terraingen.TerrainGen.getModdedNoiseGenerators(world,rand,ctx);this.minLimitPerlinNoise=ctx.getLPerlin1();this.maxLimitPerlinNoise=ctx.getLPerlin2();this.mainPerlinNoise=ctx.getPerlin();this.surfaceNoise=ctx.getHeight();this.depthNoise=ctx.getDepth();}privatevoidgenerateHeightmap(intchunkX4,intchunkY4,intchunkZ4){this.depthRegion=this.depthNoise.generateNoiseOctaves(this.depthRegion,chunkX4,chunkZ4,5,5,200.0D,200.0D,0.5D);this.mainNoiseRegion=this.mainPerlinNoise.generateNoiseOctaves(this.mainNoiseRegion,chunkX4,chunkY4,chunkZ4,5,33,5,8.55515D,4.277575D,8.55515D);this.minLimitRegion=this.minLimitPerlinNoise.generateNoiseOctaves(this.minLimitRegion,chunkX4,chunkY4,chunkZ4,5,33,5,684.412D,684.412D,684.412D);this.maxLimitRegion=this.maxLimitPerlinNoise.generateNoiseOctaves(this.maxLimitRegion,chunkX4,chunkY4,chunkZ4,5,33,5,684.412D,684.412D,684.412D);intl=0;inti1=0;for(intj1=0;j1<5;++j1){for(intk1=0;k1<5;++k1){floatf=0.0F;floatf1=0.0F;floatf2=0.0F;byteb0=2;for(intl1=-b0;l1<=b0;++l1){for(inti2=-b0;i2<=b0;++i2){Biomebiome=this.biomesForGeneration[j1+2+(k1+2)*10];floatbaseHeight=biome.getBaseHeight();floatvariation=biome.getHeightVariation();floatf5=biomeWeights[l1+2+(i2+2)*5]/(baseHeight+2.0F);f+=variation*f5;f1+=baseHeight*f5;f2+=f5;}}f/=f2;f1/=f2;f=f*0.9F+0.1F;f1=(f1*4.0F-1.0F)/8.0F;doubled12=this.depthRegion[i1]/8000.0D;if(d12<0.0D){d12=-d12*0.3D;}d12=d12*3.0D-2.0D;if(d12<0.0D){d12/=2.0D;if(d12<-1.0D){d12=-1.0D;}d12/=1.4D;d12/=2.0D;}else{if(d12>1.0D){d12=1.0D;}d12/=8.0D;}++i1;doubled13=f1;doubled14=f;d13+=d12*0.2D;d13=d13*8.5D/8.0D;doubled5=8.5D+d13*4.0D;for(intj2=0;j2<33;++j2){doubled6=(j2-d5)*12.0D*128.0D/256.0D/d14;if(d6<0.0D){d6*=4.0D;}doubled7=this.minLimitRegion[l]/512.0D;doubled8=this.maxLimitRegion[l]/512.0D;doubled9=(this.mainNoiseRegion[l]/10.0D+1.0D)/2.0D;doubled10=MathHelper.denormalizeClamp(d7,d8,d9)-d6;if(j2>29){doubled11=((j2-29)/3.0F);d10=d10*(1.0D-d11)+-10.0D*d11;}this.heightMap[l]=d10;++l;}}}}publicvoidgenerate(intchunkX,intchunkZ,ChunkPrimerprimer){generateHeightmap(chunkX*4,0,chunkZ*4);bytewaterLevel=63;for(intx4=0;x4<4;++x4){intl=x4*5;inti1=(x4+1)*5;for(intz4=0;z4<4;++z4){intk1=(l+z4)*33;intl1=(l+z4+1)*33;inti2=(i1+z4)*33;intj2=(i1+z4+1)*33;for(intheight32=0;height32<32;++height32){doubled0=0.125D;doubled1=heightMap[k1+height32];doubled2=heightMap[l1+height32];doubled3=heightMap[i2+height32];doubled4=heightMap[j2+height32];doubled5=(heightMap[k1+height32+1]-d1)*d0;doubled6=(heightMap[l1+height32+1]-d2)*d0;doubled7=(heightMap[i2+height32+1]-d3)*d0;doubled8=(heightMap[j2+height32+1]-d4)*d0;for(inth=0;h<8;++h){doubled9=0.25D;doubled10=d1;doubled11=d2;doubled12=(d3-d1)*d9;doubled13=(d4-d2)*d9;intheight=(height32*8)+h;for(intx=0;x<4;++x){doubled14=0.25D;doubled16=(d11-d10)*d14;doubled15=d10-d16;for(intz=0;z<4;++z){if(height<2){primer.setBlockState(x4*4+x,height32*8+h,z4*4+z,Blocks.BEDROCK.getDefaultState());}elseif((d15+=d16)>0.0D){primer.setBlockState(x4*4+x,height32*8+h,z4*4+z,Blocks.STONE.getDefaultState());}}d10+=d12;d11+=d13;}d1+=d5;d2+=d6;d3+=d7;d4+=d8;}}}}}publicvoidreplaceBiomeBlocks(intx,intz,ChunkPrimerprimer,IChunkGeneratorgenerator,Biome[]biomes){if(!net.minecraftforge.event.ForgeEventFactory.onReplaceBiomeBlocks(generator,x,z,primer,this.world))return;this.depthBuffer=this.surfaceNoise.getRegion(this.depthBuffer,(x*16),(z*16),16,16,0.0625D,0.0625D,1.0D);for(inti=0;i<16;++i){for(intj=0;j<16;++j){Biomebiome=biomes[j+i*16];biome.genTerrainBlocks(this.world,this.random,primer,x*16+i,z*16+j,this.depthBuffer[j+i*16]);}}}}

Now finally we need to set this all up so that our dimension actually works in game. To do that we add a new ModDimensions class. There are two distinct things we need to register here. First there is the dimension type. This is actually an enum that you can extend at runtime (Forge magic) using DimensionType.register(). The dimension type maps a dimension type ID to your world provider. In this example the ID for the dimension type and the dimension are the same but that's not actually required.

Then we also need to register the dimension itself and map it to the dimension type.