tAPI [Tutorial] Projectile Guide and Implementation

Sin Costan

Eye of Cthulhu
Tutorial: Basic Projectile Guide and Implementation
Brought to you Sin Costan - Please excuse my bad Comp Sci-ing and Thread Making Skills

Other Guides/Tutorials & Great Resources:

- Getting Started on Modding - Berberborscing
- [Tutorial] Custom Dust - BlueMagic123
- [Tutorial] Custom Boss - NPC AI and Server Syncing - BlueMagic123
- [Tutorial] Summoner Weapon - BlueMagic123
- [TUTORIAL] How to make a basic flail weapon type weapon - Gmod
- [Tutorial] Generics and Custom JSON Properties - MiraiMai
- [Tutorial] The many aspects of World Generation - TheGamingBoffin
- tAPI Community Resources - Development assistance for developers, by developers - Neojin
- http://dev.willhuxtable.com/ids/ - Uknown (Projectile AI Styles not Updated)
- Projectile AIs, Projectile AI Style IDs, Projectile IDs & Stats, MPT as an Attachment to the thread as txts and zips.


The first thing you might ask is - "Why do we need this?"
Well... The tutorial itself will answer that question for you...


Notes:


- There is a .zip file of an already completed tutorial and the complete list of AI Styles to reference when using this tutorial.
- Image files are with that .zip file, so use those when using this tutorial.
- For the Dust IDs and Buff IDs, refer to the Wikias or this handy Website, http://dev.willhuxtable.com/ids/, though the projectile aiStyles is not updated.
- Most of the example items found in this tutorial were made just for this tutorial. There will only be one item I have made for my mod "The Collection of Fun Projectiles".
- I suck at spriting.
- I wil be editing this post to fix some English-ing and include some other things.
- Time in Terraria is set in frames: 60 frames per Second
- Please read through the whole tutorial, if you have questions, post them on this thread.
- Credits to Grox (GRealm), DiverManSam (Thorium +), Zoodletec (Necro), Berberbirscing (His mod and Guide), and other Modders for some of their code!

1. Creating a Basic Projectile, Projectile Based Weapon, and Ammo
2. Using/Editing Vanilla Projectile Stats
3. Making your projectile make/do fancy effects
- Dust
- Spin
- Light Up
4. Different Methods to make the projectile unique
- DealtNPC()
- PostKill()
- AI()
- OnTileCollide()
5. Modifying your Projectile Based Weapon
- ConsumeAmmo()
- PreShoot()
6. Manipulating AI Styles
1. You are modding with tAPI
2. You have a little bit of experience or have went through the “Getting Started with Modding” tutorial (by Berberborscing)
3. Have little to no experience with projectiles
4. You will follow this tutorial step by step.

Before we start with the creating the basic items for this tutorial, I want you to make a new mod folder within the Sources folder with the name of "MPT" (Modding Projectiles Tutorial), so your ModInfo.json should be like this:

Code:
{
    "displayName":"Modding Projectiles Tutorial",
    "internalName":"MPT",
    "author": "Your Name",
    "info":"These are my Projectiles!",
    "version":"1.0",
    "includePDB": true
}

Make sure to save the textures in this tutorial and place them with their respective partners.

Let’s start by making the Projectile Based Weapon, so we’ll stick with a gun. We’ll put this inside the folder called “Items”. So we are going to make our JSON file with these parameters.

s7qBL1H.png

Code:
{
    "displayName": "Example Gun",
    "size": [52,18],
    "maxStack": 1,
    "value": [0,0,0,1],
    "rare": 1,
    "tooltip": ["Fires Example Rounds"],
    "useStyle": 5,
    "useAnimation": 15,
    "useTime": 15,
    "damage": 10,
    "knockback": 5,
    "useSound": 11,
    "ranged": true,
    "shoot": "MPT:ExampleProjectileA",
    "useAmmo": "MPT:ExampleRounds",
    "shootSpeed": 8,
    "noMelee": true,

    "recipes":
    [{
        "items": { "Dirt Block": 1 },
        "creates": 1
    }]
}

And now we are going to making two projectiles, which we’ll put inside a folder called “Projectiles”. The projectile aiStyles can be found as an attachment on the bottom of this post. Anyways, we will stick with aiStyle 0, which is just the projectile going straight forward and have it as a Musket Ball.

YhJShkf.png

ExampleProjectileA.json
Code:
{
    "displayName": "Example Projectile A",
    "size": [12,12],
    "scale": 1,
    "aiStyle": 0,
    "timeLeft": 180,
    "friendly": true,
    "hostile": false,
    "tileCollide": false,
    "penetrate": 10,
    "ranged": true,
    "maxUpdates": 1
}

7slfPGT.png

ExampleProjectileB.json
Code:
{
    "displayName": "Example Projectile B",
    "size": [12,12],
    "scale": 1,
    "aiStyle": 0,
    "timeLeft": 180,
    "friendly": true,
    "hostile": false,
    "tileCollide": false,
    "penetrate": 10,
    "ranged": true,
    "maxUpdates": 1
}

After these two items are done, we should start on our ammo, which will also be put within the “Items” folder. Since we have two types of projectiles, we will have two types of ammo to use. We will have the items have the same icon as their respective projectile.

ExampleRoundsA.json
Code:
{
    "displayName": "Example Rounds A",
    "size": [12,12],
    "scale": 1,
    "maxStack": 999,
    "value": [0,0,5,0],
    "rare": 2,
    "tooltip": ["Example Rounds A"],
    "ammo": "ExampleRounds",
    "shoot": "MPT:ExampleProjectileA",
    "damage": 10,
    "consumable": true,

    "recipes":
    [{
        "items": { "Dirt Block": 1},
        "creates": 111
    }]
}

ExampleRoundsB.json
Code:
{
    "displayName": "Example Rounds B",
    "size": [12,12],
    "scale": 1,
    "maxStack": 999,
    "value": [0,0,5,0],
    "rare": 2,
    "tooltip": ["Example Rounds B"],
    "ammo": "ExampleRounds",
    "shoot": "MPT:ExampleProjectileB",
    "damage": 10,
    "consumable": true,

    "recipes":
    [{
        "items": { "Dirt Block": 1},
        "creates": 111
    }]
}

Now we that we have all of these, compile it with tAPI Builder and test it out. This wraps up the first lesson of creating your own projectile for your future mods!

*note: Some of these items you don't need or can remove straight out in the Weapon
- "useAmmo": you can just not include it, it won't use the ammo and it's projectile
- "useAnimation" and "useTime": If "useAnimation" is greater than "useTime", it will fire multiple times per use.
- "mana": alternative to ammo if you want to make a magic weapon; it will consume mana rather than ammo
- "shoot": isn't solely restricted on projectile based weapons like magic or ranged, you can use it on a melee weapon also like Terra Blade
- "autoReuse": Allows the weapon to keep firing while holding the mouse button; If not used, will not keep firing while holding mouse button.
- "reuseDelay": Time in between uses; Like everthing else, time is in Frames.
- "noMelee": Makes item not hit anything while animated;
*note: Some of these items you don't need or can remove straight out in the Projectile
- "scale": if you don't have this, it stays at the scale of "1".
- "penetrate": if you don't have this, it only hits 1 enemy; If set to -1, infinite penetration of enemies
- "timeLeft": if you don't have this, projectile will last forever or until the game decides to despawn it;
- “maxUpdates”: not really necessary; Heat Ray has this set to 100 to keep laser effect, does not work on .png images, only with certain aiStyles.
- “tileCollide”: if you don’t have this, sets to default setting, hitting tiles
*note: ammos with the same “ammo” name are used by the weapon just the same, as seen if you have compiled and tried this out yourself.
Welcome to part 2 of the Projectiles Tutorial, Using/Editing Vanilla Projectiles.

