FANDOM


Difficulty: Difficulty-4
Clock: 40+ minutes


Under Contruction

Under Construction
This page has been marked as under construction, which means information may change rapidly as it updated, or that the page is awaiting updates.


Introduction Edit

This tutorial will show you how to create a custom World Generator, note that it is still under construction and more sections will be added. So why would you want to create a custom World Generator? There could be multiple reasons why you want to, here's a few examples:

  • Perhaps you want to create a total conversion of Terraria, and therefore don't want original Terraria tiles generating in your world.
  • You want to remove an entire biome from the game (e.g: The Corruption, or The Jungle).
  • You want to create your own landscape.

Note: It is very hard to modify the original land generator, as it is a huge piece of code and it isn't documented well; writing a new one would likely be a lot easier and less time consuming.

RequirementsEdit

  • At least a basic understanding of C# programming or a comparable programming language, try googling or this tutorial. Advanced knowledge is recommended.
  • You need to have a basic understanding of how the Terraria world works. See Terraria World Basics for more informations.
  • tConfig must be installed correctly. See the Install Guide, if you have problems.
  • You need atleast tConfig v0.23.0. You can always download the newest version here: v0.35.3a.

PreperationsEdit

Before you can start to write your own world generator, you have to create a folder named "Global" in your mod folder, if you haven't done that already. In the "Global" folder you have to save a new, empty file named "World.cs" (Unless it already exists, in which case just edit it). This file will include all of our code and we're only working with this file the whole tutorial.

How to create a blank mapEdit

Okay, remember that all our code will be saved in "Global\World.cs". The function "GenerateWorld()" is called instead of the normal world generation function. Insert this code into our file:

   public void GenerateWorld()
   {
      
   }

We want to insert all our generation code between the brackets. First we have to set where the rocklayer and the surfacelayer start:

   public void GenerateWorld()
   {
      Main.worldSurface = (int)(Main.maxTilesY * 0.3 * WorldGen.genRand.Next(90, 110) * 0.01);
      Main.rockLayer = (int)(Main.worldSurface + Main.maxTilesY * 0.2 * WorldGen.genRand.Next(90, 110) * 0.007);
   }

This is somewhat simular to the way the rocklayer and surfacelayer are calculated in the original Terraria. WorldGen.genRand is a so called "Randomizer" - it is able to calculate random nonnegative numbers. If you call it by writing WorldGen.genRand.Next() it will return an integer number. There are multiple ways to make it calculate the randomized number. If you put a single integer number into the brackets, it will return a number that is smaller than the one you put into the brackets. If you put two numbers into it (like in the example above), it will return a number that is between the two numbers but will never be as high as the highest number.

For the surfacelayer it multiplies the height of the map by 0.3 (30% of the height) and after that it multiplies it by a random number that can vary from 0.90 to 1.09 (90% to 109%). The start of the rocklayer is calcuated this way: First it takes 20% of the map height (Main.maxTilesY * 0.2), afterwards it multiplies it by a random number that can vary from 0.63 to 0.76 (63% to 76%) and at the end adds the start coordinates of the surfacelayer to it (multiplication and division first, then addition and subtraction). This is to make sure that the rocklayer always starts under the surfacelayer.

Note; This calculation is not necessary but it adds a good element of randomness. You can set the start of the surfacelayer or rocklayer to any number you want but you have to set it, otherwise it will be zero or will have the same value as the last world you played in. Cave Worlds are also possible, just set the surfacelayer and rocklayer to zero.

Okay, now that we're done with that we want to setup the spawn position because otherwise it will be [0, 0] (and therefore crash the game) or it will be adopted from the last world you played in. The code for setting the start position is pretty simple:

   Main.spawnTileX = (int)(Main.maxTilesX / 2);
   Main.spawnTileY = (int)(Main.worldSurface - 16);

