tAPI [Tutorial] Summoner Weapon (Programming knowledge required)

blushiemagic

Retinazer
tModLoader
Melee, ranged, and magic weapons are all relatively easy to create; just create a projectile, make your weapon fire it, then done. However, summoner weapons are way more difficult to create. Why? Because they are ridiculously hard-coded into the game. There are literally if statements that check for all the ID's of the vanilla summoner weapons, making it hard to create a mod summoner weapon that imitates the behavior of vanilla weapons perfectly. Even the projectile AI styles rely on the projectile IDs (fun fact, baby slimes and pigmies share the same AI style with pets). This tutorial will attempt to teach you how to get around all the hard-coding and make a summoner weapon. Warning, you will actually have to know how to program your own code!

The first thing we will start with is the player class. If you haven't already, make a class for you mod that extends ModPlayer, and add the following field to it.
Code:
using System;
using Terraria;
using TAPI;

namespace Example {
public class ExamplePlayer : ModPlayer
{
    public bool exampleMinion = false;
}}
And then you will not have to touch that file for the rest of this tutorial.

Now, in order to make everything work, we need 3 things: the item, the projectile (minions are projectiles), and the buff. First we need to make the .json files.
Code:
{
    "displayName": "Example Minion",
    "tip": "This is what shows when you hover over the buff with your mouse.",
    "noTimer": true
}
Code:
{
    "displayName": "Example Weapon",
    "width": 40, //depends on your item
    "height": 40, //depends on your item
    "tooltip": ["This is what displays when you hover your mouse over the item in the inventory."],
    "useStyle": 1,
    "useAnimation": 36, //how long you swing your item
    "useTime": 36, //make this same as useAnimation
    "maxStack": 1,
    "damage": 50, //or whatever you want
    "knockback": 4, //or whatever you want
    "autoReuse": false,
    "rare": 8, //or whatever you want
    "shootSpeed": 5, //most summon weapons have this as 10
    "mana": 10,
    "summon": true,
    "value": [0, 50, 0, 0], //sell value is a fifth of this; [platinum, gold, silver, copper] coins
    "noMelee": true,
    "useSound": 44, //or whatever you want
    "shoot": "Example:ExampleMinion",
    "buff": "Example:ExampleBuff"
}
Code:
{
    "displayName": "Example Minion",
    "width": 32, //or whatever you want
    "height": 32, //or whatever you want
    "frameCount": 11, //the number of images in your image file (I'll go over this later)
    "pet": true,
    "timeLeft": 18000,
    "penetrate": -1,
    "minion": true,
    "minionSlots": 1,
    "damage": 50, //I don't know if this actually matters
    "knockback": 4 //I don't know if this matters either
}

Now that we have that, we can work on the actual code. The simplest will be the buff code (or at least, this was the easiest part for me to figure out).
Code:
using System;
using Terraria;
using TAPI;

namespace Example.Buffs {
public class ExampleBuff : ModBuff
{
    public override void Start(Player player, int index)
    {
        player.buffTime[index] = 3600;
    }

    public override void MidUpdate(Player player)
    {
        bool flag = false;
        for(int k = 0; k < 1000; k++)
        {
            if(Main.projectile[k].active && Main.projectile[k].owner == player.whoAmI && Main.projectile[k].type == ProjDef.byName["Example:ExampleMinion"].type)
            {
                flag = true;
                break;
            }
        }
        ExamplePlayer modPlayer = player.GetSubClass<ExamplePlayer>();
        if(flag)
        {
            modPlayer.exampleMinion = true;
        }
        int type = BuffDef.byName["Example:ExampleMinion"];
        int index = -1;
        for(int k = 0; k < player.maxBuffs; k++)
        {
            if(player.buffType[k] == type)
            {
                index = k;
                break;
            }
        }
        if(index < 0)
        {
            return;
        }
        if(!modPlayer.exampleMinion)
        {
            player.DelBuff(index);
        }
        else
        {
            player.buffTime[index] = 18000;
        }
    }