You are not editing the Vanilla Projectile's property in game, rather, making a new projectile with the Vanilla Projectile's AI and Texture. Let's say you want to make Example Projectile A into a Demon Scythe rather than a bullet.

You would need to:

- Set the AI Style to the AI Style of a Demon Scythe
- Set the projectile's type to the Demon Scythe Projectile (This automatically makes the texture default to the Demon Scythe, so you won't need to make a new texture)

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class ExampleProjectileA : ModProjectile
    {
        public override void AI()
        {
            projectile.type = 45; //45 is the Demon Scythe's ID;
        }
    }
}

Remember all the properties of Example Projectile A? Well normally, Demon Scythe would only hit 5 targets and not go through walls, but instead it will hit up to 10 and go through walls!

If you want to use a vanilla projectile for your projectile, you have to match the type to it's aiStyle (Demon Scythe ID = 45 - Demon Scythe aiStyle = 18, Sharknado Minion ID = 407 - Sharknado Minion aiStyle = 62, etc. [You can find the projectile IDs as an attachment])
Nice, you made it to part 3 of this tutorial! Now here are functions that are important for this.
Code:
int DustID = Dust.NewDust(Vector2 Position, int Width, int Height, int Type, float SpeedX = 0f, float SpeedY = 0f, int Alpha = 0, Color newColor = default(Color), float Scale = 1f) //Spawns Dust
Main.dust[DustID].noGravity = true; //Causes dust to not be affected by gravity when spawned
projectile.light = float power; //Can go from 0 to a pretty high number
projectile.alpha = int transparency; //Transparency (255 makes it totally invisible)
projectile.rotation += (float)projectile.direction * float rotationSpeed; //Direction determines whether it goes clockwise or counterclockwise (1 = clockwise, -1 = counterclockwise); Max Rotation speed you can see well is 10.5f; Rotation does not work on some aiStyles

We will now start to make a .cs file for our other projectile, Example Projectile B in which we shall call the file "ExampleProjectileB.cs". We copy over ExampleProjectileA's .cs properties onto ExampleProjectileB, in which instead of calling the class ExampleProjectileA, we call it ExampleProjectileB. After that, you might want to add some of these effects into your AI() method. Let's say we want it to be a bit transparent, some spin, and produce some dust! We would have the .cs file sort of like this.

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class ExampleProjectileB : ModProjectile
    {
        public override void AI()
        {
            projectile.light = 0.9f; //Glow just enough for it to cover the projectile
            projectile.alpha = 128; //Makes it semi-transparent
            projectile.rotation += (float)projectile.direction * 0.8f; - Makes the projectile spin
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 36, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f); //Create Dust
            Main.dust[DustID].noGravity = true; //Makes the Dust not fall into the ground
        }
    }
}
Welcome to Part 4 of the Projectile Tutorial. Here, we will make your projectile do more than just look pretty!

Here are the Methods and variables we will be using in this tutorial.
Code:
public override void DealtNPC(NPC n, int hitDir, int dmgDealt, float knockback, bool crit) //Calls when you hit an enemy
public override void AI() //The projectile's AI/ what the projectile does
public override bool OnTileCollide(ref Vector2 velocityChange) //When the projectile hits a tile
public override void PostKill() //Calls after the projectile "dies" / run out of time
projectile.velocity.X = float velocity // Velocity of the projectile in the X axis
projectile.position.X = int position // Position of the projectile in the X axis
projectile.velocity.Y = float velocity //Velocity of the projectile in the Y axis
projectile.position.Y = int position // Position of the projectile in the Y axis
projectile.timeLeft = int timeInFrames //The amount of time the projectile has to live
int rand = Main.rand.Next(int rightEnd); //Makes a random integer from 0 to the right end number; Helps make random chances
n.AddBuff(int Type, int Time) //Adds buff/debuff to enemy and its countdown in terms of frames
Player owner = Main.player[projectile.owner] //Makes a Player variable of yourself
Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, int type or string type, projectile.damage, projectile.knockBack, Main.myPlayer); //Creates a new Projectile
MathHelper.Lerp(float leftEnd, float rightEnd, (float)Main.rand.NextDouble()) //Creates a random float number; Useful for random velocities
Iten.NewItem(int X, int Y, int Width, int Height, int Type or string Type, int Stack = 1, bool noBroadcast = false, int pfix = 0, bool noGrabDelay = false); //Creates an item at a certain position


Now let's go make ExampleProjectileB intense, we'll give it these properties.

- Spawn Example Projectile A every quarter of a second
- Have it impale onto enemies (though there won't be a chance for it to stick due to excessive amounts of projectiles)
- Cause the On Fire! debuff
- Bounce off walls and half the velocity
- Get life on hit (without heal effect)
- Spawn an Item after the projectile is done

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class ExampleProjectileB : ModProjectile
    {
        //The AI()
        public override void AI()
        {
            Player owner = Main.player[projectile.owner]; //Makes a player variable of owner set as the player using the projectile
            projectile.light = 0.9f;
            projectile.alpha = 128;
            projectile.rotation += (float)projectile.direction * 0.8f;
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 36, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID].noGravity = true;
            if(projectile.timeLeft % 15 == 0) //If the remainder of the timeLeft divided 15 is 0, then make the projectile; Every 15 seconds spawn a projectile basically
            {
                Projectile.NewProjectile(projectile.position.X, projectile.position.Y, MathHelper.Lerp(-1f, 1f, (float)Main.rand.NextDouble()), MathHelper.Lerp(-1f, 1f, (float)Main.rand.NextDouble()), "MPT:ExampleProjectileA", 5 * (int)owner.rangedDamage, projectile.knockBack, Main.myPlayer); //owner.rangedDamage is basically the damage multiplier for ranged weapons
            }
        }
        //When you hit an NPC
        public override void DealtNPC(NPC n, int hitDir, int dmgDealt, float knockback, bool crit)
        {
            Player owner = Main.player[projectile.owner];
            //Start of impalement code
            projectile.position.X = n.position.X;
            projectile.position.Y = n.position.Y;
            projectile.velocity.X = n.velocity.X;
            projectile.velocity.Y = n.velocity.Y;
            //End of impalement code
            int rand = Main.rand.Next(2);
            if(rand == 0)
            {
                n.AddBuff(24, 180); //On Fire! debuff for 3 seconds
            }
            else if (rand == 1)
            {
                owner.statLife += 5; //Gives 5 Health
            }
        }
        public override bool OnTileCollide(ref Vector2 velocityChange)  //Thanks to Diverman Sam for this wonderful code for bouncing off walls!
        {
            if (projectile.velocity.X != velocityChange.X)
            {
                projectile.velocity.X = -velocityChange.X/2;
            }
            if (projectile.velocity.Y != velocityChange.Y)
            {
                projectile.velocity.Y = -velocityChange.Y/2;
            }
            return false;
        }
        //After the projectile is gone
        public override void PostKill()
        {
            int rand = Main.rand.Next(5);
            Projectile.NewProjectile(projectile.position.X, projectile.position.Y, 0, 0, 296, (int) (projectile.damage * 1.5), projectile.knockBack, Main.myPlayer); // 296 is the explosion from the Inferno Fork
            if(rand == 0)
            {
                Item.NewItem((int)projectile.position.X, (int)projectile.position.Y, projectile.width, projectile.height, "MPT:ExampleRoundsB", 1, false, 0, false);
            }
        }
    }
}
Welcome to Part 5 of this tutorial! You're nearly done! Here we will be modifying your projectile based weapon.

First off, before we start with modifying the projectile based weapon.

- Change the "useAnimation" to 39 and "useTime" to 10 in "ExampleGun.json" (this makes it fire 3 times per one click)
- Add ourselves a .cs file for our ExampleGun and call it "ExampleGun.cs".

Here are the things we will be using in this part of the tutorial.