It will set your start position to the middle of the map and a bit above the start of the surface layer. But since our map will be empty, it doesn't matter much yet.

Afterwards we will generate "empty" tiles. We will do this with the following code:

   for (int i = 0; i < Main.maxTilesX; i++)
   {
      for (int j = 0; j < Main.maxTilesY; j++)
      {
         // <--- This probably isn't necessary but will prevent crashes.
         if(Main.tile[i, j] == null) 
         { 
            Main.tile[i, j] = new Tile(); 
         }
         /// --->
         Main.tile[i, j].active = false;
         Main.tile[i, j].liquid = 0;
         Main.tile[i, j].type = 0;
         Main.tile[i, j].frameX = -1;
         Main.tile[i, j].frameY = -1;
         Main.tile[i, j].wall = 0;
      }
   }

This code will start with two for next loops, to fill the tile array with empty values. An empty tile isn't active (Main.tile[i, j].active = false), has no liquid (Main.tile[i, j].liquid = 0) and has no walls (Main.tile[i, j].wall = 0). We set the type, the frameX and frameY to initial values, just to make sure everything is empty as it should be.

Okay, now we would be able to generate a map, however; we will surely fall to our death if we try to play. So we're going to replace a bit of our code with this little piece of code in the for next loop, to create a so called "flat world":

   if(j >= Main.worldSurface - 3) 
   { 
      Main.tile[i, j].active = true;
      if(j >= Main.worldSurface - 2 && j <= Main.worldSurface + 1)
      {
         Main.tile[i, j].wall = 2;
      }
   }
   else
   {
      Main.tile[i, j].active = false;
      Main.tile[i, j].wall = 0;
   }

It will check if j (the current y position of the tile we want to place) is bigger or equal (under or at the same height as) to the surfacelayer position minus three, if it is bigger it will set the current tile to active. This means it will create layers of tiles (dirt) starting at the surfacelayer position minus three. After that it checks if j is bigger or equal (under or at the same height as) the surfacelayer minus two and smaller or equal (above or at the same height as) the surfacelayer plus 1, if that is true it will set dirt walls. The code will produce 4 layers of dirt walls ranging from the start of the surfacelayer minus two to the start of the surfacelayer plus one (one tile under the start of the surfacelayer).

Our final code will look like this:

   public void GenerateWorld()
   {
      Main.worldSurface = (int)(Main.maxTilesY * 0.3 * WorldGen.genRand.Next(90, 110) * 0.01);
      Main.rockLayer = (int)(Main.worldSurface + Main.maxTilesY * 0.2 * WorldGen.genRand.Next(90, 110) * 0.007);
      Main.spawnTileX = (int)(Main.maxTilesX / 2);
      Main.spawnTileY = (int)(Main.worldSurface - 16);
      for (int i = 0; i < Main.maxTilesX; i++)
      {
         for (int j = 0; j < Main.maxTilesY; j++)
         {
            // <--- This probably isn't necessary but will prevent crashes.
            if(Main.tile[i, j] == null) 
            { 
               Main.tile[i, j] = new Tile(); 
            }
            /// --->
            if(j >= Main.worldSurface - 3) 
            { 
               Main.tile[i, j].active = true;
               if(j >= Main.worldSurface - 2 && j <= Main.worldSurface + 1)
               {
                  Main.tile[i, j].wall = 2;
               }
            }
            else
            {
               Main.tile[i, j].active = false;
               Main.tile[i, j].wall = 0;
            }
            Main.tile[i, j].liquid = 0;
            Main.tile[i, j].type = 0;
            Main.tile[i, j].frameX = -1;
            Main.tile[i, j].frameY = -1;
         }
      }
   }

You may now run the ModPack Builder and run a test! Just go and experiment with it, if you like! For quick testing, leave your favorite sourcecode editor (or text editor) open and edit the code, run Terraria and the ModPack Builder. If you want to test; save your code, execute the (still open) ModPack Builder and build your mod, go into Terraria and reload your mod. To do that, just go into the tConfig Settings menu and set your mod to "off" and then "on", after that click on "Reload Mods".