    public override void End(Player player, int index)
    {
        player.GetSubClass<ExamplePlayer>().exampleMinion = false;
    }
}}
Basically, that buff is responsible for staying as long as you have the minions, and for removing the minions when the buff is cancelled.

Note: As of version r15 of tAPI, we no longer need to make a ModItem class for the summoning weapon. Hurray for less copying source code!

Now we get to the fun part, the projectile/minion code. I do not want to give away too much of the source code, so I will leave comments at some parts for you to program yourself; this is why I said you need to actually know how to program. I recommend checking out my tutorial on NPC AI; projectiles are very similar to NPCs, except they only have two slots in the "ai" array and don't have "target" or "life" fields.
Code:
using System;
using Microsoft.Xna.Framework;
using Terraria;
using TAPI;

namespace Example.Projectiles {
public class ExampleMinion : ModProjectile
{
    //If you want to store information, place fields here.

    public override void OnSpawn()
    {
        projectile.netImportant = true;
    }

    public override void AI()
    {
        Player player = Main.player[projectile.owner];
        ExamplePlayer modPlayer = player.GetSubClass<ExamplePlayer>();
        if(!player.active)
        {
            projectile.active = false;
            return;
        }
        if(player.dead)
        {
            modPlayer.exampleMinion = false;
        }
        if(modPlayer.exampleMinion)
        {
            projectile.timeLeft = 2;
        }
        //In the source code, some variables were set up here to keep track of things; you might want to do the same.
        if(projectile.ai[1] == 0f)
        {
            if(player.rocketDelay2 > 0)
            {
                projectile.ai[0] = 1f;
            }
            //Fill in with your own code. If the player is too far away, set projectile.ai[0] = 1f. If the player is even more too far away, teleport the projectile to the player instead.
        }
        if(projectile.ai[0] != 0f)
        {
            projectile.tileCollide = false;
            //Adjust projectile.velocity to make the projectile fly towards the player here.
            //Set projectile.spriteDirection to 1 or -1 to make it look like it's facing a certain direction.
            UpdateFrame();
            //If you want, you can make dust (the colorful particles) here; in fact you can make dust wherever you want.
        }
        else
        {
            projectile.tileCollide = true;
            //Make your minion do what it would normally do here, attacking enemies or following you.
            //Make sure to set projectile.friendly to true if it is attacking, and set it to false if it is not attacking; this is very important!
            //If projectile.friendly is set to false, your minion will not do damage.
            //If projectile.friendly is set to true, your minion will destroy all the plants and stuff, which is annoying.
            //Set projectile.spriteDirection to 1 or -1 to make it look like it's facing a certain direction.
            UpdateFrame();
            projectile.velocity.Y += 0.4f; //This updates gravity.
            if(projectile.velocity.Y > 10f)
            {
                projectile.velocity.Y = 10f; //So it doesn't fall way too fast. Yes, positive is downwards.
            }
        }
        Damage();
    }

    private void Damage()
    {
        Player player = Main.player[projectile.owner];
        //Copy over the code from Projectile.Damage() from Terraria's source code, then remove the first two if statements and replace all of the "this" with "projectile".
    }

    private void UpdateFrame()
    {
        //Set projectile.frame here based on your projectile's state. I'll explain what this is later. To keep track of animation time, you can use projectile.frameCounter.
        projectile.frameCounter++; //Only if you are using projectile.frameCounter.
        projectile.frameCounter %= 10; //Or however long you want one cycle of animation to last.
    }
}}

You many be wondering what these frames I have been talking about are. They are basically what make the minions animated. In a single image file, first create an picture for your minion. Remember the size, then below that picture but in the same image file, make another picture of the same size for a different "frame" of animation. For example, the first frame could be your minion when it is standing, then the frames below could be parts of its walking animation. In the json file, update frameCount to the number of pictures/frames in your image file. Then in the UpdateFrame() method, use projectile.ai[0], projectile.velocity, and optionally projectile.frameCounter to determine which frame your minion should currently be using. The frame numberings start from 0, so the frame on the top of the image is frame 0, the one below is frame 1, and so on. In UpdateFrame(), do stuff like projectile.frame = 0.

