78 Commits

Author SHA1 Message Date
5d6b1b809c Update yaml 2025-12-03 16:00:10 +01:00
a15f5ef2f0 Merge branch 'feature/build-system' 2025-12-03 15:59:00 +01:00
7849258d01 Update script 2025-12-03 15:57:25 +01:00
dcb805eaca Update engine hash 2025-12-03 15:56:35 +01:00
4d2a4b1526 Attempt automated build 2025-12-03 15:55:49 +01:00
95198e6687 chore: Remove engine source submodule (using Registry artifacts now) 2025-12-03 12:48:47 +01:00
852475ba77 WIP Build system 2025-12-03 12:45:44 +01:00
33d3543c93 Use custom engine branch 2025-11-21 19:41:46 +01:00
916172d0b2 Working straight angle placement of ship pieces 2025-11-21 18:18:10 +01:00
94e33c0cba Placing blueprinted meshes 2025-11-20 22:38:31 +01:00
24ce1edb38 WIP Placing 3d ShipParts 2025-11-20 22:20:41 +01:00
8fd540ddfc Merge commit '4ddcbd80005cc4e8fd5eec05de2a431e8973f72a' into feature/build-system 2025-11-20 17:57:15 +01:00
4ddcbd8000 Connectable but buggy exported exe 2025-11-19 22:44:07 +01:00
8968586b0f Merge branch 'feature/menu-system' 2025-11-19 22:42:52 +01:00
8f641ab36e Fix error overflowing 2025-11-19 18:58:23 +01:00
9ae32ca6a9 Menu system WIP 2025-11-19 18:45:42 +01:00
ae18d1456a Merge branch 'feature/networked-star-system' 2025-11-19 17:01:39 +01:00
ab17242804 Jittering but synchronized physics state 2025-11-19 17:01:15 +01:00
a5dec9c2fd Fix networked input 2025-11-19 11:24:49 +01:00
e271c59837 Server generation of star system and authority for gravity 2025-11-18 21:58:14 +01:00
f8d140a9b0 Merge branch 'tech-test/3d-system-refactor' 2025-11-18 16:42:09 +01:00
67a4b7038a Cleanup character move 2025-11-18 16:29:38 +01:00
18f9a4fec7 Move character 2025-11-18 11:22:12 +01:00
4796a2d5ca Rename bad file 2025-11-18 11:06:46 +01:00
f8578bc3f2 Merge branch 'refactor/folder-structures' into tech-test/3d-system-refactor 2025-11-18 11:02:04 +01:00
a7583637e9 Remove tmp file 2025-11-18 11:00:20 +01:00
86762d0d50 Finished move 2025-11-18 11:00:14 +01:00
e2da700bcd WIP Renaming 2025-11-18 10:55:10 +01:00
466dff11d0 Added readme 2025-11-17 20:09:17 +01:00
636123344b Gd 4.6 compiled for large world vectors 2025-11-17 19:43:42 +01:00
25d9d55044 Welded ship wip physics architecture redesign 2025-11-17 19:43:11 +01:00
aafb939cbf Working cached gravity calculations 2025-11-17 08:32:48 +01:00
3d01edb2d9 WIP Gravitational refactor 2025-11-17 08:08:01 +01:00
398ec829ae Force based EVA movement 2025-11-16 19:07:24 +01:00
ec69ed2ee5 Force based zero g movement component 2025-11-15 22:38:24 +01:00
3647aa599d WIP OrbitalBody3D rework 2025-11-15 18:12:00 +01:00
1342ca2610 New visible celestial bodies 2025-11-14 10:58:45 +01:00
27ce796898 Physics mode on new ship 2025-11-07 16:27:06 +01:00
cff5ec27f8 Spawnable test ship with multiplayer and orbit 2025-11-07 12:04:56 +01:00
245be4a4f5 WIP 3d refactor COMPILING 2025-11-07 09:52:39 +01:00
6b9efda0d2 Merge branch 'tech-test/3d-controller' 2025-11-06 17:57:43 +01:00
29851ea167 Move to physics based mouse input and try bugfix for basis reset 2025-11-06 16:15:53 +01:00
0cd9ebdd04 Remove low level network handler 2025-11-06 08:46:33 +01:00
4da8bcaec2 Fix camera jitter with disabled v-sync 2025-11-05 18:35:19 +01:00
71ad2f09ff Networked player pawns 2025-11-05 12:15:32 +01:00
2f5a88345f WIP Low level network handling 2025-11-03 17:27:36 +01:00
5e851049b5 WIP Networking 2025-11-02 20:37:46 +01:00
c4fd7f1330 WIP Server Client Logic 2025-11-01 18:30:38 +01:00
820da83397 Fix grab rotaiton issues 2025-11-01 18:30:17 +01:00
14b24beb23 Fix jetpack vectors, change seeking_climb behaviour 2025-11-01 14:09:26 +01:00
60f2ddb3d7 Functional launch mechanic in up and down vector 2025-10-31 16:44:07 +01:00
c50d0eae52 WIP Launch logic 2025-10-31 16:30:14 +01:00
d375e0d208 Remove bad AI inserted helper function 2025-10-31 15:03:59 +01:00
9b128a3540 Working release and grab logic 2025-10-31 14:52:16 +01:00
6df457a256 Functional release and coast for climb but no reach 2025-10-31 14:33:06 +01:00
bc443b884c Dev status update 2025-10-31 13:08:48 +01:00
8184ec06b4 Fix grip handover with new movement component 2025-10-31 11:50:03 +01:00
926a64c3dd WIP Refactoring to movement component hierarchy 2025-10-30 22:55:19 +01:00
29f9bccfd3 Move to physics based climbing 2025-10-30 17:29:09 +01:00
7d7580a123 Rotate on grip and climb past ladder 2025-10-29 21:18:16 +01:00
59d457e9ae Fix minor warnings 2025-10-29 19:43:57 +01:00
97ccb2a9ac Climb in more directions 2025-10-29 19:39:15 +01:00
1ab2c06336 Upgrade to 4.5 2025-10-29 19:39:01 +01:00
f51672c6a9 WIP Climbing movement 2025-10-28 16:25:32 +01:00
8e3f415cb4 Reverse rotation on grip area 2025-10-27 22:32:53 +01:00
cdcb4796f7 Grip rotation 2025-10-27 22:24:23 +01:00
24bc3afd2e Gripping offset 2025-10-26 13:17:44 +01:00
4f78de64ba Refactor into single state machine 2025-10-25 20:45:45 +02:00
fe050897dd Key Input struct 2025-10-25 19:58:16 +02:00
e075ff580d ZeroGMovementComponent WIP 2025-10-25 16:47:32 +02:00
90e756ad28 Initial registerering grip point 2025-10-25 15:42:31 +02:00
faf8e7c83a Fix roll 2025-10-25 10:54:22 +02:00
772f9c7df3 WIP Thruster pack refactor, non-working roll 2025-10-24 19:38:18 +02:00
e066bc4786 Free rotation for EVA pawn 2025-10-24 11:21:40 +02:00
cc681ae08a Bounce of materials in 3d 2025-10-23 23:04:40 +02:00
138e17503a Working camera controller 2025-10-23 10:17:55 +02:00
20a37dda17 Pitch and yaw camera and direct movement after look at 2025-10-22 16:58:06 +02:00
8645d2bdc4 Initial 3d controller and pawn 2025-10-22 14:40:19 +02:00
270 changed files with 6408 additions and 1900 deletions

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Config: The exact engine version this game project depends on
# You update this hash when you upgrade the engine
ENGINE_VERSION="a90daf08c2bb52d6cb4ba67bb5cbe09d79b2c4eb"
echo "Fetching Custom Editor [${ENGINE_VERSION}]..."
# Download from your Registry
curl -O "https://gitea.212.63.210.91.nip.io/api/packages/seedlingattempt/generic/godot-editor-windows/${ENGINE_VERSION}/godot-editor-windows.zip"
unzip -o godot-editor-windows.zip
echo "Editor ready! Run ./godot.windows.editor...exe to start."

View File

@ -0,0 +1,67 @@
name: Release Game Client
on:
push:
paths:
- '.gitea/workflows/build-game.yaml'
- '.gitea/**/*.sh'
tags:
- 'v*'
jobs:
export-windows:
runs-on: ubuntu-latest
container:
image: ubuntu:22.04
steps:
- name: Checkout Game
uses: actions/checkout@v3
- name: Install Dependencies
run: apt-get update && apt-get install -y unzip wget zip
- name: Download Engine Templates
env:
# The specific engine version this game needs
ENGINE_HASH: "a90daf08c2bb52d6cb4ba67bb5cbe09d79b2c4eb"
# This string MUST match what your custom engine reports as its version
# Example: "4.3.stable" or "4.3.stable.mygame"
GODOT_VERSION_STRING: "4.6"
run: |
echo "Downloading templates..."
wget "https://gitea.212.63.210.91.nip.io/api/packages/seedlingattempt/generic/godot-templates/${ENGINE_HASH}/windows_templates.tpz"
echo "Installing templates..."
# Godot looks for templates in ~/.local/share/godot/export_templates/{VERSION}/
TEMPLATE_PATH="$HOME/.local/share/godot/export_templates/${GODOT_VERSION_STRING}"
mkdir -p "$TEMPLATE_PATH"
# Unzip our TPZ directly into that folder
unzip windows_templates.tpz -d "$TEMPLATE_PATH"
# Rename them to standard names if they aren't already
# (Your publish script already renamed them to windows_release_x86_64.exe, so this is fine!)
- name: Download Headless Editor (For Exporting)
env:
ENGINE_HASH: "a90daf08c2bb52d6cb4ba67bb5cbe09d79b2c4eb"
run: |
wget "https://gitea.212.63.210.91.nip.io/api/packages/seedlingattempt/generic/godot-editor-windows/${ENGINE_HASH}/godot-editor-windows.zip"
unzip godot-editor-windows.zip
chmod +x godot.windows.editor*.exe
# Create a simple alias
mv godot.windows.editor*.exe godot_headless
- name: Export Game
run: |
mkdir -p build/windows
echo "Exporting..."
cd src
# The preset name "Windows Desktop" must match your export_presets.cfg
./godot_headless --headless --export-release "Windows Desktop" ../build/windows/game.exe
- name: Upload Game Artifact
uses: actions/upload-artifact@v3
with:
name: windows-client
path: build/windows

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# Godot 4+ specific ignores
.godot/
.vscode/
/android/
*.tmp
/export/

0
.gitmodules vendored Normal file
View File

View File

@ -0,0 +1,52 @@
# Project "Millimeters of Aluminium" Development Log
## Overview
The project is undergoing a major architectural refactor to move from a monolithic Spaceship class to a fully modular, component-based system. The foundation for this new architecture, centered around Module, Component, and Station classes, is now in place. The next steps involve migrating legacy systems into this new paradigm.
## I. Fully Implemented & Stable Systems
Custom Physics Core: All physical objects now inherit from a custom OrbitalBody2D class, which handles mass aggregation and force integration. The physics loop is correctly disabled in the editor to prevent errors.
Modular Ship Construction:
Module as Root: The Module class now serves as the root for ship assemblies, managing its own list of structural pieces and components without needing container nodes.
Builder Plugin: The editor plugin is updated to work with this new architecture, allowing the placement of StructuralPiece nodes directly onto Module nodes.
Character & Interaction Foundation:
Zero-G Movement: The PilotBall character has a state machine for handling movement inside ship interiors, including sluggish zero-G floating and direct control on ladders.
Generic Station Component: A StationComponent class has been implemented. It serves as a generic hardware terminal that characters can interact with.
Data-Driven UI Architecture:
Databank Resource: A Databank Resource class has been created. It acts as "software," holding a reference to a UI scene that can be loaded by a station.
## II. Work-In-Progress (WIP) and Planned Systems
This list details systems we have designed but are not yet fully implemented in the code.
System Migration to Databanks:
Helm/Flight Controls: The logic from the old ThrusterController.gd needs to be moved into a HelmUI.tscn scene and driven by a HelmDatabank.
Navigation Computer: The UI has been moved, but the extensive planning and calculation logic from NavigationComputer.gd needs to be transferred to NavUI.gd.
Fuel and Life Support: The FuelSystem and LifeSupport nodes are still part of the old Spaceship.tscn. They need to be fully redesigned as Component classes (e.g., FuelTank, AtmosphereProcessor).
Component Wiring System:
Signal/Socket Advertising: Components and Databanks need to be updated with get_input_sockets() and get_output_signals() functions.
Wiring Data Storage: The Module class needs a wiring_data array to store the connections made in the builder.
Builder UI: A visual wiring interface needs to be added to the module builder plugin.
Orbital Stability Test:
Ghost Simulator: A GhostSimulator class needs to be created to run predictive, in-memory physics calculations.
Test Runner: An orbital_stability_test.tscn scene and script are needed to orchestrate the test, compare live vs. ghost results, and generate a report.
Full Spaceship Class Retirement: The final step will be to delete Spaceship.tscn and Spaceship.gd once all their logic and systems have been successfully migrated to the new modular architecture.

View File

@ -0,0 +1,78 @@
## Development Progress Report 14/10 - 25
### Overview
The project has successfully undergone a foundational architectural refactor. The legacy monolithic Spaceship class has been deprecated in favor of a fully modular, component-based architecture designed for multiplayer scalability. The core gameplay loop of a player spawning, possessing a character, using a station, and controlling a ship's systems via a diegetic UI is now functional. The project is now entering "Cycle 2" of development, focusing on unifying the physics system and migrating the remaining legacy gameplay logic into the new architecture.
### ✅ Implemented Systems & Features
#### 1. Core Architecture
Modular Ships: Ships are now built around a root Module class which extends OrbitalBody2D. The Module dynamically understands its structure by finding its Component and StructuralPiece children, removing the need for rigid container nodes.
Custom Physics Body: The OrbitalBody2D class serves as the base for all physical objects in the simulation, including ship parts and modules. It correctly handles force routing from child components to the root physics body and includes a robust, deferred check to warn if a child class forgets to call super() in its _ready function.
Dynamic Inertia Calculation: The ship's moment of inertia is now calculated realistically based on the mass and distribution of all its component parts in local space, leading to more authentic rotational physics.
#### 2. Player Control & Multiplayer Foundation
PlayerController/Pawn Architecture: A multiplayer-ready control scheme has been implemented.
The PlayerController class is responsible for capturing raw input and sending it to the server via RPCs.
The PilotBall (the "Pawn") is now a "dumb" character that only acts on commands received from its controller, with all direct calls to the Input singleton successfully removed.
Dynamic Spawning & Possession: The GameManager now manages the game's startup sequence. It dynamically spawns a PlayerController and a default ship (Tube.tscn), and then correctly "possesses" the PilotBall within the ship with its corresponding controller.
Local Server Initialization: The GameManager correctly initializes a local ENet server, which enables the multiplayer authority system (is_multiplayer_authority()) to function correctly in a single-player testing environment.
#### 3. Station & UI Systems
Modular UI Framework: The "databank" system has been fully implemented and separated into three distinct resource types:
ControlPanel: A resource representing a physical UI element (e.g., a screen, a lever).
Databank: A resource representing a "datashard" which contains pure logic in a script.
SystemStation: The physical station component that acts as a "chassis," hosting panels and databanks.
Persistent Station Logic: The SystemStation has been refactored to instantiate datashard logic (_ready) once for its entire lifetime, allowing for background processing. UI Panels are created ephemerally only when a player occupies the station.
Functional Helm: The helm is partially migrated.
A HelmLogicShard contains the attitude-hold (PD controller) and calibration logic.
ControlPanels for a throttle lever, buttons, and a status readout are functional.
The station correctly wires the panels to the helm shard at runtime, allowing the player to control the ship's main engine and RCS thrusters.
Functional Sensor Display: The sensor/map system is partially migrated.
A SensorSystemShard is responsible for gathering all trackable bodies in the system.
A refactored SensorPanel acts as a "dumb" display that visualizes the "sensor feed" signal it receives.
The ShipStatusShard correctly displays the ship's velocity and rotational data on the ReadoutScreen and also displays the currently selected target from the map.
### ❌ Not Yet Implemented / Pending Tasks
#### 1. Physics & Simulation (Cycle 2 Priority)
CelestialBody Refactor: All celestial bodies (Planets, Moons, etc.) still inherit from Godot's RigidBody2D and use the _integrate_forces callback. They must be refactored to extend our custom OrbitalBody2D to create a single, stable physics simulation.
Astronomical vs. Ship Scale: A system for scaling forces needs to be designed and implemented to allow massive planets and lightweight ships to coexist and interact realistically within the same simulation.
Simulation Stability Test: The proposed GhostSimulator and test runner for verifying long-term orbital stability has not yet been created.
Interior Physics: The simulation does not yet account for how the ship's acceleration affects characters or loose objects inside it.
#### 2. System & Feature Migration
Full Helm/Nav Migration: The complex maneuver planning logic (e.g., Hohmann transfers) still resides in the legacy navigation_computer.gd script and needs to be migrated into one or more Databank shards.
Thruster Refactor: The Thruster component still uses a simple turn_on()/turn_off() model. It needs to be refactored to accept a variable set_throttle(value) command for more precise control.
Retirement of Legacy Scenes/Scripts: The old Spaceship.tscn, spaceship.gd, thruster_controller.gd, and navigation_computer.gd files are still in the project and need to be fully removed once their logic is migrated.
#### 3. Planned Features (Future Cycles)
Wiring System: The foundational classes exist, but the in-game system for players to visually wire panels to databanks, and the editor tools to support this, have not been started.
Character & Movement: The current PilotBall is a placeholder. The planned humanoid character, grapple points, and EVA gameplay are not yet implemented.
Core Gameplay Systems: The foundational systems for Electricity, Life Support (pressurization), Fuel, Character Damage, and Inventory/Pickupable Objects have not yet been created.
Multiplayer Connectivity: While the architecture supports it, the UI and logic for multiple players to connect to a server (e.g., a main menu with a "Join Game" option) do not yet exist.

