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.
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.
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).
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.
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:
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.
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;
}}
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;
}
}}
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;
}
}
}
}
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
1/12/15: Created
Last edited: