tAPI [Tutorial] The tile spread

Dradonhunter11

Official Terrarian
hi everyone,

This tutorial will show you how to make the tile spread for your mod, but before there some explain.

Why I should make tile spread?

The tile spread is usefull if you have a biome and you want to make it spread over the like corruption/crimson or hallow.

Can I use this without destroying the entire world?

yes, actually this tutorial will show you how to to do it now.

this an exemple from my mod

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.DataStructures;
using TAPI;

namespace Notch {
public class TileSpread : ModWorld
{
    public override void PostUpdate()
    {
        if(Main.netMode != 1)
        {
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 1.5E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next((int)Main.worldSurface - 1, Main.maxTilesY - 20);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
        }
    }

    private void UpdateTile(int x, int y)
    {
        Tile tile = Main.tile[x, y];
        if(!tile.inActive() && (tile.type == TileDef.byName["Notch:ApocalypseDirt"] || tile.type == TileDef.byName["Notch:ApocalypseDust"] || tile.type == TileDef.byName["Notch:ApocalypseStone"]) && Notch.ApoMod && WorldGen.genRand.Next(1) == 0)
        {
            bool flag = true;
            while(flag)
            {
                flag = false;
                int toX = x + WorldGen.genRand.Next(-3, 4);
                int toY = y + WorldGen.genRand.Next(-3, 4);
                bool tileChanged = false;
                int targetType = Main.tile[toX, toY].type;
                if(targetType == 1 || TileDef.moss[targetType])
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 59)
                {
                    for(int j = toX - 1; j <= toX + 1; j++)
                    {
                        for(int k = toY - 1; k <= toY + 1; k++)
                        {
                            if(!Main.tile[j, k].active() || !TileDef.solid[Main.tile[j, k].type])
                            {
                                tileChanged = true;
                            }
                            if(Main.tile[j, k].lava() && Main.tile[j, k].liquid > 0)
                            {
                                tileChanged = false;
                                j = toX + 1;
                                break;
                            }
                        }
                    }
                    if(tileChanged)
                    {
                        Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    }
                }
                else if(targetType == 60)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 53)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDust"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                }
                else if(targetType == 161)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true);
                    NetMessage.SendTileSquare(-1, toX, toY, 1);
                }
            }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true);
                    NetMessage.SendTileSquare(-1, toX, toY, 1);
                }
            }
        }
    }
}

this seem a little bit complex but I will explain each part right now.

The first part is actually really simple, it actually how to make the tile convert into your tile

Code:
public override void PostUpdate()
    {
        if(Main.netMode != 1)
        {
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 1.5E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next((int)Main.worldSurface - 1, Main.maxTilesY - 20);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
        }
    }

basically, this part is used to update the tile, but let see what every single line do,

1) for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++): That line actually determines how many tiles should update each tick.
2) int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10); : That line chooses a random x-coordinate where the tile will be updated.
3) int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1); : This do the same thing in 2) but with the y coordinate
4) if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive()) : this part make that tile exist and it active
5) UpdateTile(x, y); this is actually the part that finish to convert the tile

The second part is a little bit more complex
This time everything will be important, for that, I explain everything in the main code


Code:
  private void UpdateTile(int x, int y)
    {
        Tile tile = Main.tile[x, y]; // used to setup the tile variable
        if(!tile.inActive() && (tile.type == TileDef.byName["Notch:ApocalypseDirt"] || tile.type == TileDef.byName["Notch:ApocalypseDust"] || tile.type == TileDef.byName["Notch:ApocalypseStone"]) && Notch.ApoMod && WorldGen.genRand.Next(1) == 0)  // make sure that the tile is not actuator-inactivated, and make sure it is spreadable; feel free to replace 1 with a higher number to slow down the spreading
        {
            bool flag = true; // determines whether another tile should be converted
            while(flag)
            {
                flag = false; // no more tile should be converted after this
                int toX = x + WorldGen.genRand.Next(-3, 4); // get the x-coordinate of the tile to convert
                int toY = y + WorldGen.genRand.Next(-3, 4); // get the y-coordinate of the tile to convert
                bool tileChanged = false; // setup the tile changed variable
                int targetType = Main.tile[toX, toY].type; // gets the type of the tile to be converted
                if(targetType == 1 || TileDef.moss[targetType]) // checks the ID of the target tile to see if it is convertable (this one checks for stone)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"]; //this is the part of where you put your tile, it will convert the stone to that tile
                    tileChanged = true;
                }
                else if(targetType == 0) //this line actually do the the same as the stone target line, but with dirt
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 59) //this part is used for grass spread, basically it only spreads if there is any air next to the target, liquid in this case don't count
                {
                    for(int j = toX - 1; j <= toX + 1; j++)
                    {
                        for(int k = toY - 1; k <= toY + 1; k++)
                        {
                            if(!Main.tile[j, k].active() || !TileDef.solid[Main.tile[j, k].type])
                            {
                                tileChanged = true;
                            }
                            if(Main.tile[j, k].lava() && Main.tile[j, k].liquid > 0)
                            {
                                tileChanged = false;
                                j = toX + 1;
                                break;
                            }
                        }
                    }
                    if(tileChanged)
                    {
                        Main.tile[toX, toY].type = 0;
                    }
                }
                else if(targetType == 60)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 53)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDust"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                }
                else if(targetType == 161)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0) //if a tile was converted, determines whether another should be converted (feel free to replace the 1)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true); //updates nearby tiles
                    NetMessage.SendTileSquare(-1, toX, toY, 1); // this sent to the server the conversion of the tile
                }
            }

        }
    }