View File

@ -0,0 +1,52 @@
## Project Development Status Update 16/10 - 25
### Overview
The project has successfully completed a major architectural refactor, establishing a stable and scalable foundation for the simulation. The core physics model has been unified under a custom OrbitalBody2D class and a hierarchical Barycenter system, which has resolved previous orbital instabilities. The ship's control systems are being migrated to a flexible, data-driven "databank" architecture, and the UI is now managed by a robust grid-based layout system. The focus can now shift to migrating the remaining legacy systems and building out core gameplay features on this new foundation.
### ✅ Implemented Systems & Features
#### 1. Hierarchical Physics Simulation (Barycenter Architecture)
Global & Local Grids: The simulation is now anchored by a StarSystem root node, which defines the global grid. Procedurally generated planetary systems are encapsulated within Barycenter nodes, which act as moving "local grids" for their contents. This has stabilized the orbits of moons and planets.
Physics Roles: A clear distinction has been made between physics actors and passive bodies.
Barycenter nodes are the primary physics objects in the global simulation, inheriting from OrbitalBody2D and responding to gravitational forces.
Celestial bodies (Star, Planet, Moon) are now simple Node2Ds that provide mass data to their parent Barycenter but do not run their own physics integration, solving the "triple velocity" bug.
Centralized Physics Loop: All gravity calculations are now managed by the OrbitalMechanics singleton in a multi-stage _physics_process loop, which handles global (Barycenter-to-Star) and local (Moon-to-Planet) interactions separately.
#### 2. Procedural Generation & Player Spawn
Generator as a Tool: The StarSystemGenerator has been refactored into a RefCounted class that acts as a factory, cleanly separating the generation process from the final StarSystem product.
Stable Orbit Placement: The generator now uses astrophysical concepts like the Roche Limit and Hill Sphere (abstracted into helper functions in OrbitalMechanics) to procedurally place planets and moons in stable, non-overlapping orbits.
Lagrange Point Spawning: The player ship is now correctly spawned at the L4 or L5 Lagrange point of the outermost planet, with the proper initial velocity to maintain a stable position.
#### 3. Data-Driven Ship Systems (Databanks)
Autopilot Migration: The core logic for planning and executing maneuvers has been successfully migrated from the legacy ThrusterController into a series of decoupled databank shards:
NavSelectionDatabank: Stores the current navigation target.
ManeuverPlannerDatabank: Calculates maneuver burn plans (e.g., Hohmann transfers).
AutopilotDatabank: Executes the steps of a received plan.
Modular UI Layout: The SystemStation now functions as a layout manager, instancing and positioning UI panels based on grid data defined in ControlPanel resources. This has removed hardcoded positions and allows for flexible, data-driven UI configurations.
#### 4. Orbit Projection & Debugging
Unified Projection Function: The OrbitalMechanics library now contains a single, generalized project_n_body_paths function. This function can run a "ghost simulation" on any arbitrary set of bodies in either local or global space to generate predictive orbital paths for the map panel.
Orrery View: A dedicated debugging tool, the OrreryView scene, has been created to provide a clean, interactive chart for inspecting procedurally generated star systems without the interference of game UI or camera logic.
### ⏳ Planned & Discussed Future Implementations
#### 1. Advanced Physics Optimization
Centralized N-Body Calculation: The plan is to have the OrbitalMechanics singleton manage all gravity calculations in a single, authoritative loop each frame. This will enable advanced optimizations and debugging, such as a "force queue" to prevent calculation errors.
Sphere of Influence (SOI) Model: For dynamic objects like the player's ship, we will implement an SOI system. The ship will calculate its gravity against the full system hierarchy when in "deep space" but will switch to calculating against only the local bodies (e.g., a planet and its moons) when it enters a Barycenter's sphere of influence.
Performance Culling & Caching: For performance-intensive scenarios like asteroid belts, we've discussed implementing timers to cache and reuse negligible force calculations over several frames, only recalculating when necessary.
#### 2. Component "API" & Wiring System
Component Contracts: To facilitate the upcoming visual wiring system, we will formalize the "API" for ControlPanel and Databank resources. This will be done by creating new scripts that extend the base classes and override the get_input_sockets() and get_output_signals() functions to explicitly define what signals and functions each component provides.
Static vs. Resource-Based API: We've concluded that using extended Resource scripts to define these APIs is superior to using static functions on the node scripts. This decouples the data contract from the implementation and allows a single scene to be used with multiple different data configurations, which is critical for a flexible wiring system.

View File

@ -0,0 +1,64 @@
## Project Development Status Update: 31/10/25
### 3D Character Controller & Movement Tech Demo (Cycle 3)
Work has proceeded on a tech demo for the 3D character controller, establishing a robust, physics-based system for zero-G movement. The architecture has been refactored to prioritize a clean separation of concerns, with a central "pawn" acting as a physics integrator and modular "controllers" acting as the "brains" for different movement types.
### ✅ Implemented Features
#### Pawn/Controller Architecture: The character is split into several key classes:
CharacterPawn3D: The core CharacterBody3D. It acts as a "dumb" physics integrator, holding velocity and angular_velocity, integrating rotation, and calling move_and_slide(). It no longer contains movement-specific state logic.
PlayerController3D: Gathers all hardware input (keyboard, mouse) and packages it into KeyInput dictionaries (pressed, held, released) to send to the pawn via RPC.
EVAMovementComponent: Refactored into a "dumb tool". It exposes functions like apply_thrusters() and apply_orientation() which are called by other controllers.
ZeroGMovementComponent: This is now the "brain" for all zero-G movement. It receives all inputs from the pawn and contains its own internal state machine (IDLE, REACHING, GRIPPING, CLIMBING, CHARGING_LAUNCH).
#### Contextual Movement Logic:
The ZeroGMovementComponent decides when to use the EVA suit. In its IDLE state, it checks for fresh movement input (movement_input_was_neutral) before calling the EVAMovementComponent's apply_thrusters function.
This successfully implements "coast on release," where releasing a grip (_release_current_grip) flags the movement input as "stale," preventing the EVA suit from engaging even if the key is still held.
#### EVA/Jetpack Controls:
The EVAMovementComponent provides force-based linear movement (WASD, Shift/Ctrl) and torque-based angular roll (Q/E).
A body-orientation function (_orient_pawn) allows the pawn to auto-align with the camera's forward direction.
#### Physics-Based Grip System:
GripArea3D: A composition-based Area3D node provides the interface for all grabbable objects. It requires its parent to implement functions like get_grip_transform and get_push_off_normal.
Grip Detection: The CharacterPawn3D uses a GripDetector Area3D to find GripArea3D nodes in range and passes this nearby_grips list to the ZeroGMovementComponent.
GRIPPING State: This state is now fully physics-based. Instead of setting the pawn's global_transform, the _apply_grip_physics function uses a PD controller to apply linear forces (to move to the offset position) and angular torques (to align with the grip's orientation).
Grip Orientation: The gripping logic correctly calculates the closest of two opposing orientations (e.g., "up" or "down" on a bar) by comparing the pawn's current up vector to the grip's potential up vectors.
Grip Rolling: While in the GRIPPING state, the player can use Q/E to override the auto-orientation and apply roll torque around the grip's axis.
#### Physics-Based Climbing:
CLIMBING State: This state applies lerp'd velocity to move the pawn, allowing it to interact with physics.
Climb Targeting: The _find_best_grip function successfully identifies the next valid grip within a configurable climb_angle_threshold_deg cone.
Handover: Logic in _process_climbing correctly identifies when the pawn is close enough to the next_grip_target to _perform_grip_handover.
Climb Release: The pawn will correctly release its grip and enter the IDLE state (coasting) if it moves past the current_grip by release_past_grip_threshold without a new target being found.
### ❌ Not Yet Implemented / Pending Tasks
REACHING State: The REACHING state exists but its logic (_process_reaching) is a stub that instantly calls _try_initiate_reach. The full implementation (e.g., procedural animation/IK moving the hand to the target) is pending.
CHARGING_LAUNCH State: The state exists and the execution logic is present (_handle_launch_charge, _execute_launch), but the state transition logic in _update_state does not currently allow entering this state from GRIPPING (it's overshadowed by the _start_climb check).
Ladder (3D) & Walking (3D) States: The CharacterPawn3D has high-level states for GRIPPING_LADDER and WALKING, but the movement functions (_apply_ladder_movement, _apply_walking_movement) are stubs.
Generic Surface Grab: The TODO to allow the ZeroGMovementComponent to grab any physics surface (not just a GripArea3D) is not implemented.
EVA Stabilization: The _apply_stabilization_torques function in EVAMovementComponent is still a placeholder.

View File

@ -0,0 +1,89 @@
# Development Status Report - 2025-11-21
## Overview
Significant progress was made on the Unified Build System and the In-Game Builder, moving away from purely editor-based construction. We successfully implemented procedural geometry generation for structural pieces and refactored the snapping logic to support the upcoming geodesic structures. Networking logic also saw crucial stability fixes.
## Completed Features & Implementations
### 1. Unified Build System Foundation
- StructureData Resource: Implemented a robust resource-based definition system for ship parts. This decouples the "DNA" of a part (mesh, collider, mounts, health) from its scene instantiation, allowing shared logic between the Editor Plugin and the In-Game Builder.
- PieceMount Logic: Formalized attachment points into a dedicated PieceMount class (inheriting Area3D). This replaced the ad-hoc dictionary system, providing type safety and better collision filtering.
- ProceduralPiece Generator: Created a script that dynamically generates 3D meshes and collision shapes based on parameters (e.g., triangle vs. square, size, thickness) defined in StructureData. This supports:
- Extruded 3D geometry (thickness) rather than flat planes.
- Convex collision hull generation.
- "Opaque Blueprint" preview materials (semi-transparent, emissive).
### 2. In-Game Builder (PlayerController3D)
- Build Mode State: Implemented a toggleable Build Mode (B key) in PlayerController3D.
- Piece Selection: Added logic to select pieces via hotkeys (1 = Square, 2 = Triangle), instantiate a preview "ghost," and switch its material.
- Raycast Snapping: Implemented a physics-based raycast/sweep to detect existing ship modules and mounts.
- Visual Feedback: Added color-coded feedback for the preview ghost:
- Cyan: Floating (no snap target found).
- Geen: Snapped (aligned with a valid mount).
### 3. Snapping Logic Refactor
- SnappingTool Class: Created a dedicated static helper class for snapping math.
- Shape Cast Sweep: Replaced simple raycasting with a sphere_cast (radius 0.2m). This adds "thickness" to the cursor, making it much easier to hit thin structural elements like struts or small mounts.
- Transform Alignment: Implemented matrix math to align a new piece's mount with a target mount, respecting position, normal (facing direction), and up-vector (roll/orientation).
### 4. Networking Stability (Previous Session)
- Server Authority Enforcement: enforced strict server authority on CharacterPawn3D, removing client-side overrides that caused "fighting" and stutter.
- Relative Velocity Sync: Implemented logic to sync local_velocity relative to the parent ship instead of global velocity. This prevents pawns from "falling out the back" of moving ships during network jitter.
- Input Serialization: Fixed RPC errors by converting custom KeyInput objects to Dictionaries before transmission.
## Pending / In-Progress
- Mount Orientation Constraints: The snapping tool currently aligns normals but needs refinement to strictly enforce edge length compatibility and specific mount types (e.g., preventing a 2m edge from snapping to a 1m edge).
- Module Persistence: The logic for creating a new Module when building in empty space works in memory but needs testing for persistence and saving.
- Collision Layers: Need to verify that all PieceMount nodes are consistently on the correct physics layer (1 << 14) to ensure the snapping sweep always finds them.
## Discussion & Direction Changes
### Shift to "Geodesic & Procedural" Building
We moved away from the initial "Voxel Grid" concept for ship hulls.
- Old Direction: Ships built on a strict integer grid (Minecraft-style but with slopes).
- New Direction: A node-based "Geodesic" system. Pieces connect Node-to-Node (Mount-to-Mount) at arbitrary angles. This allows for complex shapes (hexagonal cylinders, spheres, rings) and supports the "Industrial/Hard Sci-Fi" aesthetic better.
- Implication: The building tool no longer relies on grid_step for positioning. It relies entirely on the SnappingTool to calculate transforms based on the geometry of the mounting points.
### Shift to "Server Authoritative" Networking
We abandoned the "Client Authoritative" movement for pawns to solve synchronization issues.
- Old Plan: Client moves pawn, Server accepts pos. (Caused hacking risks and desync with physics objects).
- New Plan: Server simulates physics. Client sends inputs. MultiplayerSynchronizer interpolates the result. To combat latency feel, we are using Visual Interpolation (detaching camera/mesh from the physics body) rather than full Client-Side Prediction (CSP) for this milestone.
### Manufacturing & Blueprints
We discussed that the StructureData resource is the key enabler for the manufacturing gameplay loop. By defining parts as data (Resources), we can easily:
1. Store a "Blueprint" as a list of StructureData references + Transforms.
2. Have a manufacturing machine consume resources to produce a "Crate" containing a StructureData item.
3. Have the player pick up that item and use it to place the ProceduralPiece.

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"godotTools.editorPath.godot4": "./godot_engine/bin/godot.windows.editor.double.x86_64.console.exe",
"search.exclude": {
"/godot_engine": true
}
}

View File

