using Microsoft.Xna.Framework; using Siva.Buffs; using Terraria; using Terraria.DataStructures; using Terraria.ID; using Terraria.ModLoader; using System; using Microsoft.Xna.Framework.Graphics; namespace Siva.Projectiles { // This Example show how to implement simple homing projectile public class SIVANanite : ModProjectile { public static Texture2D glowMaskTexture; public static readonly int ProjectileType = ModContent.ProjectileType(); // These properties wrap the usual ai arrays for cleaner and easier to understand code. // Are we sticking to a target? public bool IsStickingToTarget { get => Projectile.ai[0] == 1f; set => Projectile.ai[0] = value ? 1f : 0f; } // Index of the current target public int TargetWhoAmI { get => (int)Projectile.ai[1]; set => Projectile.ai[1] = value; } public float TimeAttached { get => Projectile.localAI[0]; set => Projectile.localAI[0] = value; } public float Damage { get => Projectile.localAI[1]; set => Projectile.localAI[1] = value; } public override void SetStaticDefaults() { ProjectileID.Sets.CultistIsResistantTo[Projectile.type] = true; // Make the cultist resistant to this projectile, as it's resistant to all homing projectiles. Main.projFrames[Projectile.type] = 4; } // Setting the default parameters of the projectile // You can check most of Fields and Properties here https://github.com/tModLoader/tModLoader/wiki/Projectile-Class-Documentation public override void SetDefaults() { Projectile.width = 16; // The width of projectile hitbox Projectile.height = 16; // The height of projectile hitbox Projectile.scale = .8f; Projectile.aiStyle = 0; // The ai style of the projectile (0 means custom AI). For more please reference the source code of Terraria Projectile.DamageType = DamageClass.Ranged; // What type of damage does this projectile affect? Projectile.friendly = true; // Can the projectile deal damage to enemies? Projectile.hostile = false; // Can the projectile deal damage to the player? Projectile.ignoreWater = true; // Does the projectile's speed be influenced by water? Projectile.tileCollide = false; // Can the projectile collide with tiles? Projectile.timeLeft = 180; // The live time for the projectile (60 = 1 second, so 420 is 7 seconds) Projectile.penetrate = -1; Projectile.CritChance = 0; } public override void PostDraw(Color lightColor) { Texture2D NaniteGlow = new Texture2D() Main.EntitySpriteDraw() base.PostDraw(lightColor); } public override void AI() { Projectile.ai[2] += 1f; if (++Projectile.frameCounter >= 5) { Projectile.frameCounter = 0; // Or more compactly Projectile.frame = ++Projectile.frame % Main.projFrames[Projectile.type]; if (++Projectile.frame >= Main.projFrames[Projectile.type]) Projectile.frame = 0; } Lighting.AddLight(Projectile.position, Color.Red.ToVector3() * 0.7f); // Run either the Sticky AI or Normal AI // Separating into different methods helps keeps your AI clean // Main.NewText($"IsStickingToTarget = {IsStickingToTarget}"); // if (IsStickingToTarget) { // Main.NewText("Calling StickyAI()"); // debug text StickyAI(); } else { NormalAI(); } } public void NormalAI() { float maxDetectRadius = 400f; // The maximum radius at which a projectile can detect a target float projSpeed = 7f; // The speed at which the projectile moves towards the target Projectile.damage = (int)Damage; // Sets the damage of the projectile to the damage it was created with. // Trying to find NPC closest to the projectile NPC closestNPC = FindClosestNPC(maxDetectRadius); if (closestNPC != null) { // If a target is found, change the velocity of the projectile and rotate it in the direction of the target Projectile.velocity = (closestNPC.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * projSpeed; Projectile.rotation = Projectile.velocity.ToRotation(); } else { Projectile.velocity = Vector2.Zero; } } private const int MaxTimeAttached = 540; public void StickyAI() { TimeAttached += 1f; // Main.NewText($"TimeAttached: {TimeAttached}"); // debug text int npcTarget = TargetWhoAmI; if (TimeAttached >= MaxTimeAttached) { // If the nanite has stuck for 9 seconds (or more) Projectile.Kill(); } else if (Main.npc[npcTarget].active && !Main.npc[npcTarget].dontTakeDamage) { // If the target is active and can take damage // Set the projectile's position relative to the target's center Projectile.Center = Main.npc[npcTarget].Center - Projectile.velocity * 2f; Projectile.gfxOffY = Main.npc[npcTarget].gfxOffY; } else { Projectile.Kill(); } /* Very fun but a bit OP else { // Otherwise, reset the timer to 3 seconds and unstick it Projectile.timeLeft = 180; IsStickingToTarget = false; } */ } public NPC FindClosestNPC(float maxDetectDistance) { NPC closestNPC = null; float sqrMaxDetectDistance = maxDetectDistance * maxDetectDistance; foreach (NPC target in Main.npc) { if (target.CanBeChasedBy()) { float sqrDistanceToTarget = Vector2.DistanceSquared(target.Center, Projectile.Center); if (sqrDistanceToTarget < sqrMaxDetectDistance) { sqrMaxDetectDistance = sqrDistanceToTarget; closestNPC = target; } } } return closestNPC; } public override void OnSpawn(IEntitySource source) { Damage = Projectile.damage; } public const int maxNanites = 100; private readonly Point[] attachedNanites = new Point[maxNanites]; public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone) { // Main.NewText($"{damageDone}"); // debug text IsStickingToTarget = true; // we are sticking to a target Projectile.damage = 0; // Makes sure it can't deal damage, this is reset when NormalAI() is called again. TargetWhoAmI = target.whoAmI; // Set the target whoAmI Projectile.velocity = (target.Center - Projectile.Center) * 0.75f; // Change velocity based on delta center of targets (difference between entity centers) Projectile.netUpdate = true; // netUpdate this javelin int na = 0; foreach (var p in Main.ActiveProjectiles) // Refreses the duration of all of the nanites on the npc. { if (p.type == ProjectileType && p.ai[0] == 1f && p.ai[1] == TargetWhoAmI) { // Main.NewText("Refreshing duration of nanite"); // debug text p.timeLeft = 540; na++; } } // Main.NewText(na); // debug text // ExampleJavelinBuff handles the damage over time (DoT) target.AddBuff(Parasitism.BuffType, 540); } public static void Create(int naniteCount, NPC target, int projDamage) { // Main.NewText($"naniteCount = {naniteCount}"); //debug text int i = 0; while(i < naniteCount) // I'm proud of this basic function, draws the amount of nanites in a parabola above the target { float k = naniteCount/2; // Main.NewText($"k = {k}"); //debug text int x = (i - ((int)Math.Floor(k)))*4; float y = 1.0f / 6.0f * x * x; y -= target.height; x *= 4; y *= 2; Projectile.NewProjectile(target.GetSource_FromThis(), Vector2.Add(target.Center, new Vector2(x, y)), Vector2.Zero, ProjectileType, projDamage, 0f, Main.myPlayer); // Might want to use Target.Top and make it not as high above them, big bosses have nanites spawn on them I think // Main.NewText($"({x},{y})"); //debug text i += 1; } // Main.NewText($"made {i} nanites off of {naniteCount}"); //debug text } } }