Code:
public override bool ConsumeAmmo(Player p) //Calls when you fire
public override bool PreShoot(Player player, Vector2 position, Vector2 velocity, int type, int damage, float knockback) //Calls before weapon shoots projectile
int rand = Main.rand.Next(int rightEnd); //Random Chance!
p.itemAnimation //The time left in frames
p.inventory[p.selectedItem].useAnimation //total time the Animation takes
for(int x = 0; x < int shotAmount; x++) //Amount of shots per one use

Now let's give it some cool properties. We'll have it:

- Each fired round in the burst shoots 3 instead of 1 (like a shotgun)
- Consume one thing of Ammo
- Have that one thing of Ammo have a chance not to be used

Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using TAPI;
using Terraria;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MPT.Items
{
    public class ExampleGun : ModItem
    {
        public override bool ConsumeAmmo(Player p)
        {
            int rand = Main.rand.Next(9); //Random chance
            if(p.itemAnimation < p.inventory[p.selectedItem].useAnimation - 30) //Makes it consume only one ammo
            {
                if(rand == 0 || rand == 1) //Chance for you not to use the single used ammo
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return false;
            }
        }
        public override bool PreShoot(Player player, Vector2 position, Vector2 velocity, int type, int damage, float knockback)
        {
            /*Code is made by berberborscing*/
            int spread = 30; //The angle of random spread.
            float spreadMult = 0.1f; //Multiplier for bullet spread, set it higher and it will make for some outrageous spread.
            for (int i = 0; i < 3; i++) //Amount of shots
            {
                float vX = velocity.X+(float)Main.rand.Next(-spread,spread+1) * spreadMult;
                float vY = velocity.Y+(float)Main.rand.Next(-spread,spread+1) * spreadMult;
                Projectile.NewProjectile(position.X, position.Y, vX, vY, type, damage, knockback, Main.myPlayer);
            }
            return false;
        }
    }
}
Throughout the tutorial, you have actually been manipulating AI Styles, so there isn't much to cover here. However there are some general notes that you should know.

*Notes:
- Instead of using the projectile's timeLeft to handle timed occurences, you can use other ones which include...
Code:
projectile.ai[0]
projectile.ai[1]
projectile.localAI[0]
Some minor problems with using these is that some projectile AI styles may already use these, so the AI Style could be messed up if you use these to be your counters. Here's an example of how to use them.
Code:
//If the ai ticker (projectile.ai[0]) reaches 10, it does something and resets to 0.
projectile.ai[0]++
if(projectile.ai[0] > 10)
{
       //some code
       projectile.ai[0] = 0;
}
- As I said before inserting projectile.rotation to the overrided AI() may not work depending on the AIStyle
- Custom AIStyle is usually -1, but you can use 0 since it doesn't do anything.
- For those who want to mess with the vanilla code, I included it under the txt file of "Projectile AI List".

If you're done with (or copied through) with the tutorial, this is what your end product should look like (It looks like an absolute mess, but it literally is a compilation of everything that the tutorial covers...)

9Mx2zZ5.gif

Tutorial: Projectile Concepts

Notes:

- Make sure the spear's tip for the projectile is facing the top left corner.
- Make sure the mini spear is like an arrow texture
- "size" adjusts the hitbox, no need to adjust it so it is the size of the texture file
- The projectiles spawned by the mini spear don't need any velocity because it is just going down
- North Pole like weapons can do different things, for example, check out Gae Bolg in my mod page.

Ancient Spear Projectile JSON
Code:
{
    "displayName": "Ancient Spear",
    "size": [20, 20],
    "scale": 1.1,
    "aiStyle": 19,
    "friendly": true,
    "hostile": false,
    "tileCollide": false,
    "ignoreWater": true,
    "penetrate": -1,
    "ownerHitCheck": true,
    "hide": true,
    "melee": true
}

Ancient Spear Projectile CS
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace COFP.Projectiles
{
    public class AncientSpear : ModProjectile
    {
        public override void AI()
        {
            projectile.light = 0.9f;
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width, projectile.height, 24, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 100, default(Color), 0.5f);
            Main.player[projectile.owner].direction = projectile.direction; //Make's the owner face the direction the spear
            Main.player[projectile.owner].heldProj = projectile.whoAmI;
            Main.player[projectile.owner].itemTime = Main.player[projectile.owner].itemAnimation;
            projectile.position.X = Main.player[projectile.owner].position.X + (float)(Main.player[projectile.owner].width / 2) - (float)(projectile.width / 2);
            projectile.position.Y = Main.player[projectile.owner].position.Y + (float)(Main.player[projectile.owner].height / 2) - (float)(projectile.height / 2);
            projectile.position += projectile.velocity * projectile.ai[0];
            if (projectile.ai[0] == 0f)
            {
                projectile.ai[0] = 3f;
                projectile.netUpdate = true;
            }
            if (Main.player[projectile.owner].itemAnimation < Main.player[projectile.owner].itemAnimationMax / 3)
            {
                projectile.ai[0] -= 1.1f; //How far back it goes
                if (projectile.localAI[0] == 0f && Main.myPlayer == projectile.owner)
                {
                    projectile.localAI[0] = 1f;
                    if (Collision.CanHit(Main.player[projectile.owner].position, Main.player[projectile.owner].width, Main.player[projectile.owner].height, new Vector2(projectile.center().X + projectile.velocity.X * projectile.ai[0], projectile.center().Y + projectile.velocity.Y * projectile.ai[0]), projectile.width, projectile.height)) //The if statement to make a projectile after the spear point reaaches it's max distance; you can remove the if statements and the things it contains to make it a normal spear
                    {
                        Projectile.NewProjectile(projectile.center().X + projectile.velocity.X , projectile.center().Y + projectile.velocity.Y , projectile.velocity.X * 1.5f, projectile.velocity.Y * 1.5f, "COFP:MiniAncientSpear", projectile.damage , projectile.knockBack * 0.85f, projectile.owner, 0f, 0f); //Spawning the projectile
                    }
                }
            }
            else
            {
                projectile.ai[0] += 0.75f; //How Far It Goes
            }
            if (Main.player[projectile.owner].itemAnimation == 0)
            {
                projectile.Kill(); //Kills projectile after it is done
            }
            projectile.rotation = (float)Math.Atan2((double)projectile.velocity.Y, (double)projectile.velocity.X) + 2.355f; //Rotates the spear based on where you shoot
            if (projectile.spriteDirection == -1)
            {
                projectile.rotation -= 1.57f;
            }
        }
    }
}
Ancient Spear's Mini Spear JSON
Code:
{
    "displayName": "Mini Ancient Spear",
    "size": [10, 10],
    "scale": 0.75,
    "aiStyle": 1,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "ignoreWater": true,
    "penetrate": -1,
    "melee": true
}

