tModLoader Modding Tutorial 3: Some More Weapons

This is Many

Retinazer
Introduction
Hi everyone! It's been 9 months since the last tutorial because I was too lazy and busy with my projects.
In this tutorial, we'll make weapons for ranger and mage that will be way harder than a sword we did in previous tutorial, so get ready!
Plan is big, so let's begin.

What are we going to study in this tutorial:
- How to create a gun that has some interesting abilities
- How to create a staff with custom projectile


How to create a gun that has some interesting abilities
Gun is a weapon that consumes ammo and fires projectiles. This would be boring, so we'll add a few more effects:
1. Gun will fire 3 shots in a row, just like the Clockwork Assault Rifle does.
2. Each bullet has a 1/5 chance of being replaced with Silver Bullet.
3. Gun fires 1-3 projectiles at a time.
4. Have a 33% chance to not consume ammo.

For the basic gun we need this code:
C#:
using Microsoft.Xna.Framework;
using SteelMod.Content.Items.Materials;
using Terraria;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;

namespace SteelMod.Content.Items.Weapons.Ranged
{
    public class SteelRifle : ModItem
    {
        public override void SetDefaults()
        {
            // Visual properties
            Item.width = 60;
            Item.height = 20;
            Item.scale = 1.05f;
            Item.useStyle = ItemUseStyleID.Shoot; // Use style for guns
            Item.rare = ItemRarityID.Blue;

            // Combat properties
            Item.damage = 50; // Gun damage + bullet damage = final damage
            Item.DamageType = DamageClass.Ranged;
            Item.useTime = 15; // Delay between shots.
            Item.useAnimation = 15; // How long shoot animation lasts in ticks.
            Item.knockBack = 4.5f; // Gun knockback + bullet knockback = final knockback
            Item.autoReuse = false;

            // Other properties
            Item.value = 10000;
            Item.UseSound = SoundID.Item11; // Gun use sound

            // Gun properties
            Item.noMelee = true; // Item not dealing damage while held, we don’t hit mobs in the head with a gun
            Item.shoot = ProjectileID.PurificationPowder; // What kind of projectile the gun fires, does not mean anything here because it is replaced by ammo
            Item.shootSpeed = 16f; // Speed of a projectile. Mainly measured by eye
            Item.useAmmo = AmmoID.Bullet; // What ammo gun uses
        }
        public override void AddRecipes()
        {
            CreateRecipe()
                .AddIngredient<SteelShard>(9)
                .AddTile(TileID.Anvils)
                .Register();
        }
    }
}
A bit harder than a sword, but still boring.
Screenshot 2024-04-20 at 14.15.51.png


For first, let's fix gun position in hands, cuz now it looks weird.
C#:
public override Vector2? HoldoutOffset() => new Vector2(-8f, 0f); // Offset in pixels at which the player will hold the gun. -Y is up
Now it looks way better!
Screenshot 2024-04-25 at 22.59.24.png


Also we want to add a chance to not consume ammo, just like Minishark!
C#:
public override bool CanConsumeAmmo(Item ammo, Player player) => Main.rand.Next(101) <= 33; // Chance in % to not consume ammo

But all we did above is just a default generic common bloat suck gun.
That's why I decided to add some shoot modifications.
They are made in Shoot() or in ModifyShootStats(). We'll use Shoot() cuz there you can do some more things, such as the bullet quantity or replacement.
For first let's add Shoot() to our code:
C#:
        public override bool Shoot(Player player, EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockback)
        {
            return true; // return true to allow gun to shoot
        }
Creating some shoot inaccuracy:
C#:
velocity = velocity.RotatedByRandom(MathHelper.ToRadians(10));
Our gun will be able to shoot 1-3 projectiles a shot. Here we enter the spread in which number of projectiles will vary:
C#:
int NumProjectiles = Main.rand.Next(1, 4);
Note: Main.rand.Next does not return the last number, so, for eg Main.rand.Next(1, 4) can return 1, 2 or 3!!!

Finally, creating a loop in which we are going to replace proj type with other with 20% chance and create new projectile(s)
C#:
            for (int i = 0; i < NumProjectiles; i++)
            {
                // New velocity for new bullets
                Vector2 NewVelocity = velocity.RotatedByRandom(MathHelper.ToRadians(10));

                // Some random to bullet speed
                NewVelocity *= 1f - Main.rand.NextFloat(0.2f);

                // Our gun has a 1/5 chance to replace default bullets with silver ones
                if (Main.rand.NextBool(5))
                {
                    type = ProjectileID.SilverBullet;
                }

                // Creating new projectile
                Projectile.NewProjectileDirect(
                    source,
                    position,
                    NewVelocity,
                    type,
                    damage,
                    knockback,
                    player.whoAmI
                    );
            }
