/
ExampleHomingProjectile.cs
84 lines (72 loc) · 3.75 KB
/
ExampleHomingProjectile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using ExampleMod.Content.Dusts;
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace ExampleMod.Content.Projectiles
{
// This Example show how to implement simple homing projectile
// Can be tested with ExampleCustomAmmoGun
public class ExampleHomingProjectile : ModProjectile
{
public override void SetStaticDefaults() {
DisplayName.SetDefault("Example Homing Projectile"); // Name of the projectile. It can be appear in chat
ProjectileID.Sets.CultistIsResistantTo[Projectile.type] = true; // Make the cultist resistant to this projectile, as it's resistant to all homing projectiles.
}
// 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 = 8; // The width of projectile hitbox
Projectile.height = 8; // The height of projectile hitbox
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.light = 1f; // How much light emit around the projectile
Projectile.tileCollide = false; // Can the projectile collide with tiles?
Projectile.timeLeft = 600; // The live time for the projectile (60 = 1 second, so 600 is 10 seconds)
}
// Custom AI
public override void AI() {
float maxDetectRadius = 400f; // The maximum radius at which a projectile can detect a target
float projSpeed = 5f; // The speed at which the projectile moves towards the target
// Trying to find NPC closest to the projectile
NPC closestNPC = FindClosestNPC(maxDetectRadius);
if (closestNPC == null)
return;
// If found, change the velocity of the projectile and turn it in the direction of the target
// Use the SafeNormalize extension method to avoid NaNs returned by Vector2.Normalize when the vector is zero
Projectile.velocity = (closestNPC.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * projSpeed;
Projectile.rotation = Projectile.velocity.ToRotation();
}
// Finding the closest NPC to attack within maxDetectDistance range
// If not found then returns null
public NPC FindClosestNPC(float maxDetectDistance) {
NPC closestNPC = null;
// Using squared values in distance checks will let us skip square root calculations, drastically improving this method's speed.
float sqrMaxDetectDistance = maxDetectDistance * maxDetectDistance;
// Loop through all NPCs(max always 200)
for (int k = 0; k < Main.maxNPCs; k++) {
NPC target = Main.npc[k];
// Check if NPC able to be targeted. It means that NPC is
// 1. active (alive)
// 2. chaseable (e.g. not a cultist archer)
// 3. max life bigger than 5 (e.g. not a critter)
// 4. can take damage (e.g. moonlord core after all it's parts are downed)
// 5. hostile (!friendly)
// 6. not immortal (e.g. not a target dummy)
if (target.CanBeChasedBy()) {
// The DistanceSquared function returns a squared distance between 2 points, skipping relatively expensive square root calculations
float sqrDistanceToTarget = Vector2.DistanceSquared(target.Center, Projectile.Center);
// Check if it is within the radius
if (sqrDistanceToTarget < sqrMaxDetectDistance) {
sqrMaxDetectDistance = sqrDistanceToTarget;
closestNPC = target;
}
}
}
return closestNPC;
}
}
}