Ancient Spear's Mini Spear CS
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace COFP.Projectiles
{
    public class MiniAncientSpear : ModProjectile
    {
        public override void AI()
        {
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width, projectile.height, 24, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 100, default(Color), 0.4f);
            projectile.light = 0.9f;
            projectile.rotation = (float)System.Math.Atan2((double)projectile.velocity.Y, (double)projectile.velocity.X) + 1.57f;
            projectile.ai[0] += 1f;
            if (projectile.ai[0] > 30f)
            {
                projectile.ai[0] = 30f;
                projectile.velocity.Y = projectile.velocity.Y + 0.25f;
                if (projectile.velocity.Y > 16f)
                {
                    projectile.velocity.Y = 16f;
                }
                projectile.velocity.X = projectile.velocity.X * 0.995f;
            }
            projectile.rotation = (float)Math.Atan2((double)projectile.velocity.Y, (double)projectile.velocity.X) + 1.57f;
            projectile.alpha -= 50;
            if (projectile.alpha < 0)
            {
                projectile.alpha = 0;
            }
            if (projectile.owner == Main.myPlayer)
            {
                projectile.localAI[0] += 1f;
                if (projectile.localAI[0] >= 4f)
                {
                    projectile.localAI[0] = 0f;
                    int num668 = 0;
                    for (int num669 = 0; num669 < 1000; num669++)
                    {
                        if (Main.projectile[num669].active && Main.projectile[num669].owner == projectile.owner && Main.projectile[num669].type == 344)
                        {
                            num668++;
                        }
                    }
                    float num670 = (float)projectile.damage * 0.8f;
                    if (num668 > 100)
                    {
                        float num671 = (float)(num668 - 100);
                        num671 = 1f - num671 / 100f;
                        num670 *= num671;
                    }
                    if (num668 > 100)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 120)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 140)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 150)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 160)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 165)
                    {
                        projectile.localAI[0] -= 1f;
                    }
                    if (num668 > 170)
                    {
                        projectile.localAI[0] -= 2f;
                    }
                    if (num668 > 175)
                    {
                        projectile.localAI[0] -= 3f;
                    }
                    if (num668 > 180)
                    {
                        projectile.localAI[0] -= 4f;
                    }
                    if (num668 > 185)
                    {
                        projectile.localAI[0] -= 5f;
                    }
                    if (num668 > 190)
                    {
                        projectile.localAI[0] -= 6f;
                    }
                    if (num668 > 195)
                    {
                        projectile.localAI[0] -= 7f;
                    }
                    if (num670 > (float)projectile.damage * 0.1f) //You really only need to worry about changing this
                    {
                        Projectile.NewProjectile(projectile.center().X, projectile.center().Y, 0f, 0f, "COFP:Rust",  projectile.damage/4, projectile.knockBack * 0.55f, projectile.owner, 0f, (float)Main.rand.Next(3)); //This just spawns a projectile that falls down onto the ground
                    }
                }
            }
        }
    }
}
Rust JSON
Code:
{
    "displayName": "Rust",
    "size": [8, 8],
    "aiStyle": 1,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "ignoreWater": true,
    "melee": true
}

Rust CS
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace COFP.Projectiles
{
    public class Rust : ModProjectile
    {
        public override void AI()
        {
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width, projectile.height, 24, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 100, default(Color), 0.25f);
            projectile.light = 0.9f;
        }
    }
}
Welcome to the guide on making Starfury type weapons! Here we will be using the following properties for our weapon.

Code:
"holdoutOffset": [x,y], //Where you want the item to be in terms of the positions of x and y when used
Code:
"code": Folder.CSFileName //.cs properties of that filename is used for the item/projectile, rather than making a .cs file with the same name as the item/projectile
Code:
new Vector2(velocity.X, velocity.Y) //Allows you to make a new trajectory Vector
Gore.NewGore(Vector2 Position, Vector2 Velocity, string name, float Scale = 1f) //Though usually used for NPCs, we'll be making gore for our projectile like sharks from the sharknado
PlaySound(int type, int x = -1, int y = -1, int Style = 1) //Plays a sound; Type is the list ID #; x is the position in terms of x; y is the position in terms of y; Style is the ID # in the list; can be found in http://dev.willhuxtable.com/ids/.

We will be making a weapon of epic proportion... the Staff of Guide Rain! So before we start on the more complicated stuff, we'll start with our .json files. We'll need to make a file called Gores for our gore of the Guide. All the images will be included within the MPT.zip.

Code:
{
    "displayName": "Staff of Guide Rain",
    "size": [42,40],
    "maxStack": 1,
    "value": [0,0,0,1],
    "rare": 7,
    "holdoutOffset": [-8,0],
    "tooltip": ["It's raining Guides!", "Hallelujah!"],
    "useStyle": 5,
    "useAnimation": 30,
    "useTime": 5,
    "useSound": 32,
    "noMelee": true,
    "magic": true,
    "damage": 30,
    "shoot": "MPT:GuideProj1",
    "shootSpeed": 6,
    "mana": 1,
    "autoReuse": true,

    "recipes":
    [{
        "items": { "Dirt Block": 1},
        "creates": 1
    }]
}
Code:
{
    "code": "Projectiles.GuideProjectile", //Brings it to our upcoming .cs file
    "displayName": "Guide",
    "size": [27,46],
    "aiStyle": 0,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "magic": true
}

Now that we got our .json files done, we start with our item's .cs file! Here we will make it rain Guides from the sky!

Code:
using System;
using Microsoft.Xna.Framework;

using TAPI;
using Terraria;

namespace MPT.Items
{
    public class SOGR: ModItem
    {
        public Vector2 usePos = default(Vector2);

        public override bool PreShoot(Player player, Vector2 position, Vector2 velocity, int projType, int damage, float knockback)
        {
            /*Grox's Code*/
            usePos = Main.mouseWorld - player.position;
            UseStyle(player);
            position.X = Main.mouseWorld.X; //Makes the position equal to your mouse
            position.X += MathHelper.Lerp(-120f, 120f, (float)Main.rand.NextDouble()); //Allows you have the projectile spawn at random in the x direction
            position.Y -= 500; //Make the projectile spawn 500 pixels above you (About 32 tiles)
            velocity.Y = (float)(item.shootSpeed); //Makes it fall down
            velocity.X = 0; //Makes it not stray from direct path to ground
            /*End of Grox's Code*/

            /*Here we get to use different Guide poses!*/
            int rand = Main.rand.Next(6);
            if(rand == 0)
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj1", damage, knockback, player.whoAmI);
            else if (rand == 1)
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj2", damage, knockback, player.whoAmI);
            else if (rand == 2)
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj3", damage, knockback, player.whoAmI);
            else if (rand == 3)
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj4", damage, knockback, player.whoAmI);
            else if (rand == 4)
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj5", damage, knockback, player.whoAmI);
            else
                Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, "MPT:GuideProj6", damage, knockback, player.whoAmI);

            return false;
        }
    }
}

Now after that, we make our .cs file for all our Guide projectiles!

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class GuideProjectile : ModProjectile
    {
        public override void AI()
        {
            projectile.light = 0.9f;
            projectile.rotation += (float)projectile.direction * 0.1f;
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 5, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID].noGravity = true;
        }
        public override void PostKill()
        {
            int rand = Main.rand.Next(4);
           /*GoreDef.gores["ModInternalName:GoreName"] basically just calls for the gore.*/
            if (rand == 0)
            Gore.NewGore(projectile.position, new Vector2(MathHelper.Lerp(-4f, 4f, (float)Main.rand.NextDouble()), -2), GoreDef.gores["MPT:GuideGore1"], 1f);
            else if (rand == 1)
            Gore.NewGore(projectile.position, new Vector2(MathHelper.Lerp(-4f, 4f, (float)Main.rand.NextDouble()), -2), GoreDef.gores["MPT:GuideGore2"], 1f);
            else if (rand == 2)
            Gore.NewGore(projectile.position, new Vector2(MathHelper.Lerp(-4f, 4f, (float)Main.rand.NextDouble()), -2), GoreDef.gores["MPT:GuideGore2"], 1f);
            else if (rand == 3)
            Gore.NewGore(projectile.position, new Vector2(MathHelper.Lerp(-4f, 4f, (float)Main.rand.NextDouble()), -2), GoreDef.gores["MPT:GuideGore3"], 1f);
            else
            Gore.NewGore(projectile.position, new Vector2(MathHelper.Lerp(-4f, 4f, (float)Main.rand.NextDouble()), -2), GoreDef.gores["MPT:GuideGore3"], 1f);
            Main.PlaySound(4, (int)projectile.position.X, (int)projectile.position.Y, 1); //This is the splatter/general npc death sound effect
        }
    }
}

After doing all this, you should have this should be what it looks like after you're done.

Ppwg5NC.gif
Here we are with Rain Cloud type weapons, though not as accurate as the original Rain Cloud weapons, it still works. There are no new properties we are going to play with.

So the weapon we'll be making is called the Evil Cloud Staff, which will produce a cloud that rains droplets of cursed flame and ichor! Let's get started on the .json files for our weapon.

