tAPI [Tutorial] Custom Bosses - NPC AI and Server Syncing

You'd override the item's PreShoot method, like the person above said. This sounds like a fun and short puzzle, so I'll just make this code. I imagine it would go something like this:
Code:
public override bool PreShoot(Player player, Vector2 position, Vector2 velocity, int type, int damage, float knockback)
{
    float magnitude = (float)Math.Sqrt(velocity.X * velocity.X + velocity.Y * velocity.Y); //Pythagorean theorem to get the speed
    float rotation = Math.Atan2(velocity.Y, velocity.X); //gets the theta coordinate of the velocity's polar coordinates, aka angle in radians
    float direction = rotation * 4f / (float)Math.PI; //there are 8 directions, so we want this to range from 0 to 8
    float newRotation = (float)Math.Round(direction) / 4f * (float)Math.PI; //convert to a specific direction
    velocity.X = (float)Math.Cos(newRotation) * magnitude; //converting polar coordinates to Cartesian coordinates
    velocity.Y = (float)Math.Sin(newRotation) * magnitude; //same thing
    Projectile.NewProjectile(position.X, position.Y, velocity.X, velocity.Y, type, damage, knockback, Main.myPlayer); //finally create the projectile
    return false; //we already created the projectile, so the game doesn't need to do it for us
}

I know this was posted ages ago, but I'd like to ask how you could achieve something similar but make it shoot out of a boss, as part of an attack. I have a worm boss, and in a feeble attempt to make it unique, I'd like to add this code (8 or so projectiles shooting out of the bosses head) :p
 
I know this was posted ages ago, but I'd like to ask how you could achieve something similar but make it shoot out of a boss, as part of an attack. I have a worm boss, and in a feeble attempt to make it unique, I'd like to add this code (8 or so projectiles shooting out of the bosses head) :p
You'd just do the same thing, except place it in the NPC's AI method. You'll also have to replace position with npc.position and velocity with npc.velocity. You can also customize the projectile's damage; it will do double damage as whatever you put for the damage when you create the projectile, meaning it can differ from the NPC's base damage.
Make sure to use the ai array to keep track of when to shoot the projectile; don't want it to be shooting 60 times per second.
 
The rotation for our boss isn't working for some reason. Is there anyway to get the npc to look at the player (like the EoC)? I only know of npc.rotation
 
The rotation for our boss isn't working for some reason. Is there anyway to get the npc to look at the player (like the EoC)? I only know of npc.rotation
Hm, that's weird. npc.rotation is what's actually used to get the visual rotation. Just to make sure, you're using a negative AI style and using radians, right?
 
Negative aiStyle is being used but by God what the heck is a radian? XD I probably learnt it in school but forgot.

Is that just angles? I feel like an idiot for not knowing :(
 
Negative aiStyle is being used but by God what the heck is a radian? XD I probably learnt it in school but forgot.
Basically angle measurements where a full circle is 2 pi. So for example, to get a 90 degree counterclockwise rotation, you'd set npc.rotation = (float)Math.PI / 2f.
 
Basically angle measurements where a full circle is 2 pi. So for example, to get a 90 degree counterclockwise rotation, you'd set npc.rotation = (float)Math.PI / 2f.

I see, so to get 180 degrees it would just be (float)Math.PI? Anyway, I'm still not sure about the rotation. Where would I put the radians?
 
I see, so to get 180 degrees it would just be (float)Math.PI? Anyway, I'm still not sure about the rotation. Where would I put the radians?
Yeah, 180 degrees = pi radians. You just put the radians in npc.rotation (npc.rotation is assumed to be measured in radians). Also, conveniently, the methods in the Math class will return angles already in radians for you. So you can do something like:
Code:
Vector2 distance = player.Center - npc.Center;
float angle = (float)Math.Atan2(distance.Y, distance.X);
npc.rotation = angle;
to make the NPC look towards the player. Assuming the unrotated sprite looks towards the right.
 
So if the unrotated sprite looks downwards, could you subtract Math.PI / 2f from the angle? Bit of a ghetto solution if it works, but It would be much easier.
 
So if the unrotated sprite looks downwards, could you subtract Math.PI / 2f from the angle? Bit of a ghetto solution if it works, but It would be much easier.
Decreasing the angle by that would make the sprite rotate clockwise by 90 degrees, so you'd want to add that value instead. (I think that's actually what happens with the EoC.)
 
