tModLoader Tutorial: [4] Projectiles

Jofairden

Duke Fishron
tModLoader
Projectiles
Last update: 9th of June, 2016
kdcROYP.png


Table of contents:

1. Prerequisites
2. Introduction
3. The first projectile
4. Shooting in angles

kdcROYP.png

Prerequisites
Make sure you have....
  • ...the latest tModLoader installed (offical thread)
  • ...the latest Microsoft .NET Framework installed (offical .net site)
  • ...the Microsoft XNA Framework installed (you have this if you are able to play Terraria)
  • ...Microsoft Visual Studio (community = free) installed with the C# workspace (official MVS site)
  • .. actually, MVS isn't required but it's a very nice IDE to work with. You can also use notepad or notepadd++ or alike, but MVS will be super useful for noobs that makes simple mistakes because the IDE will help you program
  • ...C# knowledge, all tML mods currently are programmed with the C# language
  • ...followed the tutorial prior to this one http://forums.terraria.org/index.php?threads/tutorial-3-items.44842/
kdcROYP.png


Introduction
We're going to look at projectiles in general in this tutorial. How can we make a projectile? How can we use our custom projectiles?
This tutorial does not cover projectile AI, that's for a later tutorial

kdcROYP.png


The first projectile
Unlike our previous tutorials, we will have to create some files now. First, create a 'Projectiles' folder.
Inside this folder, create a file named WoodenArrowOfDeath.cs
If you're not using MVS, take the following skeleton. Make sure to change the namespace.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Terraria;
using Terraria.ModLoader;
using Terraria.ID;

namespace MyFirstMod.Projectiles
{
    public class WoodenArrowOfDeathItem : ModItem
    {
    }

    public class WoodenArrowOfDeath : ModProjectile
    {
    }
}
As you can see, we have two classes here. One is an item, the other is a projectile. The reason we need an item is because the item will function actual ammo.
Since we've covered items before, I don't need to explain how we need to tackle this, we can make use of CloneDefaults()
Even though I've included item.shoot and item.ammo, I wouldn't have to as they were already cloned. I did this, because those are the 2 most important settings for now.
Code:
    public class WoodenArrowOfDeathItem : ModItem
    {
        public override void SetDefaults()
        {
            item.CloneDefaults(ItemID.WoodenArrow);
            item.name = "Wooden Arrow of Death";
            item.shoot = mod.ProjectileType("WoodenArrowOfDeath");
            item.ammo = ProjectileID.WoodenArrowFriendly;
        }

        public override bool Autoload(ref string name, ref string texture, IList<EquipType> equips)
        {
            texture = "Terraria/Item_" + ItemID.WoodenArrow;
            return true;
        }

        public override void AddRecipes()
        {
            ModRecipe recipe = new ModRecipe(mod);
            recipe.SetResult(this);
            recipe.AddRecipe();
        }
    }

Before we can actually fire things, we must finish our ModProjectile. We can apply the same technique using CloneDefaults()
Code:
    public class WoodenArrowOfDeath : ModProjectile
    {
        public override void SetDefaults()
        {
            projectile.CloneDefaults(ProjectileID.WoodenArrowFriendly);
            projectile.name = "Wooden Arrow of Death";
            aiType = ProjectileID.WoodenArrowFriendly;
        }

        public override bool Autoload(ref string name, ref string texture)
        {
            texture = "Terraria/Projectile_" + ProjectileID.WoodenArrowFriendly;
            return true;
        }

        public override void OnHitNPC(NPC target, int damage, float knockback, bool crit)
        {
            target.StrikeNPCNoInteraction(target.lifeMax, 0f, -target.direction);
        }
    }
As you can see, I've made use of OnHitNPC() in this projectile, as we've learned about this hook in a previous tutorial.
These arrows will strike the target an additional time for their maximum life, this ensures any create hit by these arrows should instantly die.
Go ahaid, go in-game and craft some arrows. Take a bow and start shooting things. Everything should die.

I hope you can see the similarities between projectiles and items, their code is incredibly similar in this case. If you've followed the previous tutorial, this shouldn't all be too difficult.
Let's continue with a new projectile, an improved Terrabeam for our Better Terra Blade.

Better Terrabeam
To save you time, I'm going to give you the code of this projectile right away. You will most likely understand the full code, as it is extremely similar to what we've looked at before.
Go ahaid and paste the following code in a new file named 'BetterTerrabeam.cs' inside 'Projectiles'. Again, remember to change the namespace.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Terraria;
using Terraria.ModLoader;
using Terraria.ID;

