first commit, even though I've been "done" with the mod for like ever

This commit is contained in:
ForeverPyrite
2025-08-12 00:01:48 -05:00
commit 46bc9baab8
32 changed files with 1145 additions and 0 deletions

212
Projectiles/SIVANanite.cs Normal file
View File

@@ -0,0 +1,212 @@
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<SIVANanite>();
// 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
}
}
}