Woah, that works perfectly!
Screenshot 2024-04-25 at 23.20.32.png


I almost forgot, we also want to make our gun shoot 3 times a row, consuming only one ammo.
To do that, make Item.useTime 3 times lesser than Item.useAnimation:
C#:
            Item.useTime = 10; // Delay between shots.
            Item.useAnimation = 30; // How long shoot animation lasts in ticks. We made it 3 times larger than useTime to make gun fire thrice

Also, you need two more lines of code.
C#:
            Item.reuseDelay = 25; // How long the gun will be unable to shoot after useAnimation ends
            Item.consumeAmmoOnLastShotOnly = true; // Gun will consume only one ammo per a burst of shots

That's all, we made a gun with some interesting effects!
Full code:
C#:
using Microsoft.Xna.Framework;
using SteelMod.Content.Items.Materials;
using Terraria;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;

namespace SteelMod.Content.Items.Weapons.Ranged
{
    public class SteelRifle : ModItem
    {
        public override void SetDefaults()
        {
            // Visual properties
            Item.width = 60;
            Item.height = 20;
            Item.scale = 1.05f;
            Item.useStyle = ItemUseStyleID.Shoot; // Use style for guns
            Item.rare = ItemRarityID.Blue;

            // Combat properties
            Item.damage = 50; // Gun damage + bullet damage = final damage
            Item.DamageType = DamageClass.Ranged;
            Item.useTime = 10; // Delay between shots.
            Item.useAnimation = 30; // How long shoot animation lasts in ticks. We made it 3 times larger than useTime to make gun fire thrice
            Item.reuseDelay = 25; // How long the gun will be unable to shoot after useAnimation ends
            Item.consumeAmmoOnLastShotOnly = true; // Gun will consume only one ammo per 3 shots
            Item.knockBack = 4.5f; // Gun knockback + bullet knockback = final knockback
            Item.autoReuse = false;

            // Other properties
            Item.value = 10000;
            Item.UseSound = SoundID.Item11; // Gun use sound

            // Gun properties
            Item.noMelee = true; // Item not dealing damage while held, we don’t hit mobs in the head with a gun
            Item.shoot = ProjectileID.PurificationPowder; // What kind of projectile the gun fires, does not mean anything here because it is replaced by ammo
            Item.shootSpeed = 16f; // Speed of a projectile. Mainly measured by eye
            Item.useAmmo = AmmoID.Bullet; // What ammo gun uses
        }
        public override void AddRecipes()
        {
            CreateRecipe()
                .AddIngredient<SteelShard>(9)
                .AddTile(TileID.Anvils)
                .Register();
        }
     
        public override bool CanConsumeAmmo(Item ammo, Player player) => Main.rand.Next(101) <= 33; // Chance in % to not consume ammo

        public override Vector2? HoldoutOffset() => new Vector2(-8f, 0f); // Offset in pixels at which the player will hold the gun. -Y is up
     
        public override bool Shoot(Player player, EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockback)
        {
            // Creating some shoot inaccuracy
            velocity = velocity.RotatedByRandom(MathHelper.ToRadians(10));

            // Our gun will be able to shoot 1-3 projectiles a shot. Here we enter the spread in which number of projectiles will vary
            // Remember that Main.rand.Next does not return the last number, so Main.rand.Next(1, 4) can return 1, 2 or 3
            int NumProjectiles = Main.rand.Next(1, 4);
            for (int i = 0; i < NumProjectiles; i++)
            {
                // New velocity for new bullets
                Vector2 NewVelocity = velocity.RotatedByRandom(MathHelper.ToRadians(10));

                // Some random to bullet speed
                NewVelocity *= 1f - Main.rand.NextFloat(0.2f);

                // Our gun has a 1/5 chance to replace default bullets with silver ones
                if (Main.rand.NextBool(5))
                {
                    type = ProjectileID.SilverBullet;
                }

                // Creating new projectile
                Projectile.NewProjectileDirect(
                    source,
                    position,
                    NewVelocity,
                    type,
                    damage,
                    knockback,
                    player.whoAmI
                    );
            }

            return false; // return false to prevent the gun from shooting original projectile as we created them manually above
        }
    }
}