namespace MyFirstMod.Projectiles
{
    class BetterTerraBeam : ModProjectile
    {
        public override void SetDefaults()
        {
            projectile.CloneDefaults(ProjectileID.TerraBeam);
            projectile.name = "Terrabeam of Death";
            aiType = ProjectileID.TerraBeam;
        }

        public override bool Autoload(ref string name, ref string texture)
        {
            texture = "Terraria/Projectile_" + ProjectileID.TerraBeam;
            return true;
        }

        public override void OnHitNPC(NPC target, int damage, float knockback, bool crit)
        {
            target.AddBuff(BuffID.OnFire, 5 * 60);
        }
    }
}
As you can see, I've made this projectile apply an OnFire debuff for 5 seconds when it hits something.
Let's go back to our BetterTerraBlade ModItem in MyFirstItem.cs , we need to change the projectile the weapon shoots to our new ModProjectile.
We can do it as follows:
Code:
        public override void SetDefaults()
        {
            item.CloneDefaults(ItemID.TerraBlade);
            item.name = "Better Terra Blade";
            item.damage = 500;
            item.shoot = mod.ProjectileType("BetterTerrabeam");
        }
That's it! Go in-game and test it out! Our new Terrabeams should now apply the OnFire debuff!
kdcROYP.png


Shooting in angles
You can fire projectiles in certain angles, if you want. We can use the following snippet to do so.
Code:
float rotation = MathHelper.ToRadians(20);
for (int i = 0; i < numberProjectiles; i++)
{
   Vector2 perturbedSpeed = new Vector2(speedX, speedY).RotatedBy(MathHelper.Lerp(-rotation, rotation, i / (numberProjectiles - 1)));
   Projectile.NewProjectile(position.X, position.Y, perturbedSpeed.X, perturbedSpeed.Y, type, damage, knockBack, player.whoAmI);
}
These projectiles will fire at a certain angle. Let's implement this in our Better Terra Blade. We should use the Shoot() hook for this.
Code:
        public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
        {
            int numberProjectiles = 2;
            float rotation = MathHelper.ToRadians(20);

            for (int i = 0; i < numberProjectiles; i++)
            {
                Vector2 perturbedSpeed = new Vector2(speedX, speedY).RotatedBy(MathHelper.Lerp(-rotation, rotation, i / (numberProjectiles - 1)));
                Projectile.NewProjectile(position.X, position.Y, perturbedSpeed.X, perturbedSpeed.Y, type, damage, knockBack, player.whoAmI);
            }

            return false;
        }
Add this hook to your BetterTerraBlade class.
The reason we return false is because we don't want it to shoot the one original projectile. You can change the number of projectiles and the angle in the two variables.

