tAPI [Tutorial] Custom Bosses - NPC AI and Server Syncing

blushiemagic

Retinazer
tModLoader
This tutorial will assume you already know the basics of modding (making .json files, and simple .cs files). It will also assume you have at least very basic programming knowledge.

The difference between bosses and other NPCs

I will start off this tutorial with the following: fundamentally, bosses are the same as any other NPCs. However, in practice, there is actually a subjective difference: bosses have much more complicated AI than normal monsters. So, this tutorial will mostly be about creating your own AI for your boss.

There is one more difference, although not a large one: make sure that in your NPC's .json file, you set "boss": true.

Summary

This tutorial will attempt to cover many different things. First, it will cover the basics of what you need to program an AI. Next, it will discuss syncing between clients and the server, since of course Terraria is very fun with multiplayer. Afterwards, it will cover some basic things about bosses, such as making noises, texts, and loots. Finally, it will cover some basic behavior common to many NPC AI's that may be useful for you to incorporate (essentially some coding done for you).

Properties of NPC.cs

When you need to make your boss, you will need to make the NPC extend the TAPI.ModNPC class. Now, ModNPC has this useful property called npc. It is what you use in order to access the specific NPC that the instance of your class represents. In other words, for this tutorial, npc is the actual boss.

Now, when you program your AI, you will need npc to access its properties. The most important properties for programming an AI are ai, velocity, position, direction, spriteDirection, and localAI. ai and localAI are what you use to store information about your NPC, such as attack phase, attack cooldown, etc. I hope that velocity and position are self-explanatory. One thing that should be noted is that velocity and position are instances of Microsoft.Xna.Framework.Vector2, which is a struct. If you don't understand what I just said, this basically means feel free to edit these properties, and feel free to copy them without the original being changed. Finally, direction and spriteDirection is more complicated than I feel it should be. Truthfully, I'm not really sure what direction is used for. However, spriteDirection controls the direction your NPC appears to be facing. If spriteDirection is 1, it faces the normal direction; if it is -1, then the sprite is flipped.

Now that we have all this out of the way, here is a basic structure of a boss's .cs file. I will go into more detail later:
Code:
using System;
using Microsoft.Xna.Framework;
using Terraria;
using TAPI;

namespace YourNamespace
{
    public class YourNPC : ModNPC
    {
        public override void OnSpawn() //optional
        {

        }

        public override void AI() //this is where you program your AI
        {

        }

        public override void HitEffect(int hitDirection, double damage, bool isDead) //This is for whenever your boss gets hit by an attack. Create dust or gore.
        {

        }

        public override void SelectFrame(int frameSize) //This is for animating your boss, such as how Queen Bee changes between charging or hovering images, or how Pumpking rotates its face.
        {

        }

        public override void SetupLootRules(NPC unused) //this is what makes special things happen when your boss dies, like loot or text
        {

        }
    }
}

Server Syncing

Before we cover anything else, you will need to know about server syncing. For every step of the AI you make, you will have to consider syncing any change between the clients and the server. You want to make sure you don't send updates more than necessary, but you also need to make sure updates are sent when needed. The most important informational variable you will use when it comes to syncing is Main.netMode. If Main.netMode == 0, that means it is a singleplayer game. If Main.netMode == 1, then it is a client connected to a server. If Main.netMode == 2, then it is a server.

Now in the previous section, I mentioned two different variables for storing AI information: ai and localAI. Both of these are arrays that can store 4 floats (for those that don't know, a float is basically a decimal number, such as 1.2). So what is the difference? The ai array is what is sent between clients and servers; these four variables are what is synced between everything. On the other hand, the four variables in localAI never get synced. There actually is a way to sync the localAI array (and any other information) yourself, which I will cover later in this section.

Now time to put this all together. First, we will go over uses for Main.netMode. Many bosses can create projectiles (such as lasers) or other NPCs (such as servants of cthulhu). This is very important; you do not want an NPC to create these on a client connected to a server! In other words, if your boss creates a projectile or an enemy, you must make sure that Main.netMode != 1. Here are some examples:
Code:
if(Main.netMode != 1)
{
    Projectile.NewProjectile(x, y, speedX, speedY, type, damage, knockback, owner, ai0, ai1);
}
X and y are the position of the projectile's center, represented by floats. SpeedX and speedY are also represented by floats (it should be noted that a negative speedY means upwards, while a positive speedY means downwards). Type is the type of your projectile, represented by an int (int means integer). If you want a custom projectile, use ProjDef.byName["ModName:NameOfProjectile"].type to get the type of your projectile. Damage and knockback are self-explanatory. However, it should be noted that projectiles will do double damage to players. (Also, I'm not sure if the amount of knockback matters against a player.) Since an NPC is creating the projectile, the owner should always be Main.myPlayer. Finally, just like npcs, projectiles have their own ai, although projectile ai's only have two slots rather than four. Use ai0 and ai1 (both floats) to initialize this ai. If your projectile doesn't need the ai array, it's fine to set them to 0. When a server creates a projectile this way, it will always immediately send it to the client.