Decreasing the angle by that would make the sprite rotate clockwise by 90 degrees, so you'd want to add that value instead. (I think that's actually what happens with the EoC.)

Oops, got it the wrong way around xD Thanks, I'll see if it works now! :D

EDIT: Haha, the eye's backwards XD I think it was subtract :p
 
Last edited:
Oops, got it the wrong way around xD Thanks, I'll see if it works now! :D

EDIT: Haha, the eye's backwards XD I think it was subtract :p
Actually, now I remember. Earlier I posted this:
Vector2 distance = player.Center - npc.Center;
When it should be:
Vector2 distance = npc.Center - player.Center;
Alternatively, you can just add/subtract Math.PI to the result of Math.Atan2 to rotate 180 degrees.
Subtracting worked in that case since the image was flipped before subtracting (it was like subtracting Math.PI then adding Math.PI / 2). But in general, addition rotates counterclockwise and subtraction rotates clockwise; that is the rule for radians.
 
Last edited:
Would their be a way to make a projectile shoot out like Spazmatizm's Cursed Flame? A constant flame of lots of projectiles? I have the projectile down it's just getting it to be at the same rotation as the NPC. (so if the npc is looking South-West, the projectile will fire south-west etc.)
 
Would their be a way to make a projectile shoot out like Spazmatizm's Cursed Flame? A constant flame of lots of projectiles? I have the projectile down it's just getting it to be at the same rotation as the NPC. (so if the npc is looking South-West, the projectile will fire south-west etc.)
For the constant flame effect, just create several new projectiles each tick with random angle offsets on their velocities.
To get them to move in the same direction the NPC is facing, you can do something like:
Code:
float angle = npc.rotation + whatever; //whatever will depend on the rotation of the NPC in the image file
Vector2 velocity = new Vector2((float)Math.Cos(angle),  (float)Math.Sin(angle));
velocity *= speed; //replace speed with some float
//here you can add/subtract random amounts to the velocity's components, then use this velocity when you create the projectile
 
^ That's the easy way of doing it.

The way vanilla does it is, it sets the NPC's timeLeft to a low value (such as 10), then forces the NPC to move off screen (worm bosses and skeletron move down, EoC moves up, and so on). Boss NPCs ususally do not despawn when off-screen unless really really far, but I believe boss NPCs with a really low timeLeft are an exception.
 
Last edited:
^ That's the easy way of doing it.

The way vanilla does it is, it sets the NPC's timeLeft to a low value (such as 10), then forces the NPC to move off screen (worm bosses and skeletron move down, EoC moves up, and so on). Boss NPCs ususally do not despawn when off-screen unless really really far, but I believe boss NPCs with a really low timeLeft are an exception.
Just for the interested, the despawning mechanism works the same for all monster NPCs (whether or not it's a boss). The timeLeft property tells how long the NPC can stay off-screen before it despawns, and whenever the NPC returns to the screen, the timeLeft property is reset to NPC.activeTime. Bosses start out with a large timeLeft so they can have enough time to arrive at the player's screen regardless of how long it takes (as soon as they enter the screen it resets to the normal time). The reason the bosses don't despawn is that they keep moving towards the player and re-entering the screen, even if they do leave the screen for just a little bit. NPC.activeTime is equal to 750, which means once an NPC leaves the screen, it has 12.5 seconds to return to the screen before it despawns.
I remember the first time I "battled" Plantera, I started running away as soon as I broke the bulb, and it despawned, lol
 
Getting Errors on my boss projectile...
Building mod Z Items Mod

Validating Jsons...
Compiling code...
ElectrumElemental.cs (3,32)
Invalid token 'if' in class, struct, or interface member declaration
if (Main.netMode != 1)
^
ElectrumElemental.cs (20,32)
Invalid token '!=' in class, struct, or interface member declaration
if (Main.netMode != 1)
^
ElectrumElemental.cs (28,34)
Invalid token '(' in class, struct, or interface member declaration
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
^
ElectrumElemental.cs (65,34)
Invalid token ',' in class, struct, or interface member declaration
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
^
ElectrumElemental.cs (1,37)
Type or namespace definition, or end-of-file expected
}
^
Failed to build Z Items Mod.