Code:
{
    "displayName": "Evil Cloud Staff",
    "size": [42,40],
    "maxStack": 1,
    "value": [0,0,0,1],
    "rare": 7,
    "holdoutOffset": [-8,0],
    "tooltip": ["Summons a cloud from the lands of evil.", "Its rains knows no allies."],
    "useStyle": 5,
    "useAnimation": 30,
    "useTime": 30,
    "useSound": 21,
    "noMelee": true,
    "magic": true,
    "damage": 50,
    "shoot": "MPT:EvilCloud",
    "shootSpeed": 10,
    "mana": 1,

    "recipes":
    [{
        "items": { "Dirt Block": 1},
        "creates": 1
    }]
}
Code:
{
    "displayName": "Evil Cloud",
    "size": [54,34],
    "aiStyle": 0,
    "timeLeft": 30,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "penetrate": 10,
    "ranged": true,
    "maxUpdates": 1
}
Code:
{
    "displayName": "Evil Cloud",
    "size": [54,34],
    "aiStyle": 0,
    "timeLeft": 600,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "ranged": true,
    "maxUpdates": 1
}
Code:
{
    "displayName": "Evil Cloud",
    "size": [4,40],
    "aiStyle": 1,
    "friendly": true,
    "hostile": true,
    "tileCollide": true,
    "magic": true
}

Now that we're done with that, let's get on with making our .cs files for our cloud to make it rain some death.

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class EvilCloud : ModProjectile
    {
        public override void AI()
        {
            projectile.damage = 0; //This makes it so that the cloud itself won't do damage and just go through enemies
            projectile.light = 0.9f;
            projectile.alpha = 128;
            projectile.rotation += (float)projectile.direction * 0.1f;
            int DustID1 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 75, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            int DustID2 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID1].noGravity = true;
            Main.dust[DustID2].noGravity = true;
        }
        public override void PostKill()
        {
            Projectile.NewProjectile(projectile.Center.X, projectile.Center.Y, 0, 0, "MPT:EvilRainCloud", 0, projectile.knockBack, Main.myPlayer);
        }
    }
}
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class EvilRainCloud : ModProjectile
    {
        public override void AI()
        {
            projectile.damage = 0;
            projectile.light = 0.9f;
            projectile.alpha = 128;
            int DustID1 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 75, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            int DustID2 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID1].noGravity = true;
            Main.dust[DustID2].noGravity = true;
            if(projectile.timeLeft % 5 == 0)
            {
                int rand = Main.rand.Next(2);
                /*Apply the actual damage to the projectile by putting within the NewProjectile function, int WeaponDamage * (int) Main.player[projectile.owner].damageMultiplier (rangedDamage and such is a float so we need to convert it to an int) */
                if(rand == 0)
                {

                    Projectile.NewProjectile(projectile.Center.X + MathHelper.Lerp(-16f, 16f, (float)Main.rand.NextDouble()), projectile.Center.Y + 16, 0, 8, "MPT:IchorRain", 50 * (int) Main.player[projectile.owner].magicDamage, projectile.knockBack, Main.myPlayer);
                }
                else
                {
                    Projectile.NewProjectile(projectile.Center.X + MathHelper.Lerp(-16f, 16f, (float)Main.rand.NextDouble()), projectile.Center.Y + 16, 0, 8, "MPT:CursedRain", 50 * (int) Main.player[projectile.owner].magicDamage, projectile.knockBack, Main.myPlayer);
                }
            }
        }
        public override void PostKill()
        {
            Main.PlaySound(2, (int)projectile.position.X, (int)projectile.position.Y, 4); //Heart Crystal use Effect
        }
    }
}
IchorRain.cs
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class IchorRain : ModProjectile
    {
        public override void AI()
        {
            projectile.alpha = 128;
            projectile.light = 0.9f;
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID].noGravity = true;
        }
        public override void DealtNPC(NPC n, int hitDir, int dmgDealt, float knockback, bool crit)
        {
            n.AddBuff(69, 300);
        }
        public override void DealtPlayer(Player p, int hitDir, int dmgDealt, bool crit)
        {
            p.AddBuff(69, 300, false);
        }
        public override void PostKill()
        {
            Main.PlaySound(19, (int)projectile.position.X, (int)projectile.position.Y, 0); //Water splashing sound effect
        }
    }
}

CursedRain.cs
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class CursedRain : ModProjectile
    {
        public override void AI()
        {
            projectile.alpha = 128;
            projectile.light = 0.9f;
            int DustID = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f);
            Main.dust[DustID].noGravity = true;
        }
        public override void DealtNPC(NPC n, int hitDir, int dmgDealt, float knockback, bool crit)
        {
            n.AddBuff(39, 300);
        }
        public override void DealtPlayer(Player p, int hitDir, int dmgDealt, bool crit)
        {
            p.AddBuff(39, 300, false);
        }
        public override void PostKill()
        {
            Main.PlaySound(19, (int)projectile.position.X, (int)projectile.position.Y, 0);
        }
    }
}

After all this, it should look something like this...

4fQ0dqC.gif
Welcome to Barrier Type Weapons section. Here I will cover how to make a "Barrier" that covers the player. The item we are creating is called "Bubble Shield" in which we'll have a buff for it called "Barrier On". Here are some new properties we'll be using.

Code:
Player.HasBuff(string BuffName or int Buff) //returns an integer of either 1 or -1; 1 = Has Buff; -1 = Does not have Buff.

So before we start with the complicated stuff, we should make our .json files. We're going to be making 3 json files, "BarrierOn.json", and 2 "BubbleShield.json"s (one for item and one for projectile).

Code:
{
    "displayName": "Bubble Shield",
    "size": [52,18],
    "maxStack": 1,
    "value": [0,0,0,1],
    "rare": 1,
    "tooltip": ["Bubble of protection!"],
    "useStyle": 1,
    "useAnimation": 30,
    "useTime": 30,
    "damage": 30,
    "knockback": 10,
    "useSound": 11,
    "ranged": true,
    "buff": "MPT:BarrierOn", //Produce the BarrierOn Buff!
    "buffTime": 18000, //5 Minutes
    "shoot": "MPT:BubbleShield",
    "shootSpeed": 0, //The speed doesn't really matter

    "recipes":
    [{
        "items": { "Dirt Block": 1 },
        "creates": 1
    }]
}
Code:
{
    "displayName": "Bubble Shield",
    "size": [64,64],
    "scale": 1,
    "aiStyle": 0, //I usually use this for custom AI, but people usually set it to -1 for custom AI
    "timeLeft": 18000,
    "friendly": true,
    "hostile": false,
    "tileCollide": false,
    "penetrate": -1,
    "melee": true
}
Code:
{
    "displayName": "Bubble Shield",
    "tip": "You have a shield!",
    "noTimer": true //Makes it not display a timer
}

Now that we have that done, we are going to be making our CS files for our three objects, the Bubble Shield item and projectile and the Barrier On buff.

Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using TAPI;
using Terraria;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MPT.Items
{
    public class BubbleShield : ModItem
    {
        //Don't think this is necessary, however this is a just in case measure
        public override bool PreShoot(Player player, Vector2 position, Vector2 velocity, int type, int damage, float knockback)
        {
            player.AddBuff("MPT:BarrierOn", 18000, false);
            Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, type, damage, knockback, Main.myPlayer);
            return false;
        }
    }
}
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class BubbleShield : ModProjectile
    {
        public override void AI()
        {
            Player owner = Main.player[projectile.owner];
            if(owner.HasBuff("MPT:BarrierOn") == -1) //If the player doesn't have the buff, make the projectile despawn
            {
                projectile.Kill();
            }
            projectile.light = 0.9f;
            projectile.alpha = 128;
            projectile.position.X = owner.Center.X - 30; //Makes it position itself at the center of the player in terms of X, need to adjust this depending on how large your projectile is.
            projectile.position.Y = owner.Center.Y - 24; //Makes it position itself at the center of the player in terms of Y, need to adjust this depending on how large your projectile is.
        }
    }
}
Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Buffs
{
    public class BarrierOn : TAPI.ModBuff
    {
             //Makes the buff do nothing
    }
}
For Vanilla Dust Heat Rays.

