using Godot; using System; using System.Collections.Generic; using System.Diagnostics; public partial class Tower : Sprite2D { public bool _hovered, _aiming; public int _launchForce = 1000, _arcIterations = 50; public float _gravity, _drag; public Vector2 _aimOffset, _arcEnd; public List _arc = new(); public Marker2D _offset, _attackSpawn; public Area2D _area; public Commander _commander; public override void _Ready() { base._Ready(); _gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); _drag = (float)ProjectSettings.GetSetting("physics/2d/default_linear_damp"); _area = GetNode("Area"); _offset = GetNode("Offset"); _attackSpawn = GetNode("AttackSpawn"); } public override void _Process(double delta) { base._Process(delta); if (_commander?._actions > 0) { if (_aiming) { if (_arcEnd != GetGlobalMousePosition()) { CalculateLaunchAngle(_attackSpawn.GlobalPosition, GetGlobalMousePosition(), _launchForce, _gravity, _drag); DrawArc(); } if (Input.IsActionJustPressed("rightClick")) { _commander.UnloadAttack(); _aiming = false; } else if (Input.IsActionJustPressed("leftClick")) { _aimOffset = CalculateLaunchAngle(_attackSpawn.GlobalPosition, GetGlobalMousePosition(), _launchForce, _gravity, _drag); _commander.ShootCurrentAttack(_aimOffset); _aiming = false; } } else if (_hovered) { if (Input.IsActionJustPressed("leftClick")) { _aiming = true; _commander.LoadAttack(_attackSpawn.Position); } } } } public Vector2 CalculateLaunchAngle(Vector2 startPos, Vector2 targetPos, float launchSpeed, float gravity, float drag, int maxIterations = 20) { Vector2 toTarget = targetPos - startPos; float baseAngle = toTarget.Angle(); // Directly pointing at the target // Pure vertical angle going straight up (-Y is up in Godot) float straightUpAngle = -Mathf.Pi / 2.0f; // We use a normalized 0.0 to 1.0 search range to avoid radian sign confusion float lowT = 0.0f; // 0% = pointing straight at target float highT = 1.0f; // 100% = pointing straight up float bestAngle = baseAngle; float delta = 1.0f / 60.0f; // Matches standard physics step for (int i = 0; i < maxIterations; i++) { float testT = (lowT + highT) / 2.0f; // Interpolate smoothly between the base angle and straight up float testAngle = Mathf.LerpAngle(baseAngle, straightUpAngle, testT); float error = EvaluateSimulationError(testAngle, startPos, targetPos, launchSpeed, gravity, drag, delta); if (Mathf.Abs(error) < 0.2f) { bestAngle = testAngle; break; } // error > 0 means the simulated projectile landed below the target (+Y is down) if (error > 0) { // It fell short or low. We need to loft it higher up (move toward 1.0) lowT = testT; } else { // It flew over the target. We need to lower the arc (move toward 0.0) highT = testT; } bestAngle = testAngle; } return Vector2.FromAngle(bestAngle) * _launchForce; } public void DrawArc() { Path2D path = _commander._attack.GetNode("PredictedPath"); path.Curve.ClearPoints(); for (int i = 0; i < _arc.Count; i++) { path.Curve.AddPoint(_arc[i]); } GD.Print(path.Curve.PointCount); } private float EvaluateSimulationError(float angle, Vector2 start, Vector2 target, float speed, float gravity, float drag, float delta) { Vector2 velocity = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * speed; Vector2 position = start; float directionSign = Mathf.Sign(target.X - start.X); float maxFlightTime = 6.0f; int totalSteps = (int)(maxFlightTime / delta); _arc = [start]; for (int step = 0; step < totalSteps; step++) { velocity.Y += gravity * delta; velocity -= velocity * drag * delta; position += velocity * delta; _arc.Add(position); // Check if our projectile crossed the target line on the X axis bool reachedTargetX = (directionSign >= 0) ? (position.X >= target.X) : (position.X <= target.X); if (reachedTargetX) { return position.Y - target.Y; } } _arcEnd = _arc[^1]; // If it ran out of time or hit a wall before crossing target.X, penalize it heavily return (position.Y > target.Y) ? 99999f : -99999f; } public void StartTurn() { if (_commander != null) { _commander._actions = _commander._actionsMax; } } public void OnMouseEntered(){ _hovered = true; } public void OnMouseExited(){ _hovered = true; } }