How to create a staff with custom projectile
Vanilla has tons of projectiles. But sometimes they are not enough.
So we'll create our own projectile and make custom behaviour for it!
It will be fired from a staff, which is held differently than a gun and uses mana instead of ammo.
For the basic magic weapon we need this code:
C#:
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
using Microsoft.Xna.Framework;
using SteelMod.Content.Items.Materials;
using SteelMod.Content.Projectiles;

namespace SteelMod.Content.Items.Weapons.Magic
{
    public class ElectromagneticRod : ModItem
    {
        public override void SetDefaults()
        {
            // Helper method to quickly set basic magic weapon properties
            Item.DefaultToMagicWeapon(
                projType: ModContent.ProjectileType<ElectromagneticBolt>(), // Our own projectile
                singleShotTime: 35, // useTime & useAnimation
                shotVelocity: 9f,
                hasAutoReuse: true
                );

            Item.damage = 45;
            Item.knockBack = 5f;
            Item.value = 10000;
            Item.rare = ItemRarityID.Blue;
            Item.UseSound = SoundID.Item94; // Some electric sound
            Item.mana = 10; // This item uses 10 mana
            Item.width = Item.height = 40;
        }

        public override void AddRecipes()
        {
            CreateRecipe()
                .AddIngredient<SteelShard>(8)
                .AddTile(TileID.Anvils)
                .Register();
        }
    }
}

To staff be held not like a gun we need this code:
C#:
        public override void SetStaticDefaults()
        {
            Item.staff[Item.type] = true;
        }

ModContent.ProjectileType<ElectromagneticBolt>() gives an error because we don't have any ElectromagneticBolt in our mod. Let's create one!
Firstly, let's create one more folder where all our projectiles will be stored:
Screenshot 2024-04-25 at 23.58.47.png

Then, create .cs and .png files there. I will not create .png file because I will use no texture for projectile (it will be made out of dust).
This is the basic projectile code:
C#:
using Terraria;
using Terraria.ModLoader;

namespace SteelMod.Content.Projectiles
{
    public class ElectromagneticBolt : ModProjectile
    {
        public override void SetDefaults()
        {
            Projectile.DamageType = DamageClass.Magic; // Damage class projectile uses
            Projectile.scale = 1f; // Projectile scale multiplier
            Projectile.penetrate = 3; // How many hits projectile have to make before it dies. 3 means projectile will die on 3rd enemy. Setting this to 0 will make projectile die instantly
            Projectile.aiStyle = 0; // AI style of a projectile. 0 is default bullet AI
            Projectile.width = Projectile.height = 10; // Hitbox of projectile in pixels
            Projectile.friendly = true; // Can hit enemies?
            Projectile.hostile = false; // Can hit player?
            Projectile.timeLeft = 90; // Time in ticks before projectile dies
            Projectile.light = 0.3f; // How much light projectile provides
            Projectile.ignoreWater = true; // Does the projectile ignore water (doesn't slow down in it)
            Projectile.tileCollide = true; // Does the projectile collide with tiles, like blocks?
            Projectile.alpha = 255; // Completely transparent
        }
    }
}

Also I'll use no texture:
C#:
public override string Texture => "Terraria/Images/Projectile_0"; // We will use no texture

As we are using no texture, let's add dust to visualise the projectile.
This must happen every tick, and the projectile has a hook for this called AI():
C#:
        public override void AI() // This hook updates every tick
        {
            if (Main.netMode != NetmodeID.Server) // Do not spawn dust on server!
            {
                Dust dust = Dust.NewDustPerfect(
                    Position: Projectile.Center,
                    Type: DustID.Electric,
                    Velocity: Vector2.Zero,
                    Alpha: 100,
                    newColor: Color.White,
                    Scale: 0.9f
                    );
                dust.noGravity = true; // Dust don't have gravity
                dust.fadeIn = -1f; // Dust will fade out
            }
        }

Don't forget to add using Terraria.ID; and using Microsoft.Xna.Framework;
Looks sick!
Screenshot 2024-04-30 at 18.41.16.png


Also I want to add a little dust explosion on projectile death.
Things that happen when projectile is destroyed have to be in OnKill() hook:
C#:
        public override void OnKill(int timeLeft) // What happens on projectile death
        {
            int numDust = 20;
            for (int i = 0; i < numDust; i++) // Loop through code below numDust times
            {
                Vector2 velocity = Vector2.One.RotatedBy(MathHelper.ToRadians(360 / numDust * i)); // Circular velocity
                Dust.NewDustPerfect(Projectile.Center, DustID.Electric, velocity).noGravity = true; // Creating dust
            }
        }
