Finished move
This commit is contained in:
39
src/addons/module_builder_plugin/Status.md
Normal file
39
src/addons/module_builder_plugin/Status.md
Normal file
@ -0,0 +1,39 @@
|
||||
Project State Summary
|
||||
|
||||
Core Plugin Logic (module_builder_editor_plugin.gd)
|
||||
|
||||
The main plugin script has been refactored into a single controller that manages the entire builder workflow. Key functionalities include:
|
||||
|
||||
Custom Workspace: The plugin provides a dedicated editor tab with its own isolated SubViewport and a Node2D root, separating the building environment from the main scene.
|
||||
|
||||
Camera Controls: It now has a functional Camera2D with working pan (right-click drag) and zoom (mouse wheel) controls, as well as a "Center" button to reset the camera to the origin.
|
||||
|
||||
Visual Grid: A CustomGrid.gd script, attached to a Node2D in the viewport, draws a dynamic grid that scales with the camera's zoom level, prevents visual clutter by culling lines, and accurately aligns with the placement snapping.
|
||||
|
||||
Piece Placement: The plugin can correctly preview and place structural pieces from the bottom dock into the viewport. The pieces snap to a grid and are added as children to a Module node.
|
||||
|
||||
Module Management: It can find existing Module nodes based on the placement position of a new piece. If no module is found, a new one is instantiated.
|
||||
|
||||
Undo/Redo System: The plugin now supports a robust undo/redo system for both placing and removing structural pieces. This uses the editor's native EditorUndoRedoManager to ensure actions are reversible with Ctrl+Z and Ctrl+Shift+Z.
|
||||
|
||||
Docks: The plugin instantiates and adds custom docks for the ConstructionTree and ConstructionInspector to the left-hand side of the editor.
|
||||
|
||||
Other File Updates
|
||||
|
||||
builder_dock.gd: The script is now a simple UI controller that emits a signal when a piece button is pressed, centralizing the logic in the main plugin script.
|
||||
|
||||
module.gd: The physics recalculation logic is now a public function that correctly combines all child StructuralPiece collision shapes into a single ConcavePolygonShape2D for the parent RigidBody2D.
|
||||
|
||||
structural_piece.gd: The is_preview property is used to make preview pieces translucent and non-interactive, allowing mouse events to pass through to the viewport.
|
||||
|
||||
Outstanding Tasks
|
||||
|
||||
R key binding: The 'R' key binding to rotate pieces is not working correctly and needs to be addressed.
|
||||
|
||||
Save Functionality: The _on_save_button_pressed function needs to be implemented to save the built module as a .tscn file.
|
||||
|
||||
Dock Functionality: The ConstructionTree dock needs to be populated with the module hierarchy, and the ConstructionInspector needs to display the properties of the selected pieces.
|
||||
|
||||
Pressurize Button: The _on_pressurise_button_pressed function needs to be implemented. This will likely involve using the HullVolume.gd script.
|
||||
|
||||
This summary provides all the necessary context to continue our work on the plugin.
|
||||
88
src/addons/module_builder_plugin/builder_dock.gd
Normal file
88
src/addons/module_builder_plugin/builder_dock.gd
Normal file
@ -0,0 +1,88 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const PIECE_DIRECTORY = "res://scenes/ship/builder/pieces/"
|
||||
|
||||
@onready var pieces_container: HFlowContainer = %PiecesContainer
|
||||
@onready var clear_button: Button = %ClearButton
|
||||
|
||||
var preview_piece: StructuralPiece = null
|
||||
var active_piece_scene: PackedScene = null
|
||||
var rotation_angle: float = 0.0
|
||||
var grid_size: float = 50.0
|
||||
|
||||
signal active_piece_set(piece: StructuralPiece)
|
||||
|
||||
func _ready() -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
_setup_piece_buttons()
|
||||
clear_button.pressed.connect(_on_clear_button_pressed)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if preview_piece:
|
||||
preview_piece.queue_free()
|
||||
|
||||
func _setup_piece_buttons() -> void:
|
||||
for child in pieces_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
var dir = DirAccess.open(PIECE_DIRECTORY)
|
||||
if not dir:
|
||||
push_error("Could not open directory: ", PIECE_DIRECTORY)
|
||||
return
|
||||
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
|
||||
while file_name != "":
|
||||
if file_name.ends_with(".tscn"):
|
||||
var file_path = PIECE_DIRECTORY.path_join(file_name)
|
||||
|
||||
_create_piece_button(file_path)
|
||||
|
||||
file_name = dir.get_next()
|
||||
|
||||
dir.list_dir_end()
|
||||
|
||||
func _create_piece_button(path: String) -> void:
|
||||
var piece_scene = load(path) as PackedScene
|
||||
|
||||
if piece_scene and piece_scene.get_state().get_node_count() > 0:
|
||||
var root = piece_scene.instantiate() as StructuralPiece
|
||||
|
||||
if root:
|
||||
var button = Button.new()
|
||||
button.text = root.name
|
||||
|
||||
button.pressed.connect(Callable(self, "_on_piece_button_pressed").bind(piece_scene))
|
||||
|
||||
button.size = Vector2(100, 100)
|
||||
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
|
||||
var color_rect = root.find_child("ColorRect") as ColorRect
|
||||
if color_rect:
|
||||
var icon_texture = ImageTexture.create_from_image(Image.create(color_rect.size.x, color_rect.size.y, false, Image.FORMAT_RGBA8))
|
||||
icon_texture.get_image().fill(color_rect.color)
|
||||
button.icon = icon_texture
|
||||
|
||||
pieces_container.add_child(button)
|
||||
root.queue_free()
|
||||
|
||||
func _on_piece_button_pressed(scene: PackedScene):
|
||||
if is_instance_valid(preview_piece):
|
||||
preview_piece.queue_free()
|
||||
|
||||
active_piece_set.emit(scene)
|
||||
|
||||
|
||||
func _on_clear_button_pressed():
|
||||
if is_instance_valid(preview_piece):
|
||||
preview_piece.queue_free()
|
||||
preview_piece = null
|
||||
|
||||
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
|
||||
if not selected_nodes.is_empty() and selected_nodes[0] is Module:
|
||||
selected_nodes[0].clear_module()
|
||||
1
src/addons/module_builder_plugin/builder_dock.gd.uid
Normal file
1
src/addons/module_builder_plugin/builder_dock.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://yjbq3ihlmad8
|
||||
39
src/addons/module_builder_plugin/builder_dock.tscn
Normal file
39
src/addons/module_builder_plugin/builder_dock.tscn
Normal file
@ -0,0 +1,39 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cx6g4wgy3v8l"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://yjbq3ihlmad8" path="res://addons/module_builder_plugin/builder_dock.gd" id="1_8casj"]
|
||||
|
||||
[node name="BuilderDock" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_8casj")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Select Piece"
|
||||
|
||||
[node name="ClearButton" type="Button" parent="VBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
text = "Clear Module"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PiecesContainer" type="HFlowContainer" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
13
src/addons/module_builder_plugin/construction_inspector.tscn
Normal file
13
src/addons/module_builder_plugin/construction_inspector.tscn
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_scene format=3 uid="uid://bsubuh3qxqs8e"]
|
||||
|
||||
[node name="Construction Inspector" type="VBoxContainer"]
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.25
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Inspector"
|
||||
|
||||
[node name="Inspector" type="Control" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
14
src/addons/module_builder_plugin/construction_tree.tscn
Normal file
14
src/addons/module_builder_plugin/construction_tree.tscn
Normal file
@ -0,0 +1,14 @@
|
||||
[gd_scene format=3 uid="uid://2abscstf0tdd"]
|
||||
|
||||
[node name="Construction Tree" type="VBoxContainer"]
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.25
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Scene Hierarchy"
|
||||
|
||||
[node name="Tree" type="Tree" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
hide_root = true
|
||||
68
src/addons/module_builder_plugin/custom_grid.gd
Normal file
68
src/addons/module_builder_plugin/custom_grid.gd
Normal file
@ -0,0 +1,68 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
|
||||
@export var grid_size: float = 50.0
|
||||
|
||||
func _ready():
|
||||
set_process(true)
|
||||
|
||||
func _process(delta):
|
||||
queue_redraw()
|
||||
|
||||
func _draw():
|
||||
var camera_transform = get_viewport().get_canvas_transform()
|
||||
var camera_scale = camera_transform.get_scale().x
|
||||
var line_width = 1.0 / camera_scale
|
||||
|
||||
# Get the visible rectangle in world coordinates.
|
||||
var viewport_rect_in_world = camera_transform.affine_inverse() * get_viewport().get_visible_rect()
|
||||
var visible_rect_min = viewport_rect_in_world.position
|
||||
var visible_rect_max = viewport_rect_in_world.end
|
||||
|
||||
# --- Draw the Axes (always 2px thick in screen space) ---
|
||||
draw_line(Vector2(visible_rect_min.x, 0), Vector2(visible_rect_max.x, 0), Color.RED, 2 / camera_scale)
|
||||
draw_line(Vector2(0, visible_rect_min.y), Vector2(0, visible_rect_max.y), Color.GREEN, 2 / camera_scale)
|
||||
|
||||
# --- Draw the Major Grid Lines with Culling ---
|
||||
var major_step = grid_size
|
||||
const MIN_SCREEN_SPACING = 30.0 # Minimum pixel spacing between grid lines.
|
||||
# Skip lines if they would be too close on screen.
|
||||
while major_step * camera_scale < MIN_SCREEN_SPACING:
|
||||
major_step *= 2
|
||||
|
||||
var first_x_major = floor(visible_rect_min.x / major_step) * major_step
|
||||
var last_x_major = ceil(visible_rect_max.x / major_step) * major_step
|
||||
var first_y_major = floor(visible_rect_min.y / major_step) * major_step
|
||||
var last_y_major = ceil(visible_rect_max.y / major_step) * major_step
|
||||
|
||||
# Draw major vertical lines
|
||||
for x in range(int(first_x_major / major_step), int(last_x_major / major_step) + 1):
|
||||
var draw_x = x * major_step
|
||||
draw_line(Vector2(draw_x, visible_rect_min.y), Vector2(draw_x, visible_rect_max.y), Color(Color.GRAY, 0.5), line_width)
|
||||
|
||||
# Draw major horizontal lines
|
||||
for y in range(int(first_y_major / major_step), int(last_y_major / major_step) + 1):
|
||||
var draw_y = y * major_step
|
||||
draw_line(Vector2(visible_rect_min.x, draw_y), Vector2(visible_rect_max.x, draw_y), Color(Color.GRAY, 0.5), line_width)
|
||||
|
||||
# --- Draw Minor Grid Lines (only when zoomed in) ---
|
||||
if major_step == grid_size:
|
||||
var minor_step = grid_size / 5.0
|
||||
var first_x_minor = floor(visible_rect_min.x / minor_step) * minor_step
|
||||
var last_x_minor = ceil(visible_rect_max.x / minor_step) * minor_step
|
||||
var first_y_minor = floor(visible_rect_min.y / minor_step) * minor_step
|
||||
var last_y_minor = ceil(visible_rect_max.y / minor_step) * minor_step
|
||||
|
||||
# Draw minor vertical lines
|
||||
for x in range(int(first_x_minor / minor_step), int(last_x_minor / minor_step) + 1):
|
||||
var draw_x = x * minor_step
|
||||
# Skip major lines to avoid drawing over them.
|
||||
if fmod(draw_x, major_step) != 0:
|
||||
draw_line(Vector2(draw_x, visible_rect_min.y), Vector2(draw_x, visible_rect_max.y), Color(Color.GRAY, 0.2), line_width)
|
||||
|
||||
# Draw minor horizontal lines
|
||||
for y in range(int(first_y_minor / minor_step), int(last_y_minor / minor_step) + 1):
|
||||
var draw_y = y * minor_step
|
||||
# Skip major lines to avoid drawing over them.
|
||||
if fmod(draw_y, major_step) != 0:
|
||||
draw_line(Vector2(visible_rect_min.x, draw_y), Vector2(visible_rect_max.x, draw_y), Color(Color.GRAY, 0.2), line_width)
|
||||
1
src/addons/module_builder_plugin/custom_grid.gd.uid
Normal file
1
src/addons/module_builder_plugin/custom_grid.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cr6b1a5pvka3j
|
||||
524
src/addons/module_builder_plugin/module_builder_editor_plugin.gd
Normal file
524
src/addons/module_builder_plugin/module_builder_editor_plugin.gd
Normal file
@ -0,0 +1,524 @@
|
||||
@tool
|
||||
class_name BuilderEditor
|
||||
extends EditorPlugin
|
||||
|
||||
# --- Constants and Scene References ---
|
||||
const MAIN_EDITOR_SCENE = preload("res://addons/module_builder_plugin/module_editor.tscn")
|
||||
const BUILDER_DOCK_SCENE = preload("res://addons/module_builder_plugin/builder_dock.tscn")
|
||||
const CONSTRUCTION_TREE_SCENE = preload("res://addons/module_builder_plugin/construction_tree.tscn")
|
||||
const CONSTRUCTION_INSPECTOR_SCENE = preload("res://addons/module_builder_plugin/construction_inspector.tscn")
|
||||
|
||||
const MODULE_SCENE = preload("res://scenes/ship/builder/module.tscn")
|
||||
|
||||
# --- Dock references ---
|
||||
var builder_dock: Control
|
||||
var construction_tree_dock: Control
|
||||
var construction_inspector_dock: Control
|
||||
var tree_control: Tree
|
||||
|
||||
# --- Node References from the main screen scene ---
|
||||
var builder_world: World2D
|
||||
var main_screen: Control
|
||||
var main_viewport: SubViewport
|
||||
var zoom_label: Label
|
||||
var rotate_button: Button
|
||||
var center_button: Button
|
||||
var pressurize_button: Button
|
||||
var save_button: Button
|
||||
var builder_scene_root: Node2D
|
||||
var builder_camera: Camera2D
|
||||
|
||||
# --- State Management Enum ---
|
||||
enum BuilderState {
|
||||
IDLE,
|
||||
PLACING_STRUCTURAL,
|
||||
PLACING_COMPONENT,
|
||||
WIRING # For future use
|
||||
}
|
||||
var current_state: BuilderState = BuilderState.IDLE
|
||||
|
||||
# --- State Variables ---
|
||||
var preview_node = null # Can be either StructuralPiece or Component
|
||||
var active_scene: PackedScene = null # Can be either StructuralPiece or Component
|
||||
var rotation_angle: float = 0.0
|
||||
var grid_size: float = 50.0
|
||||
|
||||
var undo_redo: EditorUndoRedoManager
|
||||
|
||||
# --- Most of the setup functions remain the same ---
|
||||
|
||||
func _enter_tree():
|
||||
main_screen = MAIN_EDITOR_SCENE.instantiate()
|
||||
EditorInterface.get_editor_main_screen().add_child(main_screen)
|
||||
|
||||
main_viewport = main_screen.find_child("SubViewport")
|
||||
builder_camera = main_screen.find_child("Camera2D")
|
||||
|
||||
# Get button and label references
|
||||
zoom_label = main_screen.find_child("ZoomLabel")
|
||||
rotate_button = main_screen.find_child("RotateButton")
|
||||
center_button = main_screen.find_child("CenterButton")
|
||||
pressurize_button = main_screen.find_child("PressuriseButton")
|
||||
save_button = main_screen.find_child("SaveButton")
|
||||
|
||||
_setup_builder_world()
|
||||
_setup_button_connections()
|
||||
_update_ui_labels()
|
||||
|
||||
main_screen.hide()
|
||||
|
||||
undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
undo_redo.action_is_committing.connect(_on_undo_redo_action_committed)
|
||||
|
||||
func _setup_builder_world():
|
||||
builder_world = World2D.new()
|
||||
if is_instance_valid(main_viewport):
|
||||
main_viewport.world_2d = builder_world
|
||||
|
||||
builder_scene_root = Node2D.new()
|
||||
builder_scene_root.name = "BuilderRoot"
|
||||
main_viewport.add_child(builder_scene_root)
|
||||
|
||||
func _setup_docks():
|
||||
if BUILDER_DOCK_SCENE:
|
||||
builder_dock = BUILDER_DOCK_SCENE.instantiate()
|
||||
builder_dock.active_piece_set.connect(on_active_piece_set)
|
||||
add_control_to_bottom_panel(builder_dock, "Ship Builder")
|
||||
|
||||
if CONSTRUCTION_TREE_SCENE:
|
||||
construction_tree_dock = CONSTRUCTION_TREE_SCENE.instantiate()
|
||||
tree_control = construction_tree_dock.find_child("Tree")
|
||||
add_control_to_dock(DOCK_SLOT_LEFT_UR, construction_tree_dock)
|
||||
_refresh_tree_display()
|
||||
builder_world.changed.connect(_refresh_tree_display)
|
||||
|
||||
|
||||
if CONSTRUCTION_INSPECTOR_SCENE:
|
||||
construction_inspector_dock = CONSTRUCTION_INSPECTOR_SCENE.instantiate()
|
||||
add_control_to_dock(DOCK_SLOT_RIGHT_UL, construction_inspector_dock)
|
||||
|
||||
func switch_to_dock_tab(dock_control: Control, tab_name: String):
|
||||
var tab_container = dock_control.find_child("TabContainer")
|
||||
if not is_instance_valid(tab_container):
|
||||
print("Error: TabContainer not found in dock control.")
|
||||
return
|
||||
|
||||
for i in range(tab_container.get_tab_count()):
|
||||
if tab_container.get_tab_title(i) == tab_name:
|
||||
tab_container.current_tab = i
|
||||
return
|
||||
|
||||
print("Warning: Tab '%s' not found." % tab_name)
|
||||
|
||||
func _teardown_docks():
|
||||
if builder_dock:
|
||||
remove_control_from_bottom_panel(builder_dock)
|
||||
builder_dock.queue_free()
|
||||
|
||||
if construction_tree_dock:
|
||||
remove_control_from_docks(construction_tree_dock)
|
||||
construction_tree_dock.queue_free()
|
||||
builder_world.changed.disconnect(_refresh_tree_display)
|
||||
|
||||
if construction_inspector_dock:
|
||||
remove_control_from_docks(construction_inspector_dock)
|
||||
construction_inspector_dock.queue_free()
|
||||
|
||||
func _exit_tree():
|
||||
if main_screen:
|
||||
main_screen.queue_free()
|
||||
|
||||
func _has_main_screen() -> bool:
|
||||
return true
|
||||
|
||||
func _make_visible(visible):
|
||||
if main_screen:
|
||||
main_screen.visible = visible
|
||||
_setup_gui_input_listener(visible)
|
||||
|
||||
if visible:
|
||||
_setup_docks()
|
||||
else:
|
||||
_teardown_docks()
|
||||
|
||||
func _get_plugin_name():
|
||||
return "Ship Builder"
|
||||
|
||||
func _get_plugin_icon():
|
||||
return EditorInterface.get_editor_theme().get_icon("Node", "EditorIcons")
|
||||
|
||||
func _setup_gui_input_listener(connect: bool):
|
||||
if main_screen:
|
||||
if connect:
|
||||
main_screen.gui_input.connect(_on_viewport_input)
|
||||
else:
|
||||
main_screen.gui_input.disconnect(_on_viewport_input)
|
||||
|
||||
func _setup_button_connections():
|
||||
if rotate_button: rotate_button.pressed.connect(_on_rotate_button_pressed)
|
||||
if center_button: center_button.pressed.connect(_on_center_button_pressed)
|
||||
if pressurize_button: pressurize_button.pressed.connect(_on_pressurise_button_pressed)
|
||||
if save_button: save_button.pressed.connect(_on_save_button_pressed)
|
||||
|
||||
func _update_ui_labels():
|
||||
if is_instance_valid(zoom_label):
|
||||
var zoom_percent = int(builder_camera.zoom.x * 100)
|
||||
zoom_label.text = "Zoom: %d%%" % zoom_percent
|
||||
|
||||
func _process(_delta):
|
||||
_update_ui_labels()
|
||||
_refresh_tree_display()
|
||||
|
||||
func _on_viewport_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_RIGHT:
|
||||
builder_camera.position -= event.relative / builder_camera.zoom
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
builder_camera.zoom *= 1.1
|
||||
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
builder_camera.zoom /= 1.1
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
_update_preview_position()
|
||||
|
||||
match current_state:
|
||||
BuilderState.IDLE:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
_remove_piece_under_mouse()
|
||||
|
||||
BuilderState.PLACING_STRUCTURAL:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
_place_piece_from_preview()
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
on_clear_preview()
|
||||
|
||||
BuilderState.PLACING_COMPONENT:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
_place_component_from_preview()
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
on_clear_preview()
|
||||
|
||||
BuilderState.WIRING:
|
||||
pass
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEvent):
|
||||
if not event.is_pressed(): return
|
||||
if event is InputEventKey and event.as_text().to_lower() == "r":
|
||||
_on_rotate_button_pressed()
|
||||
get_tree().set_input_as_handled()
|
||||
|
||||
|
||||
func on_active_piece_set(scene: PackedScene):
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
|
||||
current_state = BuilderState.PLACING_STRUCTURAL
|
||||
active_scene = scene
|
||||
preview_node = scene.instantiate() as StructuralPiece
|
||||
preview_node.is_preview = true
|
||||
builder_scene_root.add_child(preview_node)
|
||||
_update_preview_position()
|
||||
|
||||
func _on_component_selected(component_scene: PackedScene):
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
|
||||
current_state = BuilderState.PLACING_COMPONENT
|
||||
active_scene = component_scene
|
||||
preview_node = component_scene.instantiate() as Component
|
||||
builder_scene_root.add_child(preview_node)
|
||||
|
||||
print("Now placing component: ", component_scene.resource_path)
|
||||
|
||||
func on_clear_preview():
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
preview_node = null
|
||||
active_scene = null
|
||||
current_state = BuilderState.IDLE
|
||||
|
||||
|
||||
func _update_preview_position():
|
||||
if not is_instance_valid(preview_node):
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
|
||||
match current_state:
|
||||
BuilderState.PLACING_STRUCTURAL:
|
||||
var snapped_pos = world_mouse_pos.snapped(Vector2(grid_size, grid_size))
|
||||
preview_node.global_position = snapped_pos
|
||||
preview_node.rotation = rotation_angle
|
||||
BuilderState.PLACING_COMPONENT:
|
||||
var target_module = _find_first_module()
|
||||
if target_module:
|
||||
var closest_point = _find_closest_attachment_point(target_module, world_mouse_pos)
|
||||
if closest_point:
|
||||
preview_node.global_position = closest_point.position
|
||||
else:
|
||||
preview_node.global_position = world_mouse_pos
|
||||
else:
|
||||
preview_node.global_position = world_mouse_pos
|
||||
|
||||
|
||||
# --- REFACTORED: Piece Placement ---
|
||||
func _place_piece_from_preview():
|
||||
if not is_instance_valid(preview_node) or not is_instance_valid(active_scene):
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
var snapped_pos = world_mouse_pos.snapped(Vector2(grid_size, grid_size))
|
||||
|
||||
var target_module = _find_nearby_modules(snapped_pos)
|
||||
if not target_module:
|
||||
target_module = MODULE_SCENE.instantiate() as Module
|
||||
builder_scene_root.add_child(target_module)
|
||||
target_module.global_position = snapped_pos
|
||||
target_module.owner = builder_scene_root
|
||||
|
||||
var piece_to_place = active_scene.instantiate()
|
||||
|
||||
# --- The main change: Add as a direct child of the module ---
|
||||
target_module.add_child(piece_to_place)
|
||||
piece_to_place.owner = target_module
|
||||
piece_to_place.rotation = rotation_angle
|
||||
piece_to_place.global_position = snapped_pos
|
||||
|
||||
undo_redo.create_action("Place Structural Piece")
|
||||
undo_redo.add_do_method(target_module, "add_child", piece_to_place)
|
||||
undo_redo.add_do_method(piece_to_place, "set_owner", target_module)
|
||||
undo_redo.add_do_method(target_module, "_recalculate_collision_shape")
|
||||
undo_redo.add_undo_method(target_module, "remove_child", piece_to_place)
|
||||
undo_redo.add_undo_method(target_module, "_recalculate_collision_shape")
|
||||
undo_redo.commit_action()
|
||||
|
||||
# --- Component Placement remains the same ---
|
||||
func _place_component_from_preview():
|
||||
if not is_instance_valid(preview_node) or not is_instance_valid(active_scene):
|
||||
push_error("Cannot place component: Invalid preview or scene.")
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
|
||||
var target_module = _find_first_module()
|
||||
if not target_module:
|
||||
push_error("No module found to attach component to.")
|
||||
return
|
||||
|
||||
var closest_point = _find_closest_attachment_point(target_module, world_mouse_pos)
|
||||
if not closest_point:
|
||||
print("No valid attachment point nearby.")
|
||||
return
|
||||
|
||||
var component_to_place = active_scene.instantiate() as Component
|
||||
|
||||
target_module.attach_component(component_to_place, closest_point.position, closest_point.piece)
|
||||
|
||||
undo_redo.create_action("Place Component")
|
||||
undo_redo.add_do_method(target_module, "attach_component", component_to_place, closest_point.position, closest_point.piece)
|
||||
undo_redo.add_undo_method(target_module, "remove_child", component_to_place)
|
||||
undo_redo.add_do_method(target_module, "recalculate_physical_properties")
|
||||
undo_redo.add_undo_method(target_module, "recalculate_physical_properties")
|
||||
undo_redo.commit_action()
|
||||
|
||||
preview_node.global_position = closest_point.position
|
||||
|
||||
# --- Find Nearby Modules remains the same ---
|
||||
func _find_nearby_modules(position: Vector2) -> Module:
|
||||
const OVERLAP_MARGIN = 20.0
|
||||
|
||||
if not active_scene or not active_scene.can_instantiate(): return null
|
||||
var piece_instance = active_scene.instantiate()
|
||||
var shape_node = piece_instance.find_child("CollisionShape2D")
|
||||
if not shape_node:
|
||||
piece_instance.queue_free()
|
||||
return null
|
||||
var piece_shape = shape_node.shape
|
||||
piece_instance.queue_free()
|
||||
|
||||
var enlarged_shape
|
||||
if piece_shape is RectangleShape2D:
|
||||
enlarged_shape = RectangleShape2D.new()
|
||||
enlarged_shape.size = piece_shape.size + Vector2(OVERLAP_MARGIN, OVERLAP_MARGIN) * 2
|
||||
elif piece_shape is CapsuleShape2D:
|
||||
enlarged_shape = CapsuleShape2D.new()
|
||||
enlarged_shape.radius = piece_shape.radius + OVERLAP_MARGIN
|
||||
enlarged_shape.height = piece_shape.height + OVERLAP_MARGIN
|
||||
else:
|
||||
return null
|
||||
|
||||
var space_state = builder_world.direct_space_state
|
||||
var query = PhysicsShapeQueryParameters2D.new()
|
||||
query.set_shape(enlarged_shape)
|
||||
query.transform = Transform2D(0, position)
|
||||
|
||||
var result = space_state.intersect_shape(query, 1)
|
||||
|
||||
if not result.is_empty():
|
||||
var collider = result[0].get("collider")
|
||||
if collider is StructuralPiece:
|
||||
# --- REFACTORED: The module is now the direct parent/owner ---
|
||||
if is_instance_valid(collider.owner) and collider.owner is Module:
|
||||
return collider.owner
|
||||
|
||||
return null
|
||||
|
||||
func _find_first_module() -> Module:
|
||||
for node in builder_scene_root.get_children():
|
||||
if node is Module:
|
||||
return node
|
||||
return null
|
||||
|
||||
func _remove_piece_under_mouse():
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
|
||||
var space_state = builder_world.direct_space_state
|
||||
var query = PhysicsPointQueryParameters2D.new()
|
||||
query.position = world_mouse_pos
|
||||
var result = space_state.intersect_point(query, 1)
|
||||
|
||||
if not result.is_empty():
|
||||
var collider = result[0].get("collider")
|
||||
if collider is StructuralPiece:
|
||||
_remove_piece_with_undo_redo(collider)
|
||||
elif collider is Component:
|
||||
pass
|
||||
|
||||
|
||||
# --- REFACTORED: Piece Removal ---
|
||||
func _remove_piece_with_undo_redo(piece: StructuralPiece):
|
||||
var module = piece.owner as Module
|
||||
if not is_instance_valid(module) or not module is Module:
|
||||
return
|
||||
|
||||
undo_redo.create_action("Remove Structural Piece")
|
||||
|
||||
# If this is the last structural piece of the module...
|
||||
if module.get_structural_pieces().size() == 1:
|
||||
# ...remove the entire module.
|
||||
undo_redo.add_do_method(builder_scene_root, "remove_child", module)
|
||||
undo_redo.add_undo_method(builder_scene_root, "add_child", module)
|
||||
undo_redo.add_undo_method(module, "set_owner", builder_scene_root)
|
||||
else:
|
||||
# Otherwise, just remove the single piece from its parent (the module).
|
||||
undo_redo.add_do_method(module, "remove_child", piece)
|
||||
undo_redo.add_do_method(module, "_recalculate_collision_shape")
|
||||
|
||||
undo_redo.add_undo_method(module, "add_child", piece)
|
||||
undo_redo.add_undo_method(piece, "set_owner", module) # Re-assign owner on undo
|
||||
undo_redo.add_undo_method(module, "_recalculate_collision_shape")
|
||||
|
||||
undo_redo.commit_action()
|
||||
|
||||
# --- Toolbar Button Functions (No changes needed) ---
|
||||
func _on_rotate_button_pressed():
|
||||
rotation_angle = wrapf(rotation_angle + PI / 2, 0, TAU)
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.rotation = rotation_angle
|
||||
_update_preview_position()
|
||||
|
||||
func _on_center_button_pressed():
|
||||
builder_camera.position = Vector2.ZERO
|
||||
builder_camera.zoom = Vector2(1.0, 1.0)
|
||||
|
||||
func _on_pressurise_button_pressed():
|
||||
pass
|
||||
|
||||
func _on_save_button_pressed():
|
||||
var module_to_save: Module
|
||||
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
|
||||
if not selected_nodes.is_empty() and selected_nodes[0] is Module:
|
||||
module_to_save = selected_nodes[0]
|
||||
else:
|
||||
module_to_save = _find_first_module()
|
||||
|
||||
if not is_instance_valid(module_to_save):
|
||||
push_error("Error: No Module node found or selected to save.")
|
||||
return
|
||||
|
||||
var save_dialog = EditorFileDialog.new()
|
||||
save_dialog.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE
|
||||
save_dialog.add_filter("*.tscn; Godot Scene")
|
||||
save_dialog.current_path = "res://modules/" + module_to_save.name + ".tscn"
|
||||
|
||||
EditorInterface.get_editor_main_screen().add_child(save_dialog)
|
||||
save_dialog.popup_centered_ratio()
|
||||
|
||||
save_dialog.file_selected.connect(Callable(self, "_perform_save").bind(module_to_save))
|
||||
|
||||
func _perform_save(file_path: String, module_to_save: Module):
|
||||
var save_dir = file_path.get_base_dir()
|
||||
var dir = DirAccess.open("res://")
|
||||
if not dir.dir_exists(save_dir):
|
||||
dir.make_dir_recursive(save_dir)
|
||||
|
||||
var packed_scene = PackedScene.new()
|
||||
var error = packed_scene.pack(module_to_save)
|
||||
|
||||
if error != OK:
|
||||
push_error("Error packing scene: ", error_string(error))
|
||||
return
|
||||
|
||||
var save_result = ResourceSaver.save(packed_scene, file_path)
|
||||
|
||||
if save_result == OK:
|
||||
print("Module saved successfully to ", file_path)
|
||||
else:
|
||||
push_error("Error saving scene: ", error_string(save_result))
|
||||
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
|
||||
func _on_undo_redo_action_committed():
|
||||
_refresh_tree_display()
|
||||
|
||||
# --- REFACTORED: Tree Display ---
|
||||
func _refresh_tree_display():
|
||||
if not is_instance_valid(tree_control):
|
||||
return
|
||||
|
||||
tree_control.clear()
|
||||
var root_item = tree_control.create_item()
|
||||
root_item.set_text(0, builder_scene_root.name)
|
||||
|
||||
# Iterate through all modules and populate the tree.
|
||||
for module in builder_scene_root.get_children():
|
||||
if module is Module:
|
||||
var module_item = tree_control.create_item(root_item)
|
||||
module_item.set_text(0, module.name)
|
||||
module_item.set_meta("node", module)
|
||||
|
||||
# Use the module's helper functions to find children
|
||||
for piece in module.get_structural_pieces():
|
||||
var piece_item = tree_control.create_item(module_item)
|
||||
piece_item.set_text(0, piece.name)
|
||||
piece_item.set_meta("node", piece)
|
||||
|
||||
for component in module.get_components():
|
||||
var component_item = tree_control.create_item(module_item)
|
||||
component_item.set_text(0, component.name)
|
||||
component_item.set_meta("node", component)
|
||||
|
||||
|
||||
func _find_closest_attachment_point(module: Module, world_pos: Vector2):
|
||||
var min_distance_sq = module.COMPONENT_GRID_SIZE * module.COMPONENT_GRID_SIZE * 0.5
|
||||
var closest_point = null
|
||||
|
||||
for point in module.get_attachment_points():
|
||||
var dist_sq = point.position.distance_squared_to(world_pos)
|
||||
if dist_sq < min_distance_sq:
|
||||
min_distance_sq = dist_sq
|
||||
closest_point = point
|
||||
|
||||
return closest_point
|
||||
@ -0,0 +1 @@
|
||||
uid://wndpl5b4kfkx
|
||||
83
src/addons/module_builder_plugin/module_editor.tscn
Normal file
83
src/addons/module_builder_plugin/module_editor.tscn
Normal file
@ -0,0 +1,83 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b018j62t6j24l"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cr6b1a5pvka3j" path="res://addons/module_builder_plugin/custom_grid.gd" id="1_5stev"]
|
||||
|
||||
[node name="ModuleEditor" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="MainViewport" type="SubViewportContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
stretch = true
|
||||
|
||||
[node name="SubViewport" type="SubViewport" parent="MainViewport"]
|
||||
transparent_bg = true
|
||||
handle_input_locally = false
|
||||
size = Vector2i(1152, 648)
|
||||
render_target_update_mode = 4
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="MainViewport/SubViewport"]
|
||||
zoom = Vector2(0.5, 0.5)
|
||||
|
||||
[node name="Grid" type="Node2D" parent="MainViewport/SubViewport"]
|
||||
script = ExtResource("1_5stev")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 318.0
|
||||
offset_bottom = 300.0
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RotateButton" type="Button" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Rotate"
|
||||
|
||||
[node name="CenterButton" type="Button" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Center"
|
||||
|
||||
[node name="PressuriseButton" type="Button" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Pressurize"
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ZoomLabel" type="Label" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Zoom: 100%"
|
||||
|
||||
[node name="SaveName" type="TextEdit" parent="VBoxContainer/Toolbar"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
placeholder_text = "./EngineeringModule"
|
||||
|
||||
[node name="SaveButton" type="Button" parent="VBoxContainer/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Save Scene"
|
||||
7
src/addons/module_builder_plugin/plugin.cfg
Normal file
7
src/addons/module_builder_plugin/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Module Builder Plugin"
|
||||
description="An editor tool for building modular ships."
|
||||
author="Olof Pettersson"
|
||||
version="0.1"
|
||||
script="module_builder_editor_plugin.gd"
|
||||
1
src/icon.svg
Normal file
1
src/icon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 994 B |
37
src/icon.svg.import
Normal file
37
src/icon.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7sxyli8cn36w"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
6
src/main.tscn
Normal file
6
src/main.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bkcouefvi7iup" path="res://scripts/star_system.gd" id="1_ig7tw"]
|
||||
|
||||
[node name="StarSystem" type="Node3D"]
|
||||
script = ExtResource("1_ig7tw")
|
||||
27
src/main.tscn6546160625.tmp
Normal file
27
src/main.tscn6546160625.tmp
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_scene load_steps=9 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"]
|
||||
[ext_resource type="PackedScene" uid="uid://clt4qlsjcfgln" path="res://scenes/planet.tscn" id="3_272bh"]
|
||||
[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"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_asteroid_belts = 0
|
||||
star_scene = ExtResource("2_7mycd")
|
||||
planet_scene = ExtResource("3_272bh")
|
||||
moon_scene = ExtResource("4_5vw27")
|
||||
station_scene = ExtResource("5_kek77")
|
||||
asteroid_scene = ExtResource("6_4c57u")
|
||||
sim_scale = 1e+09
|
||||
|
||||
[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("..")
|
||||
566
src/modules/3d_test_ship.tscn
Normal file
566
src/modules/3d_test_ship.tscn
Normal file
@ -0,0 +1,566 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bkwogkfqk2uxo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ktv2t"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_shb7f"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_ism2t"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ism2t"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[node name="3dTestShip" type="RigidBody3D" unique_id=246037729]
|
||||
script = ExtResource("1_ktv2t")
|
||||
physics_mode = 1
|
||||
base_mass = 10000.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate7" parent="." unique_id=1182121679 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate8" parent="." unique_id=294855274 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate9" parent="." unique_id=130054924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate4" parent="." unique_id=2133064539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate5" parent="." unique_id=1436331513 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate6" parent="." unique_id=1249365999 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate11" parent="." unique_id=1656979163 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate13" parent="." unique_id=1426276711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate14" parent="." unique_id=1212526811 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate15" parent="." unique_id=403515873 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate16" parent="." unique_id=145935239 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate17" parent="." unique_id=1662804653 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate18" parent="." unique_id=741829932 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate21" parent="." unique_id=31417961 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate22" parent="." unique_id=1845702661 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate23" parent="." unique_id=1747432968 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate24" parent="." unique_id=1486518216 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate25" parent="." unique_id=1880158566 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate26" parent="." unique_id=1506445603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate27" parent="." unique_id=1749302489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate31" parent="." unique_id=1965678834 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate32" parent="." unique_id=515940324 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate33" parent="." unique_id=313389603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate34" parent="." unique_id=363616195 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate35" parent="." unique_id=568985619 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate36" parent="." unique_id=193191417 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate38" parent="." unique_id=1152815429 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate40" parent="." unique_id=1303768723 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate41" parent="." unique_id=1489680526 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate42" parent="." unique_id=1454642421 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate43" parent="." unique_id=1322280114 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate44" parent="." unique_id=1380061102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate45" parent="." unique_id=1740305308 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate48" parent="." unique_id=587023569 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate49" parent="." unique_id=1103858035 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate50" parent="." unique_id=916625356 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate51" parent="." unique_id=2115734988 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate52" parent="." unique_id=1715698306 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate53" parent="." unique_id=369018899 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate54" parent="." unique_id=1618415296 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate57" parent="." unique_id=1148292814 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 0, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate58" parent="." unique_id=1183219370 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate59" parent="." unique_id=95522376 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate60" parent="." unique_id=960534764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, -1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate61" parent="." unique_id=1862079328 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate62" parent="." unique_id=876185578 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate64" parent="." unique_id=622302151 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate65" parent="." unique_id=2027647666 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate66" parent="." unique_id=335333911 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate63" parent="." unique_id=779321466 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate69" parent="." unique_id=391423682 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate70" parent="." unique_id=1436426809 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate71" parent="." unique_id=1045660804 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate72" parent="." unique_id=1696784058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate73" parent="." unique_id=1709873058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate74" parent="." unique_id=1071906843 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate75" parent="." unique_id=413542580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate76" parent="." unique_id=448578032 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate77" parent="." unique_id=1162322851 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate78" parent="." unique_id=790206161 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate79" parent="." unique_id=1019136641 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate80" parent="." unique_id=152922175 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate81" parent="." unique_id=771888008 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate82" parent="." unique_id=816092557 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate83" parent="." unique_id=1871920861 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate84" parent="." unique_id=103727539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate85" parent="." unique_id=1457444620 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate86" parent="." unique_id=1402217859 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate87" parent="." unique_id=293240152 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate88" parent="." unique_id=158231735 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate89" parent="." unique_id=2017317978 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate90" parent="." unique_id=1810711362 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate91" parent="." unique_id=648502427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate92" parent="." unique_id=1280848561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate93" parent="." unique_id=1000182357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate94" parent="." unique_id=663755561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate95" parent="." unique_id=977211031 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate96" parent="." unique_id=1017704164 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate97" parent="." unique_id=2095269489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate98" parent="." unique_id=615154295 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate99" parent="." unique_id=1435686924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate100" parent="." unique_id=361501534 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate101" parent="." unique_id=776176100 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate102" parent="." unique_id=1146417492 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate103" parent="." unique_id=1413321748 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate104" parent="." unique_id=1044980803 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate105" parent="." unique_id=1804409489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate106" parent="." unique_id=1076107521 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate107" parent="." unique_id=1190510681 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate108" parent="." unique_id=855909591 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate109" parent="." unique_id=946006990 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate110" parent="." unique_id=1957722835 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate111" parent="." unique_id=1708941560 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate112" parent="." unique_id=598393913 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate113" parent="." unique_id=629535431 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate114" parent="." unique_id=1483594858 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate115" parent="." unique_id=1186769437 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate116" parent="." unique_id=752889015 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate117" parent="." unique_id=175698677 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate118" parent="." unique_id=670641245 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate119" parent="." unique_id=988678524 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate120" parent="." unique_id=896262764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate121" parent="." unique_id=1336630931 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate122" parent="." unique_id=101919359 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate123" parent="." unique_id=1356736016 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate124" parent="." unique_id=742815341 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate125" parent="." unique_id=1651537246 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate126" parent="." unique_id=1253078352 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate127" parent="." unique_id=519787812 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate128" parent="." unique_id=629828036 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate129" parent="." unique_id=2010663580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate130" parent="." unique_id=1705163002 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate131" parent="." unique_id=1635599014 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate132" parent="." unique_id=789401102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate133" parent="." unique_id=1671040057 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate134" parent="." unique_id=2118015321 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate135" parent="." unique_id=1970124357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate136" parent="." unique_id=2129372302 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate137" parent="." unique_id=543355427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate138" parent="." unique_id=1885736043 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate139" parent="." unique_id=654209436 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate140" parent="." unique_id=1938132143 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate141" parent="." unique_id=486424951 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate142" parent="." unique_id=910140496 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate143" parent="." unique_id=515293159 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate144" parent="." unique_id=890871001 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate145" parent="." unique_id=1626468827 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate146" parent="." unique_id=578516444 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate147" parent="." unique_id=402255852 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate148" parent="." unique_id=1631434711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate149" parent="." unique_id=726702930 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate150" parent="." unique_id=1001521061 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Spawner" parent="." unique_id=6714366 instance=ExtResource("3_ism2t")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1071155008]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, -3)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="." unique_id=151820223]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, -3)
|
||||
|
||||
[node name="OmniLight3D3" type="OmniLight3D" parent="." unique_id=390575041]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, 4)
|
||||
|
||||
[node name="OmniLight3D4" type="OmniLight3D" parent="." unique_id=1659652061]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, 4)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1905582997]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2096937457]
|
||||
replication_config = SubResource("SceneReplicationConfig_ism2t")
|
||||
26
src/modules/physics_testing_ship.tscn
Normal file
26
src/modules/physics_testing_ship.tscn
Normal file
@ -0,0 +1,26 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://xcgmicfdqqb1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ogx5r"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_nyqc6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_3bya3"]
|
||||
|
||||
[node name="PhysicsTestingShip" type="RigidBody3D"]
|
||||
script = ExtResource("1_ogx5r")
|
||||
base_mass = 200.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_nyqc6")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, -1, 0)
|
||||
|
||||
[node name="Spawner" parent="." instance=ExtResource("3_3bya3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.021089494, 0)
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -2)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, -2)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
7
src/modules/test_ship.tscn
Normal file
7
src/modules/test_ship.tscn
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cwblg6q5qse6c"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="1_obkto"]
|
||||
|
||||
[node name="TestShip" type="Node3D"]
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("1_obkto")]
|
||||
14
src/scenes/ship/computer/control_panel.gd
Normal file
14
src/scenes/ship/computer/control_panel.gd
Normal file
@ -0,0 +1,14 @@
|
||||
@tool
|
||||
class_name ControlPanel
|
||||
extends Resource
|
||||
|
||||
## The UI scene for this panel (e.g., a lever, a screen).
|
||||
@export var ui_scene: PackedScene
|
||||
|
||||
## Describes the signals this panel emits (e.g., "lever_pulled").
|
||||
func get_output_signals() -> Dictionary:
|
||||
return {}
|
||||
|
||||
## Describes the functions this panel has to display data (e.g., "update_text").
|
||||
func get_input_sockets() -> Dictionary:
|
||||
return {}
|
||||
1
src/scenes/ship/computer/control_panel.gd.uid
Normal file
1
src/scenes/ship/computer/control_panel.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cskf26i7vnxug
|
||||
43
src/scenes/ship/computer/data_types/data_types.gd
Normal file
43
src/scenes/ship/computer/data_types/data_types.gd
Normal file
@ -0,0 +1,43 @@
|
||||
class_name DataTypes
|
||||
extends Node
|
||||
|
||||
# TODO: Add comments and export tooltips for these classes so players can understand what they hold
|
||||
|
||||
class ThrusterCalibration:
|
||||
var thruster_data: Dictionary[Thruster, ThrusterData]
|
||||
var max_pos_torque: float
|
||||
var max_neg_torque: float
|
||||
|
||||
class ThrusterData:
|
||||
enum ThrusterType {
|
||||
LINEAR,
|
||||
ROTATIONAL,
|
||||
UNCALIBRATED
|
||||
}
|
||||
var thruster_node: Thruster
|
||||
var thruster_type: ThrusterType = ThrusterType.UNCALIBRATED
|
||||
var measured_torque_vector: Vector3 # The rotational force it provides
|
||||
var measured_thrust: float # The linear force it provides
|
||||
|
||||
class ImpulsiveBurnPlan:
|
||||
var delta_v_magnitude: float
|
||||
var wait_time: float = 0.0
|
||||
var burn_duration: float
|
||||
var desired_basis: Basis
|
||||
|
||||
class PathProjection:
|
||||
var body_ref: OrbitalBody3D
|
||||
var points: Array[PathPoint]
|
||||
|
||||
func _init(b: OrbitalBody3D):
|
||||
body_ref = b
|
||||
|
||||
class PathPoint:
|
||||
var time: float # Time in seconds from the start of the projection
|
||||
var position: Vector3
|
||||
var velocity: Vector3
|
||||
|
||||
func _init(t: float, p: Vector3, v: Vector3):
|
||||
time = t
|
||||
position = p
|
||||
velocity = v
|
||||
1
src/scenes/ship/computer/data_types/data_types.gd.uid
Normal file
1
src/scenes/ship/computer/data_types/data_types.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://jew2ur3plyy6
|
||||
16
src/scenes/ship/computer/databank.gd
Normal file
16
src/scenes/ship/computer/databank.gd
Normal file
@ -0,0 +1,16 @@
|
||||
class_name Databank
|
||||
extends Node
|
||||
|
||||
var root_module: Module
|
||||
|
||||
# --- Initialization ---
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return []
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return []
|
||||
1
src/scenes/ship/computer/databank.gd.uid
Normal file
1
src/scenes/ship/computer/databank.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://osk1l75vlikn
|
||||
9
src/scenes/ship/computer/panels/button_panel.tres
Normal file
9
src/scenes/ship/computer/panels/button_panel.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="StationPanel" load_steps=3 format=3 uid="uid://c4wyouanvf86c"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cskf26i7vnxug" path="res://scenes/ship/computer/control_panel.gd" id="1_ts7kh"]
|
||||
[ext_resource type="PackedScene" uid="uid://dt1t2n7dewucw" path="res://scenes/ship/computer/UI/button_panel.tscn" id="2_72ur5"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ts7kh")
|
||||
ui_scene = ExtResource("2_72ur5")
|
||||
metadata/_custom_type_script = "uid://cskf26i7vnxug"
|
||||
274
src/scenes/ship/computer/panels/frame/panel_frame.gd
Normal file
274
src/scenes/ship/computer/panels/frame/panel_frame.gd
Normal file
@ -0,0 +1,274 @@
|
||||
@tool
|
||||
extends Container
|
||||
class_name PanelFrame
|
||||
|
||||
# A column is defined as Constants.UI_GRID_SIZE pixels in width
|
||||
@export var columns = 12
|
||||
# A row is defined as Constants.UI_GRID_SIZE pixels in height
|
||||
@export var rows = 6
|
||||
|
||||
enum WiringState { IDLE, DRAGGING_WIRE }
|
||||
var current_state: WiringState = WiringState.IDLE
|
||||
var current_schematic: WiringSchematic
|
||||
var active_wire_points: Array[Vector2] = []
|
||||
var start_socket: Socket
|
||||
var end_socket: Socket
|
||||
var wiring_mode: bool = false
|
||||
|
||||
var databanks: Array[Databank]
|
||||
var databanks_container: GridContainer
|
||||
|
||||
# --- NO CHANGE HERE ---
|
||||
# This getter is a nice way to access only the BasePanel children.
|
||||
var installed_panels: Array[BasePanel]:
|
||||
get:
|
||||
installed_panels = []
|
||||
for child in get_children():
|
||||
if child is BasePanel:
|
||||
installed_panels.append(child as BasePanel)
|
||||
return installed_panels
|
||||
|
||||
func _init() -> void:
|
||||
size = Vector2(columns * Constants.UI_GRID_SIZE, rows * Constants.UI_GRID_SIZE)
|
||||
|
||||
# --- NEW FUNCTION ---
|
||||
# This is a crucial function for all Control nodes, especially containers.
|
||||
# It tells the layout system the smallest size this container can be.
|
||||
func _get_minimum_size() -> Vector2:
|
||||
# The minimum size is simply the grid dimensions multiplied by the pixel size.
|
||||
return Vector2(columns * Constants.UI_GRID_SIZE, rows * Constants.UI_GRID_SIZE)
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_SORT_CHILDREN:
|
||||
_sort_children()
|
||||
|
||||
func build(panel_scenes: Array[PackedScene], station: SystemStation):
|
||||
# Instead of manually calling our placement function, we tell Godot
|
||||
# that the layout needs to be updated. Godot will then call
|
||||
# _notification(NOTIFICATION_SORT_CHILDREN) for us at the correct time.
|
||||
var col = 0
|
||||
var row = 0
|
||||
#print("STATION: Building panels using autolayout")
|
||||
|
||||
for panel_scene in panel_scenes:
|
||||
if not panel_scene: continue
|
||||
|
||||
var panel_instance = panel_scene.instantiate()
|
||||
if not panel_instance is BasePanel:
|
||||
panel_instance.queue_free()
|
||||
continue
|
||||
|
||||
var panel: BasePanel = panel_instance as BasePanel
|
||||
panel.initialize(station)
|
||||
|
||||
# Store the grid coordinates on the panel itself. The container will use
|
||||
# this information when it arranges its children.
|
||||
if panel.grid_height <= self.rows - row:
|
||||
panel.placed_in_col = col
|
||||
panel.placed_in_row = row
|
||||
add_child(panel)
|
||||
#print(" - panel %s placed at: Col %s, Row %s" % [panel, col, row])
|
||||
row += panel.grid_height
|
||||
else:
|
||||
var last_panel = get_children()[-1]
|
||||
col += last_panel.grid_width
|
||||
row = 0
|
||||
panel.placed_in_col = col
|
||||
panel.placed_in_row = row
|
||||
add_child(panel)
|
||||
#print(" - panel %s placed at: Col %s, Row %s" % [panel, col, row])
|
||||
row += panel.grid_height
|
||||
|
||||
queue_sort()
|
||||
|
||||
|
||||
# This is the core logic. It positions and sizes every child.
|
||||
func _sort_children():
|
||||
#print("PanelFrame Sorting children")
|
||||
for child in get_children():
|
||||
if child == databanks_container:
|
||||
print("Databanks container found %s" % child)
|
||||
fit_child_in_rect(child, Rect2(Vector2(0, rows * Constants.UI_GRID_SIZE), child.size))
|
||||
continue
|
||||
# Skip any nodes that aren't a BasePanel.
|
||||
if not child is BasePanel:
|
||||
continue
|
||||
|
||||
|
||||
var panel := child as BasePanel
|
||||
|
||||
# Calculate the desired position based on the panel's stored grid coordinates.
|
||||
var start_pos = Vector2(panel.placed_in_col * Constants.UI_GRID_SIZE, panel.placed_in_row * Constants.UI_GRID_SIZE)
|
||||
|
||||
# Calculate the desired size based on the panel's width and height in grid units.
|
||||
var panel_size = Vector2(panel.grid_width * Constants.UI_GRID_SIZE, panel.grid_height * Constants.UI_GRID_SIZE)
|
||||
|
||||
#print(" - %s, Pos %s Size %s" % [panel, start_pos, panel_size])
|
||||
|
||||
# This single function tells the container to position AND size the child
|
||||
# within the given rectangle. The Rect2's origin is the position.
|
||||
fit_child_in_rect(panel, Rect2(start_pos, panel_size))
|
||||
|
||||
# TODO: Expose grid to install panels
|
||||
|
||||
func toggle_wiring_mode():
|
||||
wiring_mode = !wiring_mode
|
||||
|
||||
for panel in installed_panels:
|
||||
panel.set_wiring_mode(wiring_mode)
|
||||
|
||||
if wiring_mode:
|
||||
_build_databanks(databanks)
|
||||
pass
|
||||
|
||||
if is_instance_valid(databanks_container):
|
||||
if wiring_mode: databanks_container.show()
|
||||
else: databanks_container.hide()
|
||||
|
||||
class InstalledDatabank:
|
||||
extends Control
|
||||
|
||||
var databank_ref: Databank
|
||||
var all_sockets: Array[Socket] = []
|
||||
var SocketScene: PackedScene = preload("res://scenes/ship/computer/wiring/socket.tscn")
|
||||
var inputs_container: VBoxContainer
|
||||
var outputs_container: VBoxContainer
|
||||
|
||||
func _populate_sockets():
|
||||
all_sockets.clear()
|
||||
|
||||
if not is_instance_valid(inputs_container):
|
||||
inputs_container = VBoxContainer.new()
|
||||
add_child(inputs_container)
|
||||
|
||||
if not is_instance_valid(outputs_container):
|
||||
outputs_container = VBoxContainer.new()
|
||||
add_child(outputs_container)
|
||||
|
||||
# Populate Input Sockets
|
||||
for socket_name in databank_ref.get_input_sockets():
|
||||
var socket = SocketScene.instantiate()
|
||||
inputs_container.add_child(socket)
|
||||
socket.initialize(socket_name, Socket.SocketType.INPUT)
|
||||
all_sockets.append(socket)
|
||||
|
||||
# Populate Output Sockets
|
||||
for socket_name in databank_ref.get_output_sockets():
|
||||
var socket = SocketScene.instantiate()
|
||||
outputs_container.add_child(socket)
|
||||
socket.initialize(socket_name, Socket.SocketType.OUTPUT)
|
||||
all_sockets.append(socket)
|
||||
|
||||
func _build_databanks(dbs_to_install: Array[Databank]):
|
||||
if not is_instance_valid(databanks_container):
|
||||
databanks_container = GridContainer.new()
|
||||
databanks_container.columns = columns
|
||||
databanks_container.add_theme_constant_override("h_separation", Constants.UI_GRID_SIZE * 3)
|
||||
databanks_container.add_theme_constant_override("v_separation", Constants.UI_GRID_SIZE + 16)
|
||||
add_child(databanks_container)
|
||||
|
||||
var installed_databanks = databanks_container.get_children()
|
||||
|
||||
for to_install in dbs_to_install:
|
||||
if installed_databanks.any(func(existing_db): return existing_db.databank_ref == to_install):
|
||||
continue
|
||||
|
||||
var installed_databank = InstalledDatabank.new()
|
||||
installed_databank.databank_ref = to_install
|
||||
databanks_container.add_child(installed_databank)
|
||||
installed_databank._populate_sockets()
|
||||
|
||||
func _gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton:
|
||||
# --- Start or End a Wire ---
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.is_pressed():
|
||||
|
||||
var socket = _get_socket_at_pos(event.position)
|
||||
if socket:
|
||||
current_state = WiringState.DRAGGING_WIRE
|
||||
if not start_socket:
|
||||
# start new wire
|
||||
start_socket = socket
|
||||
# Add start point to wire points
|
||||
active_wire_points.append(start_socket.icon.get_global_rect().get_center() - get_global_position())
|
||||
elif start_socket and socket.socket_type != start_socket.socket_type:
|
||||
end_socket = socket
|
||||
_save_new_connection()
|
||||
_reset_wiring_state()
|
||||
elif current_state == WiringState.DRAGGING_WIRE:
|
||||
# Add intermediate point
|
||||
active_wire_points.append(get_local_mouse_position())
|
||||
|
||||
elif event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
# Pop Last Point
|
||||
active_wire_points.remove_at(active_wire_points.size() - 1)
|
||||
|
||||
# Check if wire points are empty, then we remove the whole wire
|
||||
if active_wire_points.size() <= 0:
|
||||
_reset_wiring_state()
|
||||
|
||||
if event is InputEventMouseMotion and current_state == WiringState.DRAGGING_WIRE:
|
||||
queue_redraw()
|
||||
|
||||
func _draw():
|
||||
# 1. Draw all saved wires from the schematic.
|
||||
if current_schematic:
|
||||
for connection in current_schematic.connections:
|
||||
if connection.path_points.size() > 1:
|
||||
_draw_wire_path(connection.path_points, Color.GREEN)
|
||||
|
||||
# 2. Draw the active wire being dragged by the user.
|
||||
if current_state == WiringState.DRAGGING_WIRE:
|
||||
var live_path: Array[Vector2] = active_wire_points.duplicate()
|
||||
live_path.append(get_local_mouse_position())
|
||||
if live_path.size() > 1:
|
||||
_draw_wire_path(live_path, Color.YELLOW)
|
||||
|
||||
# --- NEW: Helper function to draw a multi-point path ---
|
||||
func _draw_wire_path(points: Array[Vector2], color: Color):
|
||||
for i in range(points.size() - 1):
|
||||
var p1 = points[i]
|
||||
var p2 = points[i+1]
|
||||
# var control_offset = Vector2(abs(p2.x - p1.x) * 0.5, 0)
|
||||
draw_line(p1, p2, color, 3.0)
|
||||
|
||||
func _save_new_connection():
|
||||
var new_connection = WireConnection.new()
|
||||
if start_socket.socket_type == end_socket.socket_type:
|
||||
push_error("Start socket and end socket of same type!")
|
||||
return
|
||||
|
||||
if start_socket.socket_type == Socket.SocketType.INPUT:
|
||||
new_connection.input_socket_name = start_socket.socket_name
|
||||
elif start_socket.socket_type == Socket.SocketType.OUTPUT:
|
||||
new_connection.output_socket_name = start_socket.socket_name
|
||||
|
||||
if end_socket.socket_type == Socket.SocketType.INPUT:
|
||||
new_connection.input_socket_name = end_socket.socket_name
|
||||
elif end_socket.socket_type == Socket.SocketType.OUTPUT:
|
||||
new_connection.output_socket_name = end_socket.socket_name
|
||||
|
||||
|
||||
var end_pos = end_socket.icon.get_global_rect().get_center() - get_global_position()
|
||||
active_wire_points.append(end_pos)
|
||||
new_connection.path_points = active_wire_points
|
||||
|
||||
if not current_schematic:
|
||||
current_schematic = WiringSchematic.new()
|
||||
current_schematic.connections.append(new_connection)
|
||||
print("Connection saved!")
|
||||
|
||||
func _reset_wiring_state():
|
||||
current_state = WiringState.IDLE
|
||||
start_socket = null
|
||||
end_socket = null
|
||||
active_wire_points.clear()
|
||||
queue_redraw()
|
||||
|
||||
func _get_socket_at_pos(global_pos: Vector2) -> Socket:
|
||||
for panel in installed_panels:
|
||||
for socket in panel.all_sockets:
|
||||
if is_instance_valid(socket) and socket.icon.get_global_rect().has_point(global_pos):
|
||||
return socket
|
||||
return null
|
||||
1
src/scenes/ship/computer/panels/frame/panel_frame.gd.uid
Normal file
1
src/scenes/ship/computer/panels/frame/panel_frame.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cadvugf4oqgvk
|
||||
9
src/scenes/ship/computer/panels/readout_screen.tres
Normal file
9
src/scenes/ship/computer/panels/readout_screen.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="ControlPanel" load_steps=3 format=3 uid="uid://57y6igb07e10"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cskf26i7vnxug" path="res://scenes/ship/computer/control_panel.gd" id="1_540xq"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdbqjkgsj02or" path="res://scenes/ship/computer/UI/readout_screen_panel.tscn" id="2_iy0t0"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_540xq")
|
||||
ui_scene = ExtResource("2_iy0t0")
|
||||
metadata/_custom_type_script = "uid://cskf26i7vnxug"
|
||||
9
src/scenes/ship/computer/panels/sensor_panel.tres
Normal file
9
src/scenes/ship/computer/panels/sensor_panel.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="ControlPanel" load_steps=3 format=3 uid="uid://dl7g67mtqkfx2"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cskf26i7vnxug" path="res://scenes/ship/computer/control_panel.gd" id="1_f0h3m"]
|
||||
[ext_resource type="PackedScene" uid="uid://rd1c22nsru8y" path="res://scenes/ship/computer/UI/sensor_panel.tscn" id="2_kyhrs"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_f0h3m")
|
||||
ui_scene = ExtResource("2_kyhrs")
|
||||
metadata/_custom_type_script = "uid://cskf26i7vnxug"
|
||||
9
src/scenes/ship/computer/panels/throttle_lever.tres
Normal file
9
src/scenes/ship/computer/panels/throttle_lever.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="StationPanel" load_steps=3 format=3 uid="uid://dcyr6utrk376h"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cskf26i7vnxug" path="res://scenes/ship/computer/control_panel.gd" id="1_8h7ox"]
|
||||
[ext_resource type="PackedScene" uid="uid://pq55j75t3fda" path="res://scenes/ship/computer/UI/throttle_lever_panel.tscn" id="2_8h7ox"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_8h7ox")
|
||||
ui_scene = ExtResource("2_8h7ox")
|
||||
metadata/_custom_type_script = "uid://cskf26i7vnxug"
|
||||
219
src/scenes/ship/computer/shards/helm_autopilot_databank.gd
Normal file
219
src/scenes/ship/computer/shards/helm_autopilot_databank.gd
Normal file
@ -0,0 +1,219 @@
|
||||
# scenes/ship/computer/shards/autopilot_databank.gd
|
||||
extends Databank
|
||||
class_name AutopilotShard
|
||||
|
||||
signal execution_state_changed(is_executing: bool, status: String)
|
||||
signal fmt_out(text: String)
|
||||
signal request_attitude_hold(b: bool)
|
||||
signal request_rotation(r: float)
|
||||
signal request_rotation_thrust(r: float)
|
||||
signal request_main_engine_thrust(t: float)
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["maneuver_received", "execute_plan", "set_thruster_calibration"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["execution_state_changed", "fmt_out", "request_attitude_hold", "request_rotation", "request_rotation_thrust", "request_main_engine_thrust"]
|
||||
|
||||
# --- State ---
|
||||
enum State { IDLE, WAITING_FOR_WINDOW, EXECUTING_BURN }
|
||||
var current_state: State = State.IDLE
|
||||
var current_plan: Array[DataTypes.ImpulsiveBurnPlan] = []
|
||||
var max_rot_time: float = 30.0
|
||||
var RCS_calibration: DataTypes.ThrusterCalibration
|
||||
var is_executing: bool = false
|
||||
var status: String = ""
|
||||
var current_timer: SceneTreeTimer
|
||||
|
||||
func _process(delta):
|
||||
var fmt = ""
|
||||
var state_name = State.keys()[current_state]
|
||||
if current_timer and current_timer.time_left:
|
||||
var time_str = "%d:%02d" % [int(current_timer.time_left) / 60, int(current_timer.time_left) % 60]
|
||||
var interpolated_status = status % time_str
|
||||
fmt = "Autopilot: %s\n%s" % [state_name, interpolated_status]
|
||||
else:
|
||||
fmt = "Autopilot: %s\n%s" % [state_name, status]
|
||||
|
||||
fmt_out.emit(fmt)
|
||||
|
||||
|
||||
# INPUT SOCKET: Connected to the ManeuverPlanner's "maneuver_calculated" signal.
|
||||
func maneuver_received(plan: Array[DataTypes.ImpulsiveBurnPlan]):
|
||||
current_plan = plan
|
||||
print("AUTOPILOT: Maneuver plan received.")
|
||||
status = "Plan Received.\nPress Execute."
|
||||
# In a UI, this would enable the "Execute" button.
|
||||
|
||||
# UI ACTION: An "Execute" button on a panel would call this.
|
||||
func execute_plan():
|
||||
if not current_plan.is_empty():
|
||||
current_state = State.WAITING_FOR_WINDOW
|
||||
print("AUTOPILOT: Executing plan. Waiting for first burn window.")
|
||||
|
||||
for step in current_plan:
|
||||
# status = "Performing Rotation: T- %f" % rad_to_deg(step.desired_rotation_rad)
|
||||
var time_elapsed: float = await _execute_autopilot_rotation(step)
|
||||
|
||||
current_timer = get_tree().create_timer(step.wait_time - time_elapsed)
|
||||
|
||||
status = "Waiting for burn window: T- %s"
|
||||
|
||||
await current_timer.timeout
|
||||
|
||||
await _execute_next_burn(step)
|
||||
|
||||
|
||||
func set_thruster_calibration(data: DataTypes.ThrusterCalibration):
|
||||
RCS_calibration = data
|
||||
|
||||
# --- PROCESSS FUNCTIONS: Functions being run to execute the steps of a planned transfer ---
|
||||
|
||||
func _execute_next_burn(step: DataTypes.ImpulsiveBurnPlan):
|
||||
current_state = State.EXECUTING_BURN
|
||||
|
||||
status = "Executing Main Engine Burn: %s"
|
||||
print("AUTOPILOT: Commanding main engine burn for %.2f seconds." % step.burn_duration)
|
||||
|
||||
request_main_engine_thrust.emit(1.0)
|
||||
|
||||
current_timer = get_tree().create_timer(step.burn_duration)
|
||||
|
||||
await current_timer.timeout
|
||||
|
||||
request_main_engine_thrust.emit(0.0)
|
||||
|
||||
# Transition to the next state
|
||||
if not current_plan.is_empty():
|
||||
current_state = State.WAITING_FOR_WINDOW
|
||||
else:
|
||||
current_state = State.IDLE
|
||||
execution_state_changed.emit(false, "Maneuver complete.")
|
||||
|
||||
# --- AUTOPILOT "BANG-COAST-BANG" LOGIC (REFACTORED) ---
|
||||
func _execute_autopilot_rotation(step: DataTypes.ImpulsiveBurnPlan) -> float:
|
||||
var time_window = minf(step.wait_time, max_rot_time)
|
||||
|
||||
# --- 3D REFACTOR ---
|
||||
# 1. We assume 'step.desired_rotation_rad' is now 'step.desired_basis'
|
||||
# You MUST update your planners (Hohman, Brachistochrone) to
|
||||
# calculate a target Basis (e.g., Basis.looking_at(prograde_vec, Vector3.UP))
|
||||
# and store it in the ImpulsiveBurnPlan.
|
||||
var step_target_basis: Basis = step.desired_basis # DANGER: This line assumes you updated DataTypes.
|
||||
# For this to compile, you MUST change ImpulsiveBurnPlan in data_types.gd:
|
||||
# var desired_rotation_rad: float -> var desired_basis: Basis
|
||||
var error_quaternion: Quaternion = shortest_rotation_between(root_module.global_transform.basis, step_target_basis)
|
||||
var angle_to_turn: float = error_quaternion.get_angle()
|
||||
var axis_to_turn: Vector3 = error_quaternion.get_axis()
|
||||
|
||||
var init_time = Time.get_ticks_msec()
|
||||
|
||||
if angle_to_turn < 0.01: # Already aligned
|
||||
request_rotation.emit(step_target_basis) # Send the Basis to the helm
|
||||
request_attitude_hold.emit(true)
|
||||
return 0.0
|
||||
return 0.0
|
||||
|
||||
# --- 3D Torque Calculation ---
|
||||
# This logic is now much more complex. We need to find the torque
|
||||
# vector to apply.
|
||||
# For a simple "bang-bang" controller, we just apply max torque
|
||||
# along the calculated axis.
|
||||
|
||||
# TODO: This assumes your calibration data is now 3D
|
||||
# (e.g., max_pos_torque is now max_torque_vector).
|
||||
# This needs a calibration refactor, which is complex.
|
||||
|
||||
# --- SIMPLIFIED 3D LOGIC (for now) ---
|
||||
# We'll re-use the PD controller logic from your Helm Shard
|
||||
# to get a torque vector.
|
||||
var error_torque = axis_to_turn * angle_to_turn * RCS_calibration.max_pos_torque # (This is a P-controller)
|
||||
var damping_torque = -root_module.angular_velocity * (RCS_calibration.max_pos_torque * 0.5) # (This is a D-controller)
|
||||
var desired_torque_vector = error_torque + damping_torque
|
||||
|
||||
# We are no longer calculating burn times, we are just applying
|
||||
# torque until we reach the target.
|
||||
# This is a full change from bang-bang to a PD controller.
|
||||
|
||||
# --- REFACTORING THE "BANG-BANG" LOGIC FOR 3D ---
|
||||
# Let's stick closer to your original design.
|
||||
|
||||
# 1. Get calibrated torque values (this now needs to be per-axis)
|
||||
# Let's assume a simplified calibration for now.
|
||||
var accel_torque_magnitude = RCS_calibration.max_pos_torque # Needs refactor
|
||||
var decel_torque_magnitude = RCS_calibration.max_neg_torque # Needs refactor
|
||||
|
||||
var accel_angular_accel = accel_torque_magnitude / root_module.inertia
|
||||
var decel_angular_accel = decel_torque_magnitude / root_module.inertia
|
||||
|
||||
var peak_angular_velocity = (2 * angle_to_turn * accel_angular_accel * decel_angular_accel) / (accel_angular_accel + decel_angular_accel)
|
||||
peak_angular_velocity = sqrt(abs(peak_angular_velocity)) * sign(angle_to_turn)
|
||||
|
||||
var accel_burn_time = abs(peak_angular_velocity / accel_angular_accel)
|
||||
var decel_burn_time = abs(peak_angular_velocity / decel_angular_accel)
|
||||
|
||||
var total_maneuver_time = accel_burn_time + decel_burn_time
|
||||
|
||||
if total_maneuver_time > time_window:
|
||||
accel_burn_time = time_window / 2.0
|
||||
decel_burn_time = time_window / 2.0
|
||||
|
||||
# --- Execute Maneuver (3D) ---
|
||||
|
||||
# ACCELERATION BURN: Apply torque along the axis
|
||||
request_rotation_thrust.emit(axis_to_turn * accel_torque_magnitude)
|
||||
await get_tree().create_timer(accel_burn_time).timeout
|
||||
|
||||
# DECELERATION BURN: Apply torque against the axis
|
||||
request_rotation_thrust.emit(-axis_to_turn * decel_torque_magnitude)
|
||||
await get_tree().create_timer(decel_burn_time).timeout
|
||||
|
||||
# Stop all torque
|
||||
request_rotation_thrust.emit(Vector3.ZERO)
|
||||
|
||||
# Set final hold
|
||||
request_rotation.emit(step_target_basis)
|
||||
request_attitude_hold.emit(true)
|
||||
|
||||
print("AUTOPILOT: 3D Rotation maneuver complete.")
|
||||
|
||||
return init_time - Time.get_ticks_msec()
|
||||
|
||||
# --- HELPERS ---
|
||||
|
||||
# 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
|
||||
|
||||
# A simple class to hold the result of a Basis comparison.
|
||||
class BasisComparisonResult:
|
||||
var axis: Vector3
|
||||
var angle: float
|
||||
|
||||
func _init(axis: Vector3, angle: float):
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
|
||||
# Finds the shortest rotation (as an axis and angle) between two Basis objects.
|
||||
# Returns a Dictionary: {"axis": Vector3, "angle": float}
|
||||
func shortest_rotation_between(from_basis: Basis, to_basis: Basis) -> Quaternion:
|
||||
var current_quat = from_basis.get_rotation_quaternion().normalized()
|
||||
var target_quat = to_basis.get_rotation_quaternion().normalized()
|
||||
|
||||
# Calculate the difference quaternion (rotation from 'current' to 'target')
|
||||
var diff_quat = target_quat * current_quat.inverse()
|
||||
|
||||
# Ensure we're taking the shortest path.
|
||||
# A quaternion and its negative represent the same orientation,
|
||||
# but one is the "long way around".
|
||||
if diff_quat.w < 0:
|
||||
diff_quat = -diff_quat
|
||||
|
||||
return diff_quat
|
||||
@ -0,0 +1 @@
|
||||
uid://ceqdi6jobefnc
|
||||
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://dwhpjwuobcqdu"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ship/computer/shards/helm_autopilot_databank.gd" id="1_0abvf"]
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_tpm1x"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_tpm1x")
|
||||
logic_script = ExtResource("1_0abvf")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
253
src/scenes/ship/computer/shards/helm_logic_databank.gd
Normal file
253
src/scenes/ship/computer/shards/helm_logic_databank.gd
Normal file
@ -0,0 +1,253 @@
|
||||
extends Databank
|
||||
class_name HelmLogicShard
|
||||
|
||||
# --- References ---
|
||||
@onready var thrusters: Array[Thruster] = []
|
||||
|
||||
# --- PD Controller Constants ---
|
||||
@export var HOLD_KP: float = 8000.0 # Proportional gain
|
||||
@export var HOLD_KD: float = 1200.0 # Derivative gain
|
||||
|
||||
@onready var target_rotation_rad: Basis
|
||||
var attitude_hold_enabled: bool = false
|
||||
|
||||
var thruster_calibration_data: DataTypes.ThrusterCalibration
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["shutdown_rcs", "calibrate_rcs_performance", "set_throttle_input", "set_rotation_input", "set_desired_rotation", "set_attitude_hold"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["thruster_calibrated"]
|
||||
|
||||
# The Station calls this after instantiating the shard.
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
if not is_instance_valid(root_module):
|
||||
push_error("Helm Shard initialized without a valid ship root!")
|
||||
return
|
||||
|
||||
thrusters = _find_all_thrusters(root_module)
|
||||
# You can add logic here to listen for parts being added/removed to re-scan.
|
||||
|
||||
# Default to holding the initial attitude.
|
||||
target_rotation_rad = root_module.basis
|
||||
|
||||
func _physics_process(_delta):
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# # If attitude hold is on, run the PD controller.
|
||||
# if attitude_hold_enabled:
|
||||
# _perform_manual_hold()
|
||||
|
||||
# --- INPUT SOCKETS (Called by Panels or other Shards) ---
|
||||
|
||||
## This is an "input socket" for rotational control.
|
||||
## It takes a value from -1.0 to 1.0.
|
||||
func set_rotation_input(value: float):
|
||||
if abs(value) > 0.1:
|
||||
# Manual input overrides attitude hold.
|
||||
attitude_hold_enabled = false
|
||||
var desired_torque = (calibration_data.max_pos_torque if value > 0 else calibration_data.max_neg_torque) * value
|
||||
apply_rotational_thrust(desired_torque)
|
||||
else:
|
||||
# When input stops, re-engage hold at the current rotation.
|
||||
if not attitude_hold_enabled:
|
||||
attitude_hold_enabled = true
|
||||
target_rotation_rad = root_module.basis
|
||||
|
||||
## This is an "input socket" for translational control (main thrusters).
|
||||
## It takes a value from 0.0 to 1.0.
|
||||
# --- REFACTORED: This is the key change ---
|
||||
func set_throttle_input(value: float):
|
||||
print("THRUSTER CONTROLLER: Throttle input recieved: %f.1" % value)
|
||||
# This function now works with the simple on/off thrusters.
|
||||
if not calibration_data:
|
||||
print("THRUSTER CONTROLLER: No Calibration Data Found")
|
||||
return
|
||||
for thruster in calibration_data.thruster_data:
|
||||
var thruster_data: DataTypes.ThrusterData = calibration_data.thruster_data[thruster]
|
||||
if thruster_data.thruster_type == DataTypes.ThrusterData.ThrusterType.LINEAR:
|
||||
print(" - Main thruster identified with thrust capacity: %f" % thruster_data.measured_thrust)
|
||||
if value > 0.1:
|
||||
print(" - Main Engine Activated")
|
||||
thruster.turn_on()
|
||||
else:
|
||||
print(" - Main Engine Shut Off")
|
||||
thruster.turn_off()
|
||||
|
||||
func set_desired_rotation(b: Basis):
|
||||
target_rotation_rad = b
|
||||
|
||||
func set_attitude_hold(hold: bool):
|
||||
attitude_hold_enabled = hold
|
||||
|
||||
# --- LOGIC (Migrated from ThrusterController.gd) ---
|
||||
|
||||
# func _perform_manual_hold():
|
||||
# var error = shortest_angle_between(root_module.rotation, target_rotation_rad)
|
||||
# if abs(error) > 0.001:
|
||||
# var desired_torque = (error * HOLD_KP) - (root_module.angular_velocity * HOLD_KD)
|
||||
|
||||
# apply_rotational_thrust(desired_torque)
|
||||
# else: apply_rotational_thrust(0.0)
|
||||
|
||||
# --- REFACTORED: This is the other key change ---
|
||||
func apply_rotational_thrust(desired_torque: Vector3):
|
||||
if not is_instance_valid(root_module):
|
||||
return
|
||||
|
||||
# Iterate through all available RCS thrusters that have been calibrated
|
||||
for thruster in calibration_data.thruster_data:
|
||||
var thruster_data: DataTypes.ThrusterData = calibration_data.thruster_data[thruster]
|
||||
|
||||
if thruster_data.thruster_type == DataTypes.ThrusterData.ThrusterType.ROTATIONAL:
|
||||
# If this thruster can help apply the desired torque, turn it on.
|
||||
# Otherwise, explicitly turn it off to ensure it's not firing incorrectly.
|
||||
var contribution = thruster_data.measured_torque_vector.dot(desired_torque)
|
||||
if contribution > 0 and desired_torque != Vector3.ZERO:
|
||||
thruster.turn_on()
|
||||
else:
|
||||
thruster.turn_off()
|
||||
|
||||
func shutdown_rcs():
|
||||
for thruster in thrusters:
|
||||
if not thruster.main_thruster:
|
||||
thruster.turn_off()
|
||||
|
||||
func _find_all_thrusters(node: Node) -> Array[Thruster]:
|
||||
var thrusters: Array[Thruster] = []
|
||||
for child in node.get_children():
|
||||
if child is Thruster:
|
||||
thrusters.append(child)
|
||||
if child.get_child_count() > 0:
|
||||
thrusters.append_array(_find_all_thrusters(child))
|
||||
|
||||
return thrusters
|
||||
|
||||
|
||||
# Angle difference in rad
|
||||
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
|
||||
|
||||
signal thruster_calibrated(data: DataTypes.ThrusterCalibration)
|
||||
|
||||
var calibration_data: DataTypes.ThrusterCalibration
|
||||
|
||||
# --- CALIBRATION LOGIC (Migrated from ThrusterController.gd) ---
|
||||
|
||||
## Manages the calibration sequence for all non-main thrusters.
|
||||
func calibrate_rcs_performance():
|
||||
print("Helm Shard: Beginning RCS calibration protocol...")
|
||||
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# --- Disable attitude hold during calibration ---
|
||||
var original_attitude_hold_state = attitude_hold_enabled
|
||||
attitude_hold_enabled = false
|
||||
shutdown_rcs() # Ensure all thrusters are off before we start
|
||||
await get_tree().physics_frame
|
||||
|
||||
print("Helm Shard: Attitude hold protocol: %s" % ("enabled" if attitude_hold_enabled else "disabled"))
|
||||
|
||||
calibration_data = DataTypes.ThrusterCalibration.new()
|
||||
|
||||
for thruster in thrusters:
|
||||
var data: DataTypes.ThrusterData = await _calibrate_single_thruster(thruster)
|
||||
calibration_data.thruster_data[thruster] = data
|
||||
|
||||
print(calibration_data)
|
||||
# Now that we have the data, calculate the ship's max torque values
|
||||
calibration_data.max_pos_torque = 0.0
|
||||
calibration_data.max_neg_torque = 0.0
|
||||
for data in calibration_data.thruster_data.values():
|
||||
if data.measured_torque_vector > 0:
|
||||
calibration_data.max_pos_torque += data.measured_torque_vector
|
||||
else:
|
||||
calibration_data.max_neg_torque += abs(data.measured_torque_vector)
|
||||
|
||||
print("RCS Calibration Complete: Max Pos Torque: %.2f, Max Neg Torque: %.2f" % [calibration_data.max_pos_torque, calibration_data.max_neg_torque])
|
||||
|
||||
# Auto-tune the PD controller with the new values
|
||||
if calibration_data.max_pos_torque > 0 and calibration_data.max_neg_torque > 0:
|
||||
var average_max_torque = (calibration_data.max_pos_torque + calibration_data.max_neg_torque) / 2.0
|
||||
HOLD_KP = average_max_torque * 0.1
|
||||
HOLD_KD = HOLD_KP * 1 # You can tune this multiplier
|
||||
print("PD Controller Auto-Tuned: Kp set to %.2f, Kd set to %.2f" % [HOLD_KP, HOLD_KD])
|
||||
|
||||
attitude_hold_enabled = original_attitude_hold_state
|
||||
print("Helm Shard: Calibration complete. Attitude hold is now %s." % ("enabled" if attitude_hold_enabled else "disabled"))
|
||||
|
||||
thruster_calibration_data = calibration_data
|
||||
thruster_calibrated.emit(calibration_data)
|
||||
|
||||
## Performs a test fire of a single thruster and measures the resulting change in angular velocity.
|
||||
func _calibrate_single_thruster(thruster: Thruster) -> DataTypes.ThrusterData:
|
||||
var data = DataTypes.ThrusterData.new()
|
||||
data.thruster_node = thruster
|
||||
|
||||
# Prepare for test: save initial state
|
||||
var initial_angular_velocity = root_module.angular_velocity
|
||||
var initial_linear_velocity = root_module.linear_velocity
|
||||
|
||||
var test_burn_duration = 0.5 # A very short burst
|
||||
|
||||
# --- Perform Test Fire ---
|
||||
thruster.turn_on()
|
||||
await get_tree().create_timer(test_burn_duration).timeout
|
||||
thruster.turn_off()
|
||||
|
||||
# Let the physics engine settle for one frame to ensure the velocity update is complete
|
||||
await get_tree().physics_frame
|
||||
|
||||
# --- Measure Results ---
|
||||
var delta_angular_velocity = root_module.angular_velocity - initial_angular_velocity
|
||||
var delta_linear_velocity = root_module.linear_velocity - initial_linear_velocity
|
||||
|
||||
data.measured_torque_vector = Vector3.ZERO
|
||||
data.measured_thrust = 0.0
|
||||
|
||||
# --- Calculate Performance ---
|
||||
# Torque = inertia * angular_acceleration (alpha = dw/dt)
|
||||
if root_module.inertia.length_squared() > 0:
|
||||
data.measured_torque_vector = root_module.inertia * (delta_angular_velocity / test_burn_duration)
|
||||
else:
|
||||
data.measured_torque_vector = Vector3.ZERO
|
||||
push_warning("Root module inertia is 0. Cannot calibrate torque.")
|
||||
|
||||
if root_module.mass > 0:
|
||||
data.measured_thrust = root_module.mass * (delta_linear_velocity.length() / test_burn_duration)
|
||||
else:
|
||||
data.measured_thrust = 0.0
|
||||
push_warning("Root module mass is 0. Cannot calibrate torque.")
|
||||
|
||||
if data.measured_thrust > abs(data.measured_torque_vector):
|
||||
print(" - Calibrated %s: Linear(%.3f)" % [thruster.name, data.measured_thrust])
|
||||
data.thruster_type = DataTypes.ThrusterData.ThrusterType.LINEAR
|
||||
elif data.measured_thrust < abs(data.measured_torque_vector):
|
||||
print(" - Calibrated %s: Torque(%.3f)" % [thruster.name, data.measured_torque_vector])
|
||||
data.thruster_type = DataTypes.ThrusterData.ThrusterType.ROTATIONAL
|
||||
|
||||
|
||||
# --- Cleanup: Counter the spin from the test fire ---
|
||||
if data.measured_torque_vector.length_squared() > 0.001:
|
||||
var counter_torque = -data.measured_torque_vector
|
||||
var counter_burn_duration = (root_module.inertia * root_module.angular_velocity) / counter_torque
|
||||
|
||||
# Find a thruster that can apply the counter-torque
|
||||
for other_thruster in thrusters:
|
||||
var other_data = calibration_data.thruster_data.get(other_thruster)
|
||||
if other_data and sign(other_data.measured_torque_vector) == sign(counter_torque):
|
||||
other_thruster.turn_on()
|
||||
await get_tree().create_timer(abs(counter_burn_duration)).timeout
|
||||
other_thruster.turn_off()
|
||||
break # Use the first one we find
|
||||
|
||||
await get_tree().physics_frame
|
||||
|
||||
return data
|
||||
@ -0,0 +1 @@
|
||||
uid://cfbyqvnvf3hna
|
||||
9
src/scenes/ship/computer/shards/helm_logic_databank.tres
Normal file
9
src/scenes/ship/computer/shards/helm_logic_databank.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://dghg3pbws42yu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_kih5s"]
|
||||
[ext_resource type="Script" uid="uid://cfbyqvnvf3hna" path="res://scenes/ship/computer/shards/helm_logic_databank.gd" id="1_vvsub"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_kih5s")
|
||||
logic_script = ExtResource("1_vvsub")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
37
src/scenes/ship/computer/shards/helm_ship_status.gd
Normal file
37
src/scenes/ship/computer/shards/helm_ship_status.gd
Normal file
@ -0,0 +1,37 @@
|
||||
extends Databank
|
||||
|
||||
class_name ShipStatusShard
|
||||
|
||||
## This shard emits a signal with the formatted ship status text.
|
||||
signal status_updated(text: String)
|
||||
|
||||
# Called by the Station when it's created.
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return []
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["status_updated"]
|
||||
|
||||
func _physics_process(delta):
|
||||
if not is_instance_valid(root_module):
|
||||
return
|
||||
# # 1. Gather all the data from the root module.
|
||||
# var rotation_deg = rad_to_deg(root_module.rotation)
|
||||
# var angular_vel_dps = rad_to_deg(root_module.angular_velocity)
|
||||
# var linear_vel_mps = root_module.linear_velocity.length()
|
||||
|
||||
# # 2. Build the string that will be displayed.
|
||||
# var status_text = """
|
||||
# [font_size=24]Ship Status[/font_size]
|
||||
# [font_size=18]Rotation: %.1f deg[/font_size]
|
||||
# [font_size=18]Ang. Vel.: %.2f deg/s[/font_size]
|
||||
# [font_size=18]Velocity: %.2f m/s[/font_size]
|
||||
# """ % [rotation_deg, angular_vel_dps, linear_vel_mps]
|
||||
|
||||
# # 3. Emit the signal with the formatted text.
|
||||
# status_updated.emit(status_text)
|
||||
1
src/scenes/ship/computer/shards/helm_ship_status.gd.uid
Normal file
1
src/scenes/ship/computer/shards/helm_ship_status.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://ctgl5kxyagw0f
|
||||
9
src/scenes/ship/computer/shards/helm_ship_status.tres
Normal file
9
src/scenes/ship/computer/shards/helm_ship_status.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://bx7wgunvy5hfa"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_2fbxe"]
|
||||
[ext_resource type="Script" uid="uid://ctgl5kxyagw0f" path="res://scenes/ship/computer/shards/helm_ship_status.gd" id="1_880kd"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_2fbxe")
|
||||
logic_script = ExtResource("1_880kd")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
@ -0,0 +1,71 @@
|
||||
# space_simulation/scenes/ship/computer/shards/nav_brachistochrone_planner.gd
|
||||
extends Databank
|
||||
class_name BrachistochronePlannerShard
|
||||
|
||||
## Emitted when a maneuver plan has been successfully calculated.
|
||||
signal maneuver_calculated(plan: Array[DataTypes.ImpulsiveBurnPlan])
|
||||
|
||||
# --- References ---
|
||||
var target_body: OrbitalBody3D = null
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["target_updated", "calculate_hohmann_transfer"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["maneuver_calculated"]
|
||||
|
||||
# INPUT SOCKET: Connected to the NavSelectionShard's "target_selected" signal.
|
||||
func target_updated(new_target: OrbitalBody3D):
|
||||
print("BRACHISTOCHRONE PLANNER: Target received %s." % new_target.name)
|
||||
target_body = new_target
|
||||
|
||||
# TODO: All positions and velocities for calculating should be gathered from a sensor databank
|
||||
# UI ACTION: A panel button would call this function.
|
||||
func calculate_brachistochrone_transfer():
|
||||
if not is_instance_valid(root_module) or not is_instance_valid(target_body):
|
||||
print("BRACHISTOCHRONE PLANNER: Cannot calculate without ship and target.")
|
||||
return
|
||||
|
||||
# 1. Get total main engine thrust from all thruster components
|
||||
# TODO: This should be gathered from a calibration shard
|
||||
var main_engine_thrust = 0.0
|
||||
for component in root_module.get_components():
|
||||
if component is Thruster and component.main_thruster:
|
||||
main_engine_thrust += component.max_thrust
|
||||
|
||||
if main_engine_thrust == 0.0 or root_module.mass == 0.0:
|
||||
print("BRACHISTOCHRONE PLANNER: Ship has no main engine thrust or mass.")
|
||||
return
|
||||
|
||||
var acceleration = main_engine_thrust / root_module.mass
|
||||
var distance = root_module.global_position.distance_to(target_body.global_position)
|
||||
|
||||
# Using the kinematic equation: d = (1/2)at^2, solved for t: t = sqrt(2d/a)
|
||||
# Since we accelerate for half the distance and decelerate for the other half:
|
||||
var time_for_half_journey = sqrt(distance / acceleration)
|
||||
|
||||
# --- Assemble the plan as two ImpulsiveBurnPlan steps ---
|
||||
var plan: Array[DataTypes.ImpulsiveBurnPlan] = []
|
||||
|
||||
# --- Step 1: Acceleration Burn ---
|
||||
var accel_burn = DataTypes.ImpulsiveBurnPlan.new()
|
||||
accel_burn.wait_time = 0 # Start immediately
|
||||
accel_burn.burn_duration = time_for_half_journey
|
||||
# The desired rotation is the direction vector from ship to target
|
||||
var direction_to_target = root_module.global_position.direction_to(target_body.global_position).normalized()
|
||||
var up_vec = Vector3.UP
|
||||
accel_burn.desired_basis = Basis.looking_at(direction_to_target, up_vec)
|
||||
plan.append(accel_burn)
|
||||
|
||||
# --- Step 2: Deceleration Burn (The flip is handled by the autopilot between steps) ---
|
||||
var decel_burn = DataTypes.ImpulsiveBurnPlan.new()
|
||||
decel_burn.wait_time = 0 # No coasting period
|
||||
decel_burn.burn_duration = time_for_half_journey
|
||||
# The desired rotation is opposite the first burn
|
||||
decel_burn.desired_basis = Basis.looking_at(-direction_to_target, up_vec)
|
||||
plan.append(decel_burn)
|
||||
|
||||
print("BRACHISTOCHRONE PLANNER: Plan calculated. Total time: %.2f s" % (time_for_half_journey * 2.0))
|
||||
maneuver_calculated.emit(plan)
|
||||
@ -0,0 +1 @@
|
||||
uid://ghluwjd5c5ul
|
||||
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://bnyce8i208qby"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ghluwjd5c5ul" path="res://scenes/ship/computer/shards/nav_brachistochrone_planner.gd" id="1_asajk"]
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_xdqj8"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_xdqj8")
|
||||
logic_script = ExtResource("1_asajk")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
162
src/scenes/ship/computer/shards/nav_hohman_planner.gd
Normal file
162
src/scenes/ship/computer/shards/nav_hohman_planner.gd
Normal file
@ -0,0 +1,162 @@
|
||||
# scenes/ship/computer/shards/maneuver_planner_databank.gd
|
||||
extends Databank
|
||||
class_name HohmanPlannerShard
|
||||
|
||||
## Emitted when a maneuver plan has been successfully calculated.
|
||||
signal maneuver_calculated(plan: Array[DataTypes.ImpulsiveBurnPlan])
|
||||
|
||||
# --- References ---
|
||||
var selection_shard: NavSelectionShard
|
||||
var target_body: OrbitalBody3D = null
|
||||
|
||||
# --- Configurations ---
|
||||
var boost_factor: float = 1.0
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["target_updated", "calculate_hohmann_transfer"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["maneuver_calculated"]
|
||||
|
||||
# INPUT SOCKET: Connected to the NavSelectionShard's "target_selected" signal.
|
||||
func target_updated(new_target: OrbitalBody3D):
|
||||
print("MANEUVER PLANNER: Target recieved %s." % new_target)
|
||||
target_body = new_target
|
||||
|
||||
# In a UI, this would enable the "Calculate" button.
|
||||
|
||||
func set_boost_factor(value: float):
|
||||
boost_factor = value
|
||||
|
||||
# UI ACTION: A panel button would call this function.
|
||||
func calculate_hohmann_transfer():
|
||||
if not is_instance_valid(root_module) or not is_instance_valid(target_body):
|
||||
print("MANEUVER PLANNER: Cannot calculate without ship and target.")
|
||||
return
|
||||
|
||||
var star = GameManager.current_star_system.get_star()
|
||||
if not is_instance_valid(star): return
|
||||
|
||||
var mu = OrbitalMechanics.G * star.mass
|
||||
var r1 = root_module.global_position.distance_to(star.global_position)
|
||||
var r2 = target_body.global_position.distance_to(star.global_position)
|
||||
var a_transfer = (r1 + r2) / 2.0 * boost_factor
|
||||
|
||||
var v_source_orbit = sqrt(mu / r1)
|
||||
var v_target_orbit = sqrt(mu / r2)
|
||||
|
||||
var v_transfer_periapsis = sqrt(mu * ((2.0 / r1) - (1.0 / a_transfer)))
|
||||
var v_transfer_apoapsis = sqrt(mu * ((2.0 / r2) - (1.0 / a_transfer)))
|
||||
|
||||
var delta_v1 = v_transfer_periapsis - v_source_orbit
|
||||
var delta_v2 = v_target_orbit - v_transfer_apoapsis
|
||||
|
||||
var time_of_flight = PI * sqrt(pow(a_transfer, 3) / mu)
|
||||
|
||||
var ang_vel_target = sqrt(mu / pow(r2, 3))
|
||||
var travel_angle = ang_vel_target * time_of_flight
|
||||
var required_phase_angle = PI - travel_angle
|
||||
|
||||
var vec_to_ship = (root_module.global_position - star.global_position).normalized()
|
||||
var vec_to_target = (target_body.global_position - star.global_position).normalized()
|
||||
var current_phase_angle = vec_to_ship.angle_to(vec_to_target)
|
||||
|
||||
var ang_vel_ship = sqrt(mu / pow(r1, 3))
|
||||
var relative_ang_vel = ang_vel_ship - ang_vel_target
|
||||
var angle_to_wait = current_phase_angle - required_phase_angle
|
||||
if relative_ang_vel == 0: return # Avoid division by zero
|
||||
var wait_time = abs(angle_to_wait / relative_ang_vel)
|
||||
|
||||
# TODO: Need a way to get this from a shared calibration databank shard
|
||||
var main_engine_thrust = 0.0
|
||||
for thruster in root_module.get_components():
|
||||
if thruster is Thruster and thruster.main_thruster:
|
||||
main_engine_thrust += thruster.max_thrust
|
||||
|
||||
if main_engine_thrust == 0: return
|
||||
|
||||
var acceleration = main_engine_thrust / root_module.mass
|
||||
|
||||
# --- Use the absolute value of delta_v for burn duration ---
|
||||
var burn_duration1 = abs(delta_v1) / acceleration
|
||||
var burn_duration2 = abs(delta_v2) / acceleration
|
||||
|
||||
# --- NEW: Predict the ship's state at the time of the burn ---
|
||||
var predicted_state = _predict_state_after_coast(root_module, star, wait_time)
|
||||
var predicted_velocity_vec = predicted_state["velocity"]
|
||||
|
||||
var plan: Array[DataTypes.ImpulsiveBurnPlan] = []
|
||||
var burn1 = DataTypes.ImpulsiveBurnPlan.new()
|
||||
burn1.delta_v_magnitude = abs(delta_v1)
|
||||
burn1.wait_time = wait_time
|
||||
burn1.burn_duration = burn_duration1
|
||||
|
||||
# --- Determine rotation based on the sign of delta_v ---
|
||||
# Prograde (speeding up) or retrograde (slowing down)
|
||||
var prograde_vec = predicted_velocity_vec.normalized()
|
||||
var up_vec = Vector3.UP
|
||||
var target_basis = Basis.looking_at(prograde_vec, up_vec)
|
||||
|
||||
# For retrograde (slowing down), we look "backward"
|
||||
if delta_v1 < 0:
|
||||
target_basis = Basis.looking_at(-prograde_vec, up_vec)
|
||||
|
||||
burn1.desired_basis = target_basis # Renamed in data_types.gd
|
||||
plan.append(burn1)
|
||||
|
||||
var burn2 = DataTypes.ImpulsiveBurnPlan.new()
|
||||
burn2.delta_v_magnitude = delta_v2
|
||||
burn2.wait_time = time_of_flight - burn_duration1
|
||||
burn2.burn_duration = burn_duration2
|
||||
|
||||
# --- Determine rotation for the second burn ---
|
||||
target_basis = Basis.looking_at(-prograde_vec, up_vec)
|
||||
|
||||
# For retrograde (slowing down), we look "backward"
|
||||
if delta_v2 < 0:
|
||||
target_basis = Basis.looking_at(prograde_vec, up_vec)
|
||||
var target_prograde_direction = (target_body.global_position - star.global_position).orthogonal().angle()
|
||||
burn2.desired_basis = target_prograde_direction if delta_v2 >= 0 else target_prograde_direction + PI
|
||||
plan.append(burn2)
|
||||
|
||||
print("Hohmann Plan:")
|
||||
print(" - Wait: %d s" % wait_time)
|
||||
print(" - Burn 1: %.1f m/s (%.1f s)" % [delta_v1, burn_duration1])
|
||||
print(" - Flight time: %d s" % time_of_flight)
|
||||
print(" - Burn 2: %.1f m/s (%.1f s)" % [delta_v2, burn_duration2])
|
||||
print("MANEUVER PLANNER: Hohmann transfer calculated. Emitting plan.")
|
||||
|
||||
maneuver_calculated.emit(plan)
|
||||
|
||||
# Simulates the ship's 2-body orbit around the star to predict its future state.
|
||||
func _predict_state_after_coast(body_to_trace: OrbitalBody3D, primary: OrbitalBody3D, time: float) -> Dictionary:
|
||||
# --- Simulation Parameters ---
|
||||
var time_step = 1.0 # Simulate in 1-second increments
|
||||
var num_steps = int(ceil(time / time_step))
|
||||
|
||||
# --- Initial State (relative to the primary) ---
|
||||
var ghost_relative_pos = body_to_trace.global_position - primary.global_position
|
||||
var ghost_relative_vel = body_to_trace.linear_velocity - primary.linear_velocity
|
||||
|
||||
var mu = OrbitalMechanics.G * primary.mass
|
||||
|
||||
for i in range(num_steps):
|
||||
# --- Physics Calculation ---
|
||||
var distance_sq = ghost_relative_pos.length_squared()
|
||||
if distance_sq < 1.0: break
|
||||
|
||||
var direction = -ghost_relative_pos.normalized()
|
||||
var force_magnitude = mu / distance_sq # Simplified F = mu*m/r^2 and a=F/m
|
||||
var acceleration = direction * force_magnitude
|
||||
|
||||
# --- Integration (Euler method) ---
|
||||
ghost_relative_vel += acceleration * time_step
|
||||
ghost_relative_pos += ghost_relative_vel * time_step
|
||||
|
||||
# --- Return the final state, converted back to global space ---
|
||||
return {
|
||||
"position": ghost_relative_pos + primary.global_position,
|
||||
"velocity": ghost_relative_vel + primary.linear_velocity
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
uid://bghu5lhcbcfmh
|
||||
9
src/scenes/ship/computer/shards/nav_hohman_planner.tres
Normal file
9
src/scenes/ship/computer/shards/nav_hohman_planner.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://6jj1jd14cdlt"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ship/computer/shards/nav_hohman_planner.gd" id="1_attn3"]
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_nleqa"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_nleqa")
|
||||
logic_script = ExtResource("1_attn3")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
79
src/scenes/ship/computer/shards/nav_intercept_solver.gd
Normal file
79
src/scenes/ship/computer/shards/nav_intercept_solver.gd
Normal file
@ -0,0 +1,79 @@
|
||||
# space_simulation/scenes/ship/computer/shards/nav_intercept_solver.gd
|
||||
extends Databank
|
||||
class_name InterceptSolverShard
|
||||
|
||||
signal solution_found(plan: Array[DataTypes.ImpulsiveBurnPlan])
|
||||
signal solution_impossible
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["project_n_body_paths"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["solution_found", "solution_impossible"]
|
||||
|
||||
# INPUT SOCKET: Planners will call this with a projected path.
|
||||
func solve_rendezvous_plan(
|
||||
target_path: Array[DataTypes.PathPoint],
|
||||
maneuver_type: String # e.g., "brachistochrone" or "hohmann"
|
||||
):
|
||||
if not is_instance_valid(root_module) or target_path.is_empty():
|
||||
emit_signal("solution_impossible")
|
||||
return
|
||||
|
||||
var rendezvous_point = find_earliest_rendezvous(target_path)
|
||||
|
||||
if not rendezvous_point:
|
||||
emit_signal("solution_impossible")
|
||||
return
|
||||
|
||||
# Once we have the target point (time, pos, vel), we can generate
|
||||
# the specific burn plan based on the requested type.
|
||||
var plan: Array[DataTypes.ImpulsiveBurnPlan]
|
||||
match maneuver_type:
|
||||
"brachistochrone":
|
||||
plan = _generate_brachistochrone_plan(rendezvous_point)
|
||||
# "hohmann" would be more complex, as it has constraints
|
||||
_:
|
||||
print("Unknown maneuver type for solver.")
|
||||
emit_signal("solution_impossible")
|
||||
return
|
||||
|
||||
emit_signal("solution_found", plan)
|
||||
|
||||
|
||||
# This is the core solver logic.
|
||||
func find_earliest_rendezvous(target_path: Array[DataTypes.PathPoint]) -> DataTypes.PathPoint:
|
||||
# For each point in the target's future path...
|
||||
for point in target_path:
|
||||
# 1. Calculate the required change in position (displacement).
|
||||
var delta_p = point.position - root_module.global_position
|
||||
|
||||
# 2. Calculate the required change in velocity.
|
||||
var delta_v = point.velocity - root_module.linear_velocity
|
||||
|
||||
# 3. Using kinematics (d = v_initial*t + 0.5at^2), find the constant
|
||||
# acceleration 'a' required to satisfy both delta_p and delta_v over
|
||||
# the time 'point.time'.
|
||||
# a = 2 * (delta_p - root_module.linear_velocity * point.time) / (point.time * point.time)
|
||||
var required_acceleration_vector = 2.0 * (delta_p - root_module.linear_velocity * point.time) / (point.time * point.time)
|
||||
|
||||
# 4. Check if the magnitude of this required acceleration is something our ship can actually do.
|
||||
var max_accel = root_module.main_engine_thrust / root_module.mass # Assumes we need a get_main_engine_thrust() helper
|
||||
|
||||
if required_acceleration_vector.length() <= max_accel:
|
||||
# This is the first point in time we can reach. This is our solution.
|
||||
return point
|
||||
|
||||
# If we get through the whole path and can't reach any of them, it's impossible.
|
||||
return null
|
||||
|
||||
|
||||
func _generate_brachistochrone_plan(rendezvous_point: DataTypes.PathPoint) -> Array[DataTypes.ImpulsiveBurnPlan]:
|
||||
# This function would now use the data from the solved rendezvous_point
|
||||
# to create the two-burn Brachistochrone plan, similar to before.
|
||||
# The key difference is that all calculations are now based on a confirmed possible intercept.
|
||||
var plan: Array[DataTypes.ImpulsiveBurnPlan] = []
|
||||
# ... logic to build the plan ...
|
||||
return plan
|
||||
@ -0,0 +1 @@
|
||||
uid://dsbn7ushwqrko
|
||||
87
src/scenes/ship/computer/shards/nav_projection_shard.gd
Normal file
87
src/scenes/ship/computer/shards/nav_projection_shard.gd
Normal file
@ -0,0 +1,87 @@
|
||||
# space_simulation/scenes/ship/computer/shards/nav_path_projection.gd
|
||||
extends Databank
|
||||
class_name PathProjectionShard
|
||||
|
||||
## Emitted after a requested path has been calculated.
|
||||
signal projected_system_bus(paths: Array[DataTypes.PathPoint])
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["project_n_body_paths"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["projected_system_bus"]
|
||||
|
||||
## Projects the future paths of an array of bodies interacting with each other.
|
||||
## Returns a dictionary mapping each body to its calculated PackedVector2Array path.
|
||||
func project_n_body_paths(
|
||||
bodies_to_trace: Array[OrbitalBody3D],
|
||||
num_steps: int,
|
||||
time_step: float
|
||||
):
|
||||
|
||||
# --- Step 1: Create a "ghost state" for each body ---
|
||||
# A ghost state is just a simple dictionary holding the physics properties.
|
||||
var ghost_states = []
|
||||
for body in bodies_to_trace:
|
||||
ghost_states.append({
|
||||
"body_ref": body,
|
||||
"mass": body.mass,
|
||||
"position": body.global_position,
|
||||
"velocity": body.linear_velocity # Velocity is always in the same space
|
||||
})
|
||||
|
||||
# --- Step 2: Prepare the results dictionary ---
|
||||
var paths: Dictionary = {}
|
||||
for state in ghost_states:
|
||||
paths[state.body_ref] = []
|
||||
|
||||
|
||||
# --- Step 3: Run the ghost simulation ---
|
||||
for i in range(num_steps):
|
||||
# Create a list to hold the forces for this time step
|
||||
var forces_for_step = {}
|
||||
for state in ghost_states:
|
||||
forces_for_step[state.body_ref] = Vector2.ZERO
|
||||
|
||||
# a) Calculate all gravitational forces between the ghosts
|
||||
for j in range(ghost_states.size()):
|
||||
var state_a = ghost_states[j]
|
||||
for k in range(j + 1, ghost_states.size()):
|
||||
var state_b = ghost_states[k]
|
||||
|
||||
# Calculate force between the two ghost states2:
|
||||
var distance_sq = state_a.position.distance_squared_to(state_b.position)
|
||||
if distance_sq < 1.0: return Vector2.ZERO
|
||||
|
||||
var force_magnitude = (OrbitalMechanics.G * state_a.mass * state_b.mass) / distance_sq
|
||||
var direction = state_a.position.direction_to(state_b.position)
|
||||
var force_vector = direction * force_magnitude
|
||||
|
||||
# Store the forces to be applied
|
||||
forces_for_step[state_a.body_ref] += force_vector
|
||||
forces_for_step[state_b.body_ref] -= force_vector
|
||||
|
||||
# b) Integrate forces for each ghost to find its next position
|
||||
for state in ghost_states:
|
||||
if state.mass > 0:
|
||||
var acceleration = forces_for_step[state.body_ref] / state.mass
|
||||
state.velocity += acceleration * time_step
|
||||
state.position += state.velocity * time_step
|
||||
|
||||
|
||||
|
||||
# c) Record the new position in the path
|
||||
paths[state.body_ref].append(DataTypes.PathPoint.new(i * time_step, state.position, state.velocity))
|
||||
|
||||
# --- Step 4: Prepare the results dictionary ---
|
||||
var projections: Array[DataTypes.PathProjection] = []
|
||||
for state in ghost_states:
|
||||
var projection: DataTypes.PathProjection = DataTypes.PathProjection.new(state.body_ref)
|
||||
|
||||
projection.points = paths[state.body_ref]
|
||||
|
||||
projections.append(projection)
|
||||
|
||||
projected_system_bus.emit(paths)
|
||||
@ -0,0 +1 @@
|
||||
uid://0f6v6iu3o5qo
|
||||
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://d4e5f6g7h8jaj"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ship/computer/shards/nav_path_projection.gd" id="1_proj"]
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="2_data"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_data")
|
||||
logic_script = ExtResource("1_proj")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
23
src/scenes/ship/computer/shards/nav_selection_databank.gd
Normal file
23
src/scenes/ship/computer/shards/nav_selection_databank.gd
Normal file
@ -0,0 +1,23 @@
|
||||
# scenes/ship/computer/shards/nav_selection_databank.gd
|
||||
extends Databank
|
||||
class_name NavSelectionShard
|
||||
|
||||
## Emitted whenever a new navigation target is selected from the map.
|
||||
signal target_selected(body: OrbitalBody3D)
|
||||
|
||||
var selected_body: OrbitalBody3D = null
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return ["body_selected"]
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["target_selected"]
|
||||
|
||||
# INPUT SOCKET: This function is connected to the SensorPanel's "body_selected" signal.
|
||||
func body_selected(body: OrbitalBody3D):
|
||||
if is_instance_valid(body) and body != selected_body:
|
||||
print("NAV SELECTION: New target acquired - ", body.name)
|
||||
selected_body = body
|
||||
emit_signal("target_selected", body)
|
||||
@ -0,0 +1 @@
|
||||
uid://t12etsdx2h38
|
||||
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://g4ho63f30vjm"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_d0eru"]
|
||||
[ext_resource type="Script" uid="uid://t12etsdx2h38" path="res://scenes/ship/computer/shards/nav_selection_databank.gd" id="1_mt7ap"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_d0eru")
|
||||
logic_script = ExtResource("1_mt7ap")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
34
src/scenes/ship/computer/shards/sensor_databank.gd
Normal file
34
src/scenes/ship/computer/shards/sensor_databank.gd
Normal file
@ -0,0 +1,34 @@
|
||||
extends Databank
|
||||
class_name SensorSystemShard
|
||||
|
||||
signal sensor_feed_updated(bodies: Array[OrbitalBody3D])
|
||||
|
||||
@export_group("Projection Settings")
|
||||
@export var projection_steps: int = 500
|
||||
@export var time_per_step: float = 60.0 # Project at 1-minute intervals
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return []
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["sensor_feed_updated"]
|
||||
|
||||
# We use _process instead of _physics_process to avoid slowing down the physics thread.
|
||||
# This calculation can happen on a separate frame if needed.
|
||||
func _process(_delta: float):
|
||||
var star_system = GameManager.current_star_system
|
||||
if not is_instance_valid(star_system):
|
||||
return
|
||||
|
||||
# Gather all bodies that need to be included in the simulation.
|
||||
var tracked_bodies: Array[OrbitalBody3D] = []
|
||||
tracked_bodies.append(star_system.get_star())
|
||||
tracked_bodies.append_array(star_system.get_planetary_systems())
|
||||
tracked_bodies.append_array(star_system.get_orbital_bodies())
|
||||
|
||||
if tracked_bodies.is_empty():
|
||||
return
|
||||
|
||||
sensor_feed_updated.emit(tracked_bodies)
|
||||
1
src/scenes/ship/computer/shards/sensor_databank.gd.uid
Normal file
1
src/scenes/ship/computer/shards/sensor_databank.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://diu2tgusi3vmt
|
||||
9
src/scenes/ship/computer/shards/sensor_databank.tres
Normal file
9
src/scenes/ship/computer/shards/sensor_databank.tres
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_resource type="Resource" script_class="Databank" load_steps=3 format=3 uid="uid://b0suy3sxjwhtv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="1_nbqt3"]
|
||||
[ext_resource type="Script" uid="uid://diu2tgusi3vmt" path="res://scenes/ship/computer/shards/sensor_databank.gd" id="1_uxkgc"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_nbqt3")
|
||||
logic_script = ExtResource("1_uxkgc")
|
||||
metadata/_custom_type_script = "uid://osk1l75vlikn"
|
||||
27
src/scenes/ship/systems/fuel_system.gd
Normal file
27
src/scenes/ship/systems/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
src/scenes/ship/systems/fuel_system.gd.uid
Normal file
1
src/scenes/ship/systems/fuel_system.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dx3uerblskj5r
|
||||
29
src/scenes/ship/systems/life_support.gd
Normal file
29
src/scenes/ship/systems/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
src/scenes/ship/systems/life_support.gd.uid
Normal file
1
src/scenes/ship/systems/life_support.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://buyp6t5cppitw
|
||||
6
src/scenes/tests/3d/eva_suit_controller.tscn
Normal file
6
src/scenes/tests/3d/eva_suit_controller.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bm1rbv4tuppbc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/tests/3d/eva_movement_component.gd" id="1_mb22m"]
|
||||
|
||||
[node name="EVASuitController" type="Node3D"]
|
||||
script = ExtResource("1_mb22m")
|
||||
Reference in New Issue
Block a user