Why do i get this error?

Theres the CS file:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

using TAPI;
using Terraria;

namespace ZItemsMod.NPCs
{
public class ElectrumElemental : ModNPC
{
public override void HitEffect(int hitDirection, double damage, bool isDead)
{
if (Main.netMode != 2)
{
for (int m = 0; m < (isDead ? 20 : 5); m++)
{
int dustID = Dust.NewDust(npc.position, npc.width, npc.height, 5, npc.velocity.X * 0.2f, npc.velocity.Y * 0.2f, 100, Color.Red, isDead && m % 2 == 0 ? 3f : 1f);
if (isDead && m % 2 == 0) { Main.dust[dustID].noGravity = true; }
}
if (isDead)
{
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental2"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
}

}
}
if (Main.netMode != 1)
{
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
}
}
}
 
Getting Errors on my boss projectile...
Building mod Z Items Mod

Validating Jsons...
Compiling code...
ElectrumElemental.cs (3,32)
Invalid token 'if' in class, struct, or interface member declaration
if (Main.netMode != 1)
^
ElectrumElemental.cs (20,32)
Invalid token '!=' in class, struct, or interface member declaration
if (Main.netMode != 1)
^
ElectrumElemental.cs (28,34)
Invalid token '(' in class, struct, or interface member declaration
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
^
ElectrumElemental.cs (65,34)
Invalid token ',' in class, struct, or interface member declaration
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
^
ElectrumElemental.cs (1,37)
Type or namespace definition, or end-of-file expected
}
^
Failed to build Z Items Mod.

Why do i get this error?

Theres the CS file:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

using TAPI;
using Terraria;

namespace ZItemsMod.NPCs
{
public class ElectrumElemental : ModNPC
{
public override void HitEffect(int hitDirection, double damage, bool isDead)
{
if (Main.netMode != 2)
{
for (int m = 0; m < (isDead ? 20 : 5); m++)
{
int dustID = Dust.NewDust(npc.position, npc.width, npc.height, 5, npc.velocity.X * 0.2f, npc.velocity.Y * 0.2f, 100, Color.Red, isDead && m % 2 == 0 ? 3f : 1f);
if (isDead && m % 2 == 0) { Main.dust[dustID].noGravity = true; }
}
if (isDead)
{
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental2"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
}

}
}
if (Main.netMode != 1)
{
Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
}
}
}
I formatted it so its much clearer to see, and here you can see the multitude of problems...
Code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

using TAPI;
using Terraria;