Screenshot 2024-04-30 at 18.43.49.png


That's all, we made a staff with custom projectile!
Full projectile code:
C#:
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
using Microsoft.Xna.Framework;

namespace SteelMod.Content.Projectiles
{
    public class ElectromagneticBolt : ModProjectile
    {
        public override string Texture => "Terraria/Images/Projectile_0"; // We will use no texture
        public override void SetDefaults()
        {
            Projectile.DamageType = DamageClass.Magic; // Damage class projectile uses
            Projectile.scale = 1f; // Projectile scale multiplier
            Projectile.penetrate = 3; // How many hits projectile have to make before it dies. 3 means projectile will die on 3rd enemy. Setting this to 0 will make projectile die instantly
            Projectile.aiStyle = 0; // AI style of a projectile. 0 is default bullet AI
            Projectile.width = Projectile.height = 10; // Hitbox of projectile in pixels
            Projectile.friendly = true; // Can hit enemies?
            Projectile.hostile = false; // Can hit player?
            Projectile.timeLeft = 90; // Time in ticks before projectile dies
            Projectile.light = 0.3f; // How much light projectile provides
            Projectile.ignoreWater = true; // Does the projectile ignore water (doesn't slow down in it)
            Projectile.tileCollide = true; // Does the projectile collide with tiles, like blocks?
            Projectile.alpha = 255; // Completely transparent
        }
      
        public override void AI() // This hook updates every tick
        {
            if (Main.netMode != NetmodeID.Server) // Do not spawn dust on server!
            {
                Dust dust = Dust.NewDustPerfect(
                    Position: Projectile.Center,
                    Type: DustID.Electric,
                    Velocity: Vector2.Zero,
                    Alpha: 100,
                    newColor: Color.White,
                    Scale: 0.9f
                    );
                dust.noGravity = true; // Dust don't have gravity
                dust.fadeIn = -1f;
            }
        }

        public override void OnKill(int timeLeft) // What happens on projectile death
        {
            int numDust = 20;
            for (int i = 0; i < numDust; i++) // Loop through code below numDust times
            {
                Vector2 velocity = Vector2.One.RotatedBy(MathHelper.ToRadians(360 / numDust * i)); // Circular velocity
                Dust.NewDustPerfect(Projectile.Center, DustID.Electric, velocity).noGravity = true; // Creating dust
            }
        }
    }
}

Ending
Thanks to everyone who read this tutorial! I hope your brain didn't burst from so much new information.
Also, sorry for being slow lazy snail and making no tutorials for 9 months. Hope I will not make next one for 9 months, like this one!
Any questions are allowed! See you on the next tutorial!

Previous Tutorial / Next Tutorial (coming soon!)
 
Last edited:
my friends and i are all aspiring mod creators,so i highly appreciate this tutorial! even though i barely know anything about code (which is alright,i have temporarily taken the role of concept artist/spriter), it helped the others and i understand a lot about how terraria and the items work。so thank you。 <3
also,are you still available to take questions?
 
yayyy
i got a few, actually。
1. is the 2x2 pixels thing built into terraria's graphics, or is it just a developer choice (assuming it is to make the game look smoother but still pixelated) ? in other words, do i need to rescale the sprites to fit?
2. what does the mod's name in code mean? is the name only used for coding within the mod, or for tmodloader as a whole? and does it have to be unique from other mod names, or can it repeat?
 
1. is the 2x2 pixels thing built into terraria's graphics, or is it just a developer choice (assuming it is to make the game look smoother but still pixelated) ? in other words, do i need to rescale the sprites to fit?
it is a developer choice, but 1x1 px sprites looks weird in terraria. I always do 2x2

2. what does the mod's name in code mean? is the name only used for coding within the mod, or for tmodloader as a whole? and does it have to be unique from other mod names, or can it repeat?
the internal name for a mod. eg my mod, which is named Arcturus in code and Arcturus Mod in game. and I don't know anything about repeating, never tried
 
Nice! though I wan't to ask a few things:

1 - where do you recommend learning C# from? How can I get started?
2 - how can we make weapons that function identically to some unique functioning weapons in the game, like the Solar Eruption?
 
Back
Top Bottom