WIP Thruster stuff
This commit is contained in:
15
main.tscn
15
main.tscn
@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j3j483itissq" path="res://scripts/star_system_generator.gd" id="1_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://5uqp4amjj7ww" path="res://scenes/star.tscn" id="2_7mycd"]
|
||||
@ -6,10 +6,9 @@
|
||||
[ext_resource type="PackedScene" uid="uid://74ppvxcw8an4" path="res://scenes/moon.tscn" id="4_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dm3s33o4xhqfv" path="res://scenes/station.tscn" id="5_kek77"]
|
||||
[ext_resource type="PackedScene" uid="uid://bawsujtlpmh5r" path="res://scenes/asteroid.tscn" id="6_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://cm5qsuunboxm3" path="res://scenes/developer_pawn.tscn" id="7_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctlw5diis8h1x" path="res://scenes/map_canvas.tscn" id="8_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dlck1lyrn1xvp" path="res://scenes/spaceship/spaceship.tscn" id="7_5vw27"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
[node name="StarSystem" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_planets = 1
|
||||
max_planets = 4
|
||||
@ -21,11 +20,5 @@ planet_scene = ExtResource("3_272bh")
|
||||
moon_scene = ExtResource("4_5vw27")
|
||||
station_scene = ExtResource("5_kek77")
|
||||
asteroid_scene = ExtResource("6_4c57u")
|
||||
spaceship_scene = ExtResource("7_5vw27")
|
||||
sim_scale = 0.21
|
||||
|
||||
[node name="DeveloperPawn" parent="." node_paths=PackedStringArray("map_canvas") instance=ExtResource("7_272bh")]
|
||||
input_pickable = true
|
||||
map_canvas = NodePath("../MapCanvas")
|
||||
|
||||
[node name="MapCanvas" parent="." node_paths=PackedStringArray("star_system_generator") instance=ExtResource("8_5vw27")]
|
||||
star_system_generator = NodePath("..")
|
||||
|
||||
@ -15,6 +15,12 @@ run/main_scene="uid://dogqi2c58qdc0"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
OrbitalMechanics="*res://scripts/orbital_mechanics.gd"
|
||||
GameManager="*res://scripts/game_manager.gd"
|
||||
SignalBus="*res://scripts/signal_bus.gd"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray()
|
||||
|
||||
59
scenes/spaceship/navigation_computer.tscn
Normal file
59
scenes/spaceship/navigation_computer.tscn
Normal file
@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cxpjm8tp3l1j7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cq2sgw12uj4jl" path="res://scripts/ship/navigation_computer.gd" id="1_ys00n"]
|
||||
[ext_resource type="Script" uid="uid://uh0k4c3qj4x0" path="res://scripts/map_drawer.gd" id="2_378us"]
|
||||
|
||||
[node name="NavigationComputer" type="Area2D"]
|
||||
script = ExtResource("1_ys00n")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
|
||||
[node name="NavigationUI" type="CanvasLayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="NavigationUI"]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="NavigationUI/PanelContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MapDrawer" type="Node2D" parent="NavigationUI/PanelContainer/HSplitContainer"]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("2_378us")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="NavigationUI/PanelContainer/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InfoLabel" type="Label" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ShipStatusLabel" type="Label" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PlanHohmannButton" type="Button" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Hohmann Transfer"
|
||||
|
||||
[node name="PlanFastButton" type="Button" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Fast Impulse"
|
||||
|
||||
[node name="PlanTorchshipButton" type="Button" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Torchship Burn"
|
||||
|
||||
[node name="PlanGravityAssistButton" type="Button" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Grav Assist"
|
||||
|
||||
[node name="ExecutePlanButton" type="Button" parent="NavigationUI/PanelContainer/HSplitContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Launch"
|
||||
65
scenes/spaceship/spaceship.tscn
Normal file
65
scenes/spaceship/spaceship.tscn
Normal file
@ -0,0 +1,65 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dlck1lyrn1xvp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dyqbk4lcx3mhq" path="res://scripts/ship/spaceship.gd" id="1_ae4p7"]
|
||||
[ext_resource type="Script" uid="uid://c0bx113ifxyh8" path="res://scripts/ship/thruster_controller.gd" id="2_xs8u7"]
|
||||
[ext_resource type="Script" uid="uid://dx3uerblskj5r" path="res://scripts/ship/fuel_system.gd" id="3_xs8u7"]
|
||||
[ext_resource type="Script" uid="uid://buyp6t5cppitw" path="res://scripts/ship/life_support.gd" id="4_v0rat"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxpjm8tp3l1j7" path="res://scenes/spaceship/navigation_computer.tscn" id="5_6nyhl"]
|
||||
[ext_resource type="Script" uid="uid://cpc34i7o1puq2" path="res://scripts/ship/thruster.gd" id="6_fu1d6"]
|
||||
[ext_resource type="Script" uid="uid://w1546qtaupd2" path="res://scripts/ship/ship_signal_bus.gd" id="7_yl4tl"]
|
||||
|
||||
[node name="Spaceship" type="RigidBody2D"]
|
||||
can_sleep = false
|
||||
script = ExtResource("1_ae4p7")
|
||||
|
||||
[node name="ThrusterController" type="Node" parent="."]
|
||||
script = ExtResource("2_xs8u7")
|
||||
|
||||
[node name="FuelSystem" type="Node" parent="."]
|
||||
script = ExtResource("3_xs8u7")
|
||||
|
||||
[node name="LifeSupport" type="Node" parent="."]
|
||||
script = ExtResource("4_v0rat")
|
||||
|
||||
[node name="NavigationComputer" parent="." instance=ExtResource("5_6nyhl")]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
|
||||
[node name="MainEngine" type="Node2D" parent="."]
|
||||
position = Vector2(0, 30)
|
||||
script = ExtResource("6_fu1d6")
|
||||
|
||||
[node name="RCS1" type="Node2D" parent="."]
|
||||
position = Vector2(-10, -10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 0.001
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS2" type="Node2D" parent="."]
|
||||
position = Vector2(-10, 10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 0.001
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS3" type="Node2D" parent="."]
|
||||
position = Vector2(10, -10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 0.001
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(-1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS4" type="Node2D" parent="."]
|
||||
position = Vector2(10, 10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 0.001
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(-1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="SignalBus" type="Node" parent="."]
|
||||
script = ExtResource("7_yl4tl")
|
||||
61
scenes/spaceship/spaceship.tscn4597965509.tmp
Normal file
61
scenes/spaceship/spaceship.tscn4597965509.tmp
Normal file
@ -0,0 +1,61 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://dlck1lyrn1xvp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dyqbk4lcx3mhq" path="res://scripts/ship/spaceship.gd" id="1_ae4p7"]
|
||||
[ext_resource type="Script" uid="uid://c0bx113ifxyh8" path="res://scripts/ship/thruster_controller.gd" id="2_xs8u7"]
|
||||
[ext_resource type="Script" uid="uid://dx3uerblskj5r" path="res://scripts/ship/fuel_system.gd" id="3_xs8u7"]
|
||||
[ext_resource type="Script" uid="uid://buyp6t5cppitw" path="res://scripts/ship/life_support.gd" id="4_v0rat"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxpjm8tp3l1j7" path="res://scenes/spaceship/navigation_computer.tscn" id="5_6nyhl"]
|
||||
[ext_resource type="Script" uid="uid://cpc34i7o1puq2" path="res://scripts/ship/thruster.gd" id="6_fu1d6"]
|
||||
|
||||
[node name="Spaceship" type="RigidBody2D"]
|
||||
script = ExtResource("1_ae4p7")
|
||||
|
||||
[node name="ThrusterController" type="Node" parent="."]
|
||||
script = ExtResource("2_xs8u7")
|
||||
|
||||
[node name="FuelSystem" type="Node" parent="."]
|
||||
script = ExtResource("3_xs8u7")
|
||||
|
||||
[node name="LifeSupport" type="Node" parent="."]
|
||||
script = ExtResource("4_v0rat")
|
||||
|
||||
[node name="NavigationComputer" parent="." instance=ExtResource("5_6nyhl")]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
|
||||
[node name="MainEngine" type="Node2D" parent="."]
|
||||
position = Vector2(0, 30)
|
||||
script = ExtResource("6_fu1d6")
|
||||
thrust_direction = Vector2(0, 1)
|
||||
|
||||
[node name="RCS1" type="Node2D" parent="."]
|
||||
position = Vector2(-10, -10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 10.0
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS2" type="Node2D" parent="."]
|
||||
position = Vector2(-10, 10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 10.0
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS3" type="Node2D" parent="."]
|
||||
position = Vector2(10, -10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 10.0
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(-1, 0)
|
||||
main_thruster = false
|
||||
|
||||
[node name="RCS4" type="Node2D" parent="."]
|
||||
position = Vector2(10, 10)
|
||||
script = ExtResource("6_fu1d6")
|
||||
max_thrust = 10.0
|
||||
specific_impulse_isp = 10.0
|
||||
thrust_direction = Vector2(-1, 0)
|
||||
main_thruster = false
|
||||
@ -4,37 +4,23 @@ extends RigidBody2D
|
||||
# The celestial body that this body orbits.
|
||||
@export var primary: CelestialBody
|
||||
|
||||
# Real-world gravitational constant.
|
||||
const G = 10
|
||||
|
||||
# This is a placeholder for your pixel art texture.
|
||||
@export var texture: Texture2D
|
||||
|
||||
# The radius of the body, used for drawing and future collision detection.
|
||||
@export var radius: float = 10.0
|
||||
|
||||
|
||||
# The scaling factor for the simulation. A value of 1.0 means no scaling.
|
||||
var sim_scale: float = 1.0
|
||||
|
||||
# Default color based on body type for visualization.
|
||||
var body_color: Color = Color.ORANGE_RED
|
||||
|
||||
var orbit_radius_real : float = 0.0
|
||||
|
||||
var linear_velocity_real : Vector2 = Vector2.ZERO
|
||||
var global_position_real : Vector2 = Vector2.ZERO
|
||||
var current_central_force_real : Vector2 = Vector2.ZERO
|
||||
var direction_to_primary : Vector2 = Vector2.ZERO
|
||||
var distance_to_primary : float = 0.0
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "CelestialBody"
|
||||
|
||||
func _ready() -> void:
|
||||
# Set the scaled mass based on the real-world mass and the simulation scale.
|
||||
# The scale is applied to the mass, so a smaller scale means a larger apparent mass.
|
||||
|
||||
# We will handle gravity manually, so we set the built-in gravity scale to 0.
|
||||
gravity_scale = 0.0
|
||||
|
||||
@ -59,45 +45,15 @@ func _ready() -> void:
|
||||
_:
|
||||
body_color = Color.ORANGE_RED
|
||||
|
||||
|
||||
var is_first_integration : bool = true
|
||||
|
||||
# This callback is the correct place to apply custom forces to a RigidBody2D.
|
||||
func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
|
||||
if primary and is_instance_valid(primary):
|
||||
current_central_force_real = simple_n_body_grav(primary, self.global_position)
|
||||
state.apply_central_force(current_central_force_real)
|
||||
var force = OrbitalMechanics.simple_n_body_grav(self, primary)
|
||||
state.apply_central_force(force)
|
||||
|
||||
# We force a redraw here to update the body's visual representation.
|
||||
queue_redraw()
|
||||
|
||||
func simple_n_body_grav(primary: CelestialBody, global_pos : Vector2) -> Vector2:
|
||||
var pull = calc_grav_acc_to_object(primary, global_pos)
|
||||
var inner_primary : CelestialBody = primary
|
||||
|
||||
while (inner_primary.primary is CelestialBody):
|
||||
pull = pull + calc_grav_acc_to_object(inner_primary.primary, global_pos)
|
||||
inner_primary = inner_primary.primary
|
||||
|
||||
return pull
|
||||
|
||||
func calc_grav_acc_to_object(primary: CelestialBody, global_pos : Vector2) -> Vector2:
|
||||
# Get the vector pointing from this body to its primary.
|
||||
var direction_to_primary = global_pos.direction_to(primary.global_position)
|
||||
var distance_squared = global_pos.distance_squared_to(primary.global_position)
|
||||
# Prevent division by zero or a large force if bodies are on top of each other.
|
||||
if distance_squared > 1.0:
|
||||
# Calculate the magnitude of the gravitational force using Newton's law.
|
||||
# We now use the scaled masses, which is consistent with Godot's physics engine.
|
||||
# F = G * (m1 * m2) / r^2
|
||||
var force_magnitude = (G * self.mass * primary.mass) / (distance_squared)
|
||||
|
||||
# Apply the force in the direction of the primary.
|
||||
return direction_to_primary * force_magnitude
|
||||
|
||||
return Vector2.ZERO
|
||||
|
||||
|
||||
# Override the default drawing function to draw the body.
|
||||
# This is useful for debugging and visualization.
|
||||
func _draw() -> void:
|
||||
@ -109,22 +65,3 @@ func _draw() -> void:
|
||||
else:
|
||||
# Otherwise, draw a simple placeholder circle.
|
||||
draw_circle(Vector2.ZERO, radius, body_color)
|
||||
|
||||
# This function should replace your existing one.
|
||||
func calculate_initial_orbit_real(primary_body: CelestialBody) -> Vector2:
|
||||
# 1. Use the same pixel distance the physics engine will use.
|
||||
var distance_pixels = self.global_position.distance_to(primary_body.global_position)
|
||||
if distance_pixels == 0:
|
||||
return Vector2.ZERO
|
||||
|
||||
# 2. Use the exact same G and primary mass as in _integrate_forces.
|
||||
# v = sqrt(G * M / r)
|
||||
var speed_magnitude = sqrt(G * primary_body.mass / distance_pixels)
|
||||
|
||||
# 3. Calculate the direction of the orbit (perpendicular to the primary).
|
||||
var direction_to_self = primary_body.global_position.direction_to(self.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_self.y, -direction_to_self.x)
|
||||
|
||||
# 4. Combine speed and direction, and add the primary's velocity.
|
||||
var orbital_velocity = perpendicular_direction * speed_magnitude
|
||||
return orbital_velocity + primary_body.linear_velocity
|
||||
|
||||
@ -30,11 +30,15 @@ func _ready() -> void:
|
||||
print("ERROR: MapCanvas reference not set on DeveloperPawn.")
|
||||
set_physics_process(false)
|
||||
return
|
||||
|
||||
if not map_drawer:
|
||||
map_drawer = map_canvas.map_drawer
|
||||
if map_drawer is not MapDrawer:
|
||||
print("ERROR: MapDrawer reference not set on DeveloperPawn.")
|
||||
set_physics_process(false)
|
||||
return
|
||||
|
||||
|
||||
# --- NEW: Connect to the map's signal ---
|
||||
map_drawer.body_selected_for_follow.connect(on_body_selected_for_follow)
|
||||
|
||||
|
||||
27
scripts/game_manager.gd
Normal file
27
scripts/game_manager.gd
Normal file
@ -0,0 +1,27 @@
|
||||
# GameManager.gd
|
||||
extends Node
|
||||
|
||||
# This variable will hold the reference to the currently active star system.
|
||||
var current_star_system : StarSystemGenerator = null
|
||||
var ships_in_system : Array[Spaceship]
|
||||
|
||||
# Any scene that generates a star system will call this function to register itself.
|
||||
func register_star_system(system_node):
|
||||
current_star_system = system_node
|
||||
print("GameManager: Star system registered.")
|
||||
|
||||
func register_ship(ship: Spaceship):
|
||||
if not ships_in_system.has(ship):
|
||||
ships_in_system.append(ship)
|
||||
|
||||
# A helper function for easily accessing the system's data.
|
||||
func get_system_data():
|
||||
if current_star_system:
|
||||
return current_star_system.get_system_data()
|
||||
return null
|
||||
|
||||
func get_system_bodies() -> Array[CelestialBody]:
|
||||
if current_star_system:
|
||||
return current_star_system.get_all_bodies()
|
||||
|
||||
return []
|
||||
1
scripts/game_manager.gd.uid
Normal file
1
scripts/game_manager.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c7hpyuipyv7o4
|
||||
@ -2,10 +2,10 @@ class_name MapCanvas
|
||||
extends CanvasLayer
|
||||
|
||||
# A reference to the StarSystemGenerator node.
|
||||
@export var star_system_generator: Node
|
||||
@export var star_system_generator: StarSystemGenerator
|
||||
|
||||
# A reference to the new MapDrawer node.
|
||||
@export var map_drawer: Node2D
|
||||
@export var map_drawer: MapDrawer
|
||||
|
||||
func _ready() -> void:
|
||||
# Ensure the generator and drawer references are set.
|
||||
|
||||
@ -4,6 +4,9 @@ extends Node2D
|
||||
# 1. Define the new signal at the top of the script.
|
||||
signal body_selected_for_follow(body: CelestialBody)
|
||||
|
||||
# Add a new signal for the navigation computer
|
||||
signal body_selected_for_planning(body: CelestialBody)
|
||||
|
||||
# The body at the center of the map view.
|
||||
var focal_body: CelestialBody
|
||||
# The list of bodies to be rendered in the current view.
|
||||
@ -11,16 +14,18 @@ var bodies_to_draw: Array[CelestialBody] = []
|
||||
# The scale used for drawing, stored to be accessible by the input handler.
|
||||
var draw_scale: float = 1.0
|
||||
|
||||
# A reference to the generator, with a setter to initialize the view.
|
||||
var star_system_generator: StarSystemGenerator:
|
||||
set(value):
|
||||
print("Setting star system generator")
|
||||
star_system_generator = value
|
||||
# When the reference is set, initialize the view on the star.
|
||||
#if star_system_generator and star_system_generator.has_generated:
|
||||
#set_view(star_system_generator.get_system_data().star)
|
||||
get:
|
||||
# If we don't have the reference yet, try to get it from the GameManager.
|
||||
if not star_system_generator:
|
||||
star_system_generator = GameManager.current_star_system
|
||||
return star_system_generator
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# Ensure we have a valid reference before trying to draw.
|
||||
if not is_instance_valid(star_system_generator):
|
||||
return # Wait until the star system is registered.
|
||||
|
||||
if not focal_body:
|
||||
set_view(star_system_generator.get_system_data().star)
|
||||
|
||||
@ -46,7 +51,10 @@ func set_view(new_focal_body: CelestialBody) -> void:
|
||||
if child is CelestialBody:
|
||||
bodies_to_draw.append(child)
|
||||
|
||||
print("Bodies to draw: " + str(bodies_to_draw))
|
||||
for ship in GameManager.ships_in_system:
|
||||
if ship is Spaceship and is_instance_valid(ship):
|
||||
bodies_to_draw.append(ship)
|
||||
|
||||
# If the star is the focus, also find and draw the asteroid belts.
|
||||
if focal_body is Star:
|
||||
var system_data = star_system_generator.get_system_data()
|
||||
@ -54,30 +62,22 @@ func set_view(new_focal_body: CelestialBody) -> void:
|
||||
# We'll handle drawing belts separately in the _draw function.
|
||||
pass
|
||||
|
||||
|
||||
# --- Input Handling ---
|
||||
# In the _unhandled_input function, change the click logic
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not focal_body:
|
||||
return
|
||||
|
||||
# Instead of checking for Shift-click to follow,
|
||||
# just use a normal click to select a planning target.
|
||||
if event is InputEventMouseButton and event.is_pressed():
|
||||
# Handle zoom out with Right Click
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
if is_instance_valid(focal_body.primary):
|
||||
set_view(focal_body.primary)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
# Handle zoom in and follow with Left Click
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
var clicked_body = _find_body_at_pos(event.position)
|
||||
if clicked_body:
|
||||
if event.is_shift_pressed():
|
||||
# --- NEW: On Shift-Click, emit the signal for the pawn ---
|
||||
emit_signal("body_selected_for_follow", clicked_body)
|
||||
else:
|
||||
# On a normal click, just zoom in as before.
|
||||
set_view(clicked_body)
|
||||
|
||||
if clicked_body and event.is_shift_pressed():
|
||||
set_view(clicked_body)
|
||||
elif clicked_body:
|
||||
emit_signal("body_selected_for_planning", clicked_body)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
# This function finds which body was clicked. It's refactored from your old input code.
|
||||
@ -109,7 +109,7 @@ func _draw() -> void:
|
||||
|
||||
# Add a default zoom level if focused on a body with no children.
|
||||
if max_distance == 0.0:
|
||||
max_distance = 5.0e8 # An arbitrary distance for a nice solo view.
|
||||
max_distance = 100000 # An arbitrary distance for a nice solo view.
|
||||
|
||||
var system_diameter = max_distance * 2.0
|
||||
draw_scale = min(get_viewport_size().x, get_viewport_size().y) / system_diameter
|
||||
@ -120,7 +120,7 @@ func _draw() -> void:
|
||||
for belt in star_system_generator.get_system_data().belts:
|
||||
var relative_radius = belt.centered_radius - focal_body.global_position.length()
|
||||
var scaled_radius = relative_radius * draw_scale
|
||||
draw_circle(get_viewport_center(), scaled_radius, Color.WHITE, false)
|
||||
draw_circle(get_viewport_center(), scaled_radius, Color(Color.WHITE, 0.3), false)
|
||||
|
||||
# 3. Draw each celestial body relative to the focal body.
|
||||
for body in bodies_to_draw:
|
||||
@ -149,7 +149,7 @@ func _draw() -> void:
|
||||
|
||||
# Draw orbit line for all children
|
||||
if body != focal_body and body is not Asteroid:
|
||||
#draw_circle(get_viewport_center(), scaled_pos.distance_to(get_viewport_center()), Color(Color.WHITE, 0.2), false)
|
||||
draw_circle(get_viewport_center(), scaled_pos.distance_to(get_viewport_center()), Color(Color.WHITE, 0.2), false)
|
||||
draw_string(ThemeDB.fallback_font, scaled_pos + Vector2(radius + 5, 5), body.name, HORIZONTAL_ALIGNMENT_LEFT, -1, ThemeDB.fallback_font_size, color)
|
||||
|
||||
# Draw the body itself.
|
||||
@ -162,7 +162,6 @@ func _draw() -> void:
|
||||
draw_arrow(scaled_pos, scaled_pos + velocity, Color.GREEN)
|
||||
draw_arrow(scaled_pos, scaled_pos + force, Color.RED)
|
||||
|
||||
|
||||
# Calculates an array of points representing a body's future orbital path.
|
||||
func _calculate_orbital_path(body_to_trace: CelestialBody) -> PackedVector2Array:
|
||||
if not is_instance_valid(body_to_trace) or not is_instance_valid(body_to_trace.primary):
|
||||
@ -201,7 +200,6 @@ func _calculate_relative_orbital_path(body_to_trace: CelestialBody) -> PackedVec
|
||||
|
||||
# --- Initial State ---
|
||||
var primary = body_to_trace.primary
|
||||
var G = body_to_trace.G
|
||||
var primary_mass = primary.mass
|
||||
var body_mass = body_to_trace.mass
|
||||
|
||||
@ -216,7 +214,7 @@ func _calculate_relative_orbital_path(body_to_trace: CelestialBody) -> PackedVec
|
||||
return PackedVector2Array()
|
||||
|
||||
var v_sq = ghost_relative_vel.length_squared()
|
||||
var mu = G * primary_mass # Standard Gravitational Parameter
|
||||
var mu = OrbitalMechanics.G * primary_mass # Standard Gravitational Parameter
|
||||
|
||||
# 1. Calculate the specific orbital energy. Negative energy means it's a stable orbit.
|
||||
var specific_energy = v_sq / 2.0 - mu / r_magnitude
|
||||
@ -251,7 +249,7 @@ func _calculate_relative_orbital_path(body_to_trace: CelestialBody) -> PackedVec
|
||||
|
||||
# Direction is simply towards the origin.
|
||||
var direction = -ghost_relative_pos.normalized()
|
||||
var force_magnitude = (G * primary_mass * body_mass) / distance_sq
|
||||
var force_magnitude = (OrbitalMechanics.G * primary_mass * body_mass) / distance_sq
|
||||
var force_vector = direction * force_magnitude
|
||||
|
||||
var acceleration = force_vector / body_mass
|
||||
@ -277,16 +275,17 @@ func _get_body_draw_radius(body: CelestialBody) -> float:
|
||||
elif body is Moon: return 6.0
|
||||
elif body is Station: return 7.0
|
||||
elif body is Asteroid: return 4.0
|
||||
elif body is Spaceship: return 8.0
|
||||
return 5.0
|
||||
|
||||
func _get_body_draw_color(body: CelestialBody) -> Color:
|
||||
match body.get_class_name():
|
||||
"Star": return Color.GOLD
|
||||
"Planet": return Color.BLUE
|
||||
"Moon": return Color.PURPLE
|
||||
"Station": return Color.WHITE
|
||||
"Asteroid": return Color.BROWN
|
||||
return Color.ORANGE_RED
|
||||
if body is Star: return Color.GOLD
|
||||
elif body is Planet: return Color.BLUE
|
||||
elif body is Moon: return Color.PURPLE
|
||||
elif body is Station: return Color.WHITE
|
||||
elif body is Asteroid: return Color.BROWN
|
||||
elif body is Spaceship: return Color.OLIVE_DRAB
|
||||
return Color.ORANGE_RED
|
||||
|
||||
func draw_arrow(start: Vector2, end: Vector2, color: Color):
|
||||
draw_line(start, end, color, 2.0)
|
||||
|
||||
51
scripts/orbital_mechanics.gd
Normal file
51
scripts/orbital_mechanics.gd
Normal file
@ -0,0 +1,51 @@
|
||||
# OrbitalMechanics.gd
|
||||
extends Node
|
||||
|
||||
# This singleton holds all universal physics constants and functions.
|
||||
|
||||
# The scaled gravitational constant for the entire simulation.
|
||||
const G = 10.0 # Adjust this to control the "speed" of your simulation
|
||||
|
||||
# Calculates the force of gravity exerted by a primary on an orbiter.
|
||||
func calculate_gravitational_force(orbiter: RigidBody2D, primary: RigidBody2D) -> Vector2:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var direction = orbiter.global_position.direction_to(primary.global_position)
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
|
||||
if distance_sq < 1.0: # Avoid division by zero
|
||||
return Vector2.ZERO
|
||||
|
||||
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
|
||||
return direction * force_magnitude
|
||||
|
||||
# Simplified n-body gravitational pull on orbiters.
|
||||
# Station orbiting a moon will for instance calculate forces to the moon, the planet, and the star.
|
||||
func simple_n_body_grav(primary: RigidBody2D, orbiter : RigidBody2D) -> Vector2:
|
||||
var pull = calculate_gravitational_force(primary, orbiter)
|
||||
var inner_primary : CelestialBody = primary
|
||||
|
||||
while (inner_primary.primary is CelestialBody):
|
||||
pull = pull + calculate_gravitational_force(inner_primary.primary, orbiter)
|
||||
inner_primary = inner_primary.primary
|
||||
|
||||
return pull
|
||||
|
||||
# Calculates the perfect initial velocity for a stable circular orbit.
|
||||
func calculate_circular_orbit_velocity(orbiter: RigidBody2D, primary: RigidBody2D) -> Vector2:
|
||||
if not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
if distance == 0:
|
||||
return Vector2.ZERO
|
||||
|
||||
# v = sqrt(G * M / r)
|
||||
var speed_magnitude = sqrt(G * primary.mass / distance)
|
||||
|
||||
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
|
||||
|
||||
# The final velocity must include the primary's own velocity.
|
||||
return (perpendicular_direction * speed_magnitude) + primary.linear_velocity
|
||||
1
scripts/orbital_mechanics.gd.uid
Normal file
1
scripts/orbital_mechanics.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://yj6eu6141cw6
|
||||
27
scripts/ship/fuel_system.gd
Normal file
27
scripts/ship/fuel_system.gd
Normal file
@ -0,0 +1,27 @@
|
||||
# FuelSystem.gd
|
||||
class_name FuelSystem
|
||||
extends Node
|
||||
|
||||
# Signal to notify the parent ship that its total mass has changed
|
||||
signal fuel_mass_changed
|
||||
|
||||
# A dictionary to hold different types of fuel and their amounts
|
||||
var fuel_tanks: Dictionary = {
|
||||
"ChemicalFuel": 1000.0, # in kg
|
||||
"XenonGas": 50.0 # in kg
|
||||
}
|
||||
|
||||
func request_fuel(resource_name: String, amount: float) -> bool:
|
||||
if fuel_tanks.has(resource_name) and fuel_tanks[resource_name] >= amount:
|
||||
fuel_tanks[resource_name] -= amount
|
||||
fuel_mass_changed.emit()
|
||||
return true
|
||||
else:
|
||||
print("Out of ", resource_name, "!")
|
||||
return false
|
||||
|
||||
func get_total_fuel_mass() -> float:
|
||||
var total_mass: float = 0.0
|
||||
for amount in fuel_tanks.values():
|
||||
total_mass += amount
|
||||
return total_mass
|
||||
1
scripts/ship/fuel_system.gd.uid
Normal file
1
scripts/ship/fuel_system.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dx3uerblskj5r
|
||||
29
scripts/ship/life_support.gd
Normal file
29
scripts/ship/life_support.gd
Normal file
@ -0,0 +1,29 @@
|
||||
# LifeSupport.gd
|
||||
class_name LifeSupport
|
||||
extends Node
|
||||
|
||||
# Signal to notify the parent ship of a breach and the resulting thrust vector
|
||||
signal hull_breach_detected(breach_position: Vector2, force_vector: Vector2)
|
||||
|
||||
var internal_pressure: float = 101.0 # in kPa
|
||||
var is_breached: bool = false
|
||||
|
||||
func check_for_breach(damage_position: Vector2):
|
||||
# Simple logic: any damage has a chance to cause a breach
|
||||
if randf() > 0.7 and not is_breached:
|
||||
is_breached = true
|
||||
print("Warning! Hull breach detected!")
|
||||
|
||||
# The force vector is opposite the direction from the ship's center to the breach
|
||||
var ship_center = get_parent().global_position
|
||||
var force_direction = (ship_center - damage_position).normalized()
|
||||
|
||||
hull_breach_detected.emit(damage_position, force_direction)
|
||||
|
||||
func _process(delta: float):
|
||||
if is_breached and internal_pressure > 0:
|
||||
# Atmosphere vents to space over time
|
||||
internal_pressure -= 5.0 * delta
|
||||
if internal_pressure <= 0:
|
||||
internal_pressure = 0
|
||||
print("Atmosphere depleted.")
|
||||
1
scripts/ship/life_support.gd.uid
Normal file
1
scripts/ship/life_support.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://buyp6t5cppitw
|
||||
334
scripts/ship/navigation_computer.gd
Normal file
334
scripts/ship/navigation_computer.gd
Normal file
@ -0,0 +1,334 @@
|
||||
# NavigationComputer.gd
|
||||
extends Node
|
||||
|
||||
@onready var ship: Spaceship = %Spaceship
|
||||
var ship_signal_bus: ShipSignalBus
|
||||
|
||||
|
||||
# --- Node References ---
|
||||
@onready var navigation_ui: CanvasLayer = %NavigationUI
|
||||
@onready var map_drawer: MapDrawer = %MapDrawer
|
||||
@onready var info_label: Label = %InfoLabel
|
||||
@onready var ship_status_label: Label = %ShipStatusLabel
|
||||
|
||||
# Buttons for different maneuvers
|
||||
@onready var plan_hohmann_button: Button = %PlanHohmannButton
|
||||
@onready var plan_fast_button: Button = %PlanFastButton
|
||||
@onready var plan_torchship_button: Button = %PlanTorchshipButton
|
||||
@onready var plan_gravity_assist_button: Button = %PlanGravityAssistButton
|
||||
@onready var execute_plan_button: Button = %ExecutePlanButton
|
||||
|
||||
# How many seconds before a burn we should start orienting the ship.
|
||||
const PRE_BURN_ORIENTATION_SECONDS = 30.0 # Give a larger window for the new logic
|
||||
const ROTATION_SAFETY_BUFFER = 10.0 # Seconds to ensure rotation finishes before burn
|
||||
|
||||
# A flag to make sure we only send the signal once per maneuver
|
||||
var rotation_plan_sent = false
|
||||
|
||||
# --- State Management ---
|
||||
enum State { IDLE, PLANNING, WAITING, EXECUTING }
|
||||
var current_state: State = State.IDLE
|
||||
|
||||
# --- Navigation Data ---
|
||||
var source_body: CelestialBody
|
||||
var target_body: CelestialBody
|
||||
|
||||
var current_plan # Can be an array of ImpulsiveBurn or a ContinuousBurnPlan
|
||||
|
||||
# --- Inner classes to hold maneuver data ---
|
||||
class ImpulsiveBurn:
|
||||
var delta_v_magnitude: float
|
||||
var wait_time: float = 0.0
|
||||
var burn_duration: float
|
||||
var desired_rotation_rad: float # The world rotation the ship needs to be in
|
||||
|
||||
class ContinuousBurnPlan:
|
||||
var total_travel_time: float
|
||||
var acceleration_time: float
|
||||
var initial_burn_direction: Vector2 # The world-space direction vector for the burn
|
||||
|
||||
func _ready() -> void:
|
||||
# Connect to the global signal from the SignalBus
|
||||
SignalBus.map_mode_toggled.connect(on_map_mode_toggled)
|
||||
|
||||
if is_instance_valid(ship):
|
||||
ship_signal_bus = ship.signal_bus
|
||||
|
||||
# Ensure the UI starts hidden
|
||||
if navigation_ui:
|
||||
navigation_ui.hide()
|
||||
|
||||
# Connect UI signals
|
||||
map_drawer.body_selected_for_planning.connect(_on_target_selected)
|
||||
plan_hohmann_button.pressed.connect(_on_plan_hohmann_pressed)
|
||||
plan_fast_button.pressed.connect(_on_plan_fast_pressed)
|
||||
plan_torchship_button.pressed.connect(_on_plan_torchship_pressed)
|
||||
plan_gravity_assist_button.pressed.connect(_on_plan_gravity_assist_pressed)
|
||||
execute_plan_button.pressed.connect(_on_execute_plan_pressed)
|
||||
|
||||
ship_status_label.text = ""
|
||||
|
||||
update_ui()
|
||||
|
||||
# This function is called whenever any node in the game emits the "map_mode_toggled" signal.
|
||||
func on_map_mode_toggled():
|
||||
if navigation_ui:
|
||||
# Toggle the visibility of the UI screen
|
||||
navigation_ui.visible = not navigation_ui.visible
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_ship_status_label()
|
||||
|
||||
if current_state == State.PLANNING and current_plan:
|
||||
if current_plan is Array and not current_plan.is_empty():
|
||||
var first_burn = current_plan[0]
|
||||
# The plan is not locked in, but we can see the window approaching.
|
||||
first_burn.wait_time -= delta
|
||||
var time_str = _format_seconds_to_mmss(first_burn.wait_time)
|
||||
info_label.text = "Optimal window in: %s.\nPress Execute to lock in plan." % time_str
|
||||
if first_burn.wait_time < 0:
|
||||
# If the window is missed during planning, mark the plan as stale.
|
||||
info_label.text = "Transfer window missed. Please plan a new maneuver."
|
||||
current_plan = null
|
||||
update_ui()
|
||||
|
||||
if current_state == State.WAITING and current_plan:
|
||||
if current_plan is Array and not current_plan.is_empty():
|
||||
var next_burn: ImpulsiveBurn = current_plan[0]
|
||||
next_burn.wait_time -= delta
|
||||
var time_str = _format_seconds_to_mmss(next_burn.wait_time)
|
||||
info_label.text = "Time to burn: %s" % time_str
|
||||
|
||||
# --- NEW: Emit the rotation plan ---
|
||||
# When we are inside the orientation window, and haven't sent the plan yet...
|
||||
if not rotation_plan_sent:
|
||||
# The time allowed for rotation is the time we have left, minus a safety buffer.
|
||||
var time_for_rotation = next_burn.wait_time - ROTATION_SAFETY_BUFFER
|
||||
|
||||
# Tell the thruster controller to handle it.
|
||||
ship.signal_bus.emit_signal("timed_rotation_commanded", next_burn.desired_rotation_rad, time_for_rotation)
|
||||
|
||||
rotation_plan_sent = true # Mark the plan as sent
|
||||
|
||||
if next_burn.wait_time <= 0:
|
||||
_execute_maneuver()
|
||||
|
||||
# The EXECUTING state would be handled by a dedicated autopilot script/node
|
||||
|
||||
func update_ui():
|
||||
execute_plan_button.disabled = (current_plan == null or current_state != State.PLANNING)
|
||||
|
||||
if current_state == State.PLANNING:
|
||||
# Show available plans based on engine type
|
||||
|
||||
# TODO change UI to show recommendations based on thruster type
|
||||
#if installed_engine is ChemicalThruster:
|
||||
plan_hohmann_button.show()
|
||||
plan_fast_button.show()
|
||||
plan_gravity_assist_button.show()
|
||||
#elif installed_engine is IonDrive:
|
||||
plan_torchship_button.show()
|
||||
|
||||
# --- Signal Handlers ---
|
||||
|
||||
func _on_target_selected(body: CelestialBody):
|
||||
if current_state == State.IDLE or current_state == State.PLANNING:
|
||||
target_body = body
|
||||
# Assume ship is orbiting the target's primary (e.g., the star)
|
||||
source_body = ship.primary
|
||||
current_plan = null
|
||||
current_state = State.PLANNING
|
||||
info_label.text = "Target: %s. Select a maneuver." % target_body.name
|
||||
update_ui()
|
||||
|
||||
func _on_execute_plan_pressed():
|
||||
if current_plan:
|
||||
current_state = State.WAITING
|
||||
update_ui()
|
||||
|
||||
# --- Planning Function Calls ---
|
||||
func _on_plan_hohmann_pressed():
|
||||
current_plan = _calculate_hohmann_transfer(source_body, target_body)
|
||||
# TODO: map_drawer.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_fast_pressed():
|
||||
# For simplicity, a "fast" transfer is a Hohmann with a 25% larger transfer orbit
|
||||
current_plan = _calculate_hohmann_transfer(source_body, target_body, 1.25)
|
||||
# TODO: map_drawer.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_torchship_pressed():
|
||||
current_plan = _calculate_brachistochrone_transfer()
|
||||
# TODO: map_drawer.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_gravity_assist_pressed():
|
||||
info_label.text = "Gravity Assist calculation is highly complex and not yet implemented."
|
||||
print("Placeholder for Gravity Assist logic.")
|
||||
|
||||
# --- Calculation and Execution ---
|
||||
func _calculate_hohmann_transfer(source_planet: CelestialBody, target_planet: CelestialBody, transfer_boost_factor: float = 1.0) -> Array:
|
||||
# Get the central star safely from the GameManager.
|
||||
var ship_current_primary = ship.primary
|
||||
var star = GameManager.get_system_data().star
|
||||
if not is_instance_valid(star):
|
||||
print("Hohmann Error: Could not find star in GameManager.")
|
||||
return []
|
||||
|
||||
# This maneuver requires the ship and target to orbit the same star.
|
||||
if ship_current_primary != star or target_planet.primary != star:
|
||||
info_label.text = "Invalid Transfer: Ship and target must orbit the same star."
|
||||
return []
|
||||
|
||||
# mu (μ): The Standard Gravitational Parameter of the star. It's G * M, a constant that simplifies calculations.
|
||||
var mu = OrbitalMechanics.G * star.mass
|
||||
|
||||
# r1: The ship's current orbital radius (distance from the star).
|
||||
var r1 = ship.global_position.distance_to(star.global_position)
|
||||
# r2: The target planet's orbital radius.
|
||||
var r2 = target_planet.global_position.distance_to(star.global_position)
|
||||
|
||||
# --- Hohmann Transfer Calculations ---
|
||||
|
||||
# v_source_orbit: The ship's current circular orbital speed.
|
||||
var v_source_orbit = sqrt(mu / r1)
|
||||
# v_target_orbit: The target planet's circular orbital speed.
|
||||
var v_target_orbit = sqrt(mu / r2)
|
||||
|
||||
# a_transfer: The semi-major axis (average radius) of the elliptical transfer orbit.
|
||||
var a_transfer = (r1 + r2) / 2.0 * transfer_boost_factor
|
||||
|
||||
# v_transfer_periapsis: The required speed at the start of the transfer (periapsis) to get onto the ellipse.
|
||||
var v_transfer_periapsis = sqrt(mu * ((2.0 / r1) - (1.0 / a_transfer)))
|
||||
# v_transfer_apoapsis: The speed the ship will have when it arrives at the end of the transfer (apoapsis).
|
||||
var v_transfer_apoapsis = sqrt(mu * ((2.0 / r2) - (1.0 / a_transfer)))
|
||||
|
||||
# delta_v1: The first burn. The change in speed needed to go from the source orbit to the transfer orbit.
|
||||
var delta_v1 = v_transfer_periapsis - v_source_orbit
|
||||
# delta_v2: The second burn. The change in speed needed to go from the transfer orbit to the target orbit.
|
||||
var delta_v2 = v_target_orbit - v_transfer_apoapsis
|
||||
|
||||
# time_of_flight: Half the period of the elliptical transfer orbit (Kepler's 3rd Law).
|
||||
var time_of_flight = PI * sqrt(pow(a_transfer, 3) / mu)
|
||||
|
||||
# --- Launch Window (Phase Angle) Calculations ---
|
||||
|
||||
# ang_vel_target: The angular speed of the target planet (in radians per second).
|
||||
var ang_vel_target = sqrt(mu / pow(r2, 3))
|
||||
# travel_angle: The angle the target planet will travel through during the ship's flight time.
|
||||
var travel_angle = ang_vel_target * time_of_flight
|
||||
# required_phase_angle: The starting angle needed between the ship and target for a successful intercept.
|
||||
var required_phase_angle = PI - travel_angle
|
||||
|
||||
# vec_to_ship/target: Direction vectors from the star to the ship and target.
|
||||
var vec_to_ship = (ship.global_position - star.global_position).normalized()
|
||||
var vec_to_target = (target_planet.global_position - star.global_position).normalized()
|
||||
# current_phase_angle: The angle between the ship and target right now.
|
||||
var current_phase_angle = vec_to_ship.angle_to(vec_to_target)
|
||||
|
||||
# ang_vel_ship: The ship's current angular speed.
|
||||
var ang_vel_ship = sqrt(mu / pow(r1, 3))
|
||||
# relative_ang_vel: How quickly the ship is catching up to (or falling behind) the target.
|
||||
var relative_ang_vel = ang_vel_ship - ang_vel_target
|
||||
# angle_to_wait: The angular distance the ship needs to "wait" for alignment.
|
||||
var angle_to_wait = current_phase_angle - required_phase_angle
|
||||
# wait_time: The final calculated time in seconds to wait for the optimal launch window.
|
||||
var wait_time = abs(angle_to_wait / relative_ang_vel)
|
||||
|
||||
# --- Final Plan Assembly ---
|
||||
|
||||
# Calculate burn durations
|
||||
var acceleration = ship.thruster_controller.main_engine_max_thrust() / ship.mass
|
||||
var burn_duration1 = delta_v1 / acceleration
|
||||
var burn_duration2 = delta_v2 / acceleration
|
||||
|
||||
var plan = []
|
||||
var burn1 = ImpulsiveBurn.new()
|
||||
burn1.delta_v_magnitude = delta_v1
|
||||
burn1.wait_time = wait_time
|
||||
burn1.burn_duration = burn_duration1
|
||||
# The desired rotation is the angle of the ship's prograde (tangential) velocity vector.
|
||||
burn1.desired_rotation_rad = ship.linear_velocity.angle()
|
||||
plan.append(burn1)
|
||||
|
||||
var burn2 = ImpulsiveBurn.new()
|
||||
burn2.delta_v_magnitude = delta_v2
|
||||
burn2.wait_time = time_of_flight - burn_duration1
|
||||
burn2.burn_duration = burn_duration2
|
||||
# The desired rotation for burn 2 is the tangential direction at the target orbit.
|
||||
burn2.desired_rotation_rad = (target_planet.global_position - star.global_position).orthogonal().angle()
|
||||
plan.append(burn2)
|
||||
|
||||
info_label.text = "Hohmann Plan:\nWait: %d s\nBurn 1: %.1f m/s (%.1f s)" % [wait_time, delta_v1, burn_duration1]
|
||||
return plan
|
||||
|
||||
func _calculate_brachistochrone_transfer() -> ContinuousBurnPlan:
|
||||
var distance = ship.global_position.distance_to(target_body.global_position)
|
||||
var acceleration = ship.thruster_controller.main_engine_max_thrust() / ship.mass
|
||||
if acceleration == 0: return null
|
||||
|
||||
# d = 1/2 * a * t^2 => t = sqrt(2d/a). We do this twice (accel/decel).
|
||||
var time_for_half_journey = sqrt(distance / acceleration)
|
||||
|
||||
var plan = ContinuousBurnPlan.new()
|
||||
plan.total_travel_time = 2 * time_for_half_journey
|
||||
plan.acceleration_time = time_for_half_journey
|
||||
plan.required_acceleration = acceleration
|
||||
|
||||
info_label.text = "Torchship Trajectory Calculated.\nTravel Time: %d s" % plan.total_travel_time
|
||||
return plan
|
||||
|
||||
func _execute_maneuver():
|
||||
if current_state != State.WAITING or not current_plan: return
|
||||
|
||||
current_state = State.EXECUTING
|
||||
var burn: ImpulsiveBurn = current_plan.pop_front()
|
||||
|
||||
# Tell the controller to start burning. Orientation is already handled.
|
||||
ship.thruster_controller.autopilot_start_burn(burn.burn_duration)
|
||||
|
||||
# Set up for the next leg of the journey or finish
|
||||
if not current_plan.is_empty():
|
||||
# The next "wait_time" is the coasting period between burns.
|
||||
current_state = State.WAITING
|
||||
|
||||
# Reset the flag here, preparing the system for the *next* burn's rotation command.
|
||||
rotation_plan_sent = false
|
||||
else:
|
||||
current_state = State.IDLE
|
||||
current_plan = null
|
||||
update_ui()
|
||||
|
||||
func _update_ship_status_label():
|
||||
if not is_instance_valid(ship):
|
||||
ship_status_label.text = "NO SHIP DATA"
|
||||
return
|
||||
|
||||
# Build an array of strings for each line of the display
|
||||
var status_lines = []
|
||||
|
||||
# Get rotation data from the ship
|
||||
var rotation_deg = rad_to_deg(ship.rotation)
|
||||
var angular_vel_dps = rad_to_deg(ship.angular_velocity)
|
||||
status_lines.append("Rotation: %.1f deg" % rotation_deg)
|
||||
status_lines.append("Ang. Vel.: %.5f deg/s" % angular_vel_dps)
|
||||
|
||||
# Get burn data from the thruster controller
|
||||
var burn_time = ship.thruster_controller.current_burn_time_remaining
|
||||
var thrust_force = ship.thruster_controller.current_thrust_force
|
||||
status_lines.append("Burn Time: %.1f s" % burn_time)
|
||||
status_lines.append("Thrust: %.5f N" % thrust_force)
|
||||
|
||||
# Join the lines with a newline character and update the label
|
||||
ship_status_label.text = "\n".join(status_lines)
|
||||
|
||||
# Helper function to format a float of seconds into a M:SS string
|
||||
func _format_seconds_to_mmss(seconds_float: float) -> String:
|
||||
if seconds_float < 0:
|
||||
seconds_float = 0
|
||||
var total_seconds = int(seconds_float)
|
||||
var minutes = total_seconds / 60
|
||||
var seconds = total_seconds % 60
|
||||
# "%02d" formats the seconds with a leading zero if needed (e.g., 05)
|
||||
return "%d:%02d" % [minutes, seconds]
|
||||
1
scripts/ship/navigation_computer.gd.uid
Normal file
1
scripts/ship/navigation_computer.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cq2sgw12uj4jl
|
||||
14
scripts/ship/ship_signal_bus.gd
Normal file
14
scripts/ship/ship_signal_bus.gd
Normal file
@ -0,0 +1,14 @@
|
||||
class_name ShipSignalBus
|
||||
extends Node
|
||||
|
||||
# --- Navigation & Maneuvering Events ---
|
||||
# Emitted when a maneuver plan is calculated.
|
||||
signal maneuver_planned(plan)
|
||||
# Emitted to command the start of a timed rotation.
|
||||
signal timed_rotation_commanded(target_rotation_rad, time_window)
|
||||
|
||||
# --- Thruster & Ship Status Events ---
|
||||
# Emitted when the main engine starts or stops firing.
|
||||
signal main_engine_state_changed(is_firing: bool, total_thrust: float)
|
||||
# Emitted when RCS thrusters are fired.
|
||||
signal rcs_state_changed(is_firing: bool, torque: float)
|
||||
1
scripts/ship/ship_signal_bus.gd.uid
Normal file
1
scripts/ship/ship_signal_bus.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://w1546qtaupd2
|
||||
135
scripts/ship/spaceship.gd
Normal file
135
scripts/ship/spaceship.gd
Normal file
@ -0,0 +1,135 @@
|
||||
# Spaceship.gd
|
||||
class_name Spaceship
|
||||
extends CelestialBody
|
||||
|
||||
@export var ship_name: String = "Stardust Drifter"
|
||||
@export var dry_mass: float = 1000.0 # Mass of the ship without fuel/cargo (in kg)
|
||||
@export var hull_integrity: float = 100.0
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
|
||||
@export_category("Camera")
|
||||
@export var zoom_speed: float = 1.0
|
||||
var current_time_scale: float = Engine.time_scale
|
||||
|
||||
# --- Node References to Modular Systems ---
|
||||
@onready var signal_bus: ShipSignalBus = $SignalBus
|
||||
@onready var thruster_controller: ThrusterController = $ThrusterController
|
||||
@onready var fuel_system = $FuelSystem
|
||||
@onready var life_support = $LifeSupport
|
||||
@onready var navigation_computer = $NavigationComputer
|
||||
# @onready var weapon_system = $WeaponSystem
|
||||
# @onready var power_grid = $PowerGrid
|
||||
|
||||
func _ready() -> void:
|
||||
GameManager.register_ship(self)
|
||||
|
||||
linear_damp = 0
|
||||
angular_damp_mode = RigidBody2D.DAMP_MODE_REPLACE
|
||||
angular_damp = 0
|
||||
|
||||
# Connect to signals from our subsystems
|
||||
fuel_system.fuel_mass_changed.connect(_on_fuel_mass_changed)
|
||||
life_support.hull_breach_detected.connect(_on_hull_breach)
|
||||
|
||||
# Set the initial mass of the RigidBody
|
||||
update_total_mass()
|
||||
|
||||
# Give the navigation computer a reference to this ship
|
||||
if navigation_computer:
|
||||
navigation_computer.ship = self
|
||||
|
||||
camera.make_current()
|
||||
|
||||
func _process(delta: float):
|
||||
# The queue_redraw() call tells Godot that this object needs to be redrawn
|
||||
# on the next frame, which then calls the _draw() function.
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(Vector2.ZERO, 5.0, Color.CORAL)
|
||||
|
||||
# This physics callback is for forces acting ON the ship, like damage
|
||||
func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
|
||||
# The thruster_controller will apply its own forces directly to the ship's state.
|
||||
# We can use this function for other things, like forces from hull breaches.
|
||||
|
||||
if is_instance_valid(primary):
|
||||
# The spaceship is also affected by gravity, calculated by our library
|
||||
var force = OrbitalMechanics.calculate_gravitational_force(self, primary)
|
||||
state.apply_central_force(force)
|
||||
|
||||
|
||||
# This function will now handle all non-UI input for the player-controlled ship.
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
# --- Map Toggling ---
|
||||
if event.is_action_pressed("ui_map_mode"):
|
||||
# Instead of showing/hiding a node directly, we broadcast our intent.
|
||||
# The NavigationComputer will be listening for this global signal.
|
||||
SignalBus.emit_signal("map_mode_toggled")
|
||||
|
||||
# --- Camera Zoom (from DeveloperPawn) ---
|
||||
if event is InputEventMouseButton:
|
||||
if camera:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
camera.zoom -= Vector2(zoom_speed, zoom_speed)
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.zoom += Vector2(zoom_speed, zoom_speed)
|
||||
|
||||
camera.zoom.x = clamp(camera.zoom.x, 0.01, 250.0)
|
||||
camera.zoom.y = clamp(camera.zoom.y, 0.01, 250.0)
|
||||
|
||||
# --- Time Scale Controls (from DeveloperPawn) ---
|
||||
if event.is_action_pressed("time_increase"):
|
||||
var new_value = min(current_time_scale * 1.2, 1000)
|
||||
current_time_scale = clamp(new_value, 0.5, 1000)
|
||||
Engine.time_scale = current_time_scale
|
||||
|
||||
elif event.is_action_pressed("time_decrease"):
|
||||
var new_value = max(current_time_scale * 0.833, 0.1)
|
||||
current_time_scale = clamp(new_value, 0.5, 1000)
|
||||
Engine.time_scale = current_time_scale
|
||||
|
||||
elif event.is_action_pressed("time_reset"):
|
||||
Engine.time_scale = 1.0
|
||||
|
||||
# --- Public API for Ship Management ---
|
||||
func get_total_mass() -> float:
|
||||
return self.mass
|
||||
|
||||
# Call this to take damage. Damage can have a position for breach effects.
|
||||
func take_damage(amount: float, damage_position: Vector2):
|
||||
hull_integrity -= amount
|
||||
print("%s hull integrity at %.1f%%" % [ship_name, hull_integrity])
|
||||
|
||||
if hull_integrity <= 0:
|
||||
destroy_ship()
|
||||
else:
|
||||
# Check if the hit caused a hull breach
|
||||
life_support.check_for_breach(damage_position)
|
||||
|
||||
func destroy_ship():
|
||||
print("%s has been destroyed!" % ship_name)
|
||||
queue_free()
|
||||
|
||||
# --- Signal Handlers ---
|
||||
|
||||
func _on_fuel_mass_changed():
|
||||
# Update the ship's total mass when fuel is consumed or added
|
||||
update_total_mass()
|
||||
|
||||
func _on_hull_breach(breach_position: Vector2, force_vector: Vector2):
|
||||
# A hull breach applies a continuous force at a specific point
|
||||
# For simplicity, we can apply it as a central force and torque here
|
||||
var force = force_vector * 100 # Scale the force
|
||||
apply_central_force(force)
|
||||
|
||||
# Calculate torque: Torque = r x F (cross product of position vector and force)
|
||||
var position_relative_to_center = breach_position - self.global_position
|
||||
var torque = position_relative_to_center.cross(force)
|
||||
apply_torque(torque)
|
||||
|
||||
func update_total_mass():
|
||||
var total_mass = dry_mass + fuel_system.get_total_fuel_mass()
|
||||
self.inertia = total_mass / 1000
|
||||
self.mass = total_mass / 1000
|
||||
1
scripts/ship/spaceship.gd.uid
Normal file
1
scripts/ship/spaceship.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dyqbk4lcx3mhq
|
||||
79
scripts/ship/thruster.gd
Normal file
79
scripts/ship/thruster.gd
Normal file
@ -0,0 +1,79 @@
|
||||
# Thruster.gd
|
||||
class_name Thruster
|
||||
extends Node2D
|
||||
|
||||
# Max force the thruster can produce (in scaled Newtons).
|
||||
@export var max_thrust: float = 0.1
|
||||
|
||||
# Engine efficiency. Higher is better (more "bang for your buck").
|
||||
# Measures how much momentum change you get per unit of fuel.
|
||||
@export var specific_impulse_isp: float = 300.0
|
||||
|
||||
# The type of fuel resource this thruster consumes.
|
||||
@export var fuel_resource_name: String = "ChemicalFuel"
|
||||
@export var thrust_direction: Vector2 = Vector2(0, -1)
|
||||
@export var main_thruster: bool = true
|
||||
|
||||
# A state variable to track if the thruster is active
|
||||
var is_firing: bool = false
|
||||
|
||||
# Get a reference to the parent ship.
|
||||
@onready var ship: Spaceship = get_parent()
|
||||
|
||||
func _ready() -> void:
|
||||
# This thruster announces its existence to the whole scene tree.
|
||||
add_to_group("ship_thrusters")
|
||||
|
||||
# This function calculates how much fuel is needed for a given thrust level and duration.
|
||||
func calculate_fuel_consumption(thrust_force: float, delta_time: float) -> float:
|
||||
if thrust_force <= 0: return 0.0
|
||||
# Standard rocket equation using Isp. g0 is standard gravity (9.81).
|
||||
var mass_flow_rate = thrust_force / (specific_impulse_isp * 9.81)
|
||||
return mass_flow_rate * delta_time
|
||||
|
||||
# --- Public Methods ---
|
||||
|
||||
# The controller calls this ONCE to activate the thruster.
|
||||
func turn_on():
|
||||
is_firing = true
|
||||
|
||||
# The controller calls this ONCE to deactivate the thruster.
|
||||
func turn_off():
|
||||
is_firing = false
|
||||
|
||||
# --- Godot Physics Callback ---
|
||||
func _physics_process(delta: float):
|
||||
# If the thruster is in the "firing" state, it applies its own force.
|
||||
if is_firing:
|
||||
# Calculate the force vector in the ship's local space.
|
||||
var force_local_vec = thrust_direction * max_thrust
|
||||
|
||||
# Convert the local force to world space for the physics engine.
|
||||
var force_world_vec = force_local_vec.rotated(ship.rotation)
|
||||
|
||||
# Apply the force at this thruster's specific position.
|
||||
ship.apply_force(force_world_vec, self.position)
|
||||
|
||||
# Also, ensure the visual effect is running
|
||||
queue_redraw()
|
||||
|
||||
func _draw():
|
||||
# This function is only called if the thruster is firing (due to queue_redraw)
|
||||
if not is_firing:
|
||||
return
|
||||
|
||||
# --- Draw a fiery, flickering cone ---
|
||||
# The plume goes in the OPPOSITE direction of the thrust
|
||||
var plume_direction = -thrust_direction
|
||||
var plume_length = randf_range(20.0, 30.0) # Random length for a flickering effect
|
||||
|
||||
# Define the 3 points of a triangle for the cone
|
||||
var tip = plume_direction * plume_length
|
||||
var base_offset = plume_direction.orthogonal() * 8.0
|
||||
var base1 = base_offset
|
||||
var base2 = -base_offset
|
||||
|
||||
var points = PackedVector2Array([base1, tip, base2])
|
||||
|
||||
# Draw the cone with a fiery color
|
||||
draw_polygon(points, PackedColorArray([Color.ORANGE_RED, Color.GOLD, Color.ORANGE_RED]))
|
||||
1
scripts/ship/thruster.gd.uid
Normal file
1
scripts/ship/thruster.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cpc34i7o1puq2
|
||||
194
scripts/ship/thruster_controller.gd
Normal file
194
scripts/ship/thruster_controller.gd
Normal file
@ -0,0 +1,194 @@
|
||||
# ThrusterController.gd
|
||||
class_name ThrusterController
|
||||
extends Node
|
||||
|
||||
# The main ship body this controller will act upon
|
||||
@onready var ship: Spaceship = get_parent()
|
||||
var ship_signal_bus: ShipSignalBus
|
||||
|
||||
# References to the installed thruster scenes
|
||||
@onready var main_engines: Array[Thruster] = []
|
||||
@onready var rcs_thrusters: Array[Thruster] = []
|
||||
|
||||
var current_burn_time_remaining: float = 0.0
|
||||
var current_thrust_force: float = 0.0
|
||||
|
||||
# --- Autopilot Constants ---
|
||||
#const ROTATION_POWER = 50000.0 # Torque applied by RCS
|
||||
|
||||
# --- Autopilot State ---
|
||||
var is_orienting: bool = false
|
||||
var is_burning: bool = false
|
||||
var target_rotation_rad: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
# Wait one frame to ensure all thrusters have run their _ready() function.
|
||||
await get_tree().process_frame
|
||||
|
||||
if is_instance_valid(ship):
|
||||
ship_signal_bus = ship.signal_bus
|
||||
|
||||
# Connect to the local bus signals
|
||||
ship_signal_bus.timed_rotation_commanded.connect(_on_rotation_maneuver_planned)
|
||||
|
||||
# Get all nodes in the "ship_thrusters" group that are children of our ship.
|
||||
var thruster_nodes = get_tree().get_nodes_in_group("ship_thrusters")
|
||||
for thruster : Thruster in thruster_nodes:
|
||||
# Ensure we only register thrusters that belong to this ship.
|
||||
if thruster.main_thruster:
|
||||
main_engines.append(thruster)
|
||||
else:
|
||||
rcs_thrusters.append(thruster)
|
||||
print("ThrusterController registered via group: ", thruster.name)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# The controller now actively manages its state every physics frame.
|
||||
#apply_rotational_thrust(1.0)
|
||||
if is_burning:
|
||||
_perform_main_burn()
|
||||
|
||||
# --- Private Worker Functions ---
|
||||
|
||||
func _on_rotation_maneuver_planned(target_rotation_rad: float, time_window: float):
|
||||
time_window = min(time_window, 30.0)
|
||||
print("AUTOPILOT: Received fuel-optimal rotation plan. Target: %.2f rad in %.2f s." % [target_rotation_rad, time_window])
|
||||
|
||||
var angle_to_turn = shortest_angle_between(ship.rotation, target_rotation_rad)
|
||||
|
||||
# 1. Determine the maximum possible torque we can generate.
|
||||
var max_torque = _get_total_possible_torque(sign(angle_to_turn))
|
||||
if max_torque == 0:
|
||||
print("AUTOPILOT ERROR: No thrusters available for this rotation.")
|
||||
return
|
||||
|
||||
# 2. Calculate the required burn time and angular velocity.
|
||||
# The physics formulas are: Δθ = ω * t_coast and τ = I * Δω / t_burn
|
||||
# We can simplify this to find the required burn time.
|
||||
var burn_time = time_window / 2.0 - sqrt((time_window * time_window) / 4.0 - (abs(angle_to_turn) * ship.inertia) / max_torque)
|
||||
|
||||
print("AUTOPILOT: Max torque for rotational manuever %f", max_torque)
|
||||
|
||||
if burn_time < 0 or is_nan(burn_time):
|
||||
print("AUTOPILOT WARNING: Cannot complete rotation in the given time window. Turning at max speed.")
|
||||
# Fallback: Just burn for half the time and decelerate for the other half.
|
||||
burn_time = time_window / 2.0
|
||||
|
||||
var coast_time = time_window - (2 * burn_time)
|
||||
|
||||
# 3. Execute the three-phase maneuver.
|
||||
print("AUTOPILOT: Burn-Coast-Burn plan: Burn %.2fs, Coast %.2fs, Burn %.2fs" % [burn_time, coast_time, burn_time])
|
||||
|
||||
# --- ACCELERATION BURN ---
|
||||
print("AUTOPILOT: Burn 1 started")
|
||||
|
||||
apply_rotational_thrust(sign(angle_to_turn) * max_torque)
|
||||
await get_tree().create_timer(burn_time).timeout
|
||||
|
||||
for thruster in rcs_thrusters:
|
||||
thruster.turn_off()
|
||||
print("AUTOPILOT: Burn 1 complete")
|
||||
|
||||
# --- COASTING PHASE ---
|
||||
print("AUTOPILOT: Rotation coasting")
|
||||
await get_tree().create_timer(coast_time).timeout
|
||||
|
||||
# --- DECCELERATION BURN ---
|
||||
print("AUTOPILOT: Burn 2 started")
|
||||
apply_rotational_thrust(sign(angle_to_turn) * -max_torque)
|
||||
await get_tree().create_timer(burn_time).timeout
|
||||
print("AUTOPILOT: Burn 2 complete")
|
||||
|
||||
for thruster in rcs_thrusters:
|
||||
thruster.turn_off()
|
||||
|
||||
print("AUTOPILOT: Fuel-optimal rotation maneuver complete.")
|
||||
|
||||
|
||||
func _perform_main_burn():
|
||||
current_thrust_force = main_engine_max_thrust() # Update public variable
|
||||
for engine in main_engines:
|
||||
var force_vector = Vector2.UP.rotated(ship.rotation) * -engine.max_thrust
|
||||
ship.apply_central_force(force_vector)
|
||||
|
||||
# Applies forces from the correct thrusters to achieve a desired torque.
|
||||
func apply_rotational_thrust(desired_torque: float):
|
||||
if not is_instance_valid(ship):
|
||||
return
|
||||
|
||||
# Iterate through all available RCS thrusters
|
||||
for thruster in rcs_thrusters:
|
||||
# 1. Get the thruster's position relative to the ship's center. This is its local position.
|
||||
var thruster_local_pos = thruster.position
|
||||
|
||||
# 2. Calculate the force this thruster produces, also in LOCAL space.
|
||||
var force_local_vec = thruster.thrust_direction * thruster.max_thrust
|
||||
|
||||
# 3. Calculate the torque in LOCAL space. This is now a valid calculation.
|
||||
var produced_torque = thruster_local_pos.cross(force_local_vec)
|
||||
# 4. Decide whether to fire the thruster. This check will now work correctly.
|
||||
if sign(produced_torque) == sign(desired_torque):
|
||||
thruster.turn_on()
|
||||
else:
|
||||
thruster.turn_off()
|
||||
|
||||
func main_engine_max_thrust():
|
||||
return main_engines.reduce(func(thrust, engine : Thruster): return thrust + engine.max_thrust, 0.0)
|
||||
|
||||
# --- NEW: High-Level Autopilot Commands ---
|
||||
func autopilot_start_orientation(rotation_rad: float):
|
||||
print("AUTOPILOT: Receiving command to orient to %.2f rad." % rotation_rad)
|
||||
target_rotation_rad = rotation_rad
|
||||
is_orienting = true
|
||||
|
||||
func autopilot_start_burn(duration: float):
|
||||
print("AUTOPILOT: Receiving command to burn for %.2f s." % duration)
|
||||
is_burning = true
|
||||
# We can use a timer to stop the burn automatically.
|
||||
get_tree().create_timer(duration).timeout.connect(autopilot_stop_burn)
|
||||
|
||||
func autopilot_stop_burn():
|
||||
is_burning = false
|
||||
print("AUTOPILOT: Main engine burn complete.")
|
||||
|
||||
func autopilot_stop_all_maneuvers():
|
||||
is_orienting = false
|
||||
is_burning = false
|
||||
|
||||
# --- Private Autopilot Helper Functions ---
|
||||
|
||||
# Fires all main engines for a specified duration.
|
||||
func _fire_main_engine(duration: float):
|
||||
print("AUTOPILOT: Firing main engine for %.2f seconds." % duration)
|
||||
var timer = duration
|
||||
is_burning = true # Make sure this is set if not already
|
||||
|
||||
while timer > 0:
|
||||
# Apply force from all main engines
|
||||
for engine in main_engines:
|
||||
var force_vector = Vector2.UP.rotated(ship.rotation) * -engine.max_thrust
|
||||
ship.apply_central_force(force_vector)
|
||||
|
||||
timer -= get_physics_process_delta_time()
|
||||
current_burn_time_remaining = timer # Update public variable
|
||||
await get_tree().physics_frame
|
||||
print("AUTOPILOT: Main engine burn complete.")
|
||||
|
||||
# Calculates the shortest angle between two angles (in radians).
|
||||
# The result will be between -PI and +PI. The sign indicates the direction.
|
||||
func shortest_angle_between(from_angle: float, to_angle: float) -> float:
|
||||
var difference = fposmod(to_angle - from_angle, TAU)
|
||||
if difference > PI:
|
||||
return difference - TAU
|
||||
else:
|
||||
return difference
|
||||
|
||||
# Calculates the total torque available from all thrusters for a given direction.
|
||||
func _get_total_possible_torque(direction: int) -> float:
|
||||
var total_torque: float = 0.0
|
||||
for thruster in rcs_thrusters:
|
||||
var r = thruster.position
|
||||
var force_local_vec = thruster.thrust_direction * thruster.max_thrust
|
||||
var produced_torque = r.cross(force_local_vec)
|
||||
if sign(produced_torque) == direction:
|
||||
total_torque += abs(produced_torque)
|
||||
return total_torque
|
||||
1
scripts/ship/thruster_controller.gd.uid
Normal file
1
scripts/ship/thruster_controller.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c0bx113ifxyh8
|
||||
11
scripts/signal_bus.gd
Normal file
11
scripts/signal_bus.gd
Normal file
@ -0,0 +1,11 @@
|
||||
extends Node
|
||||
|
||||
# Signal emitted when the player requests to toggle the map view.
|
||||
signal map_mode_toggled
|
||||
|
||||
# Signal emitted from the map when a body is selected to be followed.
|
||||
# It passes the selected CelestialBody as an argument.
|
||||
signal follow_target_selected(body: CelestialBody)
|
||||
|
||||
# Emitted by the NavComputer to command a timed rotation.
|
||||
signal rotation_maneuver_planned(target_rotation_rad: float, time_window_seconds: float)
|
||||
1
scripts/signal_bus.gd.uid
Normal file
1
scripts/signal_bus.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://duu8vu31yyt7r
|
||||
@ -34,6 +34,7 @@ extends Node2D
|
||||
@export var moon_scene: PackedScene
|
||||
@export var station_scene: PackedScene
|
||||
@export var asteroid_scene: PackedScene
|
||||
@export var spaceship_scene: PackedScene
|
||||
|
||||
# A scaling parameter to convert real-world units to game units.
|
||||
# 1 meter = sim_scale game units.
|
||||
@ -88,7 +89,6 @@ func _create_star() -> RigidBody2D:
|
||||
# Set the star's properties.
|
||||
star_instance.orbit_radius_real = 0.0
|
||||
star_instance.mass = SUN_MASS
|
||||
star_instance.linear_velocity_real = Vector2.ZERO
|
||||
star_instance.modulate = Color("ffe066") # Yellow
|
||||
|
||||
return star_instance
|
||||
@ -101,7 +101,8 @@ func _generate_and_place_bodies(primary: RigidBody2D) -> void:
|
||||
var num_star_stations = randi_range(min_star_stations, max_star_stations)
|
||||
|
||||
# 2. Create an "inventory" of bodies to be placed.
|
||||
var bodies_to_place = []
|
||||
var bodies_to_place = []
|
||||
bodies_to_place.append({"type": "wormhole_exit", "num": 0})
|
||||
for i in range(num_planets):
|
||||
bodies_to_place.append({"type": "planet"})
|
||||
for i in range(num_belts):
|
||||
@ -152,8 +153,35 @@ func _generate_and_place_bodies(primary: RigidBody2D) -> void:
|
||||
_create_body_in_ring(primary, station_instance)
|
||||
|
||||
current_orbit_radius = station_instance.orbit_radius_real + (station_instance.mass * orbit_buffer)
|
||||
"wormhole_exit":
|
||||
if not spaceship_scene:
|
||||
print("Spaceship scene not set in StarSystemGenerator!")
|
||||
continue
|
||||
|
||||
print("Spawning spaceship...")
|
||||
var spaceship_instance = spaceship_scene.instantiate() as Spaceship
|
||||
|
||||
# 1. Set its primary to the star
|
||||
spaceship_instance.primary = primary
|
||||
|
||||
# 2. Set its mass (must be scaled like your celestial bodies)
|
||||
spaceship_instance.mass = 1 # Example scaled mass
|
||||
|
||||
# 3. Position it in a stable orbit
|
||||
var orbit_radius = current_orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
var initial_position_vector = Vector2(orbit_radius, 0).rotated(randf() * TAU)
|
||||
spaceship_instance.global_position = primary.global_position + initial_position_vector
|
||||
|
||||
# 4. Use the OrbitalMechanics library to calculate its initial velocity
|
||||
spaceship_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(spaceship_instance, primary)
|
||||
|
||||
add_child(spaceship_instance)
|
||||
|
||||
current_orbit_radius = orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
|
||||
print("Star system generation complete.")
|
||||
|
||||
GameManager.register_star_system(self)
|
||||
|
||||
# Creates moons and stations around a primary body.
|
||||
func _create_moons_and_stations(primary: RigidBody2D) -> void:
|
||||
@ -237,20 +265,14 @@ func _create_asteroid_belt(primary: RigidBody2D, initial_offset: float) -> Aster
|
||||
|
||||
# Helper function to instantiate and place a body in a ring.
|
||||
func _create_body_in_ring(primary: CelestialBody, body_instance: CelestialBody) -> void:
|
||||
#var stable_velocity = calculate_stable_orbit_velocity(body_instance.orbit_radius_real, primary.mass_real)
|
||||
|
||||
var initial_position_vector = Vector2(body_instance.orbit_radius_real, 0).rotated(randf() * TAU)
|
||||
body_instance.global_position = primary.global_position_real + initial_position_vector
|
||||
body_instance.global_position = primary.global_position + initial_position_vector
|
||||
|
||||
primary.add_child(body_instance)
|
||||
body_instance.linear_velocity = body_instance.calculate_initial_orbit_real(primary)
|
||||
body_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(body_instance, primary)
|
||||
print("Created " + body_instance.name + " with radius " + str(body_instance.orbit_radius_real) + " and mass " + str(body_instance.mass))
|
||||
print("Initial orbital velocity is: " + str(body_instance.linear_velocity))
|
||||
|
||||
# Calculates the velocity required for a stable circular orbit.
|
||||
#func calculate_stable_orbit_velocity(orbit_radius: float, primary_mass: float) -> float:
|
||||
#return sqrt(G * primary_mass / orbit_radius)
|
||||
|
||||
# Recursively finds all celestial bodies in the scene.
|
||||
func get_all_bodies() -> Array:
|
||||
var bodies = []
|
||||
|
||||
Reference in New Issue
Block a user