namespace ZItemsMod.NPCs
{
    public class ElectrumElemental : ModNPC
    {
        public override void HitEffect(int hitDirection, double damage, bool isDead)
        {
            if (Main.netMode != 2)
            {
            for (int m = 0; m < (isDead ? 20 : 5); m++)
            {
            int dustID = Dust.NewDust(npc.position, npc.width, npc.height, 5, npc.velocity.X * 0.2f, npc.velocity.Y * 0.2f, 100, Color.Red, isDead && m % 2 == 0 ? 3f : 1f);
            if (isDead && m % 2 == 0) { Main.dust[dustID].noGravity = true; }
            }
            if (isDead)
            {
            Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental"], 1f);
            Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:ElectrumElemental2"], 1f);
            Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
            Gore.NewGore(npc.position, npc.velocity, GoreDef.gores["ZItemsMod:GoresPlate_2"], 1f);
            }
        }
        if (Main.netMode != 1)
        {
            Projectile.NewProjectile(1, 1, 1, 1, 44, 16, 2, Main.myPlayer, 0, 0);
        }
    }
}

That if statement isn't within a function, rather outside into the open sea where it won't be read. I'm guessing that you want your NPC to shoot at the player so you should probably stick it inside the AI() function. Also the parameters for your Projectile production is wrong.

Here's an example of how you do NPC projectiles, the code made by Bluemagic.

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

namespace COFP.NPCs
{
    public class FlyingFrog : ModNPC
    {
        public override void AI()
        {
            Player player = Main.player[npc.target]; //Setting the target
            npc.ai[0] += 2f;
            if (npc.localAI[0] > 0f)
            {
                npc.localAI[0] -= 1f;
            }
            
            //Setting Shoot Vector
            Vector2 center = new Vector2(npc.position.X + (float)npc.width * 0.5f, npc.position.Y + (float)npc.height * 0.5f);
            float shootToX = player.position.X + (float)player.width * 0.5f - center.X;
            float shootToY = player.position.Y - center.Y;
            float distance = (float)System.Math.Sqrt((double)(shootToX * shootToX + shootToY * shootToY));

            //Fire the projectile if the player's distance is less than 480 pixels and if there is nothing blocking the pathway to the player
            if (distance < 480f && Collision.CanHit(npc.position, npc.width, npc.height, player.position, player.width, player.height))
            {
                if (npc.velocity.Y == 0f)
                {
                    npc.velocity.X *= 0.9f;
                }
                if (Main.netMode != 1 && npc.localAI[0] == 0f)
                {
                   //I think this is right, not so certain.
                    distance = 3f / distance;
                    shootToX *= distance * 2;
                    if(shootToY > 0)
                    {
                        shootToY *= (float) distance - 0.25f;
                    }
                    else
                    {
                        shootToY *= (float) distance + 0.25f;
                    }
                    npc.localAI[0] = 30f;
                    Projectile.NewProjectile(center.X, center.Y, shootToX, shootToY, "COFP:NPCInferno", 35, 0f, Main.myPlayer, 0f, 0f);
                }
            }

        }
        public override void SelectFrame(int frameSize)
        {
            Player player = Main.player[npc.target];
            Vector2 center = new Vector2(npc.position.X + (float)npc.width * 0.5f, npc.position.Y + (float)npc.height * 0.5f);
            float shootToX = player.position.X + (float)player.width * 0.5f - center.X;
            float shootToY = player.position.Y - center.Y;
            float distance = (float)System.Math.Sqrt((double)(shootToX * shootToX + shootToY * shootToY));
            npc.spriteDirection = npc.direction;
            npc.rotation = npc.velocity.X * 0.1f;
            npc.frameCounter += 1.0;
            if (distance < 480f && Collision.CanHit(npc.position, npc.width, npc.height, player.position, player.width, player.height))              
            {
                if (npc.frameCounter <= 5)
                {
                    npc.frame.Y = 2 * frameSize;
                }
                else if(npc.frameCounter <= 10)
                {
                    npc.frame.Y = 3 * frameSize;
                }
                else if(npc.frameCounter >= 11)
                {
                    npc.frameCounter = 0;
                }
            }
            else if(npc.velocity.X != 0)
            {
                if (npc.frameCounter < 6.0)
                {
                    npc.frame.Y = 0;
                }
                else
                {
                    npc.frame.Y = frameSize;
                    if (npc.frameCounter >= 11.0)
                    {
                        npc.frameCounter = 0.0;
                    }
                }
            }
        }
    }
}
 
Back
Top Bottom