using Godot; using System; using System.Collections.Generic; using System.Diagnostics; public partial class Tower : HoverableNode { public bool _aiming; public int _launchSpeed = 1000, _arcIterations = 50; public Vector2 _aimOffset, _arcEnd; public List _arc = new(); public Marker2D _offset, _ballSpawn; public Area2D _area; public Commander _commander; public override void _Ready() { base._Ready(); _offset = GetNode("Offset"); _ballSpawn = GetNode("BallSpawn"); // Click += HandleClick; } public override void _Process(double delta) { base._Process(delta); if (_commander?._actions > 0) { if (_aiming) { if (_arcEnd != GetGlobalMousePosition()) { DrawArc(_ballSpawn.GlobalPosition, GetGlobalMousePosition()); } if (Input.IsActionJustPressed("rightClick")) { _commander.UnloadBall(); _aiming = false; } else if (Input.IsActionJustPressed("leftClick")) { _aimOffset = ToLaunchVector(CalculateLaunchAngle(_ballSpawn.GlobalPosition, GetGlobalMousePosition())); _commander.ShootCurrentBall(_aimOffset); ClearArc(); _aiming = false; } } else if (_hovered) { if (Input.IsActionJustPressed("leftClick")) { _aiming = true; _commander.LoadBall(_ballSpawn.Position); } } } } public float CalculateLaunchAngle(Vector2 START, Vector2 END, int MAX_ITERATIONS = 20) { Vector2 offset = END - START; float baseAngle = offset.Angle(); float straightUpAngle = -Mathf.Pi / 2.0f; float lowAngle = 0.0f; float highAngle = 1.0f; float bestAngle = baseAngle; for (int i = 0; i < MAX_ITERATIONS; i++) { float averageAngle = (lowAngle + highAngle) / 2.0f; float testAngle = Mathf.LerpAngle(baseAngle, straightUpAngle, averageAngle); float error = EvaluateSimulationError(testAngle, START, END); if (Mathf.Abs(error) < 0.2f) { bestAngle = testAngle; break; } if (error > 0) { lowAngle = averageAngle; } else { highAngle = averageAngle; } bestAngle = testAngle; } if (ToLaunchVector(bestAngle).Y < 0) { if (END.X < START.X) { bestAngle = (float)Math.PI; } else { bestAngle = 0; } } return bestAngle; } public void ClearArc() { Path2D path = _commander._ball.GetNode("PredictedPath"); path.Curve.ClearPoints(); } public void DrawArc(Vector2 START, Vector2 END, int MAX_ITERATIONS = 20) { float arcAngle = CalculateLaunchAngle(START, END); float speed = _launchSpeed, gravity = Globals._gravity, drag = Globals._drag, delta = 1.0f / 60.0f; Vector2 velocity = new Vector2(Mathf.Cos(arcAngle), Mathf.Sin(arcAngle)) * speed; Vector2 position = START; float directionSign = Mathf.Sign(END.X - START.X); float maxFlightTime = 6.0f; int totalSteps = (int)(maxFlightTime / delta); List arc = [START]; for (int step = 0; step < totalSteps; step++) { velocity.Y += gravity * delta; velocity -= velocity * drag * delta; position += velocity * delta; arc.Add(position); bool reachedTargetX = (directionSign >= 0) ? (position.X >= END.X) : (position.X <= END.X); if (reachedTargetX) { break; } } Path2D path = _commander._ball.GetNode("PredictedPath"); path.Curve.ClearPoints(); for (int i = 0; i < arc.Count; i++) { path.Curve.AddPoint(arc[i]); } } private float EvaluateSimulationError(float ANGLE, Vector2 START, Vector2 TARGET) { float speed = _launchSpeed, gravity = Globals._gravity, drag = Globals._drag, delta = 1.0f / 60.0f; 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); for (int step = 0; step < totalSteps; step++) { velocity.Y += gravity * delta; velocity -= velocity * drag * delta; position += velocity * delta; bool reachedTargetX = (directionSign >= 0) ? (position.X >= TARGET.X) : (position.X <= TARGET.X); if (reachedTargetX) { return position.Y - TARGET.Y; } } return (position.Y > TARGET.Y) ? 99999f : -99999f; } public void StartTurn() { if (_commander != null) { _commander._actions = _commander._actionsMax; } } public Vector2 ToLaunchVector(float ANGLE) { return Vector2.FromAngle(ANGLE) * _launchSpeed; } }