Code:
if(Main.netMode != 1)
{
    NPC.NewNPC(x, y, type);
}
X is the x-coordinate of the center of the new NPC. Y is the y-coordinate of the bottom of the new NPC. They are both ints. The type is also an int. Similar to with projectiles, you can get the type of a custom NPC with NPCDef.byName["ModName:NPCName"].type.

Sometimes you may want to initialize stuff with your new npc.
Code:
if(Main.netMode != 1)
{
    int npc = NPC.NewNPC(x, y, type);
    Main.npc[npc].velocity.Y = 5f; //This makes the new npc move downwards at a speed of 5. The f means that the number is a float.
    Main.npc[npc].ai[0] = 1f;
    Main.npc[npc].damage *= 2; //Doubles the npc's damage
    Main.npc[npc].netUpdate = true; //This lets the server know to send an update to the clients.
}
This changes the properties of the NPC, then lets the server know that the NPC's properties has changed and the clients need to by synced. Main.npc is an array that contains all the NPC's in the game, and NPC.NewNPC returns the location of the new NPC in this array. You can also do the same thing with projectiles (projectiles also have a netUpdate variable).
Keep in mind that if a projectile is hostile, clients will not be able to send it to the server; servers can send it to clients just fine, though.

Next we will go over npc.netUpdate. In the examples I just showed, you may notice that we set npc.netUpdate = true in order to send the information to the clients. This variable is rather useful. Whenever the information actually gets sent, npc.netUpdate gets set back to false after everything is synchronized, so it doesn't spam the clients with information, so essentially you set npc.netUpdate to true only when needed to avoid network spam. There are two main cases in which you'll need to set npc.netUpdate = true: when something in the code can make the NPC behave differently between the server and the clients (for example, random behavior), or when a lot of time has passed (the clients and server might be at different speeds or something). As an example of random behavior, consider Pumpking. Pumpking always switches to one out of three attack patterns at random, so the clients and server might have different attack patterns for Pumpking. To fix this, netUpdate is set to true whenever the attack pattern changes, so the clients can stay consistent with the server. You may also need to set netUpdate to true when a lot of time passes, simply because of inconsistencies in running speed. The most convenient time to do this is when things like attack pattern changes or phase changes happen.