if you see you can see a variable named Notch.ApoMod, actually this allow to make the spread happen after my Eye of apocalypse is downed, you can do the same thing with your boss

if everything work it should give you this result

This is all you need to know abot tile spread, but there is one usefull tip for making it easy to find in your modWorld file

create a new file named World and save your tile spread code in that file, it will work

thanks to @bluemagic123 for helping me with the initial tile spread code and some correction on the tutorial!
also a big thank to the apocalypse mod team for supporting of making that tutorial!

other usefull tutorial that can help
all about biome by me

thanks!
 
Last edited:
hi everyone,

This tutorial will show you how to make the tile spread for your mod, but before there some explain.

Why I should make tile spread?

The tile spread is usefull if you have a biome and you want to make it spread over the like corruption/crimson or hallow.

Can I use this without destroying the entire world?

yes, actually this tutorial will show you how to to do it now.

this an exemple from my mod

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.DataStructures;
using TAPI;

namespace Notch {
public class TileSpread : ModWorld
{
    public override void PostUpdate()
    {
        if(Main.netMode != 1)
        {
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 1.5E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next((int)Main.worldSurface - 1, Main.maxTilesY - 20);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
        }
    }

    private void UpdateTile(int x, int y)
    {
        Tile tile = Main.tile[x, y];
        if(!tile.inActive() && (tile.type == TileDef.byName["Notch:ApocalypseDirt"] || tile.type == TileDef.byName["Notch:ApocalypseDust"] || tile.type == TileDef.byName["Notch:ApocalypseStone"]) && Notch.ApoMod && WorldGen.genRand.Next(1) == 0)
        {
            bool flag = true;
            while(flag)
            {
                flag = false;
                int toX = x + WorldGen.genRand.Next(-3, 4);
                int toY = y + WorldGen.genRand.Next(-3, 4);
                bool tileChanged = false;
                int targetType = Main.tile[toX, toY].type;
                if(targetType == 1 || TileDef.moss[targetType])
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 59)
                {
                    for(int j = toX - 1; j <= toX + 1; j++)
                    {
                        for(int k = toY - 1; k <= toY + 1; k++)
                        {
                            if(!Main.tile[j, k].active() || !TileDef.solid[Main.tile[j, k].type])
                            {
                                tileChanged = true;
                            }
                            if(Main.tile[j, k].lava() && Main.tile[j, k].liquid > 0)
                            {
                                tileChanged = false;
                                j = toX + 1;
                                break;
                            }
                        }
                    }
                    if(tileChanged)
                    {
                        Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    }
                }
                else if(targetType == 60)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 53)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDust"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                }
                else if(targetType == 161)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true);
                    NetMessage.SendTileSquare(-1, toX, toY, 1);
                }
            }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true);
                    NetMessage.SendTileSquare(-1, toX, toY, 1);
                }
            }
        }
    }
}

this seem a little bit complex but I will explain each part right now.

The first part is actually really simple, it actually how to make the tile convert into your tile

Code:
public override void PostUpdate()
    {
        if(Main.netMode != 1)
        {
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
            for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 1.5E-05 * Main.worldRate; k++)
            {
                int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10);
                int y = WorldGen.genRand.Next((int)Main.worldSurface - 1, Main.maxTilesY - 20);
                if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive())
                {
                    UpdateTile(x, y);
                }
            }
        }
    }

basically, this part is used to update the tile, but let see what every single line do,

1) for(int k = 0; k < Main.maxTilesX * Main.maxTilesY * 3E-05 * Main.worldRate; k++): That line actually determines how many tiles should update each tick.
2) int x = WorldGen.genRand.Next(10, Main.maxTilesX - 10); : That line chooses a random x-coordinate where the tile will be updated.
3) int y = WorldGen.genRand.Next(10, (int)Main.worldSurface - 1); : This do the same thing in 2) but with the y coordinate
4) if(Main.tile[x, y] != null && Main.tile[x, y].liquid <= 32 && Main.tile[x, y].nactive()) : this part make that tile exist and it active
5) UpdateTile(x, y); this is actually the part that finish to convert the tile

The second part is a little bit more complex
This time everything will be important, for that, I explain everything in the main code