You might be wondering one last thing: how do you tell if there is a monster that your minion can attack? Use this piece of code to do that:
Code:
float moveToX = projectile.position.X;
float moveToY = projectile.position.Y;
float moveDist = 100000f; //This is basically the view range of your minion; you might want to make it slightly larger than this.
int attacking = -1;
for(int k = 0; k < 200; k++)
{
    if(Main.npc[k].active && !Main.npc[k].dontTakeDamage && !Main.npc[k].friendly && Main.npc[k].lifeMax > 5)
    {
        float monsterX = Main.npc[k].position.X + (float)(Main.npc[k].width / 2);
        float monsterY = Main.npc[k].position.Y + (float)(Main.npc[k].height / 2);
        float monsterDist = System.Math.Abs(projectile.position.X + (float)(projectile.width / 2) - monsterX) + System.Math.Abs(projectile.position.Y + (float)(projectile.height / 2) - monsterY);
        if(monsterDist < moveDist)
        {
            if(monsterDist < moveDist && Collision.CanHit(projectile.position, projectile.width, projectile.height, Main.npc[k].position, Main.npc[k].width, Main.npc[k].height))
            {
                moveDist = monsterDist;
                moveToX = monsterX;
                moveToY = monsterY;
                attacking = k;
            }
        }
    }
}
moveDist is the distance from the distance from the nearest monster your projectile can see; moveToX and moveToY specify the location of this monster; and attacking is the index location of the monster in Main.npc.

I'm probably leaving out many things, so feel free to ask any questions you have. Also let me know if there's any bugs; I adapted all the examples from my own summoner weapon, so some namings may or may not be wrong.

4/21/15: Updated with bug fix in r15; Added link to AI tutorial
1/12/15: Created
 
Last edited:
Interesting. I've avoided getting into modding, but your post might just force me to properly learn C#. Part of the hesitancy comes of being an old-time Borland fan: it pains me to think of Anders Hejlsberg writing a language for Microsoft. But I'm also constantly switching among about eight languages as it is, and throwing one more into the mix would add still more opportunities to confuse myself. Still, it's tempting, and I can read your code just fine (I pretend it's Object Pascal that thinks it's postmodern C++ and everything makes sense).

Thanks for the code and the nudge. I might have to open VS2013 instead of Terraria during near-future spare time.
 
Just a few corrections :dryadsmile:

This code was somewhat borrowed then adapted from a part of the summoner weapons hard-coded to work only for vanilla summon IDs. It basically deletes minions to make room for the new one if you are already at the minion cap, and allows you to summon more than one of your minion at a time if your cap is high enough (due to hard-coding stuff, any custom minion weapon would normally be disallowed from having more than one of the same minion at the same time no matter what).
The first part is true, and a derp on tAPI's behalf. In R15 the PreItemCheck wont be needed, it's fixed to work with mod minions as well now.

The second part though isn't, as far as I can tell. There does not seem to be any limit to how many of a minion you can have, I was able to get 5 of my minion on R14A without the PreKill and summoning flag. (There is a mistake with how minion slots work though, the code always thinks your minion only needs 1 slot, that is also fixed for R15)


Either way, nice tutorial :dryadsmile: Helped save me some time from digging through the code for this stuff myself :dryadtongue:
 
Just a few corrections :dryadsmile:


The first part is true, and a derp on tAPI's behalf. In R15 the PreItemCheck wont be needed, it's fixed to work with mod minions as well now.

The second part though isn't, as far as I can tell. There does not seem to be any limit to how many of a minion you can have, I was able to get 5 of my minion on R14A without the PreKill and summoning flag. (There is a mistake with how minion slots work though, the code always thinks your minion only needs 1 slot, that is also fixed for R15)