Remember that for the most part, due to how you will program the AI, the NPC will behave the same in both the clients and the server, so you will not need to set netUpdate to true. Nothing particularly bad happens if you do set it to true all the time, but think about the network load :(

NPCs and projectiles also have another variable called netAlways. It turns out that NPC's and projectiles will only be updated in a client if they are on the player's screen. If you want them to update even off-screen, set netAlways to true in the .json file for an NPC, or set it to true in a projectile's OnSpawn method. Keep in mind that if you set "boss": true in an NPC's json file, you do not need to set "netAlways": true because the game will update bosses even if they are off-screen.

Now, suppose you are making rather complicated AI, or you need to store the index locations of many other NPCs. Unfortunately, the ai array only can store 4 floats, and it is hardcoded so that only these four are sent between clients and server. However, there is in fact a way to increase the amount of data you can send; the secret lies in the ModNet class. First, you will need to make a class that extends ModNet, then create several methods. Here is a template you can use.
Code:
using System;
using Microsoft.Xna.Framework;
using Terraria;
using TAPI;

namespace YourNamespace
{
    public class Example : ModNet
    {
        public static void SendNPCData(int npc, int ignore = -1) //This sends the data for an NPC without having to use netUpdate.
        {
            NetMessage.SendData(23, -1, ignore, "", npc, 0f, 0f, 0f, 0f);
        }

        public static void SendNPCLocalData(int npc, int ignore = -1) //This sends the npc's localAI array.
        {
            float[] localAI = Main.npc[npc].localAI;
            NetMessage.SendModData(Mods.GetMod("ModName"), 1, -1, ignore, npc, localAI[0], localAI[1], localAI[2], localAI[3]); //This is NetMessage.SendModData(Mod mod, int messageID, int remoteClient, int ignoreClient, object data, object moreData, object evenMoreData, object asMuchDataAsYouWant)
        }

        public static void SendNPCAllData(int npc, int ignore = -1) //This sends the npc's data first, and then send's its localAI.
        {
            SendNPCData(npc, ignore);
            SendNPCLocalData(npc, ignore);
        }

        public override void NetReceive(BinBuffer bb, int messageID, MessageBuffer buffer) //This is what happens when we receive custom data.
        {
            switch(messageID)
            {
            case 1: //Here we copy the data into the NPC's localAI, so that everything is synchronized.
                int npc = bb.ReadInt();
                float[] localAI = Main.npc[npc].localAI;
                localAI[0] = bb.ReadFloat();
                localAI[1] = bb.ReadFloat();
                localAI[2] = bb.ReadFloat();
                localAI[3] = bb.ReadFloat();
                if(Main.netMode == 2)
                {
                    SendNPCLocalData(npc, buffer.whoAmI);
                }
                break;
            default:
                break;
            }
        }
    }
}
In this example, I have basically added a way to synchronize the NPC's localAI in addition to the normal AI. To make sure everything is sent in the right order, call Example.SendNPCAllData(npc.whoAmI) instead of setting netUpdate to true. This gives you 8 floats to synchronize information. If you want even more spaces, create fields in your NPC class then follow the same pattern.

Basic Boss Properties

Now, finally, on to actual boss stuff! Here, I will tell you how to make text pop up when your boss appears or is defeated, how to make it play sounds, how to make it play music, and how to make it drop loot.

In order to actually make your boss appear in the game, you can either use NPC.NewNPC as I showed in an earlier section, or you can use NPC.SpawnOnPlayer(int player, int type). For example, NPC.SpawnOnPlayer(Main.myPlayer, NPCDef.byName["ModName:BossName"].type). NewNPC gives you more control over the position, while SpawnOnPlayer makes the boss approach from off-screen. If you use SpawnOnPlayer, it *should* automatically handle the text for you. However, if you use NewNPC, or for some reason the text isn't handled for you, use the following code in the boss's OnSpawn method:
Code:
if(Main.netMode == 0)
{
    Main.NewText(npc.displayName + " " + Lang.misc[16], r, g, b, false);
}
else if(Main.netMode == 2)
{
    NetMessage.SendData(25, -1, -1, npc.displayName + " " + Lang.misc[16], 255, (float)r, (float)g, (float)b, 0f); //R, G, and B make up the color of the text. If you don't know what this means, look up RGB values.
    //Most bosses have a text color of r = 175, g = 75, b = 255
}
You may also want to make the boss make a roar sound when it spawns. You can add also add this to the boss's OnSpawn method:
Code:
if(Main.netMode == 2)
{
    NetMessage.SendModData(Mods.GetMod("ModName"), 2, -1, -1,15, (int)npc.position.X, (int)npc.position.Y, 0);
}
else if(Main.netMode == 0)
{
    Main.PlaySound(15, (int)npc.position.X, (int)npc.position.Y, 0);
}
Then you will need to add this to your ModNet class (see section on server syncing):
Code:
public override void NetReceive(BinBuffer bb, int messageID, MessageBuffer buffer)
{
    switch(messageID)
    {
    //other stuff
    case 2:
        Main.PlaySound(bb.ReadInt(), bb.ReadInt(), bb.ReadInt(), bb.ReadInt());
        break;
    //other stuff
    }
}
Another thing most bosses have is epic music. Fortunately, NPC .json files support a "music" property, to play music whenever the NPC exists (working as of r15 of tAPI). So for example, if you want the Wall of Flesh music to play, you would fill in the property like this:
Code:
"music": "Vanilla:Music_12"
Note that if you don't specify a music, then by default the normal boss music (Eye of Cthulhu, Duke Fishron, etc.) will play; so if you want that music, you don't need to do anything.

Finally, we will cover boss loots and defeat messages. In order to do this, you will need to know about something called loot rules. I feel that the best way to teach about loot rules are by example, so I will show you the code for a boss in the mod I am currently working on. You will need to place this method in your boss's .cs file.
Code:
public override void SetupLootRules(NPC unused)
{
    bool flag = LootRule.settingUp;
    LootRule.settingUp = true; //Lets Terraria know these are the default drops
    LootRule[] rules = new LootRule[8]; //The list that will contain the boss's drops
    rules[0] = new LootRule(null).Item("Greater Healing Potion").Stack(5, 15); //Here I create a LootRule, tell it that it will drop Greater Healing Potions, and that it will drop a single stack of 5 to 15 of them.
    rules[1] = new LootRule(null).Times((NPC npc) => 5 + Main.rand.Next(5)).Item("Heart"); //Here I create a LootRule, tell it that it will drop hearts, and that it will drop from 5 to 9 separate stacks each containing 1 heart.
    rules[2] = new LootRule(null).Chance(0.14285714285714285).Item("Bluemagic:PhantomMask"); //Here I create a LootRule and tell it that it will drop a mask with a 1/7 chance.
    rules[3] = new LootRule(null).Chance(0.1).Item("Bluemagic:PhantomTrophy"); //Here I create a LootRule and tell it that it will drop a trophy with 1/10 chance.
    rules[4] = new LootRule(null).Item("Bluemagic:PhantomPlate").Stack(5, 7); //Here I create a LootRule and tell it to drop 5-7 of a certain item.
    rules[5] = new LootRule(null).Weighted(true).LootRules(new LootRule[] //Here I create a LootRule then tell it that it will always drop exactly one of the items in the following list.
    {
        new LootRule(null).Item("Bluemagic:PhantomBlade"),
        new LootRule(null).Item("Bluemagic:PhantomSphere"),
        new LootRule(null).Item("Bluemagic:SpectreGun"),
        new LootRule(null).Item("Bluemagic:PaladinStaff")
    });
    rules[6] = new LootRule(null).Code(delegate(NPC npc) //This basically sets my Bluemagic.downedPhantom variable to true; useful for keeping track of if a boss has been defeated in a world.
    {
        LootRule.DownedBoss(ref Bluemagic.downedPhantom);
    });
    rules[7] = new LootRule(null).Code(delegate(NPC npc) //This is the part that displays the boss defeat message.
    {
        LootRule.DownedBossMessage(npc.displayName + " " + Lang.misc[17], 50, 150, 200);
    });
    LootRule.AddFor("Bluemagic:Phantom", rules); //This registers the LootRules so that they actually work.
    LootRule.settingUp = flag; //Lets Terraria know that you are done adding LootRules.
}
Essentially, LootRules are a bunch of pattern-matching. I hope this example has enough information for most drop patterns.

Useful AI Code

Here I will list a bunch of code that is useful for programming AI's, which will hopefully help people who are new to modding Terraria.
Note: If you are making an AI from scratch, you must give your NPC a negative aiStyle! If you just leave the aiStyle as 0, the NPC will always face the player and will always slow down horizontally.
Targeting a player is actually pretty simple. Just call the following:
Code:
npc.TargetClosest(faceTarget);
Where faceTarget is true or false depending on whether npc.direction should update to face its target. After this, you can get the target with npc.target. To get the actual player object that the boss is targeting, use this:
Code:
Player player = Main.player[npc.target];
The most basic behavior with a boss is moving towards a specific location, whether it be the player, above the player, etc. This part of the tutorial will assume that your boss has "noGravity", "noTileCollide", and "lavaImmune" as true.

First you need to get the location to move towards:
Code:
Vector2 moveTo = player.Center; //This player is the same that was retrieved in the targeting section.

Vector2 moveTo = player.Center + new Vector2(0f, -200f); //This is 200 pixels above the center of the player.
Now there are three ways to move towards this location. The easiest way is to teleport:
Code:
npc.position = moveTo;
Another way is to make the boss move towards the location at a certain speed. Here is the code for you to do that:
Code:
float speed = 5f; //make this whatever you want
Vector2 move = moveTo - npc.Center; //this is how much your boss wants to move
float magnitude = Math.Sqrt(move.X * move.X + move.Y * move.Y); //fun with the Pythagorean Theorem
move *= speed / magnitude; //this adjusts your boss's speed so that its speed is always constant
npc.velocity = move;
But what if you don't want it to move at a constant speed, but just want a maximum speed?
Code:
float speed = 5f;
Vector2 move = moveTo - npc.Center;
float magnitude = Math.Sqrt(move.X * move.X + move.Y * move.Y);
if(magnitude > speed)
{
    move *= speed / magnitude;
}
npc.velocity = move;
The most complicated way is to make your boss gradually turn towards the player.
Code:
float speed = 5f;
Vector2 move = moveTo - npc.Center;
float magnitude = Math.Sqrt(move.X * move.X + move.Y * move.Y);
if(magnitude > speed)
{
    move *= speed / magnitude;
}
float turnResistance = 10f; //the larger this is, the slower the npc will turn
move = (npc.velocity * turnResistance + move) / (turnResistance + 1f);
magnitude = Math.Sqrt(move.X * move.X + move.Y * move.Y);
if(magnitude > speed)
{
    move *= speed / magnitude;
}
npc.velocity = move;

Note that these aren't the only ways of making the NPC move; these are just the methods that make the NPC actively follow the player. Other ways of moving including charging at the player, similar to the Eye of Cthulhu or Queen Bee. In order to do this, we will need a slight modification of the code that moves the NPC towards the player at a constant speed. Remember that the AI method is called every game tick; simply placing any of the code I have given in the AI method will update the NPC's velocity constantly. With charging, we basically only want the velocity to update once, so the NPC can keep charging. In order to do this, we need to keep track of whether the NPC has already updated its velocity or not; this is information we will have to store, so we will use the ai array (note: if you really know what you're doing, you can find a way so that you don't even need to use the ai array).
Code:
if(npc.ai[0] <= 0f) //Checks whether the NPC is ready to start another charge.
{
    float speed = 10f; //Charging is fast.
    Vector2 move = moveTo - npc.Center;
    float magnitude = Math.Sqrt(move.X * move.X + move.Y * move.Y);
    move *= speed / magnitude;
    npc.velocity = move;
    npc.ai[0] = 200f; //This is the time before the NPC will start another charge.
    //There are 60 ticks in one second, so this will make the NPC charge for 3 and 1/3 seconds before changing directions.
}
npc.ai[0] -= 1f; //So you can keep track of how long the NPC has been charging.
If you want to make things even more fun, you can make a charge last a random amount of time. You can replace the time in the example (200f) with whatever you want; for example, 100f + Main.rand.Next(100) will make the NPC's charge last a random amount of time between 100 and 200 ticks.
So you have a boss, and you have some sprites of it; but you want your boss to be animated. Animation is achieved through things called frames. A frame is basically a single sprite of of NPC. When you create the image file for your NPC, frames should be placed above each other; for example, one sprite for your NPC should be on the top, then another sprite should be further down on the Y-axis, etc. and the last sprite should be at the bottom of the image. Finally, you must count how many frames you have, then set "frameCount" to this number in the NPC's .json file. It is important that all frames have the same size! (You may need some frames to have some blank space in order for this to happen.)

Now in order to animate your NPC, you need a method called SelectFrame. I included this method in the skeleton for a ModNPC class earlier, but here it is again:
Code:
public override void SelectFrame(int frameSize)
{

}
Here, we must introduce a new variable of the NPC class, called frame. The frame variable is a Rectangle, which has the properties X, Y, Width, and Height (all of which are integers). The NPC's frame variable determines which part of its image file gets drawn. X and Y are the coordinates of the top corner of the part that gets drawn, and Width and Height are how large the part of the image that gets drawn is. It should be noted that the origin of an image is the top left; so X increases to the right, and Y increases downwards. It should also be noted that coordinates are in pixels.

frame.X should always be 0, and frame.Width will already be set to the width of the image for you, so the full width of the image can be drawn (remember, frames are placed above each other, not next to each other). Next, remember how you gave the .json file a "frameCount" property? This is how Terraria determines the size of a frame; so frame.Height will already be set for you. The only thing you will ever need to touch is frame.Y. Notice how the SelectFrame method has a frameSize argument. This is how tall a frame is (same thing as frame.Height), and is what you will use to help determine what frame.Y should be. If you want the NPC to use its first frame, then do this:
Code:
public override void SelectFrame(int frameSize)
{
    npc.frame.Y = 0; //Uses the first frame
}
Here are some more examples of using frames:
Code:
public override void SelectFrame(int frameSize)
{
    npc.frame.Y = frameSize; //Uses the second frame
    npc.frame.Y = 2 * frameSize; //Uses the third frame
    npc.frame.Y = 3 * frameSize; //In general, setting npc.frame.Y = n * frameSize will make it use the (n - 1)th frame,
    //where n is whatever integer you type in.
}
But what about animating? In order to do this, we will need to introduce yet another variable: npc.frameCounter (this variable is a double, meaning a normal decimal number). This is what you use to keep track of how long the NPC's animation has been lasting. Here is an example of npc.frameCounter in action:
Code:
public override void SelectFrame(int frameSize)
{
    npc.frameCounter += 1.0; //This makes the animation run
    npc.frameCounter %= 10.0; //This makes it so that after 10 ticks, the animation resets to the beginning.
    //To help you with timing, there are 60 ticks in one second.
    int frame = (int)(npc.frameCounter / 2.0); //Chooses an animation frame based on frameCounter.
    npc.frame.Y = frame * frameSize; //Actually sets the frame
}

Sometimes you might want an NPC to have multiple animations; for example, one for falling, one for standing, and one for walking. You can take advantage of the npc's properties in order to determine the frame. For example, suppose you have frame 0 as the standing frame, frame 1 as the falling frame, and frames 2 to 7 as the walking frames:
Code:
public override void SelectFrame(int frameSize)
{
    if(npc.velocity.Y != 0f) //The NPC is falling or jumping
    {
        npc.frame.Y = frameSize;
        npc.frameCounter = 0.0;
    }
    else if(npc.velocity.X == 0f) //The NPC is standing
    {
        npc.frame.Y = 0;
        npc.frameCounter = 0.0;
    }
    else
    {
        npc.frameCounter += 1.0 + npc.velocity / 2.0; //The faster the NPC is walking, the faster the animation will go
        npc.frameCounter %= 10.0;
        int frame = (int)(npc.frameCounter / 2.0) + 2;
        npc.frame.Y = frame * frameSize;
    }
}
Another useful method from ModNPC is the HitEffect method, which is called whenever your NPC gets damaged. Like SelectFrame, I already included its signature in the skeleton near the beginning of this tutorial, but here it is again for reference:
Code:
public override void HitEffect(int hitDirection, double damage, bool isDead)
{

}
You'll notice three parameters. The hitDirection parameter is 1 if your NPC was attacked from the left (the attack is going towards the right), and -1 if your NPC was attacked from the right (the attack is moving leftwards). The damage parameter is self-explanatory; isDead is whether the attack was the finishing blow to your NPC.
You can basically do whatever you want in HitEffect. You can create dust to mimic blood (most NPCs in vanilla do this), and if your NPC is dead you can create gores (for example, the zombie parts that fly everywhere or the smoke when a wraith is defeated). King slime even uses this to create blue slimes. I feel that the best way to show how to use HitEffect is through examples.

The first one will create blood particles and gores (this is borrowed from my mod which borrowed from the wandering eye):
Code:
public override void HitEffect(int hitDirection, double damage, bool isDead)
{
    if(isDead)
    {
        for (int x = 0; x < 50; x++) //makes 50 red dust particles
        {
            Dust.NewDust(npc.position, npc.width, npc.height, 5, (float)(2 * hitDirection), -2f, 0, default(Color), 1f);
        }
        string type = npc.ai[0] == 1f ? "Bluemagic:Retineye" : "Bluemagic:Spazmateye";
        Gore.NewGore(npc.position, npc.velocity, type, 1f); //creates a broken piece of the NPC
        Gore.NewGore(new Vector2(npc.position.X, npc.position.Y + 14f), npc.velocity, type, 1f); //creates another broken piece of the NPC
    }
    else
    {
        for(int x = 0; x < (double)damage / (double)npc.lifeMax * 100.0; x++) //The more damage is done, the more red "blood" particles will be created
        {
            Dust.NewDust(npc.position, npc.width, npc.height, 5, (float)hitDirection, -1f, 0, default(Color), 1f);
        }
    }
}
The next example (also borrowed from my mod, which borrowed from possessed armors) will create the smoke effect:
Code:
public override void HitEffect(int hitDirection, double damage, bool isDead)
{
    if(isDead)
    {
        for(int k = 0; k < 20; k++)
        {
            int dust = Dust.NewDust(npc.position, npc.width, npc.height, 54, 0f, 0f, 50, default(Color), 1.5f);
            Main.dust[dust].velocity *= 2f;
            Main.dust[dust].noGravity = true;
        }
        int gore = Gore.NewGore(new Vector2(npc.position.X, npc.position.Y - 10f), new Vector2((float)hitDirection, 0f), 99, npc.scale); //99 is the ID for the smoke gore
        Main.gore[gore].velocity *= 0.3f;
        gore = Gore.NewGore(new Vector2(npc.position.X, npc.position.Y + (float)(npc.height / 2) - 15f), new Vector2((float)hitDirection, 0f), 99, npc.scale);
        Main.gore[gore].velocity *= 0.3f;
        gore = Gore.NewGore(new Vector2(npc.position.X, npc.position.Y + (float)npc.height - 20f), new Vector2((float)hitDirection, 0f), 99, npc.scale);
        Main.gore[gore].velocity *= 0.3f;
    }
}

With all this, you may be wondering: how do you make custom gore? You've seen that I used custom gore for "Bluemagic:Retineye" and "Bluemagic:Spazmateye". In your mod's base directory, first create a folder called "Gores" (without the quotes, of course). Then create an image file inside that folder. Congratulations, you can now create custom gore using "ModName:ImageName"! This gore will have the default behavior of flying away and rolling on the ground.
Here is the method signature for creating a gore:
Code:
Gore.NewGore(Vector2 position, Vector2 velocity, int type, float scale)

Gore.NewGore(Vector2 position, Vector2 velocity, string type, float scale)
Use the first one to create a vanilla gore, and the second to create a custom gore.
More coming soon! (sorry, I have a ton of homework)
Planned: Attack Patterns, Boss parts (like Pumpking), Drawing
I will also add things to this section as I think of them.

Feel free to ask any questions you have. Also please tell me if I made any mistake anywhere; I'm still relatively new to modding (around 3 weeks) at the time of writing this.
 
Last edited:
Very nicely written. It's very admirable to see someone take such a long time to write an in-depth and resourceful guide towards a topic that not many are familiar with.
 
Awesome! And how I can add a custom music to my boss (Not vanilla one)?
If the "music" property for your NPC's json file doesn't work (which is what happens for me), then you can adapt the code in the tutorial:
Code:
public override void ChooseTrack(ref string current)
{
if(Main.gameMenu)
{
return;
}
if(NPC.AnyNPCs("ModName:BossName"))
{
current = "Vanilla:Music_13"; //replace with whatever music you want. This one happens to be the brain of cthulhu / destroyer song.
}
}
making sure to place it in your ModBase class, then replace the "Vanilla:Music_13" with "ModName:MusicName".
 
If the "music" property for your NPC's json file doesn't work (which is what happens for me), then you can adapt the code in the tutorial:

making sure to place it in your ModBase class, then replace the "Vanilla:Music_13" with "ModName:MusicName".
Where do I put music .wav file?
 
Where do I put music .wav file?
I'm not entirely sure for that part...
*delves into source code*
Hm, I found two different things, and I'm not completely sure which to use. It looks like you can create a Sounds folder to put .wav files in. It also looks like you can create a Wavebanks folder that I'm not entirely sure what to do with. For more clarity, you could try asking people in the tAPI thread.
 
e385c9b012.png

I get this when trying to compile the music code thing.

Code:
public override void ChooseTrack(ref string current)
{
if(Main.gameMenu)
{
return;
}
if(NPC.AnyNPCs("BosseriaRebirth:GeartronPrime"))
{
current = "BosseriaRebirth:Test";
}
}
 
e385c9b012.png

I get this when trying to compile the music code thing.

Code:
public override void ChooseTrack(ref string current)
{
if(Main.gameMenu)
{
return;
}
if(NPC.AnyNPCs("BosseriaRebirth:GeartronPrime"))
{
current = "BosseriaRebirth:Test";
}
}
You're making sure to put that inside of your ModBase class, and you're putting your ModBase class inside a namespace, right?
 
How could I do that?
http://puu.sh/fnKXf/c52ccd7ad6.png
I have this
First, don't actually call that file ModBase; just make it extend ModBase. That file is basically the "center" of your mod, so I personally just give it the same name as my mod. Next, inside that file, you'll need to have this:
Code:
using System;
using Terraria;
using TAPI;

namespace YourMod
{
    public class SameNameAsFile : ModBase
    {
        public override void ChooseTrack(ref string current)
        {
            //put that code here
        }
    }
}
 
First, don't actually call that file ModBase; just make it extend ModBase. That file is basically the "center" of your mod, so I personally just give it the same name as my mod. Next, inside that file, you'll need to have this:
Code:
using System;
using Terraria;
using TAPI;

namespace YourMod
{
    public class SameNameAsFile : ModBase
    {
        public override void ChooseTrack(ref string current)
        {
            //put that code here
        }
    }
}
Ok i got it all compiled but when I summon the boss ingame it keeps the default music. Does it still have to have specifics like in tconfig or do I need to add the file extension aswell? It also could be i summon another thing alongside it.
 
Ok i got it all compiled but when I summon the boss ingame it keeps the default music. Does it still have to have specifics like in tconfig or do I need to add the file extension aswell? It also could be i summon another thing alongside it.
Hm, we need to find out whether the problem is the way custom music is loaded or something else. Try changing the music to something like "Vanilla:Music_13", and if a different music plays, then we know the method is working. If the method is working, then you need to load your custom music using wavebanks, which I have no idea about, so you'll have to ask someone on the tAPI thread. If it turns out that the method isn't working (the default music still plays), then could you show your file again?
 
Hm, we need to find out whether the problem is the way custom music is loaded or something else. Try changing the music to something like "Vanilla:Music_13", and if a different music plays, then we know the method is working. If the method is working, then you need to load your custom music using wavebanks, which I have no idea about, so you'll have to ask someone on the tAPI thread. If it turns out that the method isn't working (the default music still plays), then could you show your file again?
Yea, vanilla music works and it changed properly. Sounds like we need to use wavebanks.
 
The music json parameter is indeed broken in the current TAPI build. It is a simple matter of the json parser failing at copying the value in-game. Changing the npc class music string manually (I do that in my AI, as an example) actually does fix the issue pretty easily. You need wavebanks for custom tracks, but all my attempts so far have failed, they might be currently broken.
 
It'd be nice if you made a template (kind of like grox the great's) for this and put it up for download, just something basic, it would help me understand it better, I'm a hands on learner.
 
It'd be nice if you made a template (kind of like grox the great's) for this and put it up for download, just something basic, it would help me understand it better, I'm a hands on learner.
Hm, since bosses are usually unique, it might be hard to create some template for them; if you want examples though, you can try to decompile anyone's mod, including mine (at the moment my mod is nothing but two bosses) By decompile I really mean extract; .tapi files are basically just .zip files from what I understand.
 
Last edited:
Yeah I can do that, I just thought a basic template would be nice for people (like me) who are not good at following instructions and learn better by seeing a finished product.
 
Yeah I can do that, I just thought a basic template would be nice for people (like me) who are not good at following instructions and learn better by seeing a finished product.
Hm, I see. So basically synthesizing all the pieces of code I put in the tutorial into one whole NPC, right? I'll try to do that pretty soon.
 
Hm, I see. So basically synthesizing all the pieces of code I put in the tutorial into one whole NPC, right? I'll try to do that pretty soon.
Awesome make sure and tell me when you do!, I have been making a mod dedicated to a band I love and I've been stuck on the boss...
 
Back
Top Bottom