Code:
{
    "displayName": "Test Ray",
    "size": [32, 32],
    "aiStyle": 0, //I usually just use this for custom AI since it doesn't do anything special
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "ignoreWater": true,
    "magic": true,
    "penetrate": -1,
    "maxUpdates": 100
}

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace COFP.Projectiles
{
    public class TestRay : ModProjectile
    {
        public override void AI()
        {
            projectile.localAI[0] += 1f; //The timer
            if (projectile.localAI[0] > 3f) //The amount of ticks it takes for it to load
            {
                for (int num562 = 0; num562 < 10; num562++) //Adjust the thickness of the ray; too high a number may cause lag
                {
                    projectile.alpha = 255;
                    //Dust.newDust(Vector2 position, Size.X, Size.Y, int Dust ID, Velocity.X, Velocity.Y, int alpha (transparency), Color, float scale)
                    //Size determines where the dust will spawn (at random of course)
                    //Dust ID's can be found in this handy page http://tconfig.wikia.com/wiki/List_of_Dusts
                    int num563 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y - projectile.height/4), projectile.width, projectile.height, 60, 0f, 0f, 0, default(Color), 0.75f);
                    //Vector2(projectile.position.X, projectile.position.Y - projectile.height/4) - (Vector position) makes sure it shoots from the center of where the weapon is pointing
                    //projectile.width - Sets it to the projectile's size of X in terms of pixels (16 pixels = 1 tile block)
                    //projectile.height - Sets it to the projectile's size of Y in terms of pixels
                    //Dust ID - I set it to a red color, but if you want a black ray, use 54
                    //both of the velocity's set to zero so it won't move around
                    // alpha - set to zero so color is vibrant for the dust
                    // default(Color) - set to its default color (does not change color)
                    // scale - set to 0.75 so the dust isn't humongous for the laser
                    Main.dust[num563].scale = (float)Main.rand.Next(70, 110) * 0.013f; /*Adjust left and right values to sizes you would like to see for your dust in Main.rand.Next(int LeftEnd, int RightEnd)
                    Main.dust[num563].velocity *= 0.2f; //sets the velocity of the dust to not move that much (not sure if you would need this or not)
                    Main.dust[num563].noGravity = true; //Makes sure it doesn't fall on the ground
                    //You can of course add more dusts to the code, but as I said about the for loop determining thickness, it may cause lag.
                }
            }
        }
    }
}
I'm not gonna really bother with the item part of this, though if you want to see it, just go into MPT.zip.

First off, you need your projectile json file.

Code:
{
    "displayName": "Flame Blast",
    "size": [8, 42],
    "scale": 1,
    "aiStyle": 0, //Gonna use this for a custom AI (since aiStyle 0 does nothing)
    "timeLeft": 180,
    "friendly": true,
    "hostile": false,
    "tileCollide": true,
    "ignoreWater": false,
    "frameCount": 3 //How many frames are there in the image, in this case it is 3 images
}

Now we need our .cs file, FlameBlast.cs.

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class FlameBlast : ModProjectile
    {
        public override void AI()
        {
            projectile.light = 5f; //Makes the projectile light
            projectile.rotation = (float)Math.Atan2((double)projectile.velocity.Y, (double)projectile.velocity.X) + 1.57f; //Makes sure the projectile rotates so it is pointing at where you are shooting.
            if(projectile.frameCounter < 20) //If the frame's counter is less than 20.
            {
                projectile.frame = 2; //Set it to the 3rd frame, since the frames go from 0 to 2.
                if(projectile.damage <= 100) //Increases the projectile damage if it is less than or equal to 100
                    projectile.damage += 1;
                else
                    projectile.damage = 100;
                projectile.velocity.Y *= 1.01f; //increases the velocity by 1.01 times.
                projectile.velocity.X *= 1.01f;
            }
            else if(projectile.frameCounter >= 20 && projectile.frameCounter < 50) //If the projectile's frame counter greater than or equal to 20 and less than 50
            {
                projectile.frame = 1; //Set the frame to the second frame
                if(projectile.damage <= 100) //Increase the damage again
                    projectile.damage += 2;
                else
                    projectile.damage = 100;
                projectile.velocity.Y *= 1.02f; //Increase velocity even further
                projectile.velocity.X *= 1.02f;
                int DustID1 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width/2 + 4, projectile.height/2 + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f); //Add Dust to it too
                Main.dust[DustID1].noGravity = true;
            }
            else //If the frame counter is something other than the two statements
            {
                projectile.frame = 0; //Set the frame to the first frame
                if(projectile.damage <= 100) //Increase damage even further
                    projectile.damage += 3;
                else
                    projectile.damage = 100;
                projectile.velocity.Y *= 1.03f; //Increase speed even further
                projectile.velocity.X *= 1.03f;
                int DustID2 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 64, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 120, default(Color), 0.75f); //Add dust
                Main.dust[DustID2].noGravity = true;
            }
            projectile.frameCounter++; //A seperate counter used for animation.
        }
    }
}

So to top it all off, all you need to know from this example is that you need one property in the json file and about 2 properties in your cs file.

Code:
In the .json file
    - "frameCount": int frameNumber, //How many frames are there in your texture

in the .cs file
   - frame = int frameNumber; //Select the certain frame to show, goes from 0 to the frameCount minus 1 (0, frameCount - 1)
   - int frameCounter; //The counter to help you animate. (like a stop watch)

And here is one last example, only with the AI() hook though. Let's pretend I already did all the JSON file, with a frameCount of 4 this time and have it animate infinitely.

Code:
public override void AI()
[
    projectile.frameCounter++; //Make the counter go up, just remember that each time it goes through the AI hook, it is 1/60 of a second, so you are counting by 1/60 of a second.
    if(projectile.frameCounter < 10) //If the counter is less than 10
    {
        projectile.frame = 0; //Set it to the first frame
    }
    else if (projectile.frameCounter >= 10 && projectile.frameCounter < 20) //If the frameCounter is greater than or equal to 10 and less than 20...
    {
        projectile.frame = 1; //Set it to the second frame
    }
    else if (projectile.frameCounter >= 20 && projectile.frameCounter < 30) //If the frameCounter is greater than or equal to 20 and less than 30....
    {
        projectile.frame = 2; //Set it to the third frame
    }
    else if (projectile.frameCounter >= 30 && projectile.frameCounter < 40) //If the frameCounter is greater than or equal to 30 and less than 40...
    {
        projectile.frame = 3; //Set it to the fourth frame
    }
    else //Let's just say this assumes that the frameCounter is greater than or equal to 40.
    {
        projectile.frame = 0; //Reset it to the first frame
        projectile.frameCounter = 0; //Reset the counter so it goes back to the beginning
    }
}
So before we start with anything... Let's get our JSON files done.

Code:
{
    "displayName": "Cursed Flame Powder",
    "size": [52,18],
    "maxStack": 99,
    "value": [0,0,0,1],
    "rare": 1,
    "tooltip": ["Powder that causes Cursed Inferno on enemies."],
    "useStyle": 5,
    "useAnimation": 30,
    "useTime": 30,
    "useSound": 1,
    "consumable": true, //We want it to be used each time, since it is a powder like weapon
    "shoot": "MPT:CursedFlamePowder",
    "noUseGraphic": true, // We don't want it to show the item when used
    "shootSpeed": 2, //Adjust this for the range

    "recipes":
    [{
        "items": { "Dirt Block": 1 },
        "creates": 99
    }]
}
Code:
{
    "displayName": "Cursed Flame Powder",
    "size": [8, 8],
    "aiStyle": 0,
    "timeLeft": 120,
    "friendly": true,
    "hostile": false,
    "tileCollide": false,
    "ignoreWater": true,
    "penetrate": -1, //I sreiously don't think you'll be needing this, since 0 damage projectiles automatically go through enemies
    "maxUpdates": 5 //Shows more dust at once (As Bluemagic explained to me, this affects the speed of the projectile also)
}

Now that we're done with those.... let's get with the intricacies of the AI of the projectile!

Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace MPT.Projectiles
{
    public class CursedFlamePowder : ModProjectile
    {
        public override void AI()
        {
            projectile.light = 0.9f; //Yay light!
            projectile.alpha = 255; //Makes sure it doesn't show a texture
            for(int thickness = 0; thickness < 6; thickness++) //The thickness, adjust the interger for how thick you want it
            {
                int DustID1 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y + 2f), projectile.width + 4, projectile.height + 4, 60, projectile.velocity.X * 0.2f, projectile.velocity.Y * 0.2f, 0, default(Color), 1.3f); //Creates the "powder"
                Main.dust[DustID1].scale = (float) Main.rand.Next(70, 110) * 0.013f;
                Main.dust[DustID1].noGravity = true;
            }
            //By projectile width/height, these are the width/height of the projectile's hitbox
            if(projectile.width > 128 && projectile.height > 128) //If the projectile's width/height exceeds 128 pixels
            {
                //Keep the projectile's width/height at 128
                projectile.width = 128;
                projectile.height = 128;
            }
            else //Assume this means that the width/height of the projectile is less than 128
            {
                //Increase the width/height by 1 pixel
                projectile.width += 1;
                projectile.height += 1;
            }
       
            //Berberborscing's Code
            foreach (NPC N in Main.npc) //Sets up variable for the NPC(s) in range
            {
                Rectangle MB = new Rectangle((int)projectile.position.X + (int)projectile.velocity.X, (int)projectile.position.Y + (int)projectile.velocity.Y, projectile.width, projectile.height); //Variable sets up projectile's hitbox
                Rectangle NB = new Rectangle((int)N.position.X, (int)N.position.Y, N.width, N.height); //Variable sets up NPC's hitbox
                if (MB.Intersects(NB)) //If the two hitboxes touch...
                {
                    N.AddBuff(39, 300); //Then the enemy will receive a debuff!
                }
            }
            //End of Berberborscing's Code
        }
    }
}
Coming Soon...
Someone already beat me to it, but, this guide is great for those who want to make a simple flail/hook with a custom chain texture!

[TUTORIAL] How to make a basic flail type weapon - Gmod

Cool Things to Add To Your Code (Mostly Ripped from Vanilla Code)
Code:
public override void PostKill()
        {
            Main.PlaySound(2, (int)projectile.position.X, (int)projectile.position.Y, 14);
            for (int num369 = 0; num369 < 20; num369++)
            {
                int num370 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y), projectile.width, projectile.height, 31, 0f, 0f, 100, default(Color), 1.5f);
                Main.dust[num370].velocity *= 1.4f;
            }
            for (int num371 = 0; num371 < 10; num371++)
            {
                int num372 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y), projectile.width, projectile.height, 6, 0f, 0f, 100, default(Color), 2.5f);
                Main.dust[num372].noGravity = true;
                Main.dust[num372].velocity *= 5f;
                num372 = Dust.NewDust(new Vector2(projectile.position.X, projectile.position.Y), projectile.width, projectile.height, 6, 0f, 0f, 100, default(Color), 1.5f);
                Main.dust[num372].velocity *= 3f;
            }
            int num373 = Gore.NewGore(new Vector2(projectile.position.X, projectile.position.Y), default(Vector2), Main.rand.Next(61, 64), 1f);
            Main.gore[num373].velocity *= 0.4f;
            Gore gore85 = Main.gore[num373];
            gore85.velocity.X = gore85.velocity.X + 1f;
            Gore gore86 = Main.gore[num373];
            gore86.velocity.Y = gore86.velocity.Y + 1f;
            num373 = Gore.NewGore(new Vector2(projectile.position.X, projectile.position.Y), default(Vector2), Main.rand.Next(61, 64), 1f);
            Main.gore[num373].velocity *= 0.4f;
            Gore gore87 = Main.gore[num373];
            gore87.velocity.X = gore87.velocity.X - 1f;
            Gore gore88 = Main.gore[num373];
            gore88.velocity.Y = gore88.velocity.Y + 1f;
            num373 = Gore.NewGore(new Vector2(projectile.position.X, projectile.position.Y), default(Vector2), Main.rand.Next(61, 64), 1f);
            Main.gore[num373].velocity *= 0.4f;
            Gore gore89 = Main.gore[num373];
            gore89.velocity.X = gore89.velocity.X + 1f;
            Gore gore90 = Main.gore[num373];
            gore90.velocity.Y = gore90.velocity.Y - 1f;
            num373 = Gore.NewGore(new Vector2(projectile.position.X, projectile.position.Y), default(Vector2), Main.rand.Next(61, 64), 1f);
            Main.gore[num373].velocity *= 0.4f;
            Gore gore91 = Main.gore[num373];
            gore91.velocity.X = gore91.velocity.X - 1f;
            Gore gore92 = Main.gore[num373];
            gore92.velocity.Y = gore92.velocity.Y - 1f;
        }[/SIZE]
Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace ModInternalName
{
    [GlobalMod] public class MProjectile : TAPI.ModProjectile
    {
        public override void PostAI()
        {
            Player player = Main.player[projectile.owner];
            for (int num46 = projectile.oldPos.Length - 1; num46 > 0; num46--)
            {
                projectile.oldPos[num46] = projectile.oldPos[num46 - 1];
            }
            projectile.oldPos[0] = projectile.position;
        }
    }
}[/SIZE]
Code:
public override void PostDraw(SpriteBatch sb)
        {
            Color color21 = Lighting.GetColor((int)((double)projectile.position.X + (double)projectile.width * 0.5) / 16, (int)(((double)projectile.position.Y + (double)projectile.height * 0.5) / 16.0));
            SpriteEffects effects = SpriteEffects.None;
            int num98 = 0;
            int num99 = 0;
            float num100 = (float)(Main.projectileTexture[projectile.type].Width - projectile.width) * 0.5f + (float)projectile.width * 0.5f;
            for(int num126 = 0; num126 < 10; num126++)
            {
                Color alpha4 = projectile.GetAlpha(color21);
                float num127 = (float)(9 - num126) / 9f;
                alpha4.R = (byte)((float)alpha4.R * num127);
                alpha4.G = (byte)((float)alpha4.G * num127);
                alpha4.B = (byte)((float)alpha4.B * num127);
                alpha4.A = (byte)((float)alpha4.A * num127);
                float num128 = (float)(9 - num126) / 9f;
                Main.spriteBatch.Draw(Main.projectileTexture[projectile.type], new Vector2(projectile.oldPos[num126].X - Main.screenPosition.X + num100 + (float)num99, projectile.oldPos[num126].Y - Main.screenPosition.Y + (float)(projectile.height / 2) + projectile.gfxOffY), new Rectangle?(new Rectangle(0, 0, Main.projectileTexture[projectile.type].Width, Main.projectileTexture[projectile.type].Height)), alpha4, projectile.rotation, new Vector2(num100, (float)(projectile.height / 2 + num98)), num128 * projectile.scale, effects, 0f);
            }
        }[/SIZE]
[/SPOILER]
 

Attachments

  • AI Styles.txt
    1.2 KB · Views: 852
  • Projectile List.txt
    1.1 MB · Views: 858
  • MPT.zip
    43 KB · Views: 491
  • Projectile AI List.txt
    663.1 KB · Views: 914
Last edited:
Can I see your code please?
Here it is
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace RazeMod.Projectiles
{
public class EyeOfTheVoid : ModProjectile
{
public override void PostKill()
{
Projectile.NewProjectile(projectile.position.X, projectile.position.Y, 0, 0, "RazeMod:EyeOfTheVoidReturn", projectile.damage, projectile.knockBack, Main.myPlayer);
}
}
public override void AI()
{
{
projectile.type = 409;
}
}
}
Also thank you for helping me with the boomerang it kinda works now
 
Here it is
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace RazeMod.Projectiles
{
public class EyeOfTheVoid : ModProjectile
{
public override void PostKill()
{
Projectile.NewProjectile(projectile.position.X, projectile.position.Y, 0, 0, "RazeMod:EyeOfTheVoidReturn", projectile.damage, projectile.knockBack, Main.myPlayer);
}
}
public override void AI()
{
{
projectile.type = 409;
}
}
}
Also thank you for helping me with the boomerang it kinda works now

I found your problem... You added too many extra brackets.
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace RazeMod.Projectiles
{
    public class EyeOfTheVoid : ModProjectile
    {
        public override void PostKill()
        {
            Projectile.NewProjectile(projectile.position.X, projectile.position.Y, 0, 0, "RazeMod:EyeOfTheVoidReturn", projectile.damage, projectile.knockBack, Main.myPlayer);
        }
   } <--- Extra Bracket
        public override void AI()
        {
           {  <---Extra Bracket
               projectile.type = 409;
        } 
    }
}
 
I found your problem... You added too many extra brackets.
Code:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace RazeMod.Projectiles
{
    public class EyeOfTheVoid : ModProjectile
    {
        public override void PostKill()
        {
            Projectile.NewProjectile(projectile.position.X, projectile.position.Y, 0, 0, "RazeMod:EyeOfTheVoidReturn", projectile.damage, projectile.knockBack, Main.myPlayer);
        }
   } <--- Extra Bracket
        public override void AI()
        {
           {  <---Extra Bracket
               projectile.type = 409;
        }
    }
}
Thank you it works now!
 
Hi, I'm here now. Is it possible to have several world generation types instead of world sizes? How to make a world generator anyway? I know it's complicated but do you happen to know how? Is it possible to make projectiles home on specific block type? And is it possible to erase all other blocks and npcs and other things from the game so it shows a question mark on them? Yeah, I have a lot of things to ask :)
 
Hi, I'm here now. Is it possible to have several world generation types instead of world sizes? How to make a world generator anyway? I know it's complicated but do you happen to know how? Is it possible to make projectiles home on specific block type? And is it possible to erase all other blocks and npcs and other things from the game so it shows a question mark on them? Yeah, I have a lot of things to ask :)

For the World Generation, you should probably ask in the Getting Started with Modding thread. For the projectiles to home on specific block types? Personally I don't think so. And for the third question, you should refer to Getting Started with Modding, but as the people said within the tAPI thread, there is a way to do that, but it is highly not recommended to remove vanilla items.
 
For the World Generation, you should probably ask in the Getting Started with Modding thread. For the projectiles to home on specific block types? Personally I don't think so. And for the third question, you should refer to Getting Started with Modding, but as the people said within the tAPI thread, there is a way to do that, but it is highly not recommended to remove vanilla items.
Ok, thanks, but in theory they can be "removed" by making a world generator that uses only custom blocks and spawns only custom NPC:s, right? But town NPC:s like demonilionist can still spawn, right? But can I edit the existing ones, like change what the demoman will sell and what does he look like? Qnd is it possible to change the gravity 90° after going to the edge of the map? So the world looks like a square?
 
Ok, thanks, but in theory they can be "removed" by making a world generator that uses only custom blocks and spawns only custom NPC:s, right? But town NPC:s like demonilionist can still spawn, right? But can I edit the existing ones, like change what the demoman will sell and what does he look like? Qnd is it possible to change the gravity 90° after going to the edge of the map? So the world looks like a square?

As I said before, you should really ask these types of questions within the Getting Started with Modding thread, not in a Projectile Tutorial thread. But to answer your questions, yes, I think it is possible to make a world generator using only custom blocks. If you set the NPCs spawn to the custom biome made from those blocks, they should be the only ones to spawn since they are the ones that only have the boolean expression true for spawning from that biome. You can add items to a vanilla NPC with a MNPC.cs but I don't think you can change his appearance. As for the gravity, I'm pretty certain that you can't have gravity to be in a velocity in terms of X, because the player would technically be free falling into a wall if that were the case, as the player cannot rotate to meet the requirements of moving up or down.
 
Ok, thanks, but in theory they can be "removed" by making a world generator that uses only custom blocks and spawns only custom NPC:s, right? But town NPC:s like demonilionist can still spawn, right? But can I edit the existing ones, like change what the demoman will sell and what does he look like? Qnd is it possible to change the gravity 90° after going to the edge of the map? So the world looks like a square?
As for the sideways gravity, it's a concept that I'm interested in as well, but not quite sure if it's possibly since gravity only measures up and down force, and I know how to make NPCs sell additional things, and making the demolitionist look different is probably something you want to look into with texture pack makers.

To change what an NPC sells, make a new class called MNPC. This is where modifying mob drops and NPC sales happens.
Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace InternalModName.Items
{
    [GlobalMod]
    public class MNPC : ModNPC
    {
        public override void PostSetupShop(Chest chest, ref int lastIndex)
        {
            if (npc.type == 19) //Arms Dealer Prehardmode
            {         
                chest.item[lastIndex++].SetDefaults("InternalModName:SawedOffShotgun");
            }
        }
     }
}
You can add other conditions, such as npc.downedPlantBoss or Main.hardMode as well
19 is the arms dealer's id
 
As for the sideways gravity, it's a concept that I'm interested in as well, but not quite sure if it's possibly since gravity only measures up and down force, and I know how to make NPCs sell additional things, and making the demolitionist look different is probably something you want to look into with texture pack makers.

To change what an NPC sells, make a new class called MNPC. This is where modifying mob drops and NPC sales happens.
Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using TAPI;
using Terraria;

namespace InternalModName.Items
{
    [GlobalMod]
    public class MNPC : ModNPC
    {
        public override void PostSetupShop(Chest chest, ref int lastIndex)
        {
            if (npc.type == 19) //Arms Dealer Prehardmode
            {        
                chest.item[lastIndex++].SetDefaults("InternalModName:SawedOffShotgun");
            }
        }
     }
}
You can add other conditions, such as npc.downedPlantBoss or Main.hardMode as well
19 is the arms dealer's id
Thanks
[DOUBLEPOST=1425383474][/DOUBLEPOST]
As I said before, you should really ask these types of questions within the Getting Started with Modding thread, not in a Projectile Tutorial thread. But to answer your questions, yes, I think it is possible to make a world generator using only custom blocks. If you set the NPCs spawn to the custom biome made from those blocks, they should be the only ones to spawn since they are the ones that only have the boolean expression true for spawning from that biome. You can add items to a vanilla NPC with a MNPC.cs but I don't think you can change his appearance. As for the gravity, I'm pretty certain that you can't have gravity to be in a velocity in terms of X, because the player would technically be free falling into a wall if that were the case, as the player cannot rotate to meet the requirements of moving up or down.
Thanks, I'll move to the other tutorial... well, that was random
 
I won't have enough time today to update with the new stuff to add to the tutorials and edit it, but I can tell you what they are though. :naughty:

Rain Type Starfury: Staff of Guide Rain - Staff that rains down Guides upon enemies with sound effects and gore!
Rain Type Rain Cloud: Evil Cloud Staff - Staff that spawns a cloud that rains droplets of Ichor and Cursed Flame!
Barrier: Bubble Shield - Projectile surrounds player with high knockback with buff!
 
Ok, the Staff of Guide Rain and the Evil Cloud Staff are up, I'm going to add in some more stuff on those and the Barrier weapon later.
 
Back
Top Bottom