We can also shoot projectiles in an even arc at an angle using the following snippet:
Code:
public override bool Shoot(Player player, ref Microsoft.Xna.Framework.Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
{
    float spread = 15f * 0.0174f;
    float baseSpeed = (float)Math.Sqrt(speedX * speedX + speedY * speedY);
    double startAngle = Math.Atan2(speedX, speedY)- spread/2;
    double deltaAngle = spread/8f;
    double offsetAngle;
    int i;
    for (i = 0; i < 8;i++ )
    {
        offsetAngle = startAngle + deltaAngle * i;
        Terraria.Projectile.NewProjectile(position.X, position.Y, baseSpeed*(float)Math.Sin(offsetAngle), baseSpeed*(float)Math.Cos(offsetAngle), item.shoot, damage, knockBack, item.owner);
    }
    return false;
}

Let's say we'd want to fire 4 projectiles between the ones we are shooting in our previous code, we could do the following: (improved code slightly)
Code:
        public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
        {
            int numberProjectiles = 2;
            float rotation = MathHelper.ToRadians(20);

            for (int i = 0; i < numberProjectiles + 1; i++)
            {
                Vector2 perturbedSpeed = new Vector2(speedX, speedY).RotatedBy(MathHelper.Lerp(-rotation, rotation, i / (numberProjectiles - 1)));
                Projectile.NewProjectile(position.X, position.Y, perturbedSpeed.X, perturbedSpeed.Y, type, damage, knockBack, player.whoAmI);
            }

            int numProjectiles2 = 4;
            float spread = MathHelper.ToRadians(10);
            float baseSpeed = (float)Math.Sqrt(speedX * speedX + speedY * speedY);
            double startAngle = Math.Atan2(speedX, speedY) - spread / 2;
            double deltaAngle = spread / (float)numProjectiles2;
            double offsetAngle;

            for (int j = 0; j < numProjectiles2; j++)
            {
                offsetAngle = startAngle + deltaAngle * j;
                Projectile.NewProjectile(position.X, position.Y, baseSpeed * (float)Math.Sin(offsetAngle), baseSpeed * (float)Math.Cos(offsetAngle), type, damage, knockBack, player.whoAmI);
            }

            return false;
        }
This results in the following:
ecbd77912df1469c9499d2521309f761.png

As you can see, all projectiles neatly come from the exact middle which is where I clicked.


THIS TUTORIAL IS HIGHLY WIP, MORE COMING.

kdcROYP.png

Full codes
Code:
    public class BetterTerraBlade : ModItem
    {
        public override void SetDefaults()
        {
            item.CloneDefaults(ItemID.TerraBlade);
            item.name = "Better Terra Blade";
            item.damage = 500;
            item.shoot = mod.ProjectileType("BetterTerrabeam");
        }

        public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)
        {
            int numberProjectiles = 2;
            float rotation = MathHelper.ToRadians(20);

            for (int i = 0; i < numberProjectiles + 1; i++)
            {
                Vector2 perturbedSpeed = new Vector2(speedX, speedY).RotatedBy(MathHelper.Lerp(-rotation, rotation, i / (numberProjectiles - 1)));
                Projectile.NewProjectile(position.X, position.Y, perturbedSpeed.X, perturbedSpeed.Y, type, damage, knockBack, player.whoAmI);
            }

            int numProjectiles2 = 4;
            float spread = MathHelper.ToRadians(10);
            float baseSpeed = (float)Math.Sqrt(speedX * speedX + speedY * speedY);
            double startAngle = Math.Atan2(speedX, speedY) - spread / 2;
            double deltaAngle = spread / (float)numProjectiles2;
            double offsetAngle;

            for (int j = 0; j < numProjectiles2; j++)
            {
                offsetAngle = startAngle + deltaAngle * j;
                Projectile.NewProjectile(position.X, position.Y, baseSpeed * (float)Math.Sin(offsetAngle), baseSpeed * (float)Math.Cos(offsetAngle), type, damage, knockBack, player.whoAmI);
            }

            return false;
        }

        public override void OnHitNPC(Player player, NPC target, int damage, float knockBack, bool crit)
        {
            target.AddBuff(BuffID.OnFire, 5 * 60);

            if (target.townNPC)
            {
                player.Hurt(5, -player.direction, false, false, " is an evil :red:...");
            }
        }

        public override bool Autoload(ref string name, ref string texture, IList<EquipType> equips)
        {
            texture = "Terraria/Item_" + ItemID.TerraBlade;
            return mod.Properties.Autoload;
        }

        public override void AddRecipes()
        {
            ModRecipe recipe = new ModRecipe(mod);
            recipe.SetResult(this);
            recipe.AddRecipe();
        }
    }
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Terraria;
using Terraria.ModLoader;
using Terraria.ID;

namespace MyFirstMod.Projectiles
{
    public class WoodenArrowOfDeathItem : ModItem
    {
        public override void SetDefaults()
        {
            item.CloneDefaults(ItemID.WoodenArrow);
            item.name = "Wooden Arrow of Death";
            item.shoot = mod.ProjectileType("WoodenArrowOfDeath");
            item.ammo = ProjectileID.WoodenArrowFriendly;
        }

        public override bool Autoload(ref string name, ref string texture, IList<EquipType> equips)
        {
            texture = "Terraria/Item_" + ItemID.WoodenArrow;
            return mod.Properties.Autoload;
        }

        public override void AddRecipes()
        {
            ModRecipe recipe = new ModRecipe(mod);
            recipe.SetResult(this);
            recipe.AddRecipe();
        }
    }

    public class WoodenArrowOfDeath : ModProjectile
    {
        public override void SetDefaults()
        {
            projectile.CloneDefaults(ProjectileID.WoodenArrowFriendly);
            projectile.name = "Wooden Arrow of Death";
            aiType = ProjectileID.WoodenArrowFriendly;
        }

        public override bool Autoload(ref string name, ref string texture)
        {
            texture = "Terraria/Projectile_" + ProjectileID.WoodenArrowFriendly;
            return mod.Properties.Autoload;
        }

        public override void OnHitNPC(NPC target, int damage, float knockback, bool crit)
        {
            target.StrikeNPCNoInteraction(target.lifeMax, 0f, -target.direction);
        }
    }
}
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Terraria;
using Terraria.ModLoader;
using Terraria.ID;