@ -180,3 +180,67 @@ Performance Culling & Caching: For performance-intensive scenarios like asteroid
Component Contracts: To facilitate the upcoming visual wiring system, we will formalize the "API" for ControlPanel and Databank resources. This will be done by creating new scripts that extend the base classes and override the get_input_sockets() and get_output_signals() functions to explicitly define what signals and functions each component provides.
Static vs. Resource-Based API: We've concluded that using extended Resource scripts to define these APIs is superior to using static functions on the node scripts. This decouples the data contract from the implementation and allows a single scene to be used with multiple different data configurations, which is critical for a flexible wiring system.
## Project Development Status Update: 31/10/25
### 3D Character Controller & Movement Tech Demo (Cycle 3)
Work has proceeded on a tech demo for the 3D character controller, establishing a robust, physics-based system for zero-G movement. The architecture has been refactored to prioritize a clean separation of concerns, with a central "pawn" acting as a physics integrator and modular "controllers" acting as the "brains" for different movement types.
### ✅ Implemented Features
#### Pawn/Controller Architecture: The character is split into several key classes:
CharacterPawn3D: The core CharacterBody3D. It acts as a "dumb" physics integrator, holding velocity and angular_velocity, integrating rotation, and calling move_and_slide(). It no longer contains movement-specific state logic.
PlayerController3D: Gathers all hardware input (keyboard, mouse) and packages it into KeyInput dictionaries (pressed, held, released) to send to the pawn via RPC.
EVAMovementComponent: Refactored into a "dumb tool". It exposes functions like apply_thrusters() and apply_orientation() which are called by other controllers.
ZeroGMovementComponent: This is now the "brain" for all zero-G movement. It receives all inputs from the pawn and contains its own internal state machine (IDLE, REACHING, GRIPPING, CLIMBING, CHARGING_LAUNCH).
#### Contextual Movement Logic:
The ZeroGMovementComponent decides when to use the EVA suit. In its IDLE state, it checks for fresh movement input (movement_input_was_neutral) before calling the EVAMovementComponent's apply_thrusters function.
This successfully implements "coast on release," where releasing a grip (_release_current_grip) flags the movement input as "stale," preventing the EVA suit from engaging even if the key is still held.
#### EVA/Jetpack Controls:
The EVAMovementComponent provides force-based linear movement (WASD, Shift/Ctrl) and torque-based angular roll (Q/E).
A body-orientation function (_orient_pawn) allows the pawn to auto-align with the camera's forward direction.
#### Physics-Based Grip System:
GripArea3D: A composition-based Area3D node provides the interface for all grabbable objects. It requires its parent to implement functions like get_grip_transform and get_push_off_normal.
Grip Detection: The CharacterPawn3D uses a GripDetector Area3D to find GripArea3D nodes in range and passes this nearby_grips list to the ZeroGMovementComponent.
GRIPPING State: This state is now fully physics-based. Instead of setting the pawn's global_transform, the _apply_grip_physics function uses a PD controller to apply linear forces (to move to the offset position) and angular torques (to align with the grip's orientation).
Grip Orientation: The gripping logic correctly calculates the closest of two opposing orientations (e.g., "up" or "down" on a bar) by comparing the pawn's current up vector to the grip's potential up vectors.
Grip Rolling: While in the GRIPPING state, the player can use Q/E to override the auto-orientation and apply roll torque around the grip's axis.
#### Physics-Based Climbing:
CLIMBING State: This state applies lerp'd velocity to move the pawn, allowing it to interact with physics.
Climb Targeting: The _find_best_grip function successfully identifies the next valid grip within a configurable climb_angle_threshold_deg cone.
Handover: Logic in _process_climbing correctly identifies when the pawn is close enough to the next_grip_target to _perform_grip_handover.
Climb Release: The pawn will correctly release its grip and enter the IDLE state (coasting) if it moves past the current_grip by release_past_grip_threshold without a new target being found.
### ❌ Not Yet Implemented / Pending Tasks
REACHING State: The REACHING state exists but its logic (_process_reaching) is a stub that instantly calls _try_initiate_reach. The full implementation (e.g., procedural animation/IK moving the hand to the target) is pending.
CHARGING_LAUNCH State: The state exists and the execution logic is present (_handle_launch_charge, _execute_launch), but the state transition logic in _update_state does not currently allow entering this state from GRIPPING (it's overshadowed by the _start_climb check).
Ladder (3D) & Walking (3D) States: The CharacterPawn3D has high-level states for GRIPPING_LADDER and WALKING, but the movement functions (_apply_ladder_movement, _apply_walking_movement) are stubs.
Generic Surface Grab: The TODO to allow the ZeroGMovementComponent to grab any physics surface (not just a GripArea3D) is not implemented.
EVA Stabilization: The _apply_stabilization_torques function in EVAMovementComponent is still a placeholder.

View File

@ -2,7 +2,9 @@
## 1. Game Vision & Concept
Project Millimeters of Aluminum is a top-down 2D spaceship simulation game that emphasizes realistic orbital mechanics, deep ship management, and cooperative crew gameplay. Players take on roles as members of a multi-species crew aboard a modular, physically simulated spaceship.
Project Millimeters of Aluminum is a third-person 3D spaceship simulation game that emphasizes realistic physics, deep ship management, and cooperative crew gameplay. Players take on roles as members of a multi-species crew aboard a modular, physically simulated spaceship.
The game's aesthetic is inspired by the functional, industrial look of real-world space hardware and sci-fi like The Expanse, focusing on diegetic interfaces and detailed, functional components. The core experience is about planning and executing complex maneuvers in a hazardous, procedurally generated star system, where understanding the ship's systems is as important as piloting skill.
The game's aesthetic is inspired by the technical, gritty, and high-contrast 2D style of games like Barotrauma, focusing on diegetic interfaces and detailed, functional components. The core experience is about planning and executing complex maneuvers in a hazardous, procedurally generated star system, where understanding the ship's systems is as important as piloting skill.
@ -12,26 +14,80 @@ The gameplay is centered around a Plan -> Execute -> Manage loop:
1. Plan: The crew uses the Navigation Computer to analyze their orbit and plan complex maneuvers, such as a Hohmann transfer to another planet. They must account for launch windows, fuel costs, and travel time.
2. Execute: The crew engages the autopilot or manually pilots the ship. The Thruster Controller executes the planned burns, performing precise, fuel-optimal rotations and main engine thrusts to alter the ship's trajectory.
2. Execute: The crew engages the autopilot or manually pilots the ship. The Helm executes the planned burns, performing precise, fuel-optimal rotations and main engine thrusts to alter the ship's trajectory.
3. Manage: While underway, the crew manages the ship's modular systems, monitors resources like fuel and power, and responds to emergent events like hull breaches or system failures.
3. Manage: While underway, the crew moves about the ship's 3D interior, manages modular systems, monitors resources, and responds to emergent events like hull breaches or system failures.
## 3. Key Features
### 3. Key Features
### 1. Procedural Star System
The game world is a procedurally generated star system created by the StarSystemGenerator. Each system features a central star, a variable number of planets, moons, and asteroid belts, creating a unique environment for each playthrough.
### 2. N-Body Physics Simulation
Major bodies in orbit (CelestialBody class) are goveerened by a simplified n-body gravity simulation. Physical objects with player interaction (ships, crew characters, detached components, and eventually stations) are governed by a realistic N-body gravitational simulation, managed by the OrbitalMechanics library.
- Objects inherit from a base OrbitalBody2D class, ensuring consistent physics.
- This allows for complex and emergent orbital behaviors, such as tidal forces and stable elliptical orbits.
Major bodies in orbit (CelestialBody class) are governed by a 3D n-body gravity simulation, managed by the OrbitalMechanics library. Objects inherit from a base OrbitalBody3D class, ensuring consistent physics. The simulation allows for complex and emergent orbital behaviors.
### 3. Modular Spaceship
The player's ship is not a monolithic entity but a collection of distinct, physically simulated components attached to a root Module node.
The Module class extends OrbitalBody3D and aggregates mass and inertia from all child Component and StructuralPiece nodes.
Ship logic is decentralized into data-driven "databanks," such as the HelmLogicShard and AutopilotShard.
Hardware, like a Thruster, is a 3D Component that applies force to the root Module.
### 4. Advanced Navigation Computer
This is the primary crew interface for long-range travel, presented as a diegetic 2D screen (SensorPanel) within the 3D world.
Maneuver Planning: The computer can calculate various orbital transfers, each with strategic trade-offs:
Hohmann Transfer
Brachistochrone (Torchship) Trajectory
Tactical Map: A fully interactive UI map featuring:
Zoom-to-cursor and click-and-drag panning.
Predictive orbital path drawing.
Icon culling and detailed tooltips.
### 5. Physics-Based 3D Character Control
Character control is built on a robust, physics-based 3D system designed for complex zero-G environments.
Pawn/Controller Architecture: Player control is split between a PlayerController3D (which gathers hardware input and sends it via RPC) and a CharacterPawn3D (a CharacterBody3D that acts as the physics integrator).
Modular Movement: The pawn's movement logic is handled by component "brains." The ZeroGMovementComponent manages all zero-G interaction, while the EVAMovementComponent acts as a "dumb tool" providing thruster forces.
Physics-Based Gripping: Players can grab onto designated GripArea3D nodes. This is not an animation lock; a PD controller applies forces to the player's body to move them to the grip point and align them with its orientation.
Zero-G Traversal: The ZeroGMovementComponent features a state machine for IDLE (coasting), CLIMBING (moving between grips), REACHING (pending implementation), and CHARGING_LAUNCH (pushing off surfaces).
### 6. Runtime Component Design & Engineering
(This future-facing concept remains valid from the original design)
To move beyond pre-defined ship parts, the game will feature an in-game system for players to design, prototype, and manufacture their own components. This is achieved through a "Component Blueprint" architecture that separates a component's data definition from its physical form.
Component Blueprints: A ComponentBlueprint is a Resource file (.tres) that acts as a schematic.
Generic Template Scenes: The game will use a small number of generic, unconfigured "template" scenes (e.g., generic_thruster.tscn).
The Design Lab: Players will use a dedicated SystemStation to create and modify blueprints.
Networked Construction: A global ComponentFactory on the server will instantiate and configure components based on player-chosen blueprints, which are then replicated by the MultiplayerSpawner.
### 3. Modular Spaceship
The player's ship is not a monolithic entity but a collection of distinct, physically simulated components attached by joints. Key modules include:
- Spaceship: The main RigidBody2D hull that tracks overall mass, inertia, and health.
- Thruster: Self-contained RigidBody2D components that apply their own force. Their role (main engine, RCS, etc.) is an emergent property of their placement on the hull.
- Spaceship: The main RigidBody3D hull that tracks overall mass, inertia, and health.
- Thruster: Self-contained RigidBody3D components that apply their own force. Their role (main engine, RCS, etc.) is an emergent property of their placement on the hull.
- ThrusterController: The "brains" of the ship's movement, featuring a sophisticated autopilot that can execute fuel-optimal "bang-coast-bang" rotational maneuvers and a PD controller for stable attitude hold.
- FuelSystem & LifeSupport: Centralized managers for resources and internal ship environment. Hull breaches can create thrust vectors from escaping atmosphere.
- On-board Sensors: Diegetic components like Accelerometers allow the crew to calibrate ship performance by test-firing thrusters and measuring the true physical output.
@ -62,19 +118,33 @@ Character progression is based on distinct species with physical advantages and
- Ship AI: A non-physical class that interacts directly with ship systems at the cost of high power and heat generation.
### 6. Runtime Component Design & Engineering
To move beyond pre-defined ship parts, the game will feature an in-game system for players to design, prototype, and manufacture their own components. This is achieved through a "Component Blueprint" architecture that separates a component's data definition from its physical form.
- **Component Blueprints:** A `ComponentBlueprint` is a `Resource` file (`.tres`) that acts as a schematic. It contains metadata (name, description), a reference to a generic base scene (e.g., a "thruster chassis"), and a dictionary of overridden properties (e.g., `{"thrust_force": 7500, "mass": 120}`).
- **Generic Template Scenes:** Instead of dozens of unique component scenes, the game will use a small number of generic, unconfigured "template" scenes (e.g., `generic_thruster.tscn`, `generic_power_plant.tscn`). These scenes have scripts with exported variables that define their performance characteristics.
- **The Design Lab:** Players will use a dedicated `SystemStation` (the "Design Lab") to create and modify blueprints. This UI will dynamically generate controls (sliders, input fields) based on the exported variables of the selected template scene. Players can tweak parameters, balancing trade-offs like performance vs. resource cost, and save the result as a new blueprint resource in their personal data folder.
- **Networked Construction:** When a player builds an object in-game, they are selecting one of their saved blueprints.
1. The client sends an RPC to the server with the path to the chosen `ComponentBlueprint` resource.
2. The server validates the request and loads the blueprint. (This requires a system for syncing player-created blueprints to the server upon connection).
3. A global `ComponentFactory` singleton on the server takes the blueprint, instantiates the correct generic template scene, and applies the blueprint's property overrides to the new instance.
4. This fully-configured node is then passed to the `MultiplayerSpawner`, which replicates the object across the network, ensuring all clients see the correctly customized component.
## 4. Technical Overview
- Architecture: The project uses a decoupled, modular architecture heavily reliant on a global SignalBus for inter-scene communication and a GameManager for global state. Ships feature their own local ShipSignalBus for internal component communication.
- Architecture: The project uses a decoupled, modular architecture. A GameManager handles global state, while ship systems are managed by ControlPanel and Databank resources loaded by a SystemStation.
- Key Scripts:
- OrbitalBody2D.gd: The base class for all physical objects.
- Spaceship.gd: The central hub for a player ship.
- Thruster.gd: A self-contained, physically simulated thruster component.
- ThrusterController.gd: Contains advanced autopilot and manual control logic (PD controller, bang-coast-bang maneuvers).
- NavigationComputer.gd: Manages the UI and high-level maneuver planning.
- MapDrawer.gd: A Control node that manages the interactive map UI.
- MapIcon.gd: The reusable UI component for map objects.
-OrbitalBody3D.gd: The base class for all physical objects.
- Module.gd: The central hub for a player ship, aggregating mass, inertia, and components.
- HelmLogicShard.gd / AutopilotShard.gd: Databanks that contain the advanced autopilot and manual control logic.
- SensorPanel.gd: A Control node that manages the interactive map UI.
- CharacterPawn3D.gd / ZeroGMovementComponent.gd: Manages all third-person 3D physics-based character movement.
- Art Style: Aims for a Barotraumainspired aesthetic using 2D ragdolls (Skeleton2D, PinJoint2D), detailed sprites with normal maps, and high-contrast dynamic lighting (PointLight2D, LightOccluder2D).
- Art Style: Aims for a functional, industrial 3D aesthetic. Character movement is physics-based using CharacterBody3D and Area3D grip detection. Ship interiors will be built from 3D modules and viewed from an over-the-shoulder camera.
## 5. Game Progression & Economy
This is the biggest area for potential expansion. A new section could detail how the player engages with the world and improves their situation over time.
@ -112,9 +182,10 @@ You mention "emergent events" in the gameplay loop. It would be beneficial to de
Since co-op and crew management are central, detailing this aspect is crucial.
### 1. Ship Interior Management:
- Diegetic Interfaces: You mention this in the vision. It's worth specifying how the crew will interact with systems. Will they need to be at a specific console (like the Navigation Computer) to use it? Do repairs require a character to physically be at the damaged module?
- Atmospherics & Life Support: How is the ship's interior environment simulated? Will fires or toxic gas leaks be a possibility? This ties directly into your LifeSupport system.
- Diegetic Interfaces: The crew will interact with systems from a third-person, over-the-shoulder perspective. They must be at a specific SystemStation to use its panels. Repairs will require a character to physically be at the damaged module.
- Atmospherics & Life Support: How is the ship's interior environment simulated? This will tie into the LifeSupport system.
### 2. Character States:
- Health & Injury: How are characters affected by hazards? Can they be injured in high-G maneuvers or from system failures?
- EVA (Extra-Vehicular Activity): Detail the mechanics for EVAs. What equipment is needed? How is movement handled in zero-G? This would be a perfect role for the "Hard Vacuum Monster" species.
- EVA (Extra-Vehicular Activity): This is a core feature. The EVAMovementComponent provides force-based thruster control for linear movement and roll torque. The ZeroGMovementComponent manages gripping, climbing, and launching from the ship's exterior and interior surfaces.
- Movement for the "Hard Vacuum Monster" species can be refined from a version of the reaching component where it can grab any nearby surface and can generate enough suction strength to remain attached to a moving object.

8
Init_Prompt.md Normal file
View File

@ -0,0 +1,8 @@
You are a Godot 4.5 Code assistant. You are not overly agreeable or apologetic but still pleasant and you understand that coding can be quick with your help but that does not mean that you are infallible. Please wait for me to verify that code works before suggesting that we move on from the current task. Suggestions for next steps and features that are adjacent to what were working are very welcome however.
I will attach the full project files of the project being worked on which includes a game design document as well as a running note on the current state of the project which details implemented and planned features. Read these and report back to me. Please suggest potential bugs, features not working as intended, refactorizations for cleaner code, and missing best practices as part of this project ingestion.
Additionally you understand the following things about the version of Godot being used:
- To utilize the editor interface in you reference the global singleton `EditorInterface`. You do not need to call a function to get the a reference to it.
- `xform()` is not a function on transform objects. To achieve the same effect you would use simple transform multiplication (`Transform_A * Transform_B)`)

153
README.md Normal file
View File

@ -0,0 +1,153 @@
# Project Millimeters of Aluminum
A space simulation game built on a custom fork of the Godot Engine (4.x) with 64-bit double-precision physics enabled.
## 📋 Prerequisites
Before compiling the engine, ensure you have the following installed on your system.
### 1. Python & SCons
The Godot build system relies on SCons, which is written in Python.
* **Install Python (3.6+):** [Download Here](https://www.python.org/downloads/)
* *Windows Users:* Ensure "Add Python to PATH" is checked during installation. [Check this stackoverflow answer](https://stackoverflow.com/questions/57421669/question-about-pip-using-python-from-windows-store).
* **Install SCons:** Open your terminal/command prompt and run:
```bash
pip install scons
```
### 2. C++ Compiler
Godot requires a C++ compiler to build from source.
* **Windows:**
* Install **Visual Studio Community** (2019 or later).
* During installation, select the **"Desktop development with C++"** workload.
* **Linux:**
* Install GCC or Clang.
* *Debian/Ubuntu:* `sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev`
* **macOS:**
* Install **Xcode** from the App Store.
* Run `xcode-select --install` in the terminal.
## 🛠️ Setup & Compilation
This project uses a custom engine build to support solar-system scale coordinates (Double Precision). You **cannot** use the standard Steam or website version of Godot to open this project.
### 1. Clone the Repository
Clone the repository with the `--recursive` flag to automatically pull the engine source code submodule.
```bash
git clone --recursive [https://codeberg.org/YOUR_USERNAME/ProjectMillimetersOfAluminum.git](https://codeberg.org/YOUR_USERNAME/ProjectMillimetersOfAluminum.git)
cd ProjectMillimetersOfAluminum
```
_If you have already cloned without recursive, run:_
```
git submodule update --init --recursive
```
### 2. Configure Engine Ignore (Optional but Recommended)
To prevent your Editor from trying to import the thousands of raw assets inside the engine source folder:
1. Navigate to `godot_engine/`.
2. Create a new empty file named `.gdignore`.
3. Compile the Engine
Run the build command for your platform from the godot_engine/ directory.
**Windows:**
```Bash
cd godot_engine
scons platform=windows target=editor precision=double arch=x86_64
```
**Linux:**
```Bash
cd godot_engine
scons platform=linuxbsd target=editor precision=double arch=x86_64
```
**macOS:**
```Bash
cd godot_engine
scons platform=macos target=editor precision=double arch=x86_64
```
_Note: (-j6 flag tells the compiler to use 6 CPU cores. Adjust this number based on your hardware to speed up compilation. Not using the flag will use all available cores)_
---
## 🚀 Running the Project
Once compilation is complete (usually 10-30 minutes), the executable will be located in godot_engine/bin/.
Do not open the project with standard Godot.
Navigate to `godot_engine/bin/`.
Run the binary ending in `.double.x86_64` (e.g., `godot.windows.editor.double.x86_64.exe`).
Import and open the `project.godot` file located in the root `ProjectMillimetersOfAluminum` folder.
### Troubleshooting
"No valid compilers found" (Windows): Ensure you installed the C++ Desktop Development workload in the Visual Studio Installer. Just the editor is not enough.
Jittering Objects: If objects jitter at large distances, ensure you are running the double precision binary and not a standard build.
## 🛠 Compiling Custom Export Templates
This project uses features from the latest development branch of Godot (`master` branch). As a result, standard export templates downloaded from the Godot website may not be compatible. To export the project, you must compile custom export templates from the same source version used to build your editor.
### Prerequisites
Ensure you have a C++ build environment set up (SCons, Python, Visual Studio/GCC/MinGW). See the official Godot documentation on compiling for platform-specific instructions.
### 1. Build the Editor (Optional)
See above section.
### 2. Build the Export Templates
You need to build two templates: one for debug (used during development/testing) and one for release (optimized for final distribution).
**Windows:**
```PowerShell
# Debug Template (console enabled, debug tools)
scons platform=windows target=template_debug
# Release Template (optimized, no console)
scons platform=windows target=template_release
```
**Linux:**
```bash
# Debug Template
scons platform=linuxbsd target=template_debug
# Release Template
scons platform=linuxbsd target=template_release
```
### 3. Locate Output Files
After compilation, the binaries will be located in the bin/ directory of your Godot source folder.
- Debug: godot.windows.template_debug.x86_64.exe (or similar)
- Release: godot.windows.template_release.x86_64.exe (or similar)
### 4. Configure Export Presets
1. Open the project in Godot.
2. Go to Project > Export.
3. Select your export preset (e.g., Windows Desktop).
4. Under the Options tab, find the Custom Template section.
5. Set Debug to the path of your compiled template_debug binary.
6. Set Release to the path of your compiled template_release binary.
You can now export the project using your custom engine build!

View File

@ -1,6 +0,0 @@
[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="Node2D"]
script = ExtResource("1_ig7tw")

View File

@ -1,27 +0,0 @@
[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("..")

View File

@ -1,31 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://b1kpyek60vyof"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1abiy"]
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_risxe"]
[node name="Module" type="RigidBody2D"]
position = Vector2(-50, 50)
mass = null
center_of_mass_mode = 1
center_of_mass = Vector2(-50, 0)
inertia = null
linear_velocity = null
angular_velocity = null
script = ExtResource("1_1abiy")
base_mass = null
inertia = null
[node name="StructuralContainer" type="Node2D" parent="."]
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_risxe")]
base_mass = null
inertia = null
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_risxe")]
position = Vector2(-100, 0)
base_mass = null
inertia = null
[node name="HullVolumeContainer" type="Node2D" parent="."]
[node name="AtmosphereVisualizer" type="Node2D" parent="."]

View File

@ -1,31 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://baeikwxkh26fh"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1rae4"]
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_fbnt1"]
[node name="Module" type="RigidBody2D"]
position = Vector2(-50, 50)
mass = null
center_of_mass_mode = 1
center_of_mass = Vector2(-50, 0)
inertia = null
linear_velocity = null
angular_velocity = null
script = ExtResource("1_1rae4")
base_mass = null
inertia = null
[node name="StructuralContainer" type="Node2D" parent="."]
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
base_mass = null
inertia = null
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
position = Vector2(-100, 0)
base_mass = null
inertia = null
[node name="HullVolumeContainer" type="Node2D" parent="."]
[node name="AtmosphereVisualizer" type="Node2D" parent="."]

View File

@ -1,145 +0,0 @@
[gd_scene load_steps=20 format=3 uid="uid://didt2nsdtbmra"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_nqe0s"]
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_foqop"]
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="4_dmrms"]
[ext_resource type="PackedScene" uid="uid://2n42nstcj1n0" path="res://scenes/ship/components/hardware/system_station.tscn" id="5_nqe0s"]
[ext_resource type="Script" uid="uid://diu2tgusi3vmt" path="res://scenes/ship/computer/shards/sensor_databank.gd" id="9_ixntg"]
[ext_resource type="PackedScene" uid="uid://dt1t2n7dewucw" path="res://scenes/ship/computer/UI/button_panel.tscn" id="10_px2ne"]
[ext_resource type="Script" uid="uid://cfbyqvnvf3hna" path="res://scenes/ship/computer/shards/helm_logic_databank.gd" id="10_wkxbw"]
[ext_resource type="PackedScene" uid="uid://cdbqjkgsj02or" path="res://scenes/ship/computer/UI/readout_screen_panel.tscn" id="11_erhv3"]
[ext_resource type="Script" uid="uid://t12etsdx2h38" path="res://scenes/ship/computer/shards/nav_selection_databank.gd" id="11_xwy4s"]
[ext_resource type="Script" uid="uid://ceqdi6jobefnc" path="res://scenes/ship/computer/shards/helm_autopilot_databank.gd" id="12_4epkn"]
[ext_resource type="PackedScene" uid="uid://rd1c22nsru8y" path="res://scenes/ship/computer/UI/sensor_panel.tscn" id="12_q1rtr"]
[ext_resource type="PackedScene" uid="uid://c0bb77rmyatr0" path="res://scenes/ship/components/hardware/thruster.tscn" id="12_vmx8o"]
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="13_83bu1"]
[ext_resource type="PackedScene" uid="uid://pq55j75t3fda" path="res://scenes/ship/computer/UI/throttle_lever_panel.tscn" id="13_rsa1x"]
[ext_resource type="Script" uid="uid://ctgl5kxyagw0f" path="res://scenes/ship/computer/shards/helm_ship_status.gd" id="13_wkxbw"]
[ext_resource type="Script" uid="uid://ghluwjd5c5ul" path="res://scenes/ship/computer/shards/nav_brachistochrone_planner.gd" id="14_xwy4s"]
[ext_resource type="Script" uid="uid://bghu5lhcbcfmh" path="res://scenes/ship/computer/shards/nav_hohman_planner.gd" id="15_fll2s"]
[ext_resource type="Script" uid="uid://dsbn7ushwqrko" path="res://scenes/ship/computer/shards/nav_intercept_solver.gd" id="16_vufgi"]
[ext_resource type="Script" uid="uid://0f6v6iu3o5qo" path="res://scenes/ship/computer/shards/nav_projection_shard.gd" id="17_34v0b"]
[node name="Module" type="Node2D"]
physics_interpolation_mode = 2
script = ExtResource("1_nqe0s")
physics_mode = 1
mass = 1.0
inertia = 0.0
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
physics_interpolation_mode = 2
is_pressurized = false
base_mass = 0.0
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
physics_interpolation_mode = 2
position = Vector2(0, 100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
physics_interpolation_mode = 2
position = Vector2(0, -100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(-50, 100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(-50, 0)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(-50, -100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(50, -100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(0, -150)
rotation = 1.5708
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(0, 150)
rotation = 4.71239
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(50, 100)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
physics_interpolation_mode = 2
position = Vector2(50, 0)
is_pressurized = false
health = 0.0
base_mass = 0.0
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
position = Vector2(0, -10)
panel_scenes = Array[PackedScene]([ExtResource("11_erhv3"), ExtResource("11_erhv3"), ExtResource("12_q1rtr"), ExtResource("10_px2ne"), ExtResource("13_rsa1x")])
databank_installations = Array[Script]([ExtResource("10_wkxbw"), ExtResource("12_4epkn"), ExtResource("13_wkxbw"), ExtResource("9_ixntg"), ExtResource("11_xwy4s"), ExtResource("14_xwy4s"), ExtResource("15_fll2s"), ExtResource("16_vufgi"), ExtResource("17_34v0b")])
physics_mode = 2
[node name="Thruster" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(-95, -130)
rotation = 1.5708
main_thruster = false
physics_mode = 2
[node name="Thruster2" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(-95, 130)
rotation = 1.5708
main_thruster = false
physics_mode = 2
[node name="Thruster3" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(95, 130)
rotation = -1.5708
main_thruster = false
physics_mode = 2
[node name="Thruster4" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(95, -130)
rotation = -1.5708
main_thruster = false
physics_mode = 2
[node name="MainEngine" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(0, 195)
max_thrust = 10.0
physics_mode = 2
[node name="Spawner" parent="." instance=ExtResource("13_83bu1")]
position = Vector2(0, 27)
physics_mode = 2

13
reinit_submodules.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
while read path_key local_path
do
url_key=$(echo $path_key | sed 's/\.path/.url/')
url=$(git config -f .gitmodules --get "$url_key")
git submodule add $url $local_path
done
# https://stackoverflow.com/questions/11258737/restore-git-submodules-from-gitmodules

View File

@ -1,19 +0,0 @@
class_name Asteroid
extends OrbitalBody2D
# The orbital radius for this asteroid.
var orbital_radius: float
func get_class_name() -> String:
return "Asteroid"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# An Asteroid has negligible mass for physics calculations.
#mass = 0.001
#radius = 5.0
# You can set a default texture here.
# texture = preload("res://assets/asteroid_texture.png")
super._ready()

View File

@ -1 +0,0 @@
uid://c816xae77cbmq

View File

@ -1,8 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bawsujtlpmh5r"]
[ext_resource type="Script" uid="uid://0isnsk356que" path="res://scripts/orbital_body_2d.gd" id="1_4q05e"]
[node name="Asteroid" type="Node2D"]
script = ExtResource("1_4q05e")
base_mass = 50000.0
metadata/_custom_type_script = "uid://0isnsk356que"

View File

@ -1,17 +0,0 @@
class_name Moon
extends OrbitalBody2D
# The orbital radius for this moon.
var orbital_radius: float
func get_class_name() -> String:
return "Moon"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# A Moon has a smaller mass than a planet.
# You can set a default texture here.
# texture = preload("res://assets/moon_texture.png")
super._ready()

View File

@ -1 +0,0 @@
uid://b1xsx7er22nxn

View File

@ -1,8 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://74ppvxcw8an4"]
[ext_resource type="Script" uid="uid://b1xsx7er22nxn" path="res://scenes/celestial_bodies/moon.gd" id="1_530pw"]
[node name="Moon" type="Node2D"]
script = ExtResource("1_530pw")
base_mass = 1e+06
metadata/_custom_type_script = "uid://0isnsk356que"

View File

@ -1,15 +0,0 @@
class_name Planet
extends OrbitalBody2D
# The orbital radius for this planet.
var orbital_radius: float
func get_class_name() -> String:
return "Planet"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# You can set a default texture here.
# texture = preload("res://assets/planet_texture.png")
super._ready()

View File

@ -1 +0,0 @@
uid://5f6ipgu65urb

View File

@ -1,10 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://clt4qlsjcfgln"]
[ext_resource type="Script" uid="uid://5f6ipgu65urb" path="res://scenes/celestial_bodies/planet.gd" id="1_cktii"]
[node name="Planet" type="Node2D"]
script = ExtResource("1_cktii")
base_mass = 2.5e+07
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]

View File

@ -1,14 +0,0 @@
class_name Star
extends OrbitalBody2D
func get_class_name() -> String:
return "Star"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# A Star has no primary and a very large mass.
# You can set a default texture here, or assign it in the Inspector.
# texture = preload("res://assets/star_texture.png")
super._ready()

View File

@ -1 +0,0 @@
uid://um2sfghmii42

View File

@ -1,15 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://5uqp4amjj7ww"]
[ext_resource type="Script" uid="uid://um2sfghmii42" path="res://scenes/celestial_bodies/star.gd" id="1_mcqwg"]
[sub_resource type="CircleShape2D" id="CircleShape2D_508pf"]
radius = 200.0
[node name="Star" type="Node2D"]
script = ExtResource("1_mcqwg")
base_mass = 5e+08
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_508pf")
debug_color = Color(0.863865, 0.471779, 0.162305, 1)

View File

@ -1,17 +0,0 @@
class_name Station
extends OrbitalBody2D
# The orbital radius for this station.
var orbital_radius: float
func get_class_name() -> String:
return "Station"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# A Station has negligible mass for physics calculations.
# You can set a default texture here.
# texture = preload("res://assets/station_texture.png")
super._ready()

View File

@ -1 +0,0 @@
uid://ulw61oxppwdu

View File

@ -1,8 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dm3s33o4xhqfv"]
[ext_resource type="Script" uid="uid://ulw61oxppwdu" path="res://scenes/celestial_bodies/station.gd" id="1_rod8h"]
[node name="Station" type="Node2D"]
script = ExtResource("1_rod8h")
base_mass = 5000.0
metadata/_custom_type_script = "uid://0isnsk356que"

View File

@ -1,205 +0,0 @@
extends CharacterBody2D
class_name PilotBall
# --- Movement Constants (Friction Simulation) ---
# When in open space (no module overlap), movement is zeroed out quickly.
const EXTERIOR_DRAG_FACTOR: float = 0.05
# When pushing off hullplates (low friction, slow acceleration)
const INTERIOR_SLUGGISH_SPEED: float = 100.0
const INTERIOR_SLUGGISH_ACCEL: float = 5 # Low acceleration, simulating mass and small push
# When gripping a ladder (high friction, direct control)
const LADDER_SPEED: float = 100.0
const LADDER_ACCEL: float = 20 # High acceleration, simulating direct grip
@onready var camera: Camera2D = $Camera2D
@onready var overlap_area: Area2D = $OverlapDetector
@onready var ui_container: Control = $CanvasLayer/UIContainer
var nearby_station: SystemStation = null
var current_station: SystemStation = null
# --- State Variables ---
enum MovementState {
NO_CONTROL,
ZERO_G_INTERIOR,
LADDER_GRIP,
IN_STATION
}
var current_state: MovementState = MovementState.NO_CONTROL
var ladder_area: Area2D = null # Area of the ladder currently overlapped
var is_grabbing_ladder: bool = false # True if 'Space' is held while on ladder
# --- Overlap Detection (Assuming you use Area2D for detection) ---
var overlapping_modules: int = 0
# --- Ladder Constants ---
const LAUNCH_VELOCITY: float = 300.0
var _movement_input: Vector2 = Vector2.ZERO
var _interact_just_pressed: bool = false
var _interact_held: bool = false
# --- PUBLIC INPUT METHODS (Called by the PlayerController) ---
func set_movement_input(input_dir: Vector2):
_movement_input = input_dir
func set_interaction_input(just_pressed: bool, is_held: bool):
_interact_just_pressed = just_pressed
_interact_held = is_held
# --- New: Physics Initialization (Assuming CharacterBody2D is parented to the scene root or Ship) ---
# NOTE: CharacterBody2D cannot inherit OrbitalBody2D, so we manage its velocity manually.
func _ready():
# Set up overlap signals if they aren't already connected in the scene file
# You must have an Area2D child on PilotBall to detect overlaps.
overlap_area.body_entered.connect(on_body_entered)
overlap_area.body_exited.connect(on_body_exited)
overlap_area.area_entered.connect(_on_station_area_entered)
overlap_area.area_exited.connect(_on_station_area_exited)
camera.make_current()
func on_body_entered(body: Node2D):
# Detect Modules (which all inherit OrbitalBody2D via StructuralPiece)
if body is StructuralPiece:
overlapping_modules += 1
# Detect Ladders
if body is Ladder:
ladder_area = body.find_child("ClimbArea") # Assuming the Ladder has a specific Area2D for climbing
func on_body_exited(body: Node2D):
if body is StructuralPiece:
overlapping_modules -= 1
if body is Ladder:
if body.find_child("ClimbArea") == ladder_area:
ladder_area = null
is_grabbing_ladder = false # Force detach if the ladder moves away
# --- NEW: Functions to be called by the Station ---
func enter_station_state():
current_state = MovementState.IN_STATION
velocity = Vector2.ZERO # FIX: Stop all movement when entering a station
func exit_station_state():
# When leaving, transition to a sensible default state.
current_state = MovementState.ZERO_G_INTERIOR
func _physics_process(delta):
# This script now runs on the server and its state is synced to clients.
# It no longer checks for local input authority.
if current_state == MovementState.IN_STATION:
move_and_slide()
return
_update_movement_state() # This function now uses the new variables
process_interaction() # Process any interaction presses
# Reset input flags for the next frame
_interact_just_pressed = false
_interact_held = false
# The 'input_dir' now comes from our variable, not the Input singleton.
var input_dir = _movement_input
match current_state:
MovementState.ZERO_G_INTERIOR:
_sluggish_movement(input_dir, delta)
MovementState.LADDER_GRIP:
_ladder_movement(input_dir, delta)
# Reset input for the next frame
_movement_input = Vector2.ZERO
move_and_slide()
# This function is called every physics frame by _physics_process().
func process_interaction():
# If the interact button was not pressed this frame, do nothing.
if not _interact_just_pressed:
return
# Priority 1: Disengage from a station if we are in one.
if current_station:
current_station.disengage(self)
current_station = null
return
# Priority 2: Occupy a nearby station if we are not in one.
elif is_instance_valid(nearby_station):
current_station = nearby_station
current_station.occupy(self)
return
# Priority 3: Handle ladder launch logic.
# This part of the old logic was in _handle_interaction_input,
# but it's cleaner to check for the release of the button here.
if current_state == MovementState.LADDER_GRIP and not _interact_held:
# Launch the player away from the ladder when the interact button is released.
var launch_direction = - _movement_input.normalized()
if launch_direction == Vector2.ZERO:
# Default launch: use the character's forward direction
launch_direction = Vector2.UP.rotated(rotation)
velocity = launch_direction * LAUNCH_VELOCITY
# Immediately switch to zero-G interior state
is_grabbing_ladder = false
current_state = MovementState.ZERO_G_INTERIOR
# --- State Machine Update ---
func _update_movement_state():
# Priority 1: Ladder Grip
# This now checks the variable instead of the Input singleton.
if ladder_area and _interact_held:
is_grabbing_ladder = true
current_state = MovementState.LADDER_GRIP
return
# Priority 2: Interior Zero-G (must overlap a module/piece AND not be grabbing)
if overlapping_modules > 0:
if is_grabbing_ladder:
# If we were grabbing a ladder but released 'interact', we transition to zero-G interior
is_grabbing_ladder = false
current_state = MovementState.ZERO_G_INTERIOR
return
current_state = MovementState.ZERO_G_INTERIOR
return
# Priority 3: No Control (floating free)
is_grabbing_ladder = false
current_state = MovementState.NO_CONTROL
# --- Movement Implementations ---
func _sluggish_movement(input_dir: Vector2, delta: float):
# Simulates pushing off the wall: slow acceleration, but minimal drag
var target_velocity = input_dir * INTERIOR_SLUGGISH_ACCEL
velocity = velocity + target_velocity * delta
#velocity.lerp(velocity + interi, INTERIOR_SLUGGISH_ACCEL)
func _ladder_movement(input_dir: Vector2, delta: float):
# Simulates direct grip: fast acceleration, perfect control
var target_velocity = input_dir * LADDER_SPEED
velocity = velocity.lerp(target_velocity, LADDER_ACCEL * delta)
# --- New Functions for Station Interaction ---
func _on_station_area_entered(area: Area2D):
if area.get_parent() is SystemStation:
nearby_station = area.get_parent()
print("Near station: ", nearby_station.name)
func _on_station_area_exited(area: Area2D):
if area.get_parent() == nearby_station:
nearby_station = null
# Stations will call this to get the node where they should place their UIs.
func get_ui_container() -> Control:
return ui_container

View File

@ -1 +0,0 @@
uid://dxngvoommn5f1

View File

@ -1,36 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://chgycmkkaf7jv"]
[ext_resource type="Script" uid="uid://dxngvoommn5f1" path="res://scenes/characters/pilot_ball.gd" id="1_rhbna"]
[sub_resource type="CircleShape2D" id="CircleShape2D_6jclb"]
[sub_resource type="CircleShape2D" id="CircleShape2D_rhbna"]
[node name="PilotBall" type="CharacterBody2D"]
collision_layer = 32
collision_mask = 16
script = ExtResource("1_rhbna")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_6jclb")
debug_color = Color(0.61528, 0.358023, 1, 1)
[node name="Sprite2D" type="Sprite2D" parent="."]
[node name="Camera2D" type="Camera2D" parent="."]
zoom = Vector2(4, 4)
[node name="OverlapDetector" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionShape2D" parent="OverlapDetector"]
shape = SubResource("CircleShape2D_rhbna")
[node name="CanvasLayer" type="CanvasLayer" parent="."]
[node name="UIContainer" type="Control" parent="CanvasLayer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

View File

@ -1,37 +0,0 @@
extends Node
class_name PlayerController
# TODO: Change this to custom pawn type
var possessed_pawn: Node # The character this controller is currently driving
func _ready():
# --- FIX: Manually enable input processing for this node ---
set_process_input(true)
func _physics_process (delta):
if not is_multiplayer_authority():
return
# 1. Gather all input states for this frame.
var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
var is_interact_just_pressed = Input.is_action_just_pressed("interact")
var is_interact_held = Input.is_action_pressed("interact")
#print(is_interact_just_pressed)
#print(input_dir)
# 2. Send the collected input state to the server via RPC.
server_process_input.rpc_id(1, input_dir, is_interact_just_pressed, is_interact_held)
@rpc("any_peer", "call_local")
func server_process_input(input_dir: Vector2, is_interact_just_pressed: bool, is_interact_held: bool):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_movement_input(input_dir)
# Pass both interact states to the pawn
possessed_pawn.set_interaction_input(is_interact_just_pressed, is_interact_held)
func possess(pawn_to_control: Node):
possessed_pawn = pawn_to_control
reparent(pawn_to_control, false)
self.owner = pawn_to_control
print("PlayerController possessed: ", possessed_pawn.name)

View File

@ -1 +0,0 @@
uid://dmhwqmbwk0t8k

View File

@ -1,6 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dnre6svquwdtb"]
[ext_resource type="Script" uid="uid://dmhwqmbwk0t8k" path="res://scenes/characters/player_controller.gd" id="1_b8jga"]
[node name="PlayerController" type="Node"]
script = ExtResource("1_b8jga")

View File

@ -1,82 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://c77wxeb7gpplw"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_mtskc"]
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_aovrk"]
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="4_dwgsg"]
[node name="Module" type="Node2D"]
position = Vector2(-50, 50)
script = ExtResource("1_mtskc")
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="StructuralContainer" type="Node2D" parent="."]
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
[node name="@StaticBody2D@31031" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(0, 100)
[node name="@StaticBody2D@31033" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(100, 100)
[node name="@StaticBody2D@31035" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(100, 0)
[node name="@StaticBody2D@31037" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(100, -100)
[node name="@StaticBody2D@31039" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(100, -200)
[node name="@StaticBody2D@31041" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(0, -200)
[node name="@StaticBody2D@31043" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
position = Vector2(0, -100)
[node name="Bulkhead" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(-50, 100)
[node name="@StaticBody2D@31046" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(-50, 0)
[node name="@StaticBody2D@31048" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(-50, -100)
[node name="@StaticBody2D@31050" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(-50, -200)
[node name="@StaticBody2D@31052" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(150, -200)
[node name="@StaticBody2D@31054" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(150, -100)
[node name="@StaticBody2D@31056" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(150, 0)
[node name="@StaticBody2D@31058" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(150, 100)
[node name="@StaticBody2D@31060" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(0, 150)
rotation = 1.5708
[node name="@StaticBody2D@31062" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(100, 150)
rotation = 1.5708
[node name="@StaticBody2D@31064" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(0, -250)
rotation = 1.5708
[node name="@StaticBody2D@31066" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
position = Vector2(100, -250)
rotation = 1.5708
[node name="HullVolumeContainer" type="Node2D" parent="."]
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
[node name="Camera2D" type="Camera2D" parent="."]
position = Vector2(50, -50)

View File

@ -1,13 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://cm0rohkr6khd1"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_b1h2b"]
[node name="Module" type="Node2D"]
script = ExtResource("1_b1h2b")
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="StructuralContainer" type="Node2D" parent="."]
[node name="HullVolumeContainer" type="Node2D" parent="."]
[node name="AtmosphereVisualizer" type="Node2D" parent="."]

View File

@ -1,6 +0,0 @@
@tool
class_name Bulkhead
extends StructuralPiece
# This piece represents a wall or edge.
# No additional logic is needed right now, we just need the class_name.

View File

@ -1 +0,0 @@
uid://b4g288mje38nj

View File

@ -1,29 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://d3hitk62fice4"]
[ext_resource type="Script" uid="uid://b4g288mje38nj" path="res://scenes/ship/builder/pieces/bulkhead.gd" id="1_1wp2n"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
size = Vector2(10, 100)
[node name="Bulkhead" type="StaticBody2D"]
collision_layer = 16
collision_mask = 60
script = ExtResource("1_1wp2n")
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_1wp2n")
[node name="ColorRect" type="ColorRect" parent="."]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -5.0
offset_top = -50.0
offset_right = 5.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.6, 0.6, 0.6, 1)

View File

@ -1,6 +0,0 @@
@tool
class_name Hullplate
extends StructuralPiece
# This piece represents an interior surface.
# No additional logic is needed right now, we just need the class_name.

View File

@ -1 +0,0 @@
uid://crmwm623rh1ps

View File

@ -1,28 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://bho8x10x4oab7"]
[ext_resource type="Script" uid="uid://crmwm623rh1ps" path="res://scenes/ship/builder/pieces/hullplate.gd" id="1_ecow4"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
size = Vector2(100, 100)
[node name="Hullplate" type="StaticBody2D"]
collision_mask = 0
script = ExtResource("1_ecow4")
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_1wp2n")
[node name="ColorRect" type="ColorRect" parent="."]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.4, 0.4, 0.4, 1)

View File

@ -1,20 +0,0 @@
@tool
class_name StructuralPiece
extends OrbitalBody2D
# Does this piece block atmosphere? (e.g., a hull plate would, a girder would not).
@export var is_pressurized: bool = true
# The health of this specific piece.
@export var health: float = 100.0
# This setter is triggered by the editor.
var is_preview: bool = false:
set(value):
is_preview = value
if is_preview:
# Make the piece translucent if it's a preview.
modulate = Color(1, 1, 1, 0.5)
else:
# Make it opaque if it's a permanent piece.
modulate = Color(1, 1, 1, 1)

View File

@ -1 +0,0 @@
uid://b7f8x2qimvn37

View File

@ -1,16 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://ds4eilbvihjy7"]
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_6jsoj"]
[sub_resource type="CircleShape2D" id="CircleShape2D_jsbwo"]
[node name="StructuralPiece" type="Node2D"]
script = ExtResource("1_6jsoj")
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_jsbwo")
[node name="ColorRect" type="ColorRect" parent="."]
offset_right = 40.0
offset_bottom = 40.0

View File

@ -1,7 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dvpy3urgtm62n"]
[ext_resource type="Script" uid="uid://db1u2qqihhnq4" path="res://scenes/ship/components/hardware/spawner.gd" id="1_lldyu"]
[node name="Spawner" type="Node2D"]
script = ExtResource("1_lldyu")
metadata/_custom_type_script = "uid://calosd13bkakg"

View File

@ -1,37 +0,0 @@
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)

View File

@ -1,182 +0,0 @@
extends Node2D
class_name OrbitalBody2D
# Defines the physical behavior of this body.
enum PhysicsMode {
INDEPENDENT, # An independent body with its own physics simulation (planets, characters in EVA).
COMPOSITE, # A body that aggregates mass and forces from ANCHORED children (ships, modules).
ANCHORED # A component that is "bolted on" and defers physics to a COMPOSITE parent.
}
@export var physics_mode: PhysicsMode = PhysicsMode.INDEPENDENT
var current_grid_authority: OrbitalBody2D = null
# Mass of this individual component
@export var base_mass: float = 1.0
@export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody2D children
@export var linear_velocity: Vector2 = Vector2.ZERO
@export var angular_velocity: float = 0.0
# Variables to accumulate forces applied during the current physics frame
var accumulated_force: Vector2 = Vector2.ZERO
var accumulated_torque: float = 0.0
# Placeholder for Moment of Inertia.
@export var inertia: float = 1.0
func _ready():
# Ensure mass update runs immediately before the first _physics_process.
recalculate_physical_properties()
set_physics_process(not Engine.is_editor_hint())
physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
# --- PUBLIC FORCE APPLICATION METHODS ---
# This method is called by a component (like Thruster) at its global position.
func apply_force(force: Vector2, pos: Vector2 = self.global_position):
# This is the force routing logic.
match physics_mode:
PhysicsMode.INDEPENDENT:
_add_forces(force, pos)
PhysicsMode.COMPOSITE:
_add_forces(force, pos)
## If we are the root, accumulate the force and calculate torque on the total body.
#accumulated_force += force
#
## Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
## 'r' is the vector from the center of mass (global_position) to the point of force application (position).
#var r = position - global_position
#var torque = r.x * force.y - r.y * force.x
#accumulated_torque += torque
PhysicsMode.ANCHORED:
# If we are not the root, we must route the force to the next OrbitalBody2D parent.
var p = get_parent()
while p:
if p is OrbitalBody2D:
# Recursively call the parent's apply_force method.
# This sends the force (and its original global position) up the chain.
p.apply_force(force, pos)
return # Stop at the first OrbitalBody2D parent
p = p.get_parent()
push_error("Anchored OrbitalBody2D has become dislodged and is now Composite.")
physics_mode = PhysicsMode.COMPOSITE
apply_force(force, position)
func _add_forces(force: Vector2, position: Vector2 = Vector2.ZERO):
# If we are the root, accumulate the force and calculate torque on the total body.
accumulated_force += force
# Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
# 'r' is the vector from the center of mass (global_position) to the point of force application (position).
var r = position - global_position
var torque = r.x * force.y - r.y * force.x
accumulated_torque += torque
func _update_mass_and_inertia():
mass = base_mass
for child in get_children():
if child is OrbitalBody2D:
child._update_mass_and_inertia() # Recurse into children
mass += child.mass
print("Node: %s, Mass: %f" % [self, mass])
func _physics_process(delta):
if not Engine.is_editor_hint():
# Note: We're not integrating forces for anchored bodies
# anchored bodies add forces to their parents and
match physics_mode:
PhysicsMode.INDEPENDENT:
_integrate_forces(delta)
PhysicsMode.COMPOSITE:
_integrate_forces(delta)
func _integrate_forces(delta):
# Safety Check for Division by Zero
var sim_mass = mass
if sim_mass <= 0.0:
# If mass is zero, stop all physics to prevent NaN explosion.
accumulated_force = Vector2.ZERO
accumulated_torque = 0.0
return
## 1. Calculate and accumulate gravitational force (F_g)
#var total_gravity_force = OrbitalMechanics.calculate_n_body_gravity_forces(self)
#
## 2. Total all forces: F_total = F_g + F_accumulated_from_thrusters
#var total_force = total_gravity_force +
# 3. Apply Linear Physics (F = ma)
var linear_acceleration = accumulated_force / sim_mass # Division is now safe
linear_velocity += linear_acceleration * delta
global_position += linear_velocity * delta
# 4. Apply Rotational Physics (T = I * angular_acceleration)
var angular_acceleration = accumulated_torque / inertia
angular_velocity += angular_acceleration * delta
rotation += angular_velocity * delta
# 5. Reset accumulated forces for the next frame
accumulated_force = Vector2.ZERO
accumulated_torque = 0.0
# This is the new, corrected function.
func recalculate_physical_properties():
# For non-composite bodies, the calculation is simple.
if physics_mode != PhysicsMode.COMPOSITE:
mass = base_mass
# --- THE FIX ---
# An independent body doesn't calculate inertia from parts.
# We ensure it has a non-zero default value to prevent division by zero.
if inertia <= 0.0:
inertia = 1.0
return
var all_parts: Array[OrbitalBody2D] = []
_collect_anchored_parts(all_parts)
if all_parts.is_empty():
mass = base_mass
inertia = 1.0
return
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
var total_mass = 0.0
var weighted_local_pos_sum = Vector2.ZERO
for part in all_parts:
total_mass += part.base_mass
# We get the part's position *relative to the root module*
var local_pos = part.global_position - self.global_position
weighted_local_pos_sum += local_pos * part.base_mass
var local_center_of_mass = Vector2.ZERO
if total_mass > 0:
local_center_of_mass = weighted_local_pos_sum / total_mass
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
var total_inertia = 0.0
for part in all_parts:
# Get the part's position relative to the root module again
var local_pos = part.global_position - self.global_position
# The radius is the distance from the part's local position to the ship's local center of mass
var r_squared = (local_pos - local_center_of_mass).length_squared()
total_inertia += part.base_mass * r_squared
# --- Step 3: Assign the final values ---
self.mass = total_mass
# We apply a scaling factor here because our "units" are pixels.
# This brings the final value into a range that feels good for gameplay.
# You can tune this factor to make ships feel heavier or lighter.
self.inertia = total_inertia * 0.01
#print("Physics Recalculated: Mass=%.2f kg, Inertia=%.2f" % [mass, inertia])
# A recursive helper function to get an array of all OrbitalBody2D children
func _collect_anchored_parts(parts_array: Array):
parts_array.append(self)
for child in get_children():
# TODO: this assumes that all OrbitalBody2D that are attached are done in a clean chain without breaks, which may not be the case
if child is OrbitalBody2D and child.physics_mode == PhysicsMode.ANCHORED:
child._collect_anchored_parts(parts_array)

View File

@ -1 +0,0 @@
uid://0isnsk356que

View File

@ -1,13 +0,0 @@
[gd_resource type="Resource" script_class="GameConfig" load_steps=5 format=3 uid="uid://cv15sck8rl2b7"]
[ext_resource type="PackedScene" uid="uid://chgycmkkaf7jv" path="res://scenes/characters/pilot_ball.tscn" id="1_s0mxw"]
[ext_resource type="PackedScene" uid="uid://didt2nsdtbmra" path="res://modules/Tube.tscn" id="2_75b4c"]
[ext_resource type="PackedScene" uid="uid://dnre6svquwdtb" path="res://scenes/characters/player_controller.tscn" id="2_sk8k5"]
[ext_resource type="Script" uid="uid://bfc6u1f8sigxj" path="res://scripts/singletons/game_config.gd" id="3_75b4c"]
[resource]
script = ExtResource("3_75b4c")
player_controller_scene = ExtResource("2_sk8k5")
default_pawn_scene = ExtResource("1_s0mxw")
default_ship_scene = ExtResource("2_75b4c")
metadata/_custom_type_script = "uid://bfc6u1f8sigxj"

View File

@ -1,141 +0,0 @@
# GameManager.gd
extends Node
var config: GameConfig
# --- Dictionaries to track players and their objects ---
var player_controllers: Dictionary = {} # Key: player_id, Value: PlayerController node
var player_pawns: Dictionary = {} # Key: player_id, Value: PilotBall node
# This variable will hold the reference to the currently active star system.
var current_star_system : StarSystem = null
var registered_spawners: Array[Spawner] = []
var waiting_players: Array[int] = [] # A queue for players waiting to spawn
func _ready():
# Load the configuration resource from its fixed path.
config = load("res://scripts/singletons/default_game_config.tres")
if not config:
push_error("GameManager could not load game_config.tres!")
return
# We need to initialize a network peer for the authority system to work,
# even in a single-player game. This creates a "server" for just you.
var peer = ENetMultiplayerPeer.new()
peer.create_server(5999) # Port number can be anything for a local game
multiplayer.multiplayer_peer = peer
# When the local server is created, it automatically calls on_player_connected(1).
multiplayer.peer_connected.connect(on_player_connected)
start_game()
# Called when the game starts (e.g., from _ready() in StarSystemGenerator)
func start_game():
# For a single-player game, we simulate a player connecting with ID 1.
on_player_connected(1)
# This would be connected to a network signal in a multiplayer game.
func on_player_connected(player_id: int):
print("GameManager: Player %d connected." % player_id)
# 1. Spawn a controller for the new player.
var controller = config.player_controller_scene.instantiate()
controller.name = "PlayerController_%d" % player_id
add_child(controller)
player_controllers[player_id] = controller
# 2. Attempt to spawn a pawn for them immediately.
_attempt_to_spawn_player(player_id)
func _attempt_to_spawn_player(player_id: int):
if registered_spawners.is_empty():
# No spawners available, add the player to the waiting queue.
if not player_id in waiting_players:
waiting_players.append(player_id)
print("GameManager: No spawners available. Player %d is now waiting." % player_id)
# You could show a "Waiting for available spawner..." UI here.
else:
# Spawners are available, proceed with spawning.
spawn_player_pawn(player_id)
# NEW: A function to process the waiting queue.
func _try_spawn_waiting_player():
if not waiting_players.is_empty() and not registered_spawners.is_empty():
var player_to_spawn = waiting_players.pop_front()
print("GameManager: Spawner is now available. Spawning waiting player %d." % player_to_spawn)
spawn_player_pawn(player_to_spawn)
func spawn_player_pawn(player_id: int):
if not player_controllers.has(player_id):
push_error("Cannot spawn pawn for non-existent player %d" % player_id)
return
# --- NEW SPAWNING LOGIC ---
if registered_spawners.is_empty():
push_error("GameManager: No spawners available to create pawn!")
return
# For now, we'll just pick the first available spawner.
# Later, you could present a UI for the player to choose.
var spawn_point: Spawner = registered_spawners[0]
if not is_instance_valid(spawn_point):
push_error("GameManager: Spawn point not found!")
return
var owning_module = spawn_point.get_root_module()
if not is_instance_valid(owning_module):
push_error("GameManager: Registered spawner has no owning module!")
return
var pawn = config.default_pawn_scene.instantiate()
owning_module.add_child(pawn)
pawn.owner = owning_module
# 2. Set its position and initial velocity from the spawner.
pawn.global_position = spawn_point.global_position
player_pawns[player_id] = pawn
# 3. Possess the pawn with the player's controller.
player_controllers[player_id].possess(pawn)
# Any scene that generates a star system will call this function to register itself.
func register_star_system(system_node):
current_star_system = system_node
print("GameManager: Star system registered.")
func register_ship(ship: OrbitalBody2D):
if not is_instance_valid(current_star_system):
return
current_star_system.system_data.ships.append(ship)
# Spawners call this function to announce their presence.
func register_spawner(spawner_node: Spawner):
if not spawner_node in registered_spawners:
registered_spawners.append(spawner_node)
print("GameManager: Spawner '%s' registered." % spawner_node.name)
# NEW: If a player is waiting, try to spawn them now.
_try_spawn_waiting_player()
# A helper function for easily accessing the system's data.
func get_system_data() -> SystemData:
if current_star_system:
return current_star_system.get_system_data()
return null
func get_all_trackable_bodies() -> Array[OrbitalBody2D]:
var all_bodies: Array[OrbitalBody2D] = []
if current_star_system:
# First, get all the celestial bodies (planets, moons, etc.)
var system_data = current_star_system.get_system_data()
if system_data:
all_bodies.append_array(system_data.all_bodies())
# Next, add all registered ships to the list.
return all_bodies

View File

@ -1,241 +0,0 @@
# OrbitalMechanics.gd
extends Node
# This singleton holds all universal physics constants and functions.
# The scaled gravitational constant for the entire simulation.
const G = 1.0 # Adjust this to control the "speed" of your simulation
const MIN_INFLUENCE_THRESHOLD = 0.00001
const ROCHE_LIMIT_MASS_MULTIPLIER = 0.5
class BodyTuple:
var body_a: OrbitalBody2D
var body_b: OrbitalBody2D
var cached_forces: Dictionary[BodyTuple, Vector2] = {
}
# --- Centralized Physics Process ---
func _physics_process(_delta: float) -> void:
var star_system: StarSystem = GameManager.current_star_system
if not star_system:
return
var star = star_system.get_star()
var planetary_systems = star_system.get_planetary_systems()
# TODO: Would this be true in case we are working with a system that is just a rouge planet or a brown dwarf?
if not star:
return
# 1: Calculate star system pull
# a: Get the star and top level Barycenters
var top_level_bodies: Array[OrbitalBody2D] = [star]
top_level_bodies.append_array(planetary_systems)
# b: calculate and apply pull between these
apply_n_body_forces(top_level_bodies)
# 2: Calculate Barycenters local pull
for system in planetary_systems:
# a: Get each Planetary Barycenters OrbitalBody2Ds (including Ships, Satelites, and Stations fully within the Barycenter)
var system_attractors = system.get_internal_attractors()
# b: Calculate and apply pull within each Barycenter
apply_n_body_forces(system_attractors)
# 3: Calculate top level Ships, Satelites, and Stations pull
# a: Get top level OrbitalBody2Ds of non-celestial classes
for star_orbiter in star_system.get_orbital_bodies():
# b: Split into Star Orbiting and On-Approach using mass/distance ratios to Barycenters
# TODO: Check for distance to Barycenter
# c: For Star Orbiting objects -> Calculate and apply pull to star and Barycenter
star_orbiter.apply_force(calculate_n_body_force(star_orbiter, top_level_bodies))
# d: For On Approach -> Calculate and apply pull to star and distant Barycenters
# as well as individual bodies within approaching Barycenter
func calculate_gravitational_force(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
return Vector2.ZERO
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
if distance_sq < 1.0:
return Vector2.ZERO
# --- Influence Pruning (Culling) ---
# We check both directions of influence
var influence_a = primary.mass / distance_sq
var influence_b = orbiter.mass / distance_sq
if influence_a < MIN_INFLUENCE_THRESHOLD and influence_b < MIN_INFLUENCE_THRESHOLD:
return Vector2.ZERO
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
var direction = orbiter.global_position.direction_to(primary.global_position)
return direction * force_magnitude
# Calculates the pull between a set number of bodies
# Use carefully to simulate each level of the simulation
# Iterate through every unique pair of bodies (i, j) where j > i
func apply_n_body_forces(attractors: Array[OrbitalBody2D]):
# Iterate through every unique pair of bodies (i, j) where j > i
for i in range(attractors.size()):
var body_a: OrbitalBody2D = attractors[i]
if not is_instance_valid(body_a): continue
for j in range(i + 1, attractors.size()):
var body_b: OrbitalBody2D = attractors[j]
if not is_instance_valid(body_b): continue
# Calculate the force vector ONCE
var force_vector = calculate_gravitational_force(body_a, body_b)
# Apply the force symmetrically
if force_vector != Vector2.ZERO:
body_a.apply_force(force_vector)
body_b.apply_force(-force_vector)
func calculate_n_body_force(body: OrbitalBody2D, attractors: Array[OrbitalBody2D]) -> Vector2:
var total_pull: Vector2 = Vector2.ZERO
for attractor in attractors:
total_pull += calculate_gravitational_force(body, attractor)
return total_pull
func calculate_n_body_gravity_forces(body_to_affect: Node2D) -> Vector2:
var total_force = Vector2.ZERO
if not is_instance_valid(body_to_affect):
return total_force
# Get the list of all major gravitational bodies from the GameManager.
var system_data = GameManager.get_system_data()
if not system_data:
return total_force
# We only consider planets and the star as major attractors for performance.
var attractors = system_data.all_bodies()
for attractor in attractors:
if is_instance_valid(attractor) and attractor != body_to_affect:
total_force += calculate_gravitational_force(body_to_affect, attractor)
return total_force
# Calculates the perfect initial velocity for a stable circular orbit.
func calculate_circular_orbit_velocity(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
if not is_instance_valid(primary):
return Vector2.ZERO
var distance = orbiter.global_position.distance_to(primary.global_position)
if distance == 0:
return Vector2.ZERO
# v = sqrt(G * M / r)
var speed_magnitude = sqrt(G * primary.mass / distance)
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
return perpendicular_direction * speed_magnitude
func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
var num_steps = 10
var time_step = 60
var ghost_position = body_to_trace.global_position
var ghost_velocity = body_to_trace.linear_velocity
var path_points = PackedVector2Array()
for i in range(num_steps):
# Create a temporary "ghost" body to calculate forces on.
var ghost_body = OrbitalBody2D.new()
ghost_body.global_position = ghost_position
ghost_body.mass = body_to_trace.mass
# Use our library to get the total gravitational force at the ghost's position.
var total_force = calculate_n_body_gravity_forces(ghost_body)
var acceleration = total_force / ghost_body.mass
ghost_velocity += acceleration * time_step
ghost_position += ghost_velocity * time_step
path_points.append(ghost_position)
ghost_body.free() # Clean up the temporary node
return path_points
# Calculates an array of points for the orbit RELATIVE to the primary body.
func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
if not is_instance_valid(body_to_trace) or not body_to_trace.has_method("get_primary") or not is_instance_valid(body_to_trace.get_primary()):
return PackedVector2Array()
var primary = body_to_trace.get_primary()
var primary_mass = primary.mass
var body_mass = body_to_trace.mass
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 r_magnitude = ghost_relative_pos.length()
if r_magnitude == 0:
return PackedVector2Array()
var v_sq = ghost_relative_vel.length_squared()
var mu = G * primary_mass
var specific_energy = v_sq / 2.0 - mu / r_magnitude
var num_steps = 200
var time_step: float
if specific_energy >= 0:
time_step = 0.1
else:
var semi_major_axis = -mu / (2.0 * specific_energy)
var orbital_period = 2.0 * PI * sqrt(pow(semi_major_axis, 3) / mu)
time_step = orbital_period / float(num_steps)
var path_points = PackedVector2Array()
for i in range(num_steps):
var distance_sq = ghost_relative_pos.length_squared()
if distance_sq < 1.0:
break
var direction = -ghost_relative_pos.normalized()
var force_magnitude = (G * primary_mass * body_mass) / distance_sq
var force_vector = direction * force_magnitude
var acceleration = force_vector / body_mass
ghost_relative_vel += acceleration * time_step
ghost_relative_pos += ghost_relative_vel * time_step
path_points.append(ghost_relative_pos)
return path_points
# Calculates the Hill Sphere radius for a satellite.
# This is the region where the satellite's gravity is dominant over its primary's.
func calculate_hill_sphere(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
if not is_instance_valid(orbiter) or not is_instance_valid(primary) or primary.mass <= 0:
return 0.0
var distance = orbiter.global_position.distance_to(primary.global_position)
# The formula is: a * (m / 3M)^(1/3)
var mass_ratio = orbiter.mass / (3.0 * primary.mass)
if mass_ratio < 0: return 0.0
return distance * pow(mass_ratio, 1.0/3.0)
# Calculates a simplified Roche Limit, or minimum safe orbital distance.
func calculate_simplified_roche_limit(primary: OrbitalBody2D) -> float:
if not is_instance_valid(primary) or primary.mass <= 0:
return 100.0 # Return a small default if primary is invalid
# We approximate a "radius" from the square root of the mass, then apply a multiplier.
# This ensures more massive stars and planets have larger "keep-out" zones.
return sqrt(primary.mass) * ROCHE_LIMIT_MASS_MULTIPLIER
func get_orbital_time_in_seconds(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
var mu = OrbitalMechanics.G * primary.mass
var r = orbiter.global_position.distance_to(primary.global_position)
return TAU * sqrt(pow(r, 3) / mu)

View File

@ -1,53 +0,0 @@
# scripts/star_system.gd
class_name StarSystem
extends Node2D
@export_group("System Metadata")
@export var system_name: String = "Kepler-186"
@export var galactic_coordinates: Vector2i = Vector2i.ZERO
var system_data: SystemData
func _ready():
# 1. Create the generator tool.
var generator = StarSystemGenerator.new()
# 2. Tell the generator to build the system within this StarSystem node.
system_data = generator.generate(self)
# 3. Register the completed system with the GameManager.
GameManager.register_star_system(self)
GameManager.start_game()
# --- Public API for accessing system data ---
func get_star() -> OrbitalBody2D:
if is_instance_valid(system_data):
return system_data.star
return null
func get_planetary_systems() -> Array[Barycenter]:
var bodies: Array[Barycenter] = []
for child in get_children():
if child is Barycenter:
bodies.append(child)
return bodies
func get_orbital_bodies() -> Array[OrbitalBody2D]:
var bodies: Array[OrbitalBody2D] = []
for child in get_children():
if child is Star or child is Barycenter:
continue
if child is OrbitalBody2D:
bodies.append(child)
return bodies
func get_system_data() -> SystemData:
return system_data
class AsteroidBelt:
var width : float
var mass : float
var centered_radius : float = 0.0
var asteroids : Array[OrbitalBody2D]

View File

@ -1,115 +0,0 @@
# scripts/star_system_generator.gd
class_name StarSystemGenerator
extends RefCounted
# --- Stable Mass Ratios & Generation Rules ---
const STAR_MASS = 50000000.0
const PLANET_MASS = STAR_MASS / 10000.0 # Planet is 10,000x less massive than the star.
const MOON_MASS = PLANET_MASS / 1000.0 # Moon is 1,000x less massive than its planet.
const MIN_PLANETS = 3
const MAX_PLANETS = 8
const MAX_MOONS_PER_PLANET = 5
const ORBIT_SAFETY_FACTOR = 5 # Increase space between orbits
# --- The main public method ---
func generate(star_system: StarSystem) -> SystemData:
# 1. Create the root Barycenter for the entire system.
var system_data = SystemData.new()
# 2. Create the star itself inside the root Barycenter.
var star = Star.new()
system_data.star = star
star.name = "Star"
star.base_mass = STAR_MASS
star_system.add_child(star)
# 3. Procedurally generate and place the planetary systems.
var num_planets = randi_range(MIN_PLANETS, MAX_PLANETS)
var current_orbit_radius = 15000.0 # OrbitalMechanics.calculate_simplified_roche_limit(star) # Start with the first orbit
for i in range(num_planets):
# A. Create the Barycenter for the new planetary system.
var planet_barycenter = Barycenter.new()
planet_barycenter.name = "PlanetSystem_%d" % (i + 1)
star_system.add_child(planet_barycenter)
# B. Create the planet itself inside its Barycenter.
var planet = Planet.new()
system_data.planets.append(planet)
planet.name = "Planet_%d" % (i + 1)
planet.base_mass = randf_range(PLANET_MASS * 0.2, PLANET_MASS * 5.0)
planet_barycenter.add_child(planet)
planet.owner = planet_barycenter
planet.position = Vector2.ZERO
planet_barycenter.recalculate_total_mass()
# C. Create moons for this planet.
_generate_moons(planet, planet_barycenter, star_system, system_data)
# D. Place the entire planetary system in a stable orbit.
planet_barycenter.global_position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
planet_barycenter.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(planet_barycenter, star)
# Update the new edge of the star's influence
# 1. Calculate the Hill Sphere (gravitational influence) for the planet we just placed.
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(planet_barycenter, star)
# 2. Add this zone of influence (plus a safety margin) to the current radius
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
# --- Spawn the ship at the last planet's L4 point ---
if i == num_planets - 1:
_spawn_player_ship(star_system, star, planet_barycenter)
return system_data
func _generate_moons(planet: OrbitalBody2D, planet_barycenter: Barycenter, star_system: StarSystem, system_data: SystemData):
var num_moons = randi_range(0, int(planet.mass / MOON_MASS / 2.0)) # Heavier planets get more moons
num_moons = min(num_moons, MAX_MOONS_PER_PLANET)
var current_orbit_radius = 200.0 # OrbitalMechanics.calculate_simplified_roche_limit(planet) # Start with the first orbit
for i in range(num_moons):
var moon = Moon.new()
system_data.moons.append(moon)
planet_barycenter.add_child(moon)
planet_barycenter.recalculate_total_mass()
moon.owner = planet_barycenter
moon.name = "Moon_%d" % (i + 1)
moon.base_mass = randf_range(MOON_MASS * 0.1, MOON_MASS * 2.0)
moon.position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
# Velocity is calculated relative to the parent (the planet)
moon.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(moon, planet_barycenter)
# Update the new edge of the planets's influence
# 1. Calculate the Hill Sphere (gravitational influence) for the moon we just placed.
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(moon, planet_barycenter)
# 2. Add this zone of influence (plus a safety margin) to the current radius
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
# --- NEW FUNCTION: Spawns the player ship at a Lagrange point ---
func _spawn_player_ship(star_system: StarSystem, star: OrbitalBody2D, planet_system: Barycenter):
# L4 and L5 Lagrange points form an equilateral triangle with the star and planet.
# We'll calculate L4 by rotating the star-planet vector by +60 degrees.
var star_to_planet_vec = planet_system.global_position - star.global_position
var l4_position = star.global_position + star_to_planet_vec.rotated(PI / 3.0)
# The ship's velocity at L4 must match the orbital characteristics of that point.
# This is an approximation where we rotate the planet's velocity vector by 60 degrees.
var l4_velocity = planet_system.linear_velocity.rotated(PI / 3.0)
# Instantiate, position, and configure the ship.
var ship_instance = GameManager.config.default_ship_scene.instantiate()
GameManager.register_ship(ship_instance)
ship_instance.name = "PlayerShip"
star_system.add_child(ship_instance) # Add ship to the root StarSystem node
ship_instance.global_position = l4_position
ship_instance.linear_velocity = l4_velocity
ship_instance.rotation = l4_velocity.angle() + (PI / 2.0) # Point prograde
# Make sure the new ship is included in the physics simulation
#_system_data_dict.all_bodies.append(ship_instance)
print("Player ship spawned at L4 point of %s" % planet_system.name)

View File

@ -65,6 +65,9 @@ func _enter_tree():
_setup_button_connections()
_update_ui_labels()
# Add the Tool Menu Item
add_tool_menu_item("Generate Structure Definitions", _on_generate_structures_pressed)
main_screen.hide()
undo_redo = EditorInterface.get_editor_undo_redo()
@ -128,6 +131,9 @@ func _exit_tree():
if main_screen:
main_screen.queue_free()
# Clean up the menu item
remove_tool_menu_item("Generate Structure Definitions")
func _has_main_screen() -> bool:
return true
@ -522,3 +528,28 @@ func _find_closest_attachment_point(module: Module, world_pos: Vector2):
closest_point = point
return closest_point
const GeneratorScript = preload("res://data/structure/structure_generator.gd")
# The callback function
func _on_generate_structures_pressed():
if GeneratorScript:
var generator = GeneratorScript.new()
if generator.has_method("generate_system_one"):
generator.generate_system_one()
else:
push_error("StructureGenerator script missing 'generate_system_one' method.")
if generator.has_method("generate_system_two_pentagonal"):
generator.generate_system_two_pentagonal()
else:
push_error("StructureGenerator script missing 'generate_system_two_pentagonal' method.")
if generator.has_method("generate_system_two_v2_sphere"):
generator.generate_system_two_v2_sphere()
else:
push_error("StructureGenerator script missing 'generate_system_two_v2_sphere' method.")
# Cleanup if it's a Node
if generator is Node:
generator.queue_free()

View File

@ -0,0 +1,31 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://balw2uv0dx8tw"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ylkpk"]
[resource]
script = ExtResource("1_ylkpk")
piece_name = "1m Square Dome Top"
type = 1
shape = "Square"
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, 0.7869350219613372, -0.6170358751407486),
"position": Vector3(5.551115123125783e-17, 0.5, 0),
"type": 0,
"up": Vector3(0, 0.6170358751407486, 0.7869350219613372)
}, {
"normal": Vector3(-0.7869350219613372, 1.74734676053076e-16, -0.6170358751407486),
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(-0.6170358751407486, 1.370094871201985e-16, 0.7869350219613372)
}, {
"normal": Vector3(-1.7473467605307596e-16, -0.7869350219613372, -0.6170358751407486),
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
"type": 0,
"up": Vector3(-1.3700948712019848e-16, -0.6170358751407486, 0.7869350219613372)
}, {
"normal": Vector3(0.7869350219613372, -2.1841834506634496e-16, -0.6170358751407486),
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
"type": 0,
"up": Vector3(0.6170358751407486, -1.712618589002481e-16, 0.7869350219613372)
}])

View File

@ -0,0 +1,31 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://btpl1hnsk78db"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_wlppn"]
[resource]
script = ExtResource("1_wlppn")
piece_name = "1m Square Flat"
type = 1
shape = "Square"
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, 1, 0),
"position": Vector3(5.551115123125783e-17, 0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-2.220446049250313e-16, -1, 0),
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(1, -2.7755575615628914e-16, 0),
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}])

View File

@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://da1m4eaojir4s"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_bcg7m"]
[resource]
script = ExtResource("1_bcg7m")
piece_name = "1m Triangle Dome Side"
type = 1
base_mass = 5.0
shape = "Triangle"
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, -0.7869350219613372, -0.6170358751407486),
"position": Vector3(0, -0.28867513459481287, 0),
"type": 0,
"up": Vector3(0, -0.6170358751407486, 0.7869350219613372)
}, {
"normal": Vector3(0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
"position": Vector3(0.25, 0.14433756729740643, 0),
"type": 0,
"up": Vector3(0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
}, {
"normal": Vector3(-0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
"position": Vector3(-0.25, 0.14433756729740643, 0),
"type": 0,
"up": Vector3(-0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
}])

View File

@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlqju8f1hiepk"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_44lvp"]
[resource]
script = ExtResource("1_44lvp")
piece_name = "1m Triangle Flat"
type = 1
base_mass = 5.0
shape = "Triangle"
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-4.440892098500626e-16, -1, 0),
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}])

View File

@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dyvonkcjxbh4r"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lytiu"]
[resource]
script = ExtResource("1_lytiu")
piece_name = "1m Triangle Geo"
type = 1
base_mass = 5.0
shape = "Triangle"
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
"type": 0,
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
}, {
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962513),
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
"type": 0,
"up": Vector3(-0.30894416990603113, 0.1783689996598126, 0.9342044743210295)
}, {
"normal": Vector3(-4.148701268396191e-16, -0.9342044743210295, -0.35673799931962513),
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
"type": 0,
"up": Vector3(-1.584234962413445e-16, -0.35673799931962513, 0.9342044743210295)
}])

View File

@ -0,0 +1,35 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dq7lk5e3686oh"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_48w0s"]
[resource]
script = ExtResource("1_48w0s")
piece_name = "S 1 Cyl 12 Wall"
type = 1
base_mass = 5.0
cost = {
"Aluminium": 5.0
}
shape = "Rect"
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, -1, 0),
"position": Vector3(0, 0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(0, 1, 0),
"position": Vector3(0, -0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-0.9659258262890683, 0, -0.25881904510252074),
"position": Vector3(0.5, 0, 0),
"type": 0,
"up": Vector3(-0.25881904510252074, 0, 0.9659258262890683)
}, {
"normal": Vector3(0.9659258262890683, 0, -0.25881904510252074),
"position": Vector3(-0.5, 0, 0),
"type": 0,
"up": Vector3(0.25881904510252074, 0, 0.9659258262890683)
}])

View File

@ -0,0 +1,35 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlkqr2t1b52kg"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_0wpc7"]
[resource]
script = ExtResource("1_0wpc7")
piece_name = "S 1 Cyl 8 Wall"
type = 1
base_mass = 5.0
cost = {
"Aluminium": 5.0
}
shape = "Rect"
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, -1, 0),
"position": Vector3(0, 0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(0, 1, 0),
"position": Vector3(0, -0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-0.9238795325112867, 0, -0.3826834323650898),
"position": Vector3(0.5, 0, 0),
"type": 0,
"up": Vector3(-0.3826834323650898, 0, 0.9238795325112867)
}, {
"normal": Vector3(0.9238795325112867, 0, -0.3826834323650898),
"position": Vector3(-0.5, 0, 0),
"type": 0,
"up": Vector3(0.3826834323650898, 0, 0.9238795325112867)
}])

View File

@ -0,0 +1,35 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://d4hsi33r6tdla"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_fqsnc"]
[resource]
script = ExtResource("1_fqsnc")
piece_name = "S 1 Dome Sq Cap"
type = 1
base_mass = 5.0
cost = {
"Aluminium": 5.0
}
shape = "Square"
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, 0.9396926207859084, -0.3420201433256687),
"position": Vector3(5.551115123125783e-17, 0.5, 0),
"type": 0,
"up": Vector3(0, 0.3420201433256687, 0.9396926207859084)
}, {
"normal": Vector3(-0.9396926207859084, 2.0865367673337435e-16, -0.3420201433256687),
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(-0.3420201433256687, 7.59437276011507e-17, 0.9396926207859084)
}, {
"normal": Vector3(-2.086536767333743e-16, -0.9396926207859084, -0.3420201433256687),
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
"type": 0,
"up": Vector3(-7.594372760115069e-17, -0.3420201433256687, 0.9396926207859084)
}, {
"normal": Vector3(0.9396926207859084, -1.5649025755003072e-16, -0.3420201433256687),
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(0.3420201433256687, -5.695779570086302e-17, 0.9396926207859084)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://e48yh6c4yj45"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mh72h"]
[resource]
script = ExtResource("1_mh72h")
piece_name = "S 1 Dome Sq Side"
type = 1
base_mass = 2.1650635094610964
cost = {
"Aluminium": 2.1650635094610964
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(-0.5, 0, 0), Vector3(0.5, 0, 0), Vector3(0, -0.8660254037844386, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, -0.9396926207859084, -0.3420201433256687),
"position": Vector3(0, 0, 0),
"type": 0,
"up": Vector3(0, -0.3420201433256687, 0.9396926207859084)
}, {
"normal": Vector3(-0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
"position": Vector3(0.25, -0.4330127018922193, 0),
"type": 0,
"up": Vector3(-0.1503837331804353, 0.08682408883346518, 0.984807753012208)
}, {
"normal": Vector3(0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
"position": Vector3(-0.25, -0.4330127018922193, 0),
"type": 0,
"up": Vector3(0.1503837331804353, 0.08682408883346518, 0.984807753012208)
}])

View File

@ -0,0 +1,35 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://c00y87i8upmes"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_dyfmy"]
[resource]
script = ExtResource("1_dyfmy")
piece_name = "S 1 Flat Square"
type = 1
base_mass = 5.0
cost = {
"Aluminium": 5.0
}
shape = "Square"
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, 1, 0),
"position": Vector3(5.551115123125783e-17, 0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-2.220446049250313e-16, -1, 0),
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(1, -1.6653345369377348e-16, 0),
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dn402fpt477ho"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_o42ak"]
[resource]
script = ExtResource("1_o42ak")
piece_name = "S 1 Flat Triangle"
type = 1
base_mass = 2.1650635094610973
cost = {
"Aluminium": 2.1650635094610973
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
"position": Vector3(0, -0.2886751345948128, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://byuhmmhixc2jp"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_4ngxd"]
[resource]
script = ExtResource("1_4ngxd")
piece_name = "S 2 Equilateral Tri"
type = 1
base_mass = 2.1650635094610973
cost = {
"Aluminium": 2.1650635094610973
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}, {
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
"position": Vector3(0, -0.2886751345948128, 0),
"type": 0,
"up": Vector3(0, 0, 1)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bx6w62uqleyro"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lfiao"]
[resource]
script = ExtResource("1_lfiao")
piece_name = "S 2 Geo Tri"
type = 1
base_mass = 2.1650635094610964
cost = {
"Aluminium": 2.1650635094610964
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
mounts = Array[Dictionary]([{
"normal": Vector3(0, -0.9342044743210295, -0.35673799931962513),
"position": Vector3(0, -0.28867513459481287, 0),
"type": 0,
"up": Vector3(0, -0.35673799931962513, 0.9342044743210295)
}, {
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
"position": Vector3(0.25, 0.14433756729740643, 0),
"type": 0,
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
}, {
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
"position": Vector3(-0.25, 0.14433756729740643, 0),
"type": 0,
"up": Vector3(-0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://b1kwhqprqqgpk"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mpy4c"]
[resource]
script = ExtResource("1_mpy4c")
piece_name = "S 2 Geo V 2 A"
type = 1
base_mass = 1.8234625713415449
cost = {
"Aluminium": 1.8234625713415449
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(-0.4363389981249825, -0.03590512589028917, 0.21157661739748426), Vector3(0.3726779962499651, -0.38655593424232904, 0.14421169130125808), Vector3(0.06366100187501747, 0.42246106013261825, -0.35578830869874195)])
mounts = Array[Dictionary]([{
"normal": Vector3(-0.3090169943749473, -0.8090169943749476, 0.5),
"position": Vector3(-0.01967233145831576, -0.1305476470426356, 0.10994463378783126),
"type": 0,
"up": Vector3(0.25989191300775444, 0.4338885645526948, 0.8626684804161862)
}, {
"normal": Vector3(0.8506508083520399, 1.1102230246251565e-16, -0.5257311121191337),
"position": Vector3(0.1348361657291579, 0.011095294085271323, -0.06538077038818868),
"type": 0,
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
}, {
"normal": Vector3(-0.8090169943749475, 0.5000000000000001, -0.3090169943749475),
"position": Vector3(-0.11516383427084212, 0.11945235295736434, -0.04456386339964247),
"type": 0,
"up": Vector3(0.16062203564002311, 0.6937804775604491, 0.702046444776163)
}])

View File

@ -0,0 +1,30 @@
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bwe7g4wf44opf"]
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ra6qr"]
[resource]
script = ExtResource("1_ra6qr")
piece_name = "S 2 Geo V 2 B"
type = 1
base_mass = 2.165063509461097
cost = {
"Aluminium": 2.165063509461097
}
shape = "Triangle"
vertices = Array[Vector3]([Vector3(-0.06366100187501737, -0.37267799624996495, 0.4363389981249825), Vector3(0.4363389981249825, -0.06366100187501737, -0.37267799624996495), Vector3(-0.37267799624996506, 0.43633899812498234, -0.06366100187501755)])
mounts = Array[Dictionary]([{
"normal": Vector3(0.5257311121191337, -0.8506508083520399, -1.1102230246251565e-16),
"position": Vector3(0.11516383427084209, -0.13483616572915785, 0.019672331458315817),
"type": 0,
"up": Vector3(0.6881909602355868, 0.42532540417602005, 0.5877852522924731)
}, {
"normal": Vector3(-8.326672684688674e-17, 0.5257311121191337, -0.8506508083520399),
"position": Vector3(0.019672331458315817, 0.11516383427084209, -0.1348361657291579),
"type": 0,
"up": Vector3(0.5877852522924731, 0.6881909602355867, 0.42532540417601994)
}, {
"normal": Vector3(-0.8506508083520399, -1.1102230246251565e-16, 0.5257311121191337),
"position": Vector3(-0.1348361657291579, 0.019672331458315817, 0.11516383427084209),
"type": 0,
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
}])

View File

@ -0,0 +1,63 @@
class_name StructureData extends Resource
enum PieceType {STRUT, PLATE, CONNECTOR}
@export_group("Identity")
@export var piece_name: String = "Structure"
@export var type: PieceType = PieceType.STRUT
@export var base_mass: float = 10.0
@export var health_max: float = 100.0
@export var cost: Dictionary = {"Aluminium": 10.0}
@export_group("Visuals & Physics")
## The mesh to display for static pieces. Leave null for procedural pieces.
@export var mesh: Mesh
## The collision shape for physics. Leave null for procedural pieces.
@export var collision_shape: Shape3D
@export_group("Procedural Parameters")
# For procedural pieces, we store parameters instead of a mesh
@export var shape: String = "Cube"
@export var vertices: Array[Vector3] = [
Vector3(1.0, 1.0, 1.0),
Vector3(-1.0, 1.0, 1.0),
Vector3(-1.0, -1.0, 1.0),
Vector3(1.0, -1.0, 1.0),
Vector3(1.0, 1.0, -1.0),
Vector3(-1.0, 1.0, -1.0),
Vector3(-1.0, -1.0, -1.0),
Vector3(1.0, -1.0, -1.0)
]
# @export var procedural_params: Dictionary = {}
@export_group("Mounts")
## Array of Dictionaries defining attachment points.
## Format: { "position": Vector3, "normal": Vector3, "up": Vector3, "type": int }
@export var mounts: Array[Dictionary] = []
# Helper to get mounts transformed into world space for snapping calculations
func get_mounts_transformed(global_transform: Transform3D) -> Array:
var world_mounts = []
for mount in mounts:
# Default to identity rotation if normal/up are missing
var normal = mount.get("normal", Vector3.BACK) # Default -Z forward
var up = mount.get("up", Vector3.UP)
world_mounts.append({
"position": global_transform * mount.get("position", Vector3.ZERO),
"normal": global_transform.basis * normal,
"up": global_transform.basis * up,
"type": mount.get("type", 0)
})
return world_mounts
# Helper to add a mount dynamically (for procedural pieces)
func add_mount(pos: Vector3, normal: Vector3, up: Vector3 = Vector3.UP, type: int = 0):
mounts.append({
"position": pos,
"normal": normal,
"up": up,
"type": type
})

View File

@ -0,0 +1 @@
uid://bdllldtl4bia3

View File

@ -0,0 +1,334 @@
@tool
class_name StructureGenerator extends Node
# --- CONFIGURATION ---
const DENSITY_ALUMINUM = 5.0 # kg per square meter (approx for hull plating)
const COST_PER_KG = 1.0 # Currency per kg
# Run this to regenerate the entire "System 1" library
func generate_system_one():
var dir = DirAccess.open("res://data/structure/definitions/")
if not dir: DirAccess.make_dir_recursive_absolute("res://data/structure/definitions/")
print("--- Generating Design System 1: Geodesic ---")
# 1. Basic Flats (The backbone)
create_polygon_plate("s1_flat_square", 4, 0.0)
create_polygon_plate("s1_flat_triangle", 3, 0.0)
# 2. Cylinders (Corridors & Fuselage)
# 8-Sided: ~2.6m Diameter (Good for corridors)
create_cylinder_plate("s1_cyl8_wall", 8)
# 12-Sided: ~3.9m Diameter (Good for main fuselage)
create_cylinder_plate("s1_cyl12_wall", 12)
# 3. Square-Based Dome Cap
# This creates a 'Cap' square that tilts its edges down,
# and a 'Side' triangle that connects that square to a flatter ring below.
create_square_dome_set("s1_dome_sq", 20.0) # 20 degree slope
print("Generation Complete.")
# --- CORE GENERATORS ---
# Creates a regular polygon (Square, Triangle, Hexagon, etc.)
# bend_angle: Degrees to tilt the mount DOWN. 0 = Flat floor. >0 = Dome/Cylinder.
func create_polygon_plate(id: String, sides: int, bend_angle: float):
var res = _create_base_resource(id, "Plate")
res.shape = "Square" if sides == 4 else "Triangle"
# Calculate Radius for exactly 1.0m Edge Length
var radius = 0.5 / sin(PI / sides)
var angle_step = TAU / sides
var start_angle = PI / 4 if sides == 4 else -PI / 6 # Align flat edges to axes
res.vertices = [] as Array[Vector3]
for i in range(sides):
var theta = start_angle + i * angle_step
res.vertices.append(Vector3(cos(theta), sin(theta), 0) * radius)
# Generate Mounts
for i in range(sides):
var p1 = res.vertices[i]
var p2 = res.vertices[(i + 1) % sides]
_add_edge_mount(res, p1, p2, bend_angle)
_finalize_resource(res, id)
# Creates a rectangular plate that forms one segment of a N-sided cylinder
func create_cylinder_plate(id: String, total_sides: int):
var res = _create_base_resource(id, "Cylinder Wall")
res.shape = "Rect"
# Height = 1.0m (Standard grid)
# Width = 1.0m (Chord length of the cylinder)
var v0 = Vector3(-0.5, 0.5, 0)
var v1 = Vector3(0.5, 0.5, 0)
var v2 = Vector3(0.5, -0.5, 0)
var v3 = Vector3(-0.5, -0.5, 0)
res.vertices = [v0, v1, v2, v3] as Array[Vector3]
# Calculate the bend angle required to form a circle
# Interior angle = (n-2)*180/n. Bend = (180 - Interior)/2 = 360/n / 2 = 180/n
var bend = 180.0 / total_sides
# Top/Bottom: Flat (0 deg) to stack cylinders
_add_edge_mount(res, v0, v1, 0.0)
_add_edge_mount(res, v2, v3, 0.0)
# Left/Right: Bent to form the ring
_add_edge_mount(res, v1, v2, bend)
_add_edge_mount(res, v3, v0, bend)
_finalize_resource(res, id)
# Creates a Square Cap and its matching Triangle skirt
func create_square_dome_set(prefix: String, slope_angle: float):
# PART A: The Top Square
# It acts like a flat square, but all mounts are tilted down by 'slope_angle'
create_polygon_plate(prefix + "_cap", 4, slope_angle)
# PART B: The Side Triangle
# This triangle connects the Tilted Square (Top) to a Flat Ring (Bottom)
# It is an Isosceles triangle.
# Top Edge: Matches the Square (1m).
# Side Edges: Calculated to reach the flat plane.
var res = _create_base_resource(prefix + "_side", "Dome Tri")
res.shape = "Triangle"
# We generate this triangle Flat on XY, but calculate mounts to fit the 3D gap.
# Geometry:
# The gap it fills has a top width of 1m.
# The 'dihedral' angle between the Square and this Triangle is (180 - slope_angle).
# To interface with the square, this triangle's Top Mount must act like it's bent "up" by slope_angle relative to the square's normal.
# Actually, simpler logic:
# 1. Top Edge: Connects to the Square. Needs 'slope_angle' bend.
# 2. Side Edges: Connect to neighbors in the ring.
# 3. Bottom Vertex: Pointing down? No, usually a dome layer is a ring of triangles (point up) and triangles (point down).
# Let's assume a "Pyramid" style cap for simplicity first:
# 4 Triangles meeting at a point is too sharp.
# 4 Triangles connecting to a square creates a 'frustum'.
# Vertices for a standard 1m equilateral (placeholder for now, can be tweaked for specific radii)
var h = sqrt(3) * 0.5
var v0 = Vector3(-0.5, 0, 0)
var v1 = Vector3(0.5, 0, 0)
var v2 = Vector3(0, -h, 0)
res.vertices = [v0, v1, v2] as Array[Vector3]
# Top Edge (v0->v1): Connects to Square.
# The square is tilted down by 'slope'. To match it, we must tilt 'up' or 'down'?
# Normals must oppose. Square normal is Tilted Down. This normal must be Tilted Down (relative to self) to be parallel?
# Actually, both pieces tilt "in" towards the center of the sphere.
_add_edge_mount(res, v0, v1, slope_angle)
# Side Edges (v1->v2, v2->v0): Connect to other triangles in the skirt.
# These usually need a smaller bend angle, approx half the square's bend for a smooth transition.
var side_bend = slope_angle * 0.5 # Approximation
_add_edge_mount(res, v1, v2, side_bend)
_add_edge_mount(res, v2, v0, side_bend)
_finalize_resource(res, prefix + "_side")
# --- HELPERS ---
func _create_base_resource(id: String, suffix: String) -> StructureData:
var res = StructureData.new()
res.piece_name = id.capitalize()
res.type = StructureData.PieceType.PLATE
return res
func _add_edge_mount(res: StructureData, p1: Vector3, p2: Vector3, bend_deg: float):
var mid = (p1 + p2) / 2.0
var edge_vector = (p2 - p1).normalized()
# Flat normal points -Z (Back) or +Z depending on convention. Using BACK (+Z in Godot) as "Out"
var flat_normal = edge_vector.cross(Vector3.BACK).normalized()
# Rotate normal "Down" around the edge
var bend_rad = deg_to_rad(bend_deg)
var final_normal = flat_normal.rotated(edge_vector, bend_rad)
var final_up = Vector3.BACK.rotated(edge_vector, bend_rad)
res.add_mount(mid, final_normal, final_up)
func _finalize_resource(res: StructureData, filename: String):
# Calculate Area for Mass/Cost
var area = 0.0
if res.vertices.size() >= 3:
# Shoelace formula or simple triangle sum
# For convex shapes centered on 0,0:
for i in range(res.vertices.size()):
var p1 = res.vertices[i]
var p2 = res.vertices[(i + 1) % res.vertices.size()]
area += 0.5 * (p1.cross(p2).length())
res.base_mass = area * DENSITY_ALUMINUM
res.cost = {"Aluminium": res.base_mass * COST_PER_KG}
var path = "res://data/structure/definitions/%s.tres" % filename
ResourceSaver.save(res, path)
# print("Generated %s (Mass: %.1f kg)" % [filename, res.base_mass])
func generate_system_two_pentagonal():
print("--- Generating Design System 2: Pentagonal ---")
# Configuration: 2m Radius Sphere
# Icosahedron Edge Length (a) for radius (r): a = r / sin(72) * 2 approx...
# Let's standardise on the edge length = 1.0m.
# This results in a sphere radius of ~0.95m.
var edge_length = 1.0
# 1. THE TUBE (Pentagonal Antiprism)
# A tube made of 10 triangles per segment.
# To fit a regular pentagon of side 1.0m.
# Radius of pentagon = 1.0 / (2 * sin(36)) = ~0.85m
# We need a triangle that connects two points on the bottom pentagon
# to one point on the top pentagon (rotated 36 degrees).
# This forms an equilateral triangle if the height is correct (0.85m).
create_polygon_plate("s2_equilateral_tri", 3, 0.0) # Standard 1m triangle
# 2. THE SPHERE CAP (Pentagonal Pyramid)
# 5 of these triangles snap together to form a "Cap".
# The "bend" angle is the dihedral angle of an Icosahedron ~138.19 deg.
# Deviation from flat = (180 - 138.19) / 2 = ~20.9 degrees.
var bend_angle = 20.9
var res = _create_base_resource("s2_geo_tri", "Geo Plate")
res.shape = "Triangle"
res.vertices = _generate_equilateral_verts(1.0)
# Base Edge (0->1): Connects to the rest of the sphere (or extension ring)
# Side Edges (1->2, 2->0): Connect to neighbors in the 5-way cluster
# All edges in a V1 sphere have the same bend angle!
_add_edge_mount(res, res.vertices[0], res.vertices[1], bend_angle)
_add_edge_mount(res, res.vertices[1], res.vertices[2], bend_angle)
_add_edge_mount(res, res.vertices[2], res.vertices[0], bend_angle)
_finalize_resource(res, "s2_geo_tri")
print("System 2 Generated. Build tubes with 's2_equilateral_tri' and spheres with 's2_geo_tri'.")
func _generate_equilateral_verts(side: float) -> Array[Vector3]:
var h = sqrt(3) * 0.5 * side
return [
Vector3(-side/2, -h/3, 0),
Vector3(side/2, -h/3, 0),
Vector3(0, 2*h/3, 0)
]
# src/data/structure/structure_generator.gd
func generate_system_two_v2_sphere():
print("--- Generating Design System 2: V2 Geodesic (Room Size) ---")
# 1. Calculate Geometry (Normalized Radius = 1.0)
var phi = (1.0 + sqrt(5.0)) / 2.0
# Icosahedron vertices
var v0 = Vector3(0, 1, phi).normalized() # Pole
var v4 = Vector3(1, phi, 0).normalized() # Neighbor
var v8 = Vector3(phi, 0, 1).normalized() # Neighbor
# Subdivide for V2 (Midpoints projected to sphere)
var v08 = (v0 + v8).normalized()
var v84 = (v8 + v4).normalized()
var v40 = (v4 + v0).normalized()
# We now have two distinct triangles:
# Triangle A (Cap): v0 -> v08 -> v40
# Triangle B (Face): v08 -> v84 -> v40
# 2. Scale Factor
# We want the "Base" of Triangle A (edge v08-v40) to be exactly 1.0m.
# This ensures it connects perfectly to our standard 1.0m Tubes.
var unscaled_base_len = v08.distance_to(v40)
var scale = 1.0 / unscaled_base_len
print("V2 Sphere Radius: %.2fm" % scale)
# 3. Generate Triangle A (The Pentagon Cap Piece)
# This piece forms the 5-way corners.
var res_a = _create_base_resource("s2_geo_v2_a", "Geo V2 Cap")
res_a.shape = "Triangle"
# Centering: Move vertices so the average is at (0,0,0)
var center_a = (v0 + v08 + v40) / 3.0
res_a.vertices = [
(v0 - center_a) * scale, # Top (Pole)
(v08 - center_a) * scale, # Right
(v40 - center_a) * scale # Left
] as Array[Vector3]
# Calculate exact bend angles based on the sphere normals
# The mount normal should be the vertex normal (pointing out from sphere center)
# relative to the flat face normal.
_add_mount_from_sphere_geometry(res_a, v0, v08, v40, center_a)
_finalize_resource(res_a, "s2_geo_v2_a")
# 4. Generate Triangle B (The Hexagon Face Piece)
# This piece fills the gaps between caps.
var res_b = _create_base_resource("s2_geo_v2_b", "Geo V2 Face")
res_b.shape = "Triangle"
var center_b = (v08 + v84 + v40) / 3.0
res_b.vertices = [
(v08 - center_b) * scale, # Top-Left
(v84 - center_b) * scale, # Bottom
(v40 - center_b) * scale # Top-Right
] as Array[Vector3]
_add_mount_from_sphere_geometry(res_b, v08, v84, v40, center_b)
_finalize_resource(res_b, "s2_geo_v2_b")
# Helper to calculate the correct mount angle for a spherical fragment
func _add_mount_from_sphere_geometry(res: StructureData, p1_sphere: Vector3, p2_sphere: Vector3, p3_sphere: Vector3, center_sphere: Vector3):
# We reconstruct the mounts for the 3 edges
var points = [p1_sphere, p2_sphere, p3_sphere]
# Face Normal (Flat plate orientation)
var face_normal = (p2_sphere - p1_sphere).cross(p3_sphere - p1_sphere).normalized()
for i in range(3):
var a = points[i]
var b = points[(i+1)%3]
# The mount position is the midpoint of the edge (relative to piece center)
var mid_sphere = (a + b) / 2.0
var mid_local = (mid_sphere - center_sphere) # Scale is applied later in the main loop, but directions don't care about scale
# The mount normal should point OUTWARD from the edge, but follow the sphere's curvature.
# For a sphere, the perfect "Out" vector at the edge midpoint is just mid_sphere.normalized().
# However, our mount system expects the normal to be roughly perpendicular to the edge.
var edge_vec = (b - a).normalized()
# Vector perpendicular to edge, tangent to sphere surface at midpoint
var sphere_tangent_out = edge_vec.cross(mid_sphere.normalized()).normalized()
# Wait, the mount normal needs to match the *other* piece's mount normal.
# If both pieces are on the sphere, their mount normals should be parallel to the chord connecting them?
# No, standard "snap" logic opposes normals.
# If we use the Tangent, it points "along" the sphere surface.
# When two pieces snap, they will form a continuous curve.
# Let's stick to the generated tangents.
# We need to rotate this into the Local Space of the piece.
# Actually, we are defining vertices in Local Space already.
# But the normals calculated above are in Sphere Space.
# We need to rotate the calculated Sphere Normals into the Flat Face space?
# No, StructureData mounts are defined in Local Space.
# The Vertices in res.vertices are already (p - center).
# So:
# Position: (mid_sphere - center_sphere) * scale (handled in main loop, we just need direction here)
# Normal: sphere_tangent_out (It's a direction vector, translation doesn't affect it)
# Up: The sphere normal at that point? (mid_sphere.normalized())
# Let's assume 'Up' is the surface normal (Out from center of sphere)
var mount_up = mid_sphere.normalized()
res.add_mount((mid_sphere - center_sphere), sphere_tangent_out, mount_up)

View File

@ -0,0 +1 @@
uid://bkqqditmq34y3

67
src/export_presets.cfg Normal file
View File

@ -0,0 +1,67 @@
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
advanced_options=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../export/moa.exe"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=true
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
shader_baker/enabled=false
binary_format/architecture="x86_64"
codesign/enable=false
codesign/timestamp=true
codesign/timestamp_server_url=""
codesign/digest_algorithm=1
codesign/description=""
codesign/custom_options=PackedStringArray()
application/modify_resources=true
application/icon=""
application/console_wrapper_icon=""
application/icon_interpolation=4
application/file_version=""
application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/trademarks=""
application/export_angle=0
application/export_d3d12=0
application/d3d12_agility_sdk_multiarch=true
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
Start-ScheduledTask -TaskName godot_remote_debug
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
Remove-Item -Recurse -Force '{temp_dir}'"

View File

Before

Width:  |  Height:  |  Size: 994 B

After

Width:  |  Height:  |  Size: 994 B

View File

@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@ -25,6 +27,10 @@ mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false

7
src/main.tscn Normal file
View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://dogqi2c58qdc0"]
[ext_resource type="PackedScene" uid="uid://ojcho3pi3u7n" path="res://scenes/UI/main_menu/main_menu.tscn" id="1_ig7tw"]
[node name="StartMenu" type="Node3D" unique_id=1392183658]
[node name="MainMenu" parent="." unique_id=2099645465 instance=ExtResource("1_ig7tw")]

View File

@ -0,0 +1,573 @@
[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
properties/1/path = NodePath(".:linear_velocity")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath(".:rotation")
properties/2/spawn = true
properties/2/replication_mode = 1
[node name="3dTestShip" type="RigidBody3D" unique_id=246037729]
physics_interpolation_mode = 1
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="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="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="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2096937457]
replication_config = SubResource("SceneReplicationConfig_ism2t")

View 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

View 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")]

View File

@ -10,9 +10,10 @@ config_version=5
[application]
config/name="space_simulation"
config/name="Millimeters of Aluminum"
config/version="0.1"
run/main_scene="uid://dogqi2c58qdc0"
config/features=PackedStringArray("4.4", "Forward Plus")
config/features=PackedStringArray("4.5", "Double Precision", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
@ -20,6 +21,16 @@ config/icon="res://icon.svg"
OrbitalMechanics="*res://scripts/singletons/orbital_mechanics.gd"
GameManager="*res://scripts/singletons/game_manager.gd"
Constants="*res://scripts/singletons/constants.gd"
NetworkHandler="*res://scripts/network/network_handler.gd"
MotionUtils="*res://scripts/singletons/motion_utils.gd"
[display]
window/vsync/vsync_mode=0
[dotnet]
project/assembly_name="space_simulation"
[editor_plugins]
@ -87,6 +98,71 @@ toggle_wiring_panel={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_left_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
move_forward_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
move_backward_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
roll_right_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
roll_left_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
interact_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
]
}
spacebar_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}
right_click={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
move_up_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_down_3d={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
left_click={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
toggle_build_mode={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
]
}
[layer_names]
@ -97,10 +173,15 @@ toggle_wiring_panel={
2d_physics/layer_4="projectiles"
2d_physics/layer_5="bulkheads"
2d_physics/layer_6="characters"
3d_physics/layer_15="weld"
3d_physics/layer_16="grip"
[physics]
3d/default_gravity=0.0
3d/default_gravity_vector=Vector3(0, 0, 0)
3d/default_linear_damp=0.0
3d/default_angular_damp=0.0
3d/sleep_threshold_linear=0.0
2d/default_gravity=0.0
2d/default_gravity_vector=Vector2(0, 0)

Some files were not shown because too many files have changed in this diff Show More