1. Encountering issues with the Localization/Translations with 1.3.5? Please report them via the section linked below so that we can account for all of them and have them addressed. Thanks! Localization Bugs & Issues Section
  2. We have hub threads for known bugs/issues and statuses of fixes for Mobile, 3DS, and Console platforms (UPDATED):
    Click here for Mobile
    Click here for 3DS
    Click here for PS3/PS4/PS Vita/XBox 360/XBox One

tModLoader Official tModLoader Help Thread

Discussion in 'General Mod Discussion' started by Jofairden, Jul 30, 2015.

  1. Jofairden

    Jofairden Retinazer

    [​IMG]
    (last update: 25th of October 2016)


    Hello and welcome to the tModLoader Resources & Help thread!
    This thread should help you with any questions such as "How do I make my own tModLoader mod?" or "How can I make my own custom item?"
    This will also be the place to easily find resources, src (if permitted) and useful links.


    What is tModLoader?
    tModLoader is like an API such as the old tConfig and discontinued tAPI. It is literally a mod to make mods. As a developer, you might already know the Terraria src (sourcecode) is an utter mess. tModLoader aims to make it far and far easier for you to create your mod, as well as share your mod for others to use. Traditionally, stand-alone mods are created. An upside to this way of modding is that you can literally do whatever you want to without being limited to an API's possibilities. A downside is that stand-alone mods usually aren't compatible with eachother, as well as the developer(s) needing to know the Terraria src.
    [​IMG]
    You can find all tModLoader methods, fields and properties in the official documentation which is on GitHub.
    You could also choose to use my interactive table containing the same info, but here you can dynamically search what you're looking for. (outdated)
    Another really handy site is to use the old tConfig wikipedia or the tAPI documentation.
    If you need help immediately, I suggest you join our Discord Channel. If you're lucky, there'll be people online helping you right away!


    Useful locations/paths
    Below you can find folder locations which mind come in handy when using tModLoader. Only default paths are listed.

    XNA .dll location / files
    If you're on windows, navigate to C:\Windows\Microsoft.NET\assembly\GAC_32\
    You can find the folders for the XNA framework in there. Alternatively, you could paste this search string in the path bar:
    Code:
    search-ms:displayname=Search%20Results%20in%20GAC_32&crumb=filename%3A~<Microsoft.XNA%20OR%20System.Generic.String%3AMicrosoft.XNA&crumb=fileextension%3A~<Microsoft.XNA*.dll%20filename%3A~<Microsoft.XNA*.dll%20OR%20System.Generic.String%3AMicrosoft.XNA*.dll&crumb=location:C%3A%5CWindows%5CMicrosoft.NET%5Cassembly%5CGAC_32
    COPY these files, you should not move them or cut + paste.

    Default Terraria location
    Here the main files of Terraria are stored. These locations are default locations chosen during the installation of the Terraria game.

    Mac: Library/Application Support/Steam/steamapps/common/Terraria/Terraria.app/Contents/MacOS
    Linux: .local/share/Steam/steamapps/common/Terraria
    Windows: C:\Program Files (x86)\Steam\steamapps\common\Terraria​

    GoG Terraria location
    Here the main files of Terraria are stored. These locations are default locations chosen during the installation of the Terraria game.

    Mac: ??
    Linux: ??
    Windows: C:\GOG Games\Terraria​

    Default Terraria documents location

    Mac: /Users/account/Library/Application Support/Terraria
    Linux: ~/.local/share/Terraria OR $XDG_DATA_HOME/Terraria
    Windows: %UserProfile%\Documents\My Games\Terraria​

    Default tModLoader documents location


    Mac: /Users/account/Library/Application Support/Terraria/ModLoader
    Linux: ~/.local/share/Terraria/ModLoader OR $XDG_DATA_HOME/Terraria/ModLoader
    Windows: %UserProfile%\Documents\My Games\Terraria\ModLoader​

    Default tModLoader mods' src folder
    Here the sourcecode of mods are stored in their own folders. Usually you won't have this and is only available for the particular mods' developer(s).

    Mac: /Users/account/Library/Application Support/Terraria/ModLoader/Mod Sources
    Linux: ~/.local/share/Terraria/ModLoader/Mod Sources OR $XDG_DATA_HOME/Terraria/ModLoader/Mod Sources
    Windows: %UserProfile%\Documents\My Games\Terraria\ModLoader\Mod Sources​

    Default tModLoader 'mods' folder
    Here, .tmod and .enabled files are stored. These files literally store the contents of a mod and if the mod is enabled in-game.

    Mac: /Users/account/Library/Application Support/Terraria/ModLoader/Mods
    Linux: ~/.local/share/Terraria/ModLoader/Mods OR $XDG_DATA_HOME/Terraria/ModLoader/Mods
    Windows: %UserProfile%\Documents\My Games\Terraria\ModLoader\Mods​

    Frequently Asked Questions (FAQ)
    Remember you should ALWAYS show us the error(s) you've received when asking for help. Without showing us errors or code, we literally cannot help you with your troubles. For actual How-To's/Guides, visit my next post in this thread.

    "Why did I lose all my characters and worlds upon installation of tModLoader?" // "How do I make characters and worlds for tModLoader?"
    tModLoader uses its own folders for storing worlds and characters. This makes sure your vanilla files are nicely seperated from tModLoader modded files. tModLoader worlds aren't compatible with vanilla anyway, vanilla worlds are compatible with tModLoader though. Navigate to the main Terraria documents path, which you can find above, and copy your vanilla world files and character files into the ModLoader/Worlds and ModLoader/Players paths.


    "Can I copy my old json code to work with tModLoader?"

    No, as of yet json is not supported and tModLoader solely uses c#.

    "What is the autoload property in tModLoader?"
    The autoload property, found in the SetModInfo() method, is a property which will automatically load your mods' files. If you do not set AutoLoad to true you'll need to load your files manually in the Load() method.

    "How do I even install tModLoader?"
    tModLoader comes packaged in a .zip file, which you can unzip using an unzipper such as WinRAR. All you need to do is navigate to the default Terraria location, which you can find above, make a backup of your origional files and copy the contents of the .zip file. Let the files overwrite when asked.

    "How can I revert to vanilla Terraria?"
    You should've made a backup of all files you had to replace when installing tModLoader. These include, but are not limited to: FNA.dll, MP3Sharp.dll, Terraria.exe
    If you have, restore these files. If you haven't, you can delete these files and have Steam verify the integrity of game cache. (this'll redownload the vanilla files)
    You should now have vanilla Terraria again.

    "Which text-editor or IDE should I use?"
    I've created a special section below for text-editors and IDE choices.

    "Why are people reporting my mod doesn't work in multiplayer?"
    This is likely due to your mod having some code that isn't optimized for MultiPlayer. This can be quite a difficult thing to do.
    In case your users get errors, have them post them to your thread as well as the tML thread.
    (please keep in mind tML is WIP, so MP is WIP too!)


    "What is all the fuzz about converting to tile positions?"
    A nice clear explanation by @Eldrazi:

    "How can I create X thing? (your own item, npc, projectile etc.)"

    Before you ask any questions, you should take a close look at the ExampleMod first. This mod contains almost everything that's possible, but of course the possibilities aren't limited to this mod's contents. Remember that you should create your files using the C# (CSharp) language.

    "Why can't I build / rebuild mods on Mac/Linux?"

    "Why are chests suddenly missing items?"
    This should be fixed by now.

    "How does the 'velocity' system work in Terraria?"
    Go below to the 'Snippets' part, and look for a snippet on this subject.

    Text-Editors and IDEs
    Are you having trouble choosing your text-editor and/or IDE? Look no further!
    Green lit means they're my personal preference!
    I also have a tutorial on how to setup your mod in Microsoft Visual Studio! (IDE) HERE

    Text-editors
    Sublime text: http://www.sublimetext.com/
    Notepad++: http://notepad-plus-plus.org/
    Vim: http://www.vim.org/

    Atom: https://atom.io/
    Brackets: http://brackets.io/
    Visual Studio Code: https://code.visualstudio.com/

    IDEs (Integraded Development Environment)
    Visual Studio: https://www.visualstudio.com
    Tutorial on setting up Visual Studio: http://forums.terraria.org/index.ph...et-up-your-mod-using-visual-studio-mvs.26476/
    [​IMG]
    tModLoader author(s): @bluemagic123
    , @jopojelly, @Chicken Bones
    tModLoader official thread: http://forums.terraria.org/index.php?threads/1-3-tmodloader-a-modding-api.23726/
    Set up your tModLoader mod in MVS: (C# tutorials at the bottom) http://forums.terraria.org/index.ph...et-up-your-mod-using-visual-studio-mvs.26476/
    tModLoader docs: https://github.com/bluemagic123/tModLoader/wiki

    Of course, thanks Re-Logic for creating Terraria, and thanks @bluemagic123 for creating tModLoader.

    tConfig wiki (lots of info): http://tconfig.wikia.com/wiki/TConfig_Wiki
    tAPI docs: http://tapi.axxim.net/docs/
     
    Last edited: Oct 24, 2016
  2. Jofairden

    Jofairden Retinazer

    Snippet: 'How does the velocity system work?'
    Soon, I will add better examples than this.


    Snippet: 'Breathing' lighting (used for vanilla souls, aka Main.essScale)

    The following code is used by vanilla souls. It creates a sort of breathing effect and also adapts to lighting around it.
    Red color:
    Code:
            public override void Update(ref float gravity, ref float maxFallSpeed)
            {
                float num = (float)Main.rand.Next(90, 111) * 0.01f;
                num *= Main.essScale;
                Lighting.AddLight((int)((item.position.X + (float)(item.width / 2)) / 16f), (int)((item.position.Y + (float)(item.height / 2)) / 16f), 0.5f * num, 0.3f * num, 0.05f * num);
            }
    Green color:
    Code:
            public override void Update(ref float gravity, ref float maxFallSpeed)
            {
                float num = (float)Main.rand.Next(90, 111) * 0.01f;
                num *= Main.essScale;
                Lighting.AddLight((int)((item.position.X + (float)(item.width / 2)) / 16f), (int)((item.position.Y + (float)(item.height / 2)) / 16f), 0.1f * num, 0.5f * num, 0.2f * num);
            }

    Snippet: Firing projectile in a random/even spread (shotgun etc.)
    To use these as a callable method:
    Use the following methods (place them anywhere you can access them)
    Both need the following arguments: float speedX, float speedY, int angle, int num
    angle is in degrees (0-360) and num the amount of projectiles to generate.
    Code:
            public static Microsoft.Xna.Framework.Vector2[] evenSpread (float speedX,  float speedY, int angle, int num)
            {
                var posArray = new Microsoft.Xna.Framework.Vector2[num];
                float spread = (float)(angle * 0.0174532925);
                float baseSpeed = (float)System.Math.Sqrt(speedX * speedX + speedY * speedY);
                double startAngle = System.Math.Atan2(speedX, speedY) - spread / 2;
                double deltaAngle = spread / (float)num;
                double offsetAngle;
                for (int i = 0; i < num; ++i)
                {
                    offsetAngle = startAngle + deltaAngle * i;
                    posArray[i] = new Microsoft.Xna.Framework.Vector2(baseSpeed * (float)System.Math.Sin(offsetAngle), baseSpeed * (float)System.Math.Cos(offsetAngle));
                }
                return (Microsoft.Xna.Framework.Vector2[])posArray;
            }
    Code:
            public static Vector2[] randomSpread(float speedX, float speedY, int angle, int num)
            {
                var posArray = new Vector2[num];
                float spread = (float)(angle * 0.0174532925);
                float baseSpeed = (float)System.Math.Sqrt(speedX * speedX + speedY * speedY);
                double baseAngle = System.Math.Atan2(speedX, speedY);
                double randomAngle;
                for (int i = 0; i < num; ++i)
                {
                    randomAngle = baseAngle + (Main.rand.NextFloat() - 0.5f) * spread;
                    posArray[i] = new Vector2(baseSpeed * (float)System.Math.Sin(randomAngle), baseSpeed * (float)System.Math.Cos(randomAngle));
                }
                return (Vector2[])posArray;
            }

    They both return a Vector2 array holding each individual projectile's speedX and speedY,
    so to acces them: YouArrayName[index].X and YourArrayName[index].Y
    Here's an example of how it could be used:
    Code:
                Vector2[] speeds = ModHelper.Projectile.evenArc( 16f,  16f,  45,  5);
                for (int i = 0; i < 5; ++i)
                {
                    Terraria.Projectile.NewProjectile(Main.player[0].position.X, Main.player[0].position.Y, speeds[i].X, speeds[i].Y, 45, 45, 15f);
                }

    Random spread: (original)
    Code:
    public override bool Shoot(Player player, ref Microsoft.Xna.Framework.Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
    {
        float spread = 45f * 0.0174f;//45 degrees converted to radians
        float baseSpeed = (float)Math.Sqrt(speedX * speedX + speedY * speedY);
        double baseAngle = Math.Atan2(speedX, speedY);
        double randomAngle = baseAngle+(Main.rand.NextFloat()-0.5f)*spread;
        speedX = baseSpeed*(float)Math.Sin(randomAngle);
        speedY = baseSpeed*(float)Math.Cos(randomAngle);
        return true;
    }

    Evenly spaced arc: (original)
    Code:
    public override bool Shoot(Player player, ref Microsoft.Xna.Framework.Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
    {
        float spread = 45f * 0.0174f;
        float baseSpeed = (float)Math.Sqrt(speedX * speedX + speedY * speedY);
        double startAngle = Math.Atan2(speedX, speedY)- spread/2;
        double deltaAngle = spread/8f;
        double offsetAngle;
        int i;
        for (i = 0; i < 8;i++ )
        {
            offsetAngle = startAngle + deltaAngle * i;
            Terraria.Projectile.NewProjectile(position.X, position.Y, baseSpeed*(float)Math.Sin(offsetAngle), baseSpeed*(float)Math.Cos(offsetAngle), item.shoot, damage, knockBack, item.owner);
        }
        return false;
    }

    What you could do with it: fire demon scythes all around the player
    An easier way of doing this would be to use the evenSpread method from above, and then using 360 degrees.
    Code:
    public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
    {
        float spread = 45f * 0.0174f;
        double startAngle = Math.Atan2(speedX, speedY)- spread/2;
        double deltaAngle = spread/8f;
        double offsetAngle;
        int i;
        for (i = 0; i < 4; i++ )
        {
            offsetAngle = (startAngle + deltaAngle * (i + i*i) / 2f) + 32f * i;
            Terraria.Projectile.NewProjectile(position.X, position.Y, (float)(Math.Sin(offsetAngle)*5f), (float)(Math.Cos(offsetAngle)*5f), item.shoot, damage, knockBack, item.owner);
            Terraria.Projectile.NewProjectile(position.X, position.Y, (float)(-Math.Sin(offsetAngle)*5f), (float)(-Math.Cos(offsetAngle)*5f), item.shoot, damage, knockBack, item.owner);
        }
        return false;
    }

    Snippet: Referencing vanilla items (to access their defaults)
    Sometimes you might wanna know a value of a vanilla item and use it for yourself. I've had this quite often. Here's a little snippet how you can set-up a reference item and use its properties:
    Code:
    int refItemID = ItemID.NebulaBlaze;
    Item refItem = new Item();
    refItem.netDefaults(refItemID);
    The example above uses Nebula Blaze, but you can enter any item type and it'll work! Properties can then be accessed like so:
    Code:
    item.shoot = refItem.shoot;
    item.shootSpeed = refItem.shootSpeed;

    Snippet: Vanilla Yoyo's and their defaults
    There's another part on how to create a custom yoyo below!
    Format:
    <image> <name> <internal id/type>
    [​IMG] Wooden Yoyo 3278
    [​IMG] Malaise 3279
    [​IMG] Artery 3280
    [​IMG] Amazon 3281
    [​IMG] Cascade 3282
    [​IMG] Chik 3283
    [​IMG] Code 2 3284
    [​IMG] Rally 3285
    [​IMG] Yelets 3286
    [​IMG] Red's Throw 3287
    [​IMG] Valkyrie Yoyo 3288
    [​IMG] Amarok 3289
    [​IMG] Hel-Fire 3290
    [​IMG] Kraken 3291
    [​IMG] The Eye of Cthulhu 3292
    [​IMG] Terrarian 3389
    You can clone these defaults using the following code, make sure to change the type to whichever you want to clone from
    Code:
    item.CloneDefaults(3278)
    You can do the same thing for a custom yoyo projectile.
    Code:
      else if (type == 3262 || type >= 3278 && type <= 3292 || type >= 3315 && type <= 3317)
      {
        this.name = "Yoyo";
        this.useStyle = 5;
        this.width = 24;
        this.height = 24;
        this.noUseGraphic = true;
        this.useSound = 1;
        this.melee = true;
        this.channel = true;
        this.noMelee = true;
        this.shoot = 541 + type - 3278;
        this.useAnimation = 25;
        this.useTime = 25;
        this.shootSpeed = 16f;
    
        if (type == 3278) // Wooden yoyo
        {
          this.knockBack = 2.5f;
          this.damage = 9;
          this.value = Item.sellPrice(0, 0, 1, 0);
          this.rare = 0;
        }
        else if (type == 3285) // Rally
        {
          this.knockBack = 3.5f;
          this.damage = 14;
          this.value = Item.sellPrice(0, 0, 50, 0);
          this.rare = 1;
        }
        else if (type == 3279) // Malaise
        {
          this.knockBack = 4.5f;
          this.damage = 16;
          this.value = Item.sellPrice(0, 1, 0, 0);
          this.rare = 1;
        }
        else if (type == 3280) // Artery
        {
          this.knockBack = 4f;
          this.damage = 17;
          this.value = Item.sellPrice(0, 1, 0, 0);
          this.rare = 1;
        }
        else if (type == 3281) // Amazon
        {
          this.knockBack = 3.75f;
          this.damage = 20;
          this.value = Item.sellPrice(0, 1, 30, 0);
          this.rare = 3;
        }
        else if (type == 3317) // Valor
        {
          this.knockBack = 3.85f;
          this.damage = 22;
          this.value = Item.sellPrice(0, 1, 50, 0);
          this.rare = 3;
          this.shoot = 564;
        }
        else if (type == 3282) // Cascade
        {
          this.knockBack = 4.3f;
          this.damage = 27;
          this.value = Item.sellPrice(0, 1, 80, 0);
          this.rare = 3;
        }
        else if (type == 3262) // Code 1
        {
          this.knockBack = 3.25f;
          this.damage = 21;
          this.value = Item.buyPrice(0, 5, 0, 0);
          this.rare = 2;
          this.shoot = 534;
        }
        else if (type == 3315) // Format C
        {
          this.knockBack = 3.25f;
          this.damage = 29;
          this.value = Item.sellPrice(0, 4, 0, 0);
          this.rare = 3;
          this.shoot = 562;
        }
        else if (type == 3316) // Gradient
        {
          this.knockBack = 3.8f;
          this.damage = 34;
          this.value = Item.sellPrice(0, 4, 0, 0);
          this.rare = 3;
          this.shoot = 563;
        }
        else if (type == 3283) // Chik
        {
          this.knockBack = 3.15f;
          this.damage = 39;
          this.value = Item.sellPrice(0, 4, 0, 0);
          this.rare = 4;
        }
        else if (type == 3289) // Amarok
        {
          this.knockBack = 2.8f;
          this.damage = 43;
          this.value = Item.sellPrice(0, 4, 0, 0);
          this.rare = 4;
        }
        else if (type == 3290) // Hel-Fire
        {
          this.knockBack = 4.5f;
          this.damage = 41;
          this.value = Item.sellPrice(0, 4, 0, 0);
          this.rare = 4;
        }
        else if (type == 3284) // Code 2
        {
          this.knockBack = 3.8f;
          this.damage = 47;
          this.value = Item.buyPrice(0, 25, 0, 0);
          this.rare = 5;
        }
        else if (type == 3286) // Yelets
        {
          this.knockBack = 3.1f;
          this.damage = 60;
          this.value = Item.sellPrice(0, 5, 0, 0);
          this.rare = 7;
        }
        else if (type == 3291) // Kraken
        {
          this.knockBack = 4.3f;
          this.damage = 90;
          this.value = Item.sellPrice(0, 11, 0, 0);
          this.rare = 8;
        }
        else if (type == 3288 || type == 3287) // Red's Throw & Valkyrie Yoyo
        {
          this.knockBack = 4.5f;
          this.damage = 70;
          this.rare = 9;
          this.value = Item.sellPrice(0, 4, 0, 0);
        }
        else if (type == 3292) // The Eye of Cthulhu
        {
          this.knockBack = 3.5f;
          this.damage = 115;
          this.value = Item.sellPrice(0, 11, 0, 0);
          this.rare = 8;
        }
        else // ? Probably a fallback
        {
          this.knockBack = 4f;
          this.damage = 15;
          this.rare = 2;
          this.value = Item.sellPrice(0, 1, 0, 0);
        }
      }
      else if (type == 3389) // Terrarian
      {
        this.name = "Yoyo";
        this.useStyle = 5;
        this.width = 24;
        this.height = 24;
        this.noUseGraphic = true;
        this.useSound = 1;
        this.melee = true;
        this.channel = true;
        this.noMelee = true;
        this.shoot = 603;
        this.useAnimation = 25;
        this.useTime = 25;
        this.shootSpeed = 16f;
        this.damage = 190;
        this.knockBack = 6.5f;
        this.value = Item.sellPrice(0, 10, 0, 0);
        this.crit = 10;
        this.rare = 10;
      }

    Snippet: The essentials of a CUSTOM YOYO
    (How-to: Custom Yoyo)

    It's this simple: have the actual yoyo and the custom yoyo projectile clone the vanilla base's defaults. You can find some code above.
    In case you don't use Terraria.ID:
    Code:
    Wooden Yoyo ID = 3278
    Wooden Yoyo Projectile ID = 541
    Yoyo itself (ModItem)
    Code:
            public override void SetDefaults()
            {
                item.CloneDefaults(ItemID.WoodYoyo);
                item.name = "My Custom Yoyo";
                item.shoot = mod.ProjectileType("MyCustomYoyoProjectile");
            }

    Yoyo projectile (ModProjectile)
    Code:
            public override void SetDefaults()
            {
                projectile.CloneDefaults(ProjectileID.WoodYoyo);
            }

    I believe the length of the yoyo and the time it is possible to use it is all in the AI, so to change that you'll actually need to have Terraria's SRC and change the AI code for yourself (and make sure you use that custom AI)

    Snippet: Dust chart (IDs) (all vanilla dusts)
    Listed numbers are their ID, when moving to the left subtract and moving to the right add to it. e.g: right of 74 would be 75.[​IMG]
    These ones are numbered.. Here's a full, not numbered, chart (but you can use the above numbering as reference)
    I believe each row contains 100 total dusts, this should help. Remember the size of the texture of each dust is 3 pieces of dust, so the next row is from the 4th to the 6th dust sprite. Then from the 7th to the 9th etc.
    Dust.png

    [​IMG]
    Guide: Getting into Terraria (C# - CSharp) Modding

    Before you get into modding you should at least now the very basics of C#.
    You'll mainly learn these basics creating console apps and doing stuff.
    Don't be afraid to try something out yourself!
    Experience with other programming is a mayor plus and will help you out a lot.

    Links of a few useful C# stuff:
    http://www.tutorialspoint.com/csharp/
    http://csharp.net-tutorials.com/
    http://www.homeandlearn.co.uk/csharp/csharp.html
    Brackeys Youtube C# Series (series) (also not a finished series, covers a decent amount of things though, don't bother following it to create a 'complete' app)

    Links of Terraria modding:
    Official tModLoader thread: http://forums.terraria.org/index.php?threads/1-3-tmodloader-a-modding-api.23726/
    Setting up your mod in Microsoft Visual Studio: http://forums.terraria.org/index.ph...et-up-your-mod-using-visual-studio-mvs.26476/


    Guide: Everything about Build.txt

    Every line in builds.txt equals a new property!

    To see all possible 'properties':
    author (string)
    author or authors of the mod.

    version (string)
    the version of the mod, eg: v0.1, 0.1, 1, v1.0, v10.5 etc.

    displayName (string)
    the displayName of the mod

    dllReferences
    A list of dll references your mod has. You must place the dll files in the Mod Sources folder to build, and in the Mods folder to load. Name them by their file name without the extension.

    modReferences
    A list of mods that your mod depends on. Name them by their file name without the extension.

    noCompile
    Whether this mod should compile source code when being built or use dll files pre-compiled by the mod maker. By default this will be false if you do not include this property. If this is set to true, make sure to name your mod file "All.dll" if it does not use Microsoft.Xna.Framework. If it does use Microsoft.Xna.Framework, you will need two dll files: "Windows.dll" must reference Microsoft.Xna.Framework.dll and the Windows version of Terraria.exe, while "Other.dll" must reference FNA.dll and a non-Windows version of Terraria.exe.

    homepage
    A link to a website with your mod's information. A button will appear in the mod browser (below your mod info) which links to this

    hideCode
    If true, the unpacked mod that results from using tModReader will not include .dll files. These files are the compiled mod. Defaults to true.

    hideResources
    If true, the unpacked mod that results from using tModReader will not include resource files, such as .png files and .wav files. Use this if you would like to have your sounds and images freely available to other modders. Defaults to true.

    includeSource
    If true, the unpacked mod that results from using tModReader will include .cs files. Use this if you would like to share your code freely. Defaults to false.

    buildIgnore
    A list of file patterns to ignore for building the mod. Useful for reducing .tmod file size. Use to ignore version control files, reference images, failed code experiments, and other files. Here are some examples: .git\*, referenceImages\*.png, .gitattributes, *.csproj

    includePDB
    If true, line numbers in exceptions and stack traces will appear if the mod experiences problems. Also, Edit and Continue using Visual Studio is possible.

    languageVersion
    A number from 4 to 6 indicating the C# language version to use.

    Example build.txt: (ExampleMod)
    Code:
    author = bluemagic123
    version = v0.7.1.1
    displayName = Example Mod
    homepage = http://forums.terraria.org/index.php?threads/1-3-tmodloader-a-modding-api.23726/
    hideCode = false
    hideResources = false
    includeSource = true
    buildIgnore = .git\*, root\*, src\*, obj\*, bin\*, .vs\*, .gitattributes, *.config, *.md, LICENSE, *.sln, *.cs.user, *.csproj
    includePDB = true
    languageVersion = 4
    


    Guide: How to use and the uses of the Terraria.ID namespace

    Terraria.ID is a really nice namespace that contains virtually all vanilla ID's for you to use. You need an item id? Sure! You need a projectile id? Sure! All you need to do is add the following to the top of your file:
    Code:
    using Terraria.ID;
    Next, you can use all of these:
    Code:
    AchievementHelperID
    AnimationID
    BuffID
    ChainID
    Colors
    DustID
    ExtrasID
    GlowMaskID
    GoreID
    InvasionID
    ItemID
    MessageID
    MountID
    NPCID
    PlayerTextureID
    PlayerVariantID
    ProjectileID
    SetFactory
    StatusID
    TileEntityID
    TileID
    WallID
    
    For example, to get the ID of a Cutlass: (672)
    Code:
    ItemID.Cutlass
    To get the ID of a Demon Scythe projectile: (45)
    Code:
    ProjectileID.DemonScythe
    If you've set up your mod environment using an IDE, such as MVS, (you can use my tutorial) then IntelliSense will help you a lot like this:
    idemvs.png
    IntelliSense will show you what you can access, as well as what it is and does etc.

    Guide: How to work with 'Global' classes
    GlobalItem, GlobalNPC, GlobalProjectile etc.

    First and foremost, Global named classes are created to alter VANILLA subjects. (or possibly another mod's subject(s))
    For example, if you make a class deriving from the GlobalItem class, your intention is probably to alter vanilla items.
    For GlobalNPC, you'll alter NPCs. With GlobalProjectile, you'll alter projectiles... and so forth

    Apart from the examples below, you can for example use Global classes to change stats of a(n) item/npc/projectile.
    To see an example of that, click the spoiler.
    The following example changes the damage of friendly wooden arrows to 666
    Code:
        public class MyGlobalProjectile : GlobalProjectile
        {
            public override void SetDefaults(Projectile projectile)
            {
                if (projectile.type == 1)
                    projectile.damage = 666;
            }
        }

    Examples:
    In my Creative Mode Mod, I've used GlobalNPC to disable all NPCs spawnrate whenever /peace is enabled.
    It also disables NPCs being able to hit players and NPCs (friendly).
    Code:
        public class PeaceGlobalNPC : GlobalNPC
        {
            // CanHitPlayer shifted to ModPlayer [Pre-Alpha R3]
    
            public override void EditSpawnRate(Player player, ref int spawnRate, ref int maxSpawns)
            {
                // No Spawns in peace mode
                if (CreativeMode.PEACE)
                {
                    spawnRate = 0;
                    maxSpawns = 0;
                }
            }
    
            public override bool CanHitPlayer(NPC npc, Player target, ref int cooldownSlot)
            {
                return (CreativeMode.PEACE) ? false : base.CanHitPlayer(npc, target, ref cooldownSlot);
            }
    
            public override bool? CanHitNPC(NPC npc, NPC target)
            {
                return (CreativeMode.PEACE) ? false : base.CanHitNPC(npc, target);
            }
        }

    Also, CMM (Creative Mode Mod) uses GlobalItem and GlobalProjectile for the /debugmode command. (to draw their debug info)
    The snippets below aren't the full code, I shortened them so their easier to understand. (they only draw the type)
    Code:
        public class DebugModeGlobalProjectile : GlobalProjectile
        {
            public override void PostDraw(Projectile projectile, SpriteBatch spriteBatch, Color lightColor)
            {
                if (CreativeMode.DMproj)
                {
                    try
                    {
                        if (!CreativeMode.DMproj1 && projectile.friendly)
                        {
                            return;
                        }
    
                        Vector2 center = projectile.Center - Main.screenPosition;
                        Vector2 pos = projectile.position;
                        int type = projectile.type;
         
                        string typet = string.Format("type: {0}", type);
         
                        float cVar = (float)CreativeMode.cVar;
    
                        Vector2 cbase = new Vector2(center.X - projectile.width * projectile.scale * 2 / 16f, center.Y - projectile.height / 2);
    
                        spriteBatch.DrawString(Main.fontMouseText, typet, new Vector2(cbase.X - Main.fontMouseText.MeasureString(typet).X / 2, cbase.Y - Main.fontMouseText.MeasureString(typet).Y), Color.White);
         
                    }
                    catch (Exception e)
                    {
                        CreativeMode.RunError(this.GetType(), e);
                        return;
                    }
                }
            }
        }
    Code:
        public class DebugModeGlobalItem : GlobalItem
        {
            public override void PostDrawInWorld(Item item, SpriteBatch spriteBatch, Color lightColor, Color alphaColor, float rotation, float scale)
            {
                if (CreativeMode.DMitem)
                {
                    try
                    {
                        Vector2 center = item.Center - Main.screenPosition;
                        Vector2 pos = item.position;
                        int type = item.type;
    
                        string typet = string.Format("type: {0}", type);
    
                        float cVar = (float)CreativeMode.cVar;
    
                        Vector2 cbase = new Vector2(center.X - item.width * item.scale * 2 / 16f, center.Y - item.height / 2);
    
                        spriteBatch.DrawString(Main.fontMouseText, typet, new Vector2(cbase.X - Main.fontMouseText.MeasureString(typet).X / 2, cbase.Y - Main.fontMouseText.MeasureString(typet).Y), Color.White);
                    }
                    catch (Exception e)
                    {
                        CreativeMode.RunError(this.GetType(), e);
                        return;
                    }
                }
            }
        }

    [​IMG]
    How-to: create a custom RECIPE
    How-to: initialize the ModRecipe (start here)
    You can add recipes via the AddRecipes() method.
    This method can be used in many files, not just only the main .cs file (Mod override file)
    You can for example call AddRecipes() from within item files to reduce clutter in your main file.
    First you must set up a new ModRecipe like so:
    Code:
    new ModRecipe(Terraria.ModLoader.Mod mod);
    You need to pass the mod you're working with so ModLoader knows where to add the recipe.
    If you're calling this from within your main .cs file you can simply pass 'this'.
    Code:
    ModRecipe recipe = new ModRecipe(this);
    If you're calling this from within an item .cs file you can simply pass 'mod'.
    Code:
    ModRecipe recipe = new ModRecipe(mod);

    How to add an ingredient
    Simply call AddIngredient. Here's some examples:
    Add 5 pieces of wood to the recipe ingredients
    Code:
    recipe.AddIngredient(Terraria.ID.ItemID.Wood, 5);
    Add your own custom item to the recipe ingredients
    Easiest way: use 'null' as it will work everywhere. You can also use 'this' in te main .cs file.
    By default the stack will be 1, so if you only require 1 there's no need to enter a stack
    Code:
    recipe.AddIngredient(null, "ExampleItem");

    How to set the result
    Set the result of the recipe (30 copper coins for example)
    Code:
    recipe.SetResult(Terraria.ID.ItemID.CopperCoin, 30);
    Same thing applies here: use 'null' or 'this' if you want to pass a custom item.

    How to add all these options to the recipe
    Simply call AddRecipe():
    Code:
    recipe.AddRecipe();
    Next you will have to reset the new modrecipe so you can work on a new one like so:
    Again pass 'this'
    Code:
    recipe = new ModRecipe(this);
    Or 'mod'
    Code:
    recipe = new ModRecipe(mod);

    An example of all the above information
    The following example adds these recipes to the game (via the main .cs file):
    • Turn 5 normal wood into 30 copper
    • Turn 30 copper into 1 gold
    Code:
    ModRecipe recipe = new ModRecipe(this);
    recipe.AddIngredient(Terraria.ID.ItemID.Wood, 5);
    recipe.SetResult(Terraria.ID.ItemID.CopperCoin, 30);
    recipe.AddRecipe();
    
    recipe = new ModRecipe(this);
    recipe.AddIngredient(Terraria.ID.ItemID.CopperCoin, 30);
    recipe.SetResult(Terraria.ID.ItemID.GoldCoin);
    recipe.AddRecipe();

    My personal approach to adding recipes
    For the sake of clarity, I tend to do it like this:
    First I create my ModRecipe:
    Code:
    ModRecipe recipe;
    Then I can simply call a new ModRecipe each time I create a new recipe.
    Here's the above example code with my personal approach:
    Code:
    ModRecipe recipe;
    
    recipe = new ModRecipe(this);
    recipe.AddIngredient(Terraria.ID.ItemID.Wood, 5);
    recipe.SetResult(Terraria.ID.ItemID.CopperCoin, 30);
    recipe.AddRecipe();
    
    recipe = new ModRecipe(this);
    recipe.AddIngredient(Terraria.ID.ItemID.CopperCoin, 30);
    recipe.SetResult(Terraria.ID.ItemID.GoldCoin);
    recipe.AddRecipe();

    How-to: create a custom ITEM (ModItem)
    For a full documentation of methods for the ModItem class look at the official docs on Github.

    Please note: if you have set Autoload to true you can skip this first step.

    Adding the item to the load function (start here)
    Code:
    AddItem(string name, ModItem item, string texture);
    First you must pass the internal name of the item. I tend to just add everything together to avoid conflicts.
    For example: "My Super-duper awesome Sword" becomes "MySuperduperawesomeSword"

    Next you must pass the actual ModItem class, this is the class which will contain the actual code for the item.
    So whatever you are going to name your class for this item, enter that here followed by ().
    I would name my class SuperduperawesomeSword so I refer to it like this:
    Code:
    new SuperduperawesomeSword()
    And at last you must specify the location of the texture. This starts at your Mod Sources folder and usually ends in Items, Items/Images, Items/Textures, Textures
    "MyMod/Items" or "MyMod/Items/Images" or "MyMod/Items/Textures" or "MyMod/Textures"
    Whichever structure you like best!

    So the final result will be something like:
    Code:
    AddItem("MySuperduperawesomeSword", new SuperduperawesomeSword(), "MyMod/Items/Textures");

    Creating the neccessary item files
    Next create a new .cs file in your items folder. Some people like to keep all item files seperate, and some like to group things together.
    For example: Swords.cs, Hammers.cs, or even certain sets: SuperSetItems1.cs, SuperSetItems2.cs, AwesomeSetItems.cs
    It is probably the easiest to take my following 'template' and paste it in. All you have to do is change the namespace and classname to what you need to make it work.
    Code:
    using Terraria;
    using Terraria.ModLoader;
    
    namespace MyMod.Items {
    public class SuperduperawesomeSword : ModItem
    {
      public override void SetDefaults()
      {
         base.SetDefaults();
         item.name = "Example Sword";
         item.damage = 50;
         item.melee = true;
         item.width = 40;
         item.height = 40;
         item.useTime = 20;
         item.useAnimation = 20;
         item.useStyle = 1;
         item.knockBack = 6;
         item.value = 10; // Value is in copper
         item.rare = 2;
         item.useSound = 1;
         item.autoReuse = true;
    
         AddTooltip("This is my super duper awesome sword.");
      }
    
      public override void AddRecipes()
      {
        ModRecipe recipe = new ModRecipe(mod);
        recipe.AddIngredient(Terraria.ID.ItemID.DirtBlock, 10);
        recipe.SetResult(this);
        recipe.AddRecipe();
      }
    }
    
    Remember that custom items should always derive from the ModItem class, hence the ": ModItem"
    For more info about inheritance & derived classes: http://msdn.microsoft.com/en-us/library/ms228387(v=vs.90).aspx
    As you can see, it's also possible to add a recipe via the item file itself! As long as you're using the AddRecipes() method

    Also you must make sure you have a texture for your item. You defined this path to the texture in the previous step.
    If you're using AutoLoad you must override and set the texture path manually in case your using a custom path.
    Please see the special part for overriding the OverLoad method!
    The default path for your texture is the same path as where your item .cs file is stored at. So usually "MyMod/Items/SuperduperawesomeSword.png"

    Settings the item defaults
    If you want you can also take the 'template' from the previous step and change to your likings.
    Remember there are way more options to explore than what this example shows you!

    How to override Autoload
    Add this method to your ModItem class:
    Code:
    public override bool Autoload(ref string name, ref string texture, ref EquipType? equip)
    {
       return base.Autoload(ref name, ref texture, ref equip);
    }
    Next you can change any of the properties. For example the texture.
    Returning true makes it actually autoload, returning false stops it from autoloading.

    How-to: iterate the player's accessories slots
    Not to be confused with player.miscEquips since its actually the stuff like grappling hook slot, mount slot, etc. The accessories are in player.armor.
    Which means you need to iterate through them like so:
    Code:
    for(int k = 3; k < 8 + player.extraAccessorySlots; k++) {
    // do your stuff here!
    }
    We're starting from 3 and 0 since that'll make sure we don't check the helmet slot, body armor slot and leg slot!
    You can then access the 'item' like so:
    Code:
    player.armor[k]
    You are accessing using the index k, which will range from 3 until 8 + player.extraAccessorySlots
    Then it'll function as working with any kind of item, so you can for example do:
    Code:
    if (player.armor[k].type == ItemID.ManaFlower) {
    // do stuff here
    }
    This code will check for the accessory to be a mana flower.

    How-to: create a custom DUST (ModDust)
    Dust are quite easy to create. Their textures consist of 3 different dust sprites.
    You can take a look at the dust reference chart above for the vanilla dusts.
    If you want your dust to have custom behaviour, you must use the Update() hook.
    A full documentation on ModDust is found on the wiki
    Here's an example code (taken from ExampleMod) along with it's texture
    Code:
        public class Sparkle : ModDust
        {
            public override void OnSpawn(Dust dust)
            {
                dust.velocity *= 0.4f;
                dust.noGravity = true;
                dust.noLight = true;
                dust.scale *= 1.5f;
            }
    
            public override bool Update(Dust dust)
            {
                dust.position += dust.velocity;
                dust.rotation += dust.velocity.X * 0.15f;
                dust.scale *= 0.99f;
                float light = 0.35f * dust.scale;
                Lighting.AddLight(dust.position, light, light, light);
                if (dust.scale < 0.5f)
                {
                    dust.active = false;
                }
                return false;
            }
        }
    Texture:
    Sparkle.png
    The game randomly takes one of the 3 sprites upon drawing dust.

    How-to: create a BOSS
    Alright...
    If you've come so far, and want to make a boss.. I'm pretty sure you know how to do it.
    (lots of work)

    How-to: create a custom BUFF (ModBuff)
    Custom buffs are quite easy, honestly.
    The only weird thing is, their settings aren't stored the same way as NPCs for example.
    You can't just type buff.Name = "My Buff" to set its name. Instead, it's all stored in Terraria.Main
    There are just a few properties stored in the ModBuff itself, these are (bools): canBeCleared, longerExpertBuff
    You should use the Update() hook to have the buff do things every tick.

    There are many more settings you can use which I'm not showing in this example, such as Main.buffAlpha
    Make sure to have a proper setup using an IDE so you can see all for yourself. (link to a tutorial above)
    Screenshot_6.png

    A buff can virtually do anything: spawn dust, damage players, damage mobs, spawn projectiles etc. I highly recommend you use ModPlayer/ModNPC for most stuff though, reason below in red.

    Example:
    The below example is taken from the Negadium mod.
    It's a buff that sets a ModPlayer bool property to true, as well as randomly spawning dust.
    Please note that almost ALWAYS a lot of custom stuff is done using ModPlayer/ModNPC and buffs only set or change certain properties
    The reason for this is USUALLY because ModPlayer/ModNPC can do a lot more, such as altering DrawLayers and such.

    Code:
        public class Mharadium : ModBuff
        {
            //  Gorateron, debuff used when Mharadium Ore is in the inventory
    
            public override void SetDefaults()
            {
                Main.buffName[Type] = "Mharadium";
                Main.buffNoTimeDisplay[Type] = true;
                Main.buffTip[Type] = "You have a weird feeling, it may be the radioactivity";
                Main.buffNoSave[Type] = true;
                Main.debuff[Type] = true; // must be set for canBeCleared = false to work
                this.canBeCleared = false;
            }
    
            public override void Update(Player player, ref int buffIndex)
            {
                NegadiumModPlayer modPlayer = (NegadiumModPlayer)player.GetModPlayer(mod, "NegadiumModPlayer");
                modPlayer.mharadiumbuff = true;
    
                if (Main.rand.Next(0, 101) <= 12)
                {
                    Dust.NewDust(player.position, 20, 20, DustID.Blood);
                }
            }
        }
    Also, please don't use Main.rand.Next(0, 101) <= 12
    Instead use Main.rand.NextDouble() <= 0.12 which is way more accurate.
    (I just figured it'd be good to keep it in, so you can see that everyone makes mistakes.. even I)

    Texture:
    You can use the following base sprite to create your custom buff texture (drawn below the inventory)
    BuffTemplate.png

    How-to: add and use custom sounds (ModSound)
    You can add custom sounds easily to your mod. The following code comes from the Negadium mod and is a thunder-like sound.
    As you can see in the code, you can easily change the volume, pan and pitch of the sound.
    Code:
        public class BlazingArcSound : ModSound
        {
            public override void PlaySound(ref SoundEffectInstance soundInstance, float volume, float pan, SoundType type)
            {
                if (soundInstance.State == SoundState.Playing)
                    return;
    
                soundInstance.Volume = volume;
                soundInstance.Pan = pan;
                soundInstance.Pitch = 0f;
                Main.PlaySoundInstance(soundInstance);
            }
        }
    The following part makes sure the sound doesn't keep looping itself (and interrupting)
    Code:
                if (soundInstance.State == SoundState.Playing)
                    return;
    You can then call the sound as follows, make sure to change the position and string in soundslot to what you need
    Code:
    Main.PlaySound(SoundLoader.customSoundType, projectile.position, mod.GetSoundSlot(SoundType.Custom, "Sounds/BlazingArcSound"));

    Coming:
    • Tiles :)red: no, I'm lazy)
    • Walls (also lazy)
    • ModNPC
    • ModSound
    • ModPlayer
    • Mounts
    • Helpfull Math stuff (sin, cos, PI etc.)
     
    Last edited: Mar 24, 2016
  3. Exeton

    Exeton Terrarian

    Thanks for making this, I'll be sure to check this if I need any coding help.
     
    Jofairden likes this.
  4. Jofairden

    Jofairden Retinazer

    Be sure to ask if you need help, I'll do my best!
     
    Exeton likes this.
  5. Jofairden

    Jofairden Retinazer

    First major update: (31-07-2015)
    • Cool fancy headers ^^
    • Merged my own snippets into Snippets.cs
    • Added some math snippets
    • I've been working on the help section, it is supposed to cover common errors that require answers
    • The categories section is going to provide FAST & easy lookup on how to do things in that category, also common questions answered. This will expand as tModLoader grows.
     
  6. Eared

    Eared Pixel Pirate

    Any way to QuickSpawnItem for mod items?
     
  7. Jofairden

    Jofairden Retinazer

    Yes there is. I'm currently working on an extended ExampleMod which contains Snippet.cs and loads of differences.
    Please wait a few minutes and I have it in the thread and explain it to you
     
    Eared likes this.
  8. Jofairden

    Jofairden Retinazer

    Okay well I have no clue when I'm going to upload my own examplemod
    But here's how you can do it:

    Code:
    Terraria.ModLoader.ModLoader.GetMod("Modnamehere").ItemType("ModItemnamehere");
     
    Eared likes this.
  9. Eared

    Eared Pixel Pirate

    Not working :rolleyes:
     
  10. Jofairden

    Jofairden Retinazer

    It is.
    Just wait till I have uploaded my ExampleMod edit, I'll tell you where you need to look.
     
    Eared likes this.
  11. Jofairden

    Jofairden Retinazer

    Go to Items/ExampleItems.cs
    the top one has MeleeEffects:
    player.QuickSpawnItem(modItems.ExampleItem);

    This spawn 1 exampleitem every time you swing.
    Also check out ExampleMod/Helper.cs
    --- Double Post Merged, Aug 1, 2015, Original Post Date: Aug 1, 2015 ---
    Second major update: (01-08-2015)
    • Expanded the CATEGORIES section way more, added info about Items (a ton)
    • Deleted all the info, it was bugging the thread
    • Merged several errors in HELP under the same section
    • Rewritten and expanded Snippet.cs
    • Added proper documentation
    • Added extended ExampleMod and seperate Snippet.cs
    • Added examples
     
    Eared likes this.
  12. Atomdex

    Atomdex Terrarian

    Thanks for this cool Mod Loader, I just startet a few hours ago and I have two questions, how can I check the Player is full live and how can I check that the Player attacks an enemy Mob with his Sword or Projecile , thanks :D
     
  13. Jofairden

    Jofairden Retinazer

    To check the player's health you would do
    Code:
    if (player.statLifeMax2 == 400 || player.statLifeMax2 == 500)
    I'm not 100% sure since there's also player.statLifeMax which might be the one to go to 400, and player.statLifeMax2 to go to 500
    player.statLife will get the current life (eg 350/500)
    So fiddle around a bit with that

    I think for your second question it would be wise to wait for custom projectile support, I'm figuring it would be the easiest to just add that check to the projectile itself.
    But, you can take a look at the OnHitNPC method. You can do a lot of things.

    You could for example do:
    Code:
                if (target.type == Terraria.ID.NPCID.BlueSlime)
                {
                    target.AddBuff(24, 60);
                }
    this should add a debuff if the target is a blue slime.
     
  14. Rawdy4

    Rawdy4 Official Terrarian

    is this like the new tapi or is it way different
     
  15. Jofairden

    Jofairden Retinazer

    Snippet is not a new tAPI, but tModLoader sort of is. Snippet is just a module I created to do some things faster in the code :)
     
  16. Fastmapler

    Fastmapler Skeletron Prime

    Nice guide to start out with, but you forgot on how to require a specific block near by to craft an item (i.e. Iron Anvil). Do you know how to do this? I tried to use "AddTile", but the way I tried did not work.
     
  17. Jofairden

    Jofairden Retinazer

    I didn't forget about that, at least not in my current version. Please note this is not in any way an up-to-date version. Here's an example of how you'd add a requiredTile
    This is taken from a mod I'm currently working on ^^
    Code:
    Recipes.QuickRecipe(this, ItemID.SlimeStaff, 1, new int[,] { { ItemID.Gel, 200 }, { ModItems.OddStaff, 1 } }, TileID.TinkerersWorkbench);
    If you want the above example, I can give you that snippet really quick,

    Code:
            public static void QuickRecipe(Mod mod, int createItemType, int createItemStack, int[,] requiredItemsArray, int requiredTile)
            {
                ModRecipe recipe = new ModRecipe(mod);
                for (int i = 0; i < requiredItemsArray.Length / 2; i++)
                {
                    recipe.AddIngredient(requiredItemsArray[i, 0], requiredItemsArray[i, 1]);
                }
                recipe.AddTile(requiredTile);
                recipe.SetResult(createItemType, createItemStack);
                recipe.AddRecipe();
            }
     
  18. Lord Hungry

    Lord Hungry Skeletron Prime

    Could you make a tutorial on how to create a NPC please?
     
  19. Jofairden

    Jofairden Retinazer

    First of all go here: https://github.com/bluemagic123/tModLoader/wiki/ModNPC
    ModNPC will be the class you're going to override. SetDefaults() is a more global method used in many classes to set certain values. For example, it would could npc.name
    As you might notice on the docs it's not very advanced yet, so I would wait with making a custom NPC untill there's more customization.

    Again, I haven't been updating this thread much but I should more. Tomorrow I'll be gone (actually today, 2;14 AM for me here) but I'm planning on working on this thread again afterwards (will be Monday for me)
     
  20. Lord Hungry

    Lord Hungry Skeletron Prime

    ok thanks
     
    NickGamingYT likes this.