Code:
  private void UpdateTile(int x, int y)
    {
        Tile tile = Main.tile[x, y]; // used to setup the tile variable
        if(!tile.inActive() && (tile.type == TileDef.byName["Notch:ApocalypseDirt"] || tile.type == TileDef.byName["Notch:ApocalypseDust"] || tile.type == TileDef.byName["Notch:ApocalypseStone"]) && Notch.ApoMod && WorldGen.genRand.Next(1) == 0)  // make sure that the tile is not actuator-inactivated, and make sure it is spreadable; feel free to replace 1 with a higher number to slow down the spreading
        {
            bool flag = true; // determines whether another tile should be converted
            while(flag)
            {
                flag = false; // no more tile should be converted after this
                int toX = x + WorldGen.genRand.Next(-3, 4); // get the x-coordinate of the tile to convert
                int toY = y + WorldGen.genRand.Next(-3, 4); // get the y-coordinate of the tile to convert
                bool tileChanged = false; // setup the tile changed variable
                int targetType = Main.tile[toX, toY].type; // gets the type of the tile to be converted
                if(targetType == 1 || TileDef.moss[targetType]) // checks the ID of the target tile to see if it is convertable (this one checks for stone)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"]; //this is the part of where you put your tile, it will convert the stone to that tile
                    tileChanged = true;
                }
                else if(targetType == 0) //this line actually do the the same as the stone target line, but with dirt
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 59) //this part is used for grass spread, basically it only spreads if there is any air next to the target, liquid in this case don't count
                {
                    for(int j = toX - 1; j <= toX + 1; j++)
                    {
                        for(int k = toY - 1; k <= toY + 1; k++)
                        {
                            if(!Main.tile[j, k].active() || !TileDef.solid[Main.tile[j, k].type])
                            {
                                tileChanged = true;
                            }
                            if(Main.tile[j, k].lava() && Main.tile[j, k].liquid > 0)
                            {
                                tileChanged = false;
                                j = toX + 1;
                                break;
                            }
                        }
                    }
                    if(tileChanged)
                    {
                        Main.tile[toX, toY].type = 0;
                    }
                }
                else if(targetType == 60)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                    tileChanged = true;
                }
                else if(targetType == 53)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDust"];
                    tileChanged = true;
                }
                else if(targetType == 0)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseDirt"];
                }
                else if(targetType == 161)
                {
                    Main.tile[toX, toY].type = TileDef.byName["Notch:ApocalypseStone"];
                    tileChanged = true;
                }
                if(tileChanged)
                {
                    if(WorldGen.genRand.Next(1) == 0) //if a tile was converted, determines whether another should be converted (feel free to replace the 1)
                    {
                        flag = true;
                    }
                    WorldGen.SquareTileFrame(toX, toY, true); //updates nearby tiles
                    NetMessage.SendTileSquare(-1, toX, toY, 1); // this sent to the server the conversion of the tile
                }
            }

        }
    }

if you see you can see a variable named Notch.ApoMod, actually this allow to make the spread happen after my Eye of apocalypse is downed, you can do the same thing with your boss

if everything work it should give you this result

This is all you need to know abot tile spread, but there is one usefull tip for making it easy to find in your modWorld file

create a new file named World and save your tile spread code in that file, it will work

thanks!
This Is Awesome :)
 
This Is Awesome :)
Awesome lag generator you mean :p
Only your first loop iterates 152 times every postUpdate() call(lol!)(if world updates every frame it would be like 9120 calls/s and im not even counting secound loop)
 
Last edited:
Awesome lag generator you mean :p
Only your first loop iterates 152 times every postUpdate() call(lol!)(if world updates every frame it would be like 9120 calls/s and im not even counting secound loop)


where did you put that? in modtile?

for no lag you need to put it in your mod world
 
Awesome lag generator you mean :p
Only your first loop iterates 152 times every postUpdate() call(lol!)(if world updates every frame it would be like 9120 calls/s and im not even counting secound loop)
Do you realize that that number of iterations is literally nothing compared to the number of iterations the Terraria source code itself does? :p
In fact, those for loops are taken from the source code itself, for updating corruption / crimson / hallow blocks, plants, and pretty much anything that randomly updates.
 
Do you realize that that number of iterations is literally nothing compared to the number of iterations the Terraria source code itself does? :p
In fact, those for loops are taken from the source code itself, for updating corruption / crimson / hallow blocks, plants, and pretty much anything that randomly updates.
And that's optimal way ?
I mean isnt it better to reduce the loops than later slow down spread by random function ?
 
And that's optimal way ?
Yes, because tAPI lacks hooks for random updates. The other option is to use Update in ModTile, which gets called every single tick and can eventually lead to millions of updates in a single tick. Even the Terraria source code, which regularly loops through all 1000 projectile slots many times per tick, isn't that bad.
Edit: I should also clarify, using this code myself has caused no change in performance that I can notice. It would be nice if tAPI had a random update hook though so multiple mods don't have to do this separately :(
 
Back
Top Bottom