namespace MyFirstMod.Projectiles
{
    class BetterTerrabeam : ModProjectile
    {
        public override void SetDefaults()
        {
            projectile.CloneDefaults(ProjectileID.TerraBeam);
            projectile.name = "Terrabeam of Death";
            aiType = ProjectileID.TerraBeam;
        }

        public override bool Autoload(ref string name, ref string texture)
        {
            texture = "Terraria/Projectile_" + ProjectileID.TerraBeam;
            return mod.Properties.Autoload;
        }

        public override void OnHitNPC(NPC target, int damage, float knockback, bool crit)
        {
            target.AddBuff(BuffID.OnFire, 5 * 60);
        }
    }
}

Go to next tutorial: Not yet available
 
Some Questions
How do i set how much projectile gonna be shoot at one use of item
How do i set dust to projectile and light color
 
what is the next tutorial going to be?
I will possibly make some new tutorials this week or next week. It will cover dusts, and also NPCInfo / ItemInfo / ProjectileInfo (so the info classes)
Some Questions
How do i set how much projectile gonna be shoot at one use of item
How do i set dust to projectile and light color
To control how fast an item is 'used', you need to set item.useAnimation and item.useTime. For example, setting both to 24 makes it 'decently' fast and one full animation will fire one time. If you make useTime lower than useAnimation, it will fire more in one animation.
You can spawn dusts in any of the projectile's AI hooks with Dust.NewDust
 
I understand the motivation aspect of this. I have that same problem in Java lol but, if you ever got back into it. I think many of us would appreciate it you did great work.
 
I understand the motivation aspect of this. I have that same problem in Java lol but, if you ever got back into it. I think many of us would appreciate it you did great work.
actually I made a next tutorial but it's very small. should be on the forum somewhere. main issue right now is: time. I've got other stuff to do.
 
Hi im new to modding, is there anyone here that knows if it is possible to change the animation of a sword swing or maybe an item that you can add on to your sword to make it perform combos or different animations? It is possible coding wise, but how do you implement it into the game? Thx
 
Hi im new to modding, is there anyone here that knows if it is possible to change the animation of a sword swing or maybe an item that you can add on to your sword to make it perform combos or different animations? It is possible coding wise, but how do you implement it into the game? Thx
In all seriousness, it would be really difficult to create even a simple combo hit system rather than an animation. The reasoning behind this is that the way that Terraria handles weapons is it selects the type of animation from the animation type, which there are currently only 5 or 7, don't really remember how many there were. You would have to make a whole new animation style for the weapon if you want to do something like this and fool around with sprite manipulation.

You could of course, handle this with a projectile instead of using trying to make a whole new animation style. With this idea, you would have to make the item itself invisible, have it animated according to the channel and a counter, then have it maintain at a certain position relative to the player, of course, you would still need to factor in the hand animation. It gets quite complicated still... However you'll have that fancy sword animation at least. You can check out an item/projectile that does it through my github. It's a magic weapon and the projectile is under projectiles/magic.
 
Thx Sin Costan, Guess ill have to wait a while until i learn more about the terraria side of coding, but thx anyways! GL with ur mods
 
what code can i use to add a repuired pickaxe power to my MeowZium block and do i put the code in the .cs file in placeables or tiles?
 
does that code give 55% i just wanna know so i know the portions
[doublepost=1487626519,1487625927][/doublepost]
because when i added the axe power to an item. 27 was like 150% or something like that so was kinda confused
axe powers are multiplied by 5, but I'm pretty sure pick powers aren't
 
axe powers are multiplied by 5, but I'm pretty sure pick powers aren't
awesome thanks. helps alot. the tutorial i was using (because i hate reading) was videos and he didnt explain anything just showed you how to make the basics
[doublepost=1487647381,1487647329][/doublepost]
axe powers are multiplied by 5, but I'm pretty sure pick powers aren't
i think my only choice is to read these tutorials because every other vid tutorial is inefficient
 
Hey i was just wondering is there anyway to open the original code of terraria to see items and such, so i can look at and see the coding of the game not from a creation stand point but a finished product. i feel being able to do this will show me various code that would help me learn to do the things i wish to do.
 
This code:
Code:
        public override bool Autoload(ref string name, ref string texture)

Causes this error:
error CS0161: 'MyFirstMod.Projectiles.CelestialFuryProjectile.Autoload(ref string, ref string)': not all code paths return a value

What's going wrong? (Wow, I have an error report on every tutorial except tutorial 1.)
 
Back
Top Bottom