For example you could spawn stone instead of dirt, just set the type to one instead of zero. If you want grass, insert the following code into the for next loop, just before it closes.

   WorldGen.SpreadGrass(i, (int)(Main.worldSurface - 3), 0, 2, false);

How to create a surfaceEdit

Allright, this is where stuff becomes much harder, so I will assume you have some experience with programming. Now we want to create a more natural surface with hills, plains, mountains and so on. Before we start, we have to settle some things:

The underground of the map we are going to generate will be almost entirely filled with stone and the surface will be divided into sections. Each section has a specific type and width.

  • Type 0 will be flat land, with small variation only.
  • Type 1 will be a small increase of the height.
  • Type 2 will be a small decrease of the height.
  • Type 3 will be a big increase of the height.
  • Type 4 will be a big decrease of the height.

Note that this is what the original world generator does too.

Remember that we need the GenerateWorld() function, also remember that deeper tiles are at a "bigger" position of the tiles array.

   public void GenerateWorld()
   {
      int SectionType = 0;
      int SectionWidth = 0;
      double SectionHeight = (double)Main.maxTilesY * 0.3;
      SectionHeight *= (double)WorldGen.genRand.Next(90, 110) * 0.005;
      double rockLayer = SectionHeight + (double)Main.maxTilesY * 0.2;
      rockLayer *= (double)WorldGen.genRand.Next(90, 110) * 0.01;
      double worldSurfaceL = SectionHeight;
      double rockLayerL = rockLayer;

First it initializes the SectionType as well as the SectionWidth and set both to zero. For the SectionHeight, which will be the height of the first section, it takes 30% of the map height and afterwards it multiplies it with a value that can range from 0,45 to 0,54 (45% to 54%) for randomization. To make sure that the rockLayer starts is under the surface it takes the height of the section, adds another 20% of the map height for it and after that randomization will be done again (90% to 109% this time). On a small map, this will make the height of the surface section ranging from 162 to 194 and the rockLayer ranging from 378 to 456 in Y position. We also want to know which Y position the deepest point of the worldSurface has and which Y position the deepest point where the rockLayer starts at has. This will be used later to calculate the start of the rockLayer and the worldSurface for the map.

      for (int i = 0; i < Main.maxTilesX; i++)
      {

Now we start a for next loop, to create the tiles from the left border to the right border of the map.

         if (SectionHeight > worldSurfaceL)
         {
            worldSurfaceL = SectionHeight;
         }
         if (rockLayer > rockLayerL)
         {
            rockLayerL = rockLayer;
         }

This is the calculation for the deepest points.

         if (SectionWidth <= 0)
         {
            SectionType = WorldGen.genRand.Next(0, 5);
            SectionWidth = WorldGen.genRand.Next(5, 40);
            if (SectionType == 0)
            {
               SectionWidth *= (int)((double)WorldGen.genRand.Next(5, 30) * 0.2);
            }
         }
         SectionWidth--;

Allright, the variable SectionWidth is not only the variable containing the width of the section but is also a counter. Everytime we setup a line of blocks from the top to the buttom of the world, the SectionWidth will be reduced, because we're finished with that vertical line. If the SectionWidth is zero, we will setup a new section with a width ranging from 5 to 39 and a type ranging from 0 to 4. If the section is a flat section, it's size may increase almost sixfold, it will never be reduced though, as 5 * 0.2 is 1 and 29 * 0.2 is 5.8. This will make flat sections bigger.

         if (SectionType == 0) // small variation only
         {
            while (WorldGen.genRand.Next(0, 7) == 0)
            {
               SectionHeight += (double)WorldGen.genRand.Next(-1, 2);
            }
         }
         else if (SectionType == 1) // generally decrease (higher ground)
         {
            while (WorldGen.genRand.Next(0, 4) == 0)
            {
               SectionHeight -= 1.0;
            }
            while (WorldGen.genRand.Next(0, 10) == 0)
            {
               SectionHeight += 1.0;
            }
         }
         else if (SectionType == 2) // generally increase (lower ground)
         {
            while (WorldGen.genRand.Next(0, 4) == 0)
            {
               SectionHeight += 1.0;
            }
            while (WorldGen.genRand.Next(0, 10) == 0)
            {
               SectionHeight -= 1.0;
            }
         }
         else if (SectionType == 3) // steep decrease (much higher ground)
         {
            while (WorldGen.genRand.Next(0, 2) == 0)
            {
               SectionHeight -= 1.0;
            }
            while (WorldGen.genRand.Next(0, 6) == 0)
            {
               SectionHeight += 1.0;
            }
         }
         else if (SectionType == 4) // steep increase (much lower ground)
         {
            while (WorldGen.genRand.Next(0, 2) == 0)
            {
               SectionHeight += 1.0;
            }
            while (WorldGen.genRand.Next(0, 5) == 0)
            {
               SectionHeight -= 1.0;
            }
         }

This section is a bit tricky. We use WorldGen.GenRand.Next() to generate a number ranging from 0 to another number, if the returned number is zero, we will decrease or increase the section. Note that we use while instead of if, this means if WorldGen.GenRand.Next() returns 0 multiple times, the section will be increased or decreased multiple times as well. A good example for this is SectionType 4. It checks if the value generated by the randomizer is zero, if it is it increases the number, therefor making it deeper in the game. It's equal to "in 1 of 2 cases increase the SectionHeight by 1.0", so the chance for this happening once is 50%, the chance for this happening twice is 25% (50% * 50% = 0.5 * 0.5), the third time it is only 12.5, etc. So it is likely, that this happens more than once. When it comes to Type 0, it is much less likely, only in 1 of 7 cases it will either increase or decrease by 1.0 and that is only 14% (1 divided by 7 equals 0.14), the same thing happening twice is only 2%, that is next to nothing.

         if (SectionHeight < (double)Main.maxTilesY * 0.17)
         {
            SectionHeight = (double)Main.maxTilesY * 0.17;
            SectionWidth = 0;
         }
         else if (SectionHeight > (double)Main.maxTilesY * 0.3)
         {
            SectionHeight = (double)Main.maxTilesY * 0.3;
            SectionWidth = 0;
         }

This code will set the maximum and minimum values of the SectionHeight, on a small map the deepest it can go is 360 and the highest it can go is 204 in Y position. Also if the maximum or minimum is reached, the SectionWidth will be set to zero, this will make sure, that the SectionHeight will go normal soon again.

         if ((i < 275 || i > Main.maxTilesX - 275) && SectionHeight > (double)Main.maxTilesY * 0.25)
         {
            SectionHeight = (double)Main.maxTilesY * 0.25;
            SectionWidth = 1;
         }

This code will control the height at the map edges. On a small map it will be around 300 and that is relatively deep.

         while (WorldGen.genRand.Next(0, 3) == 0)
         {
            rockLayer += (double)WorldGen.genRand.Next(-2, 3);
         }
         if (rockLayer < SectionHeight + (double)Main.maxTilesY * 0.05)
         {
            rockLayer += 1.0;
         }
         if (rockLayer > SectionHeight + (double)Main.maxTilesY * 0.35)
         {
            rockLayer -= 1.0;
         }

This will control the rocklayer a bit. In 1 of 3 cases it is allowed to increase or decrease ranging from -2 to 2. If it goes too near to the SectionHeight (60 blocks away in a small world) it will increase and if goes too far away (420 blocks away in a small world) from it, it will decrease. Okay, we're almost done, now we can setup the actual blocks:

         for (int j = 0; j < Main.maxTilesY; j++)
         {
            if ((double)j < SectionHeight)
            {
               Main.tile[i, j].active = false;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
            else if ((double)j < rockLayer)
            {
               Main.tile[i, j].active = true;
               Main.tile[i, j].type = 0;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
            else
            {
               Main.tile[i, j].active = true;
               Main.tile[i, j].type = 1;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
         }
      }

We setup a line of blocks from the top to the bottom of the map. The variable j contains the current Y position we're working with. If it is higher than the SectionHeight, the block will be air, if it is below the rocklayer, the block will be stone, everything else is just normal dirt.

      Main.worldSurface = worldSurfaceL + 25.0;
      Main.rockLayer = Main.worldSurface + (double)((int)((rockLayerL - Main.worldSurface) / 6.0) * 6);
      
      int spawnTileX = Main.maxTilesX / 2 + WorldGen.genRand.Next(-5, 6);
      for (int spawnTileY = 0; spawnTileY < Main.maxTilesY; spawnTileY++)
      {
         if (Main.tile[spawnTileX, spawnTileY].active)
         {
            Main.spawnTileX = spawnTileX;
            Main.spawnTileY = spawnTileY;
            break;
         }
      }

The worldSurface is 25 blocks below the lowest point of the surface. The rockLayer starts the at worldSurface plus the deepest point where the rockLayer starts minus the worldSurface. Note that these weird calculations are done, to cut the post decimal positions from the number, otherwise we might run into trouble. Now for the spawn, it will be at the middle of the map, plus or minus 5. Also it will be on the surface of the map. This is our full code:

   public void GenerateWorld()
   {
      int SectionType = 0;
      int SectionWidth = 0;
      double SectionHeight = (double)Main.maxTilesY * 0.3;
      SectionHeight *= (double)WorldGen.genRand.Next(90, 110) * 0.005;
      double rockLayer = SectionHeight + (double)Main.maxTilesY * 0.2;
      rockLayer *= (double)WorldGen.genRand.Next(90, 110) * 0.01;
      double worldSurfaceL = SectionHeight;
      double rockLayerL = rockLayer;

      for (int i = 0; i < Main.maxTilesX; i++)
      {

         if (SectionHeight > worldSurfaceL)
         {
            worldSurfaceL = SectionHeight; // the deepest the surface goes down
         }
         if (rockLayer > rockLayerL)
         {
            rockLayerL = rockLayer;  // the lowest the rocklayer starts
         }

// when the section runs out, make a new section. 
         if (SectionWidth <= 0)
         {
            SectionType = WorldGen.genRand.Next(0, 5);
            SectionWidth = WorldGen.genRand.Next(5, 40);
            if (SectionType == 0)
            {
               SectionWidth *= (int)((double)WorldGen.genRand.Next(5, 30) * 0.2);
            }
         }
         SectionWidth--;
// end section width assesment, begin section height assessment

         if (SectionType == 0) // small variation only
         {
            while (WorldGen.genRand.Next(0, 7) == 0)
            {
               SectionHeight += (double)WorldGen.genRand.Next(-1, 2);
            }
         }
         else if (SectionType == 1) // generally decrease (higher ground)
         {
            while (WorldGen.genRand.Next(0, 4) == 0)
            {
               SectionHeight -= 1.0;
            }
            while (WorldGen.genRand.Next(0, 10) == 0)
            {
               SectionHeight += 1.0;
            }
         }
         else if (SectionType == 2) // generally increase (lower ground)
         {
            while (WorldGen.genRand.Next(0, 4) == 0)
            {
               SectionHeight += 1.0;
            }
            while (WorldGen.genRand.Next(0, 10) == 0)
            {
               SectionHeight -= 1.0;
            }
         }
         else if (SectionType == 3) // steep decrease (much higher ground)
         {
            while (WorldGen.genRand.Next(0, 2) == 0)
            {
               SectionHeight -= 1.0;
            }
            while (WorldGen.genRand.Next(0, 6) == 0)
            {
               SectionHeight += 1.0;
            }
         }
         else if (SectionType == 4) // steep increase (much lower ground)
         {
            while (WorldGen.genRand.Next(0, 2) == 0)
            {
               SectionHeight += 1.0;
            }
            while (WorldGen.genRand.Next(0, 5) == 0)
            {
               SectionHeight -= 1.0;
            }
         }

// if the section is too high or low, put the height at the bound and stop the section. 
         if (SectionHeight < (double)Main.maxTilesY * 0.17)
         {
            SectionHeight = (double)Main.maxTilesY * 0.17;
            SectionWidth = 0;
         }
         else if (SectionHeight > (double)Main.maxTilesY * 0.3)
         {
            SectionHeight = (double)Main.maxTilesY * 0.3;
            SectionWidth = 0;
         }

// control height at the sides of the world
         if ((i < 275 || i > Main.maxTilesX - 275) && SectionHeight > (double)Main.maxTilesY * 0.25)
         {
            SectionHeight = (double)Main.maxTilesY * 0.25;
            SectionWidth = 1;
         }

// push rocklayer down to between .05-.35maxY below the surface
         while (WorldGen.genRand.Next(0, 3) == 0)
         {
            rockLayer += (double)WorldGen.genRand.Next(-2, 3);
         }
         if (rockLayer < SectionHeight + (double)Main.maxTilesY * 0.05)
         {
            rockLayer += 1.0;
         }
         if (rockLayer > SectionHeight + (double)Main.maxTilesY * 0.35)
         {
            rockLayer -= 1.0;
         }

// make empty tiles down to SectionHeight, dirt down to rockLayer, stone below that
         for (int j = 0; j < Main.maxTilesY; j++)
         {
            if ((double)j < SectionHeight)
            {
               Main.tile[i, j].active = false;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
            else if ((double)j < rockLayer)
            {
               Main.tile[i, j].active = true;
               Main.tile[i, j].type = 0;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
            else
            {
               Main.tile[i, j].active = true;
               Main.tile[i, j].type = 1;
               Main.tile[i, j].frameX = -1;
               Main.tile[i, j].frameY = -1;
            }
         }
      }

// now that the surface is made, set some values for levels. 
      Main.worldSurface = worldSurfaceL + 25.0;
      Main.rockLayer = Main.worldSurface + (double)((int)((rockLayerL - Main.worldSurface) / 6.0) * 6);
      
      int spawnTileX = Main.maxTilesX / 2 + WorldGen.genRand.Next(-5, 6);
      for (int spawnTileY = 0; spawnTileY < Main.maxTilesY; spawnTileY++)
      {
         if (Main.tile[spawnTileX, spawnTileY].active)
         {
            Main.spawnTileX = spawnTileX;
            Main.spawnTileY = spawnTileY;
            break;
         }
      }
   }

Now we're done, enjoy your generated map and don't hesitate to experiment with this piece of code! Also, if you want grass now, here is the code:

// spawn grass on the surface
      for (int i = 0; i < Main.maxTilesX; i++)
      {
         for (int j = 0; j < Main.maxTilesY; j++)
         {
            if (Main.tile[i, j].active)
            {
               try
               {
                  WorldGen.grassSpread = 0;
                  WorldGen.SpreadGrass(i, j, 0, 2, true);
               }
               catch
               {
                  WorldGen.grassSpread = 0;
                  WorldGen.SpreadGrass(i, j, 0, 2, false);
               }
            }
         }
      }

CreditsEdit

Since I am the writer of this tutorial, only I am mentioned on the wiki, I think this is unfair for the persons who were helping figuring out, how to create a map generator and how the original generator works. So I want to thank cphlebas much for his help. Thank you.

- S. P. Gardebiter 06:43, April 10, 2012 (UTC)

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.