Either way, nice tutorial :dryadsmile: Helped save me some time from digging through the code for this stuff myself :dryadtongue:
Ah, I think I know what happened. I set "pet" to true for my minion just so I could be consistent with the vanilla minions. This is what leads to the part where minions are deleted to make room for the new one, and if it isn't a certain ID, then an else statement kills duplicates of the minion instead (and ignores the minion cap), which is how things like the frost hydra behaves. So that code was also getting run for my custom minion.

Anyways, thanks for letting me know about the change in R15 :)
 
Wow! Nice guide! This helped me a lot.
But, when I tried to build my mod, this happened.
upload_2015-2-1_8-14-6.png
 
I think I want to try this sometime, I wondered what it be like to have summoning staffs for one of each Normal and Mecha Boss. Only be able to summon one of each (save for the twins, since they have one for their selves and that they come in pairs. Or make a staff for them anyway) Thanks for posting this!
 
Great guide here. I only wish this had been available when I started my minion last year.

Anyway, I found that my minion wasn't doing any damage to enemies. I believe that this was because I had "pet": true, in my .json file. I set this to false, but found that my minion was disappearing on collision, even though I had set penetrate to -1. Your solution to this was to set pst to true then copy all of the source AI except the part that disables damage for pets. I didn't want to have such a massive slab of code in my already chunky minions .cs file.

Fortunately, I found a solution. If you remove "pet": true, from your .json file and include the following code in your minion's .cs file...
Code:
public override bool OnTileCollide(ref Vector2 velocityChange)
{
    //any other collision stuff goes here

    return false;
}
Your minion will no longer disappear on collision and will still damage any NPCs it attacks. ...unless there's something else in my .cs file that I missed. Can someone confirm this for me, please?

btw, I'm using my minion's flight animation for my avatar. What do you think?
 
Great guide here. I only wish this had been available when I started my minion last year.

Anyway, I found that my minion wasn't doing any damage to enemies. I believe that this was because I had "pet": true, in my .json file. I set this to false, but found that my minion was disappearing on collision, even though I had set penetrate to -1. Your solution to this was to set pst to true then copy all of the source AI except the part that disables damage for pets. I didn't want to have such a massive slab of code in my already chunky minions .cs file.

Fortunately, I found a solution. If you remove "pet": true, from your .json file and include the following code in your minion's .cs file...
Code:
public override bool OnTileCollide(ref Vector2 velocityChange)
{
    //any other collision stuff goes here

    return false;
}
Your minion will no longer disappear on collision and will still damage any NPCs it attacks. ...unless there's something else in my .cs file that I missed. Can someone confirm this for me, please?

btw, I'm using my minion's flight animation for my avatar. What do you think?
The only reason I copy over most of the source AI is that some of the AI styles have projectile type-dependent parts everywhere (such as how baby slimes, pygmies, and pets all share the same aiStyle), so that just using an aiStyle would not work. However, some specific aiStyles definitely would work if you just use them (I believe some of the flying ones, such as the raven, work just fine). That OnTileCollide method is probably just needed for one of those type-dependent AIs.
 
I still get some issues with this, despite the solution that Pumpking found:
AZP16bi.png
 
Is your minion's class name called expansionMinion or ExpansionMinion? Make sure you copy your capitalizations exactly.
It didn't work either way

EDIT: Actually fixed it, but now I have this error:
qF69BSx.png
 
Last edited by a moderator:
It didn't work either way

EDIT: Actually fixed it, but now I have this error:
qF69BSx.png
For the last error, you need to replace base with projectile.
For the first two, is Chaser the minion class? If so, you need to add a field called summoning to it, like is shown in the tutorial.
 
For the last error, you need to replace base with projectile.
For the first two, is Chaser the minion class? If so, you need to add a field called summoning to it, like is shown in the tutorial.
zMmHeY8.png

I have no idea what this means, but now it's the only error
 
Back
Top Bottom