tModLoader Official tModLoader Help Thread

it was on inital boot of tmod, so before.
were not trying to develop a mod just trying to play together and i thought this would be the place to ask
Hmm, well that's strange.
I'm probably going to get this extremely wrong, but from how I can see it there is two possible reasons.
A. The terraria files that tmodloader uses to run are from a different platform (Terraria was downloaded on steam but tmodloader was downloaded from a separate website)
B. Again, Tmodloader is downlaoded from a different website, and the Terraria files that it uses are out of date, or corrupt.
 
Hmm, well that's strange.
I'm probably going to get this extremely wrong, but from how I can see it there is two possible reasons.
A. The terraria files that tmodloader uses to run are from a different platform (Terraria was downloaded on steam but tmodloader was downloaded from a separate website)
B. Again, Tmodloader is downlaoded from a different website, and the Terraria files that it uses are out of date, or corrupt.
so ive been trying to figure it out, im thinking it might be because the first version of terraria that is on his computer is the newest update, i know tmod isnt quite there yet.
weve had him uninstall and reinstal terraria and tmod and its not really doing anything.

other than that he got tmod and terraria both from steam so i really dont know what the issue might be

unless there away to downgrade to 4.3.6 then i dont know any way to fix it.
 
so ive been trying to figure it out, im thinking it might be because the first version of terraria that is on his computer is the newest update, i know tmod isnt quite there yet.
weve had him uninstall and reinstal terraria and tmod and its not really doing anything.

other than that he got tmod and terraria both from steam so i really dont know what the issue might be

unless there away to downgrade to 4.3.6 then i dont know any way to fix it.
It doesn't matter if terraria is a higher version than tmodloader, tmod wil just use the files relevant to it's version. Of course, if the version of terraria is lower than tmodloader, (which I don't think is possible in the first place,) then it will not work.

Unfortunately I can only say that your friend's computer may not be the right OS update? Though I don't think that installation is possible if the computer isn't updated.
 
I’ve been trying to figure out how to make a projectile orbit an NPC. I found some code on how to make a projectile rotate a player by Sin Costan (tModLoader - [Tutorial] Projectile Guide and Implementation: tModLoader Edition), but I can’t figure out how to make it rotate an NPC. I’ve tried using Main.npc[Projectile.owner] instead of Main.player[Projectile.owner], but I get an error “index was outside of the bounds of the array” ingame (no idea what that means or how to fix it). Can anyone help?
 
Terraria_ я просто хочу знать, где золото_ 2023-03-15 22-46-29 (online-video-cutter.com).gif

Guys, help me fix my fishing rod. The float flies out from under the feet. The problem is shown in the gif. Here is the code:
p.s. first, a normal fishing rod, next mine

using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;
using Terraria;
using Microsoft.Xna.Framework;

namespace Testmod.Items
{
public class bobonator : ModItem
{
public override void SetStaticDefaults()
{
DisplayName.SetDefault("Test fishing rod");
Tooltip.SetDefault("Test description");
}

public override void SetDefaults()
{
Item.width = 48;
Item.height = 48;
Item.useTime = 25;
Item.useAnimation = 20;
Item.useStyle = 1;
Item.value = 10000;
Item.rare = 2;
Item.UseSound = SoundID.Item1;
Item.useTurn = true;
Item.autoReuse = true;

Item.shootSpeed = 10f;


Item.fishingPole = 15;
Item.shoot = ProjectileID.BobberWooden;


}

public override void AddRecipes()
{
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.DirtBlock, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}

}
}
 
Last edited:
View attachment 401777
Guys, help me fix my fishing rod. The float flies out from under the feet. The problem is shown in the gif. Here is the code:
p.s. first, a normal fishing rod, next mine

using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;
using Terraria;
using Microsoft.Xna.Framework;

namespace Testmod.Items
{
public class bobonator : ModItem
{
public override void SetStaticDefaults()
{
DisplayName.SetDefault("Test fishing rod");
Tooltip.SetDefault("Test description");
}

public override void SetDefaults()
{
Item.width = 48;
Item.height = 48;
Item.useTime = 25;
Item.useAnimation = 20;
Item.useStyle = 1;
Item.value = 10000;
Item.rare = 2;
Item.UseSound = SoundID.Item1;
Item.useTurn = true;
Item.autoReuse = true;

Item.shootSpeed = 10f;


Item.fishingPole = 15;
Item.shoot = ProjectileID.BobberWooden;


}

public override void AddRecipes()
{
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.DirtBlock, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}

}
}
you will need to add a projectile offset
I don't remember the code exactly but riptidedev has a video showing how to do it.
 
Hello, whenever I join my world I crash after 3-5 minutes without any crash logs or error messages. I think vines in the Abyss are causing this to happen. Any ideas on how to fix this?
Here's the client log: hastebin
 
Hello, whenever I join my world I crash after 3-5 minutes without any crash logs or error messages. I think vines in the Abyss are causing this to happen. Any ideas on how to fix this?
Here's the client log: hastebin
Does it still happen in a world without the Calamity mod loaded? If not, you might have better luck on the Calamity-specific Discord. I know there's a link from their Steam workshop page. Steam Workshop::Calamity Mod
 
Does it still happen in a world without the Calamity mod loaded? If not, you might have better luck on the Calamity-specific Discord. I know there's a link from their Steam workshop page. Steam Workshop::Calamity Mod
It doesn't, since there are no calamity vines without calamity mod. If you're asking whether or not it happens in another world with calamity, it probably doesn't. Something must be glitched out in this world. However I dont want to lose the progress made in this world so I'm asking if there is a way to edit out the vines or something.
Edit: Apparently using Fargo's Loot Bonus with Calamity and defeating Skeletron breaks things in your world, causing the vines to crash your game. If you have the same issue, disable Fargo's Loot Bonus and load a backup where you haven't defeated Skeletron yet.
 
Last edited:
please help with projectile and stuff
Image:
1679757547952.png


Code for projectile:
using System;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace Modthingig.Projectiles
{
public class DirtOrb : ModProjectile
{
public override void SetDefaults()
{
projectile.Damage = 7;
projectile.widith = 5;
projectile.height = 5;
projectile.name = "Dirt Orb";
projectiles.aistyle = 0;
projectile.timeLeft = 600;
projectile.friendly = true;
aiType = ProjectileID.SwordBeam;
}
public override bool Autoload(ref string name, ref string texture)
{
texture = "Terraria/Projectile_" + ProjectileID.DirtBall;
return true;
}
public override void OnHitNPC(Player player, NPC target, int damage, float knockback, bool crit)
{
target.AddBuff(BuffID.Oozed, 3 * 60);
}
}
}
Code for sword using projectile (just in case I need to know something about the execution):
using System;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace Modthingig.Items
{
public class TrueDirtySword : ModItem
{
public override void SetStaticDefaults()
{
DisplayName.SetDefault("True Dirty Sword"); // By default, capitalization in classnames will add spaces to the display name. You can customize the display name here by uncommenting this line.
Tooltip.SetDefault("The true sword of dirtyness-even though it has stone as part of it" +
"\nThrows an orb of dirt");
}
public override void SetDefaults()
{
Item.damage = 20;
Item.DamageType = DamageClass.Melee;
Item.width = 40;
Item.height = 40;
Item.useTime = 20;
Item.useAnimation = 20;
Item.useStyle = 1;
Item.knockBack = 6;
Item.value = 10000;
Item.rare = 2;
Item.UseSound = SoundID.Item1;
Item.autoReuse = true;
Item.shootSpeed = 10;
Item.shoot = Modthingig.ProjectileType("Dirt Orb");
}
public override void AddRecipes()
{
Recipe recipe = CreateRecipe();
recipe.AddIngredient<Items.DirtySword>();
recipe.AddIngredient<Items.StonySword>();
recipe.AddTile(TileID.Anvils);
recipe.Register();
}
public override void OnHitNPC(Player player, NPC target, int damage, float knockback, bool crit)
{
target.AddBuff(BuffID.Stoned, 5 * 60);
}
}
}
 
How do I make an enemy spawn after defeating a certain boss. Also I have wanted to buff the cultist in my mod but I was able to buff his hp, defense, and contact damage but his projectile dmg doesn't change, I tried using globalProjectile for each of his projectiles but the problem is that the damage of his projectiles don't have any raw damage values.
 
Hello im having problems with tmodloader it crashes when i start it and i cant figure out whats worng the crashing just randomly began ill attach the log file here
Pls help me i really wanna play
 

Attachments

  • client.log
    7.8 KB · Views: 32
20230515152606_1.jpg

so i got this error and idk what to do. i copied a code from example mod and i found the other errors which i had, but i don't understand this, i don't think im missing a directive or assembly reference, i'm using notepad if that helps.
i changed some bits of the code, like dmg and the name and stuff but i didn't change anything that had anything to do with that,
here's the code , please help!


using Microsoft.Xna.Framework;
using System;
using Terraria;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;
using Terraria.ModLoader.Utilities;

namespace Terramori.Content.NPCs
{
// This ModNPC serves as an example of a completely custom AI.
public class SproutMole : ModNPC
{
// Here we define an enum we will use with the State slot. Using an ai slot as a means to store "state" can simplify things greatly. Think flowchart.
private enum ActionState
{
Asleep,
Notice,
Jump,
Hover,
Fall
}

// Our texture is 36x36 with 2 pixels of padding vertically, so 38 is the vertical spacing.
// These are for our benefit and the numbers could easily be used directly in the code below, but this is how we keep code organized.
private enum Frame
{
Asleep,
Notice,
Falling,
Flutter1,
Flutter2,
Flutter3
}

// These are reference properties. One, for example, lets us write AI_State as if it's NPC.ai[0], essentially giving the index zero our own name.
// Here they help to keep our AI code clear of clutter. Without them, every instance of "AI_State" in the AI code below would be "npc.ai[0]", which is quite hard to read.
// This is all to just make beautiful, manageable, and clean code.
public ref float AI_State => ref NPC.ai[0];
public ref float AI_Timer => ref NPC.ai[1];
public ref float AI_FlutterTime => ref NPC.ai[2];

public override void SetStaticDefaults() {
Main.npcFrameCount[NPC.type] = 1;// make sure to set this for your modnpcs.

// Specify the debuffs it is immune to
NPCID.Sets.DebuffImmunitySets.Add(Type, new NPCDebuffImmunityData {
SpecificallyImmuneTo = new int[] {
BuffID.Poisoned // This NPC will be immune to the Poisoned debuff.
}
});
}

public override void SetDefaults() {
NPC.width = 36; // The width of the npc's hitbox (in pixels)
NPC.height = 36; // The height of the npc's hitbox (in pixels)
NPC.aiStyle = -1; // This npc has a completely unique AI, so we set this to -1. The default aiStyle 0 will face the player, which might conflict with custom AI code.
NPC.damage = 15;
NPC.defense = 4;
NPC.lifeMax = 50;//The amount of health that this npc has
NPC.HitSound = SoundID.NPCHit1; // The sound the NPC will make when being hit.
NPC.DeathSound = SoundID.NPCDeath1; // The sound the NPC will make when it dies.
NPC.value = 25f; // How many copper coins the NPC will drop when killed.
}

public override float SpawnChance(NPCSpawnInfo spawnInfo) {
// we would like this npc to spawn in the overworld.
return SpawnCondition.OverworldDaySlime.Chance * 0.1f;
}

// Our AI here makes our NPC sit waiting for a player to enter range, jumps to attack, flutter mid-fall to stay afloat a little longer, then falls to the ground. Note that animation should happen in FindFrame
public override void AI() {
// The npc starts in the asleep state, waiting for a player to enter range
switch (AI_State) {
case (float)ActionState.Asleep:
FallAsleep();
break;
case (float)ActionState.Notice:
Notice();
break;
case (float)ActionState.Jump:
Jump();
break;
case (float)ActionState.Hover:
Hover();
break;
case (float)ActionState.Fall:
if (NPC.velocity.Y == 0) {
NPC.velocity.X = 0;
AI_State = (float)ActionState.Asleep;
AI_Timer = 0;
}

break;
}
}

// Here, because we use custom AI (aiStyle not set to a suitable vanilla value), we should manually decide when Flutter Slime can fall through platforms
public override bool? CanFallThroughPlatforms() {
if (AI_State == (float)ActionState.Fall && NPC.HasValidTarget && Main.player[NPC.target].Top.Y > NPC.Bottom.Y) {
// If Flutter Slime is currently falling, we want it to keep falling through platforms as long as it's above the player
return true;
}

return false;
// You could also return null here to apply vanilla behavior (which is the same as false for custom AI)
}

private void FallAsleep() {
// TargetClosest sets npc.target to the player.whoAmI of the closest player.
// The faceTarget parameter means that npc.direction will automatically be 1 or -1 if the targeted player is to the right or left.
// This is also automatically flipped if npc.confused.
NPC.TargetClosest(true);

// Now we check the make sure the target is still valid and within our specified notice range (500)
if (NPC.HasValidTarget && Main.player[NPC.target].Distance(NPC.Center) < 500f) {
// Since we have a target in range, we change to the Notice state. (and zero out the Timer for good measure)
AI_State = (float)ActionState.Notice;
AI_Timer = 0;
}
}

private void Notice() {
// If the targeted player is in attack range (250).
if (Main.player[NPC.target].Distance(NPC.Center) < 250f) {
// Here we use our Timer to wait .33 seconds before actually jumping. In FindFrame you'll notice AI_Timer also being used to animate the pre-jump crouch
AI_Timer++;

if (AI_Timer >= 20) {
AI_State = (float)ActionState.Jump;
AI_Timer = 0;
}
}
else {
NPC.TargetClosest(true);

if (!NPC.HasValidTarget || Main.player[NPC.target].Distance(NPC.Center) > 500f) {
// Out targeted player seems to have left our range, so we'll go back to sleep.
AI_State = (float)ActionState.Asleep;
AI_Timer = 0;
}
}
}

private void Jump() {
AI_Timer++;

if (AI_Timer == 1) {
// We apply an initial velocity the first tick we are in the Jump frame. Remember that -Y is up.
NPC.velocity = new Vector2(NPC.direction * 2, -10f);
}
else if (AI_Timer > 40) {
// after .66 seconds, we go to the hover state. //TODO, gravity?
AI_State = (float)ActionState.Hover;
AI_Timer = 0;
}
}

private void Hover() {
AI_Timer++;

// Here we make a decision on how long this flutter will last. We check netmode != 1 to prevent Multiplayer Clients from running this code. (similarly, spawning projectiles should also be wrapped like this)
// netMode == 0 is SP, netMode == 1 is MP Client, netMode == 2 is MP Server.
// Typically in MP, Client and Server maintain the same state by running deterministic code individually. When we want to do something random, we must do that on the server and then inform MP Clients.
if (AI_Timer == 1 && Main.netMode != NetmodeID.MultiplayerClient) {
AI_FlutterTime = Main.rand.NextBool() ? 100 : 50;

// Informing MP Clients is done automatically by syncing the npc.ai array over the network whenever npc.netUpdate is set.
// Don't set netUpdate unless you do something non-deterministic ("random")
NPC.netUpdate = true;
}

// Here we add a tiny bit of upward velocity to our npc.
NPC.velocity += new Vector2(0, -.35f);

// ... and some additional X velocity when traveling slow.
if (Math.Abs(NPC.velocity.X) < 2) {
NPC.velocity += new Vector2(NPC.direction * .05f, 0);
}

// after fluttering for 100 ticks (1.66 seconds), our Flutter Slime is tired, so he decides to go into the Fall state.
if (AI_Timer > AI_FlutterTime) {
AI_State = (float)ActionState.Fall;
AI_Timer = 0;
}
}

public override bool ModifyCollisionData(Rectangle victimHitbox, ref int immunityCooldownSlot, ref MultipliableFloat damageMultiplier, ref Rectangle npcHitbox) {
// We can use ModifyCollisionData to customize collision damage.
// Here we double damage when this npc is in the falling state and the victim is almost directly below the npc
if (AI_State == (float)ActionState.Fall) {
// We can modify npcHitbox directly to implement a dynamic hitbox, but in this example we make a new hitbox to apply bonus damage
// This math creates a hitbox focused on the bottom center of the original 36x36 hitbox:
// --> ☐☐☐
// ☐☒☐
Rectangle extraDamageHitbox = new Rectangle(npcHitbox.X + 12, npcHitbox.Y + 18, npcHitbox.Width - 24, npcHitbox.Height - 18);
if (victimHitbox.Intersects(extraDamageHitbox)){
damageMultiplier *= 2f;
Main.NewText("Game Over");
}
}
return true;
}
}
}



and im pretty sure its talking about this

public override bool ModifyCollisionData(Rectangle victimHitbox, ref int immunityCooldownSlot, ref MultipliableFloat damageMultiplier, ref Rectangle npcHitbox) { etc...


someone please help and tell me how to fix it, im making an omori mod and im new to modding so id really appreciate it :)
 
So, my boss ai works fine, but the animation is very broken. When ai[1] is 0, the sprite is stacked on top of itself, so that there are three bosses on top of eachother. When ai[1] is 1 or 2 the boss completely dissapears. And when ai[1] is anything else, it dissapears again. What is happening, and how can i fix it? Heres my code:
C#:
using Terraria.ModLoader;
using Terraria;
using Terraria.ID;
using Microsoft.Xna.Framework;
using Terraria.ModLoader.Utilities;
using Terraria.GameContent.ItemDropRules;
using OneFourTestMod.Items;
using System;
using OneFourTestMod.Projectiles;
using Mono.Cecil;
using static Terraria.ModLoader.PlayerDrawLayer;

namespace OneFourTestMod.Enemies
{
    [AutoloadBossHead]
    public class SandGod : ModNPC
    {
        private int ai;
        private int attacktimer = 0;
        private bool fastspeed = false;

        private bool stunned;
        private int stunnedtimer;

        public override void SetStaticDefaults()
        {
            DisplayName.SetDefault("The God of Sand");
            Main.npcFrameCount[NPC.type] = Main.npcFrameCount[6];
        }

        public override void SetDefaults()
        {
            NPC.width = 128;
            NPC.height = 128;
            NPC.damage = 100;
            NPC.defense = 25;
            NPC.lifeMax = 2500;
            NPC.value = 5000f;
            NPC.knockBackResist = 0;
            NPC.lavaImmune = true;
            NPC.noTileCollide = true;
            NPC.noGravity = true;
            NPC.aiStyle = -1; //120 is empress of light
            NPC.HitSound = SoundID.NPCHit1;
            NPC.DeathSound = SoundID.NPCDeath1;
            NPC.friendly = false;
            NPC.dontTakeDamageFromHostiles = true;
            NPC.boss = true;
            Music = MusicID.Boss2;
        }

        public override void ScaleExpertStats(int numPlayers, float bossLifeScale)
        {
            NPC.lifeMax = (int)(NPC.lifeMax * bossLifeScale);
            NPC.damage = (int)(NPC.damage * 1.3f);
        }

        public override void ModifyNPCLoot(NPCLoot npcLoot)
        {
            npcLoot.Add(ItemDropRule.Common(ItemID.SandBlock, 1, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.Sandstone, 2, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.HardenedSand, 2, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.Ruby, 1, 1, 100));
            
            if (Main.rand.Next(1,2) == 1)
            {
                npcLoot.Add(ItemDropRule.Common(ModContent.ItemType<TestSword>(), 3));
            } else
            {
                npcLoot.Add(ItemDropRule.Common(ModContent.ItemType<TestGun>(), 3));
            }
        }

        
        public override void AI()
        {
            // Gets the player and Target vector
            NPC.TargetClosest(true);
            Player player = Main.player[NPC.target];
            Vector2 target = NPC.HasPlayerTarget ? player.Center : Main.npc[NPC.target].Center;

            // Ensures the npc is not rotated
            NPC.rotation = 0.0f;
            NPC.netAlways = true;
            NPC.TargetClosest(true);

            // Ensures npc life is not greater than its max life
            if (NPC.life >= NPC.lifeMax)
            {
                NPC.life = NPC.lifeMax;
            }

            // Despawning
            if (NPC.target < 0 || NPC.target == 255 || player.dead || !player.active)
            {
                NPC.TargetClosest(false);
                NPC.direction = 1;
                NPC.velocity.Y = NPC.velocity.Y - 0.1f;
                if (NPC.timeLeft > 20)
                {
                    NPC.timeLeft = 20;
                    return;
                }
            }

            // Stunned
            if (stunned)
            {
                NPC.velocity.X = 0.0f;
                NPC.velocity.Y = 0.0f;
                stunnedtimer++;
                if (stunnedtimer >= 100)
                {
                    stunned = false;
                    stunnedtimer = 0;
                }
            }

            // Increment ai
            ai++;

            // Movement
            NPC.ai[0] = (float)ai * 1f;
            int distance = (int)Vector2.Distance(target, NPC.Center);
            if ((double)NPC.ai[0] < 300)
            {
                NPC.ai[1] = 0;
                MoveTowards(NPC, target, (float)(distance > 300 ? 13f : 7f), 30f);
                NPC.netUpdate = true;
            } else if ((double)NPC.ai[0] >= 300 && (double)NPC.ai[0] < 450.0)
            {
                stunned = true;
                NPC.ai[1] = 1;
                NPC.defense = 50;
                NPC.damage = 25;
                MoveTowards(NPC, target, (float)(distance > 300 ? 13f : 7f), 30f);
                NPC.netUpdate = true;
            } else if ((double)NPC.ai[0] >= 450.0)
            {
                NPC.ai[1] = 2;
                stunned = false;
                NPC.damage = (int)(NPC.damage * 0.5f * 1.3f);
                NPC.defense = 25;
                if (!fastspeed)
                {
                    fastspeed = true;
                } else
                {
                    if ((double)NPC.ai[0] % 50 == 0)
                    {
                        float speed = 12f;
                        Vector2 vector = new Vector2(NPC.position.X + (float)NPC.width * 0.5f, NPC.position.Y + (float)NPC.height * 0.5f);
                        float x = player.position.X + (float)(player.width / 2) - vector.X;
                        float y = player.position.Y + (float)(player.height / 2) - vector.Y;
                        float distance2 = (float)Math.Sqrt(x * x + y * y);
                        float factor = speed / distance2;
                        NPC.velocity.X = x * factor * 1.5f;
                        NPC.velocity.Y = y * factor * 1.5f;
                    }
                }
                NPC.netUpdate = true;
            }

            // Attack
            if ((double)NPC.ai[0] % (Main.expertMode ? 100 : 150) == 0 && !stunned && !fastspeed)
            {
                attacktimer++;
                if (attacktimer <= 2)
                {
                    NPC.ai[1] = 3;
                    NPC.velocity.X = 0f;
                    NPC.velocity.Y = 0f;
                    Vector2 shootPos = NPC.Center;
                    float accuracy = 5f * (NPC.life / NPC.lifeMax);
                    Vector2 shootVel = target - shootPos + new Vector2(Main.rand.NextFloat(-accuracy, accuracy), Main.rand.NextFloat(-accuracy, accuracy));
                    shootVel.Normalize();
                    shootVel *= 14.5f;
                    for (int i = 0; i < (Main.expertMode ? 10 : 6); i++)
                    {
                        Projectile.NewProjectile(NPC.GetBossSpawnSource(player.whoAmI), new Vector2(shootPos.X + (float)(-100 * NPC.direction) + (float)Main.rand.Next(-40, 41), shootPos.Y - (float)Main.rand.Next(-50, 40)), shootVel, ModContent.ProjectileType<SandGodProjectile>(), NPC.damage / 3, 5f);
                    }
                } else
                {
                    attacktimer = 0;
                }
            }

            if ((double)NPC.ai[0] >= 650.0)
            {
                ai = 0;
                fastspeed = false;
            }
        }

        private void MoveTowards(NPC npc, Vector2 playertarget, float speed, float turnresistance)
        {
            var move = playertarget - npc.Center;
            float length = move.Length();
            if (length > speed)
            {
                move *= speed / length;
            }
            move = (npc.velocity * turnresistance + move) / (turnresistance + 1f);
            length = move.Length();
            if (length > speed)
            {
                move *= speed / length;
            }
            npc.velocity = move;
        }

        public override void FindFrame(int frameHeight)
        {
            if (NPC.ai[1] == 0)
            {
                NPC.frame.Y = 0;
            } else if (NPC.ai[1] == 1)
            {
                NPC.frame.Y = frameHeight;
            } else  if (NPC.ai[1] == 2)
            {
                NPC.frame.Y = frameHeight * 2;
            } else if (NPC.ai[1] == 3)
            {
                NPC.frame.Y = frameHeight * 3;
            } else
            {
                NPC.frame.Y = frameHeight * 4;
            }
        }
    }
}
 
So, my boss ai works fine, but the animation is very broken. When ai[1] is 0, the sprite is stacked on top of itself, so that there are three bosses on top of eachother. When ai[1] is 1 or 2 the boss completely dissapears. And when ai[1] is anything else, it dissapears again. What is happening, and how can i fix it? Heres my code:
C#:
using Terraria.ModLoader;
using Terraria;
using Terraria.ID;
using Microsoft.Xna.Framework;
using Terraria.ModLoader.Utilities;
using Terraria.GameContent.ItemDropRules;
using OneFourTestMod.Items;
using System;
using OneFourTestMod.Projectiles;
using Mono.Cecil;
using static Terraria.ModLoader.PlayerDrawLayer;

namespace OneFourTestMod.Enemies
{
    [AutoloadBossHead]
    public class SandGod : ModNPC
    {
        private int ai;
        private int attacktimer = 0;
        private bool fastspeed = false;

        private bool stunned;
        private int stunnedtimer;

        public override void SetStaticDefaults()
        {
            DisplayName.SetDefault("The God of Sand");
            Main.npcFrameCount[NPC.type] = Main.npcFrameCount[6];
        }

        public override void SetDefaults()
        {
            NPC.width = 128;
            NPC.height = 128;
            NPC.damage = 100;
            NPC.defense = 25;
            NPC.lifeMax = 2500;
            NPC.value = 5000f;
            NPC.knockBackResist = 0;
            NPC.lavaImmune = true;
            NPC.noTileCollide = true;
            NPC.noGravity = true;
            NPC.aiStyle = -1; //120 is empress of light
            NPC.HitSound = SoundID.NPCHit1;
            NPC.DeathSound = SoundID.NPCDeath1;
            NPC.friendly = false;
            NPC.dontTakeDamageFromHostiles = true;
            NPC.boss = true;
            Music = MusicID.Boss2;
        }

        public override void ScaleExpertStats(int numPlayers, float bossLifeScale)
        {
            NPC.lifeMax = (int)(NPC.lifeMax * bossLifeScale);
            NPC.damage = (int)(NPC.damage * 1.3f);
        }

        public override void ModifyNPCLoot(NPCLoot npcLoot)
        {
            npcLoot.Add(ItemDropRule.Common(ItemID.SandBlock, 1, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.Sandstone, 2, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.HardenedSand, 2, 200, 400));
            npcLoot.Add(ItemDropRule.Common(ItemID.Ruby, 1, 1, 100));
           
            if (Main.rand.Next(1,2) == 1)
            {
                npcLoot.Add(ItemDropRule.Common(ModContent.ItemType<TestSword>(), 3));
            } else
            {
                npcLoot.Add(ItemDropRule.Common(ModContent.ItemType<TestGun>(), 3));
            }
        }

       
        public override void AI()
        {
            // Gets the player and Target vector
            NPC.TargetClosest(true);
            Player player = Main.player[NPC.target];
            Vector2 target = NPC.HasPlayerTarget ? player.Center : Main.npc[NPC.target].Center;

            // Ensures the npc is not rotated
            NPC.rotation = 0.0f;
            NPC.netAlways = true;
            NPC.TargetClosest(true);

            // Ensures npc life is not greater than its max life
            if (NPC.life >= NPC.lifeMax)
            {
                NPC.life = NPC.lifeMax;
            }

            // Despawning
            if (NPC.target < 0 || NPC.target == 255 || player.dead || !player.active)
            {
                NPC.TargetClosest(false);
                NPC.direction = 1;
                NPC.velocity.Y = NPC.velocity.Y - 0.1f;
                if (NPC.timeLeft > 20)
                {
                    NPC.timeLeft = 20;
                    return;
                }
            }

            // Stunned
            if (stunned)
            {
                NPC.velocity.X = 0.0f;
                NPC.velocity.Y = 0.0f;
                stunnedtimer++;
                if (stunnedtimer >= 100)
                {
                    stunned = false;
                    stunnedtimer = 0;
                }
            }

            // Increment ai
            ai++;

            // Movement
            NPC.ai[0] = (float)ai * 1f;
            int distance = (int)Vector2.Distance(target, NPC.Center);
            if ((double)NPC.ai[0] < 300)
            {
                NPC.ai[1] = 0;
                MoveTowards(NPC, target, (float)(distance > 300 ? 13f : 7f), 30f);
                NPC.netUpdate = true;
            } else if ((double)NPC.ai[0] >= 300 && (double)NPC.ai[0] < 450.0)
            {
                stunned = true;
                NPC.ai[1] = 1;
                NPC.defense = 50;
                NPC.damage = 25;
                MoveTowards(NPC, target, (float)(distance > 300 ? 13f : 7f), 30f);
                NPC.netUpdate = true;
            } else if ((double)NPC.ai[0] >= 450.0)
            {
                NPC.ai[1] = 2;
                stunned = false;
                NPC.damage = (int)(NPC.damage * 0.5f * 1.3f);
                NPC.defense = 25;
                if (!fastspeed)
                {
                    fastspeed = true;
                } else
                {
                    if ((double)NPC.ai[0] % 50 == 0)
                    {
                        float speed = 12f;
                        Vector2 vector = new Vector2(NPC.position.X + (float)NPC.width * 0.5f, NPC.position.Y + (float)NPC.height * 0.5f);
                        float x = player.position.X + (float)(player.width / 2) - vector.X;
                        float y = player.position.Y + (float)(player.height / 2) - vector.Y;
                        float distance2 = (float)Math.Sqrt(x * x + y * y);
                        float factor = speed / distance2;
                        NPC.velocity.X = x * factor * 1.5f;
                        NPC.velocity.Y = y * factor * 1.5f;
                    }
                }
                NPC.netUpdate = true;
            }

            // Attack
            if ((double)NPC.ai[0] % (Main.expertMode ? 100 : 150) == 0 && !stunned && !fastspeed)
            {
                attacktimer++;
                if (attacktimer <= 2)
                {
                    NPC.ai[1] = 3;
                    NPC.velocity.X = 0f;
                    NPC.velocity.Y = 0f;
                    Vector2 shootPos = NPC.Center;
                    float accuracy = 5f * (NPC.life / NPC.lifeMax);
                    Vector2 shootVel = target - shootPos + new Vector2(Main.rand.NextFloat(-accuracy, accuracy), Main.rand.NextFloat(-accuracy, accuracy));
                    shootVel.Normalize();
                    shootVel *= 14.5f;
                    for (int i = 0; i < (Main.expertMode ? 10 : 6); i++)
                    {
                        Projectile.NewProjectile(NPC.GetBossSpawnSource(player.whoAmI), new Vector2(shootPos.X + (float)(-100 * NPC.direction) + (float)Main.rand.Next(-40, 41), shootPos.Y - (float)Main.rand.Next(-50, 40)), shootVel, ModContent.ProjectileType<SandGodProjectile>(), NPC.damage / 3, 5f);
                    }
                } else
                {
                    attacktimer = 0;
                }
            }

            if ((double)NPC.ai[0] >= 650.0)
            {
                ai = 0;
                fastspeed = false;
            }
        }

        private void MoveTowards(NPC npc, Vector2 playertarget, float speed, float turnresistance)
        {
            var move = playertarget - npc.Center;
            float length = move.Length();
            if (length > speed)
            {
                move *= speed / length;
            }
            move = (npc.velocity * turnresistance + move) / (turnresistance + 1f);
            length = move.Length();
            if (length > speed)
            {
                move *= speed / length;
            }
            npc.velocity = move;
        }

        public override void FindFrame(int frameHeight)
        {
            if (NPC.ai[1] == 0)
            {
                NPC.frame.Y = 0;
            } else if (NPC.ai[1] == 1)
            {
                NPC.frame.Y = frameHeight;
            } else  if (NPC.ai[1] == 2)
            {
                NPC.frame.Y = frameHeight * 2;
            } else if (NPC.ai[1] == 3)
            {
                NPC.frame.Y = frameHeight * 3;
            } else
            {
                NPC.frame.Y = frameHeight * 4;
            }
        }
    }
}
C#:
Main.npcFrameCount[NPC.type] = Main.npcFrameCount[6];
This line tells Terraria that this NPC should have as many frames as NPCID 6 (the Eater of Souls) has. If you want your NPC to have 6 frames, replace Main.npcFrameCount[6] with just 6.
 
Back
Top Bottom