summaryrefslogtreecommitdiff
path: root/Bullet.cs
blob: 9202e3a57625724c27a4f6a91eea8e75cd011bc7 (plain)
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using Godot;
using testing.interfaces;

public partial class Bullet : Area2D
{
	[Export] public float Speed { get; set; } = 600.0f;
	[Export] public float Damage { get; set; } = 10.0f;
	public bool PierceEntity { get; set; } = false;
	public bool PierceTerrain { get; set; } = false;
	public bool ricochet { get; set; } = true;
	public int BounceLimit { get; set; } = 5;
	private int bounces;
	public Vector2 Direction { get; set; } = Vector2.Zero;

	public override void _Ready()
	{
		bounces = 0;
		if (Direction != Vector2.Zero)
		{
			Direction = Direction.Normalized();
			Rotation = Direction.Angle();
		}
	}

	public override void _PhysicsProcess(double delta)
	{
		if (Direction == Vector2.Zero) return;

		float remainingDistance = Speed * (float)delta;
		int maxSteps = 4;

		while (remainingDistance > 0.001f && maxSteps > 0)
		{
			maxSteps--;
			Vector2 motion = Direction * remainingDistance;

			// Cast a ray along the motion path plus a small buffer offset (5 units)
			Vector2 rayEnd = GlobalPosition + motion + Direction * 5.0f;

			var spaceState = GetWorld2D().DirectSpaceState;
			var query = PhysicsRayQueryParameters2D.Create(GlobalPosition, rayEnd, CollisionMask);
			query.Exclude = new Godot.Collections.Array<Rid> { GetRid() };
			query.HitFromInside = true;

			var result = spaceState.IntersectRay(query);

			if (result.Count == 0)
			{
				GlobalPosition += motion;
				break;
			}

			Vector2 hitPos = result["position"].AsVector2();
			Vector2 hitNormal = result["normal"].AsVector2();
			GodotObject colliderObj = result["collider"].AsGodotObject();
			Node2D collider = colliderObj as Node2D;

			float distToHit = GlobalPosition.DistanceTo(hitPos);
			float distMoved = Mathf.Min(remainingDistance, distToHit);
			remainingDistance -= distMoved;

			if (collider is IDamageable target)
			{
				target.TakeDamage(Damage);
				if (!PierceEntity)
				{
					QueueFree();
					return;
				}
				GlobalPosition = hitPos + Direction * 0.1f;
			}
			else
			{
				if (ricochet && bounces < BounceLimit)
				{
					if (hitNormal == Vector2.Zero)
					{
						Direction = -Direction;
					}
					else
					{
						Direction = Direction.Bounce(hitNormal).Normalized();
					}

					Rotation = Direction.Angle();
					bounces++;

					// Offset position slightly outward along hitNormal to avoid sticking inside geometry
					GlobalPosition = hitPos + (hitNormal != Vector2.Zero ? hitNormal : Direction) * 2.0f;
				}
				else if (!PierceTerrain)
				{
					QueueFree();
					return;
				}
				else
				{
					GlobalPosition = hitPos + Direction * 0.1f;
				}
			}
		}
	}
}