Compare commits
73 Commits
feature/sh
...
tech-test/
| Author | SHA1 | Date | |
|---|---|---|---|
| 67a4b7038a | |||
| 18f9a4fec7 | |||
| 4796a2d5ca | |||
| f8578bc3f2 | |||
| a7583637e9 | |||
| 86762d0d50 | |||
| e2da700bcd | |||
| 466dff11d0 | |||
| 636123344b | |||
| 25d9d55044 | |||
| aafb939cbf | |||
| 3d01edb2d9 | |||
| 398ec829ae | |||
| ec69ed2ee5 | |||
| 3647aa599d | |||
| 1342ca2610 | |||
| 27ce796898 | |||
| cff5ec27f8 | |||
| 245be4a4f5 | |||
| 6b9efda0d2 | |||
| 29851ea167 | |||
| 0cd9ebdd04 | |||
| 4da8bcaec2 | |||
| 71ad2f09ff | |||
| 2f5a88345f | |||
| 5e851049b5 | |||
| c4fd7f1330 | |||
| 820da83397 | |||
| 14b24beb23 | |||
| 60f2ddb3d7 | |||
| c50d0eae52 | |||
| d375e0d208 | |||
| 9b128a3540 | |||
| 6df457a256 | |||
| bc443b884c | |||
| 8184ec06b4 | |||
| 926a64c3dd | |||
| 29f9bccfd3 | |||
| 7d7580a123 | |||
| 59d457e9ae | |||
| 97ccb2a9ac | |||
| 1ab2c06336 | |||
| f51672c6a9 | |||
| 8e3f415cb4 | |||
| cdcb4796f7 | |||
| 24bc3afd2e | |||
| 4f78de64ba | |||
| fe050897dd | |||
| e075ff580d | |||
| 90e756ad28 | |||
| faf8e7c83a | |||
| 772f9c7df3 | |||
| e066bc4786 | |||
| cc681ae08a | |||
| 138e17503a | |||
| 20a37dda17 | |||
| 8645d2bdc4 | |||
| d8055752d5 | |||
| 21bbbacbbe | |||
| d18c87a051 | |||
| a89154a1c2 | |||
| c662714997 | |||
| 6a4492ef37 | |||
| 85815d957b | |||
| ec6ca92360 | |||
| f25464df03 | |||
| 2ceddb2bbf | |||
| 1228a79cae | |||
| 425e857ba9 | |||
| 588fa29484 | |||
| c14b07d24f | |||
| c61fa2b917 | |||
| 9dac569ad6 |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "godot_engine"]
|
||||
path = godot_engine
|
||||
url = https://codeberg.org/seedlingattempt/godot.git
|
||||
@ -1,8 +1,9 @@
|
||||
Project "Stardust Drifter" Development Status
|
||||
Overview
|
||||
# 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
|
||||
|
||||
## 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.
|
||||
|
||||
@ -22,7 +23,7 @@ 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
|
||||
## II. Work-In-Progress (WIP) and Planned Systems
|
||||
|
||||
This list details systems we have designed but are not yet fully implemented in the code.
|
||||
|
||||
@ -49,3 +50,197 @@ Ghost Simulator: A GhostSimulator class needs to be created to run predictive, i
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
@ -1,8 +1,10 @@
|
||||
# Game Design Document: Project Stardust Drifter (Working Title)
|
||||
# Game Design Document: Project Millimeters of Aluminum (Working Title)
|
||||
|
||||
## 1. Game Vision & Concept
|
||||
|
||||
Project Stardust Drifter 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.
|
||||
@ -39,16 +95,16 @@ The player's ship is not a monolithic entity but a collection of distinct, physi
|
||||
### 4. Advanced Navigation Computer
|
||||
This is the primary crew interface for long-range travel.
|
||||
- Maneuver Planning: The computer can calculate various orbital transfers, each with strategic trade-offs:
|
||||
- Hohmann Transfer: The most fuel-efficient route.
|
||||
- Fast Transfer: A quicker but more fuel-intensive option.
|
||||
- Brachistochrone (Torchship) Trajectory: For ships with high-efficiency engines like Ion Drives, enabling constant-thrust travel.
|
||||
- Gravity Assist: Planned for future implementation.
|
||||
- Hohmann Transfer: The most fuel-efficient route.
|
||||
- Fast Transfer: A quicker but more fuel-intensive option.
|
||||
- Brachistochrone (Torchship) Trajectory: For ships with high-efficiency engines like Ion Drives, enabling constant-thrust travel.
|
||||
- Gravity Assist: Planned for future implementation.
|
||||
- Tactical Map: A fully interactive UI map that replaces custom drawing with instanced, clickable icons for all bodies. It features:
|
||||
- Zoom-to-cursor and click-and-drag panning.
|
||||
- Predictive orbital path drawing for all objects.
|
||||
- Icon culling at a distance to reduce clutter.
|
||||
- Custom hover effects and detailed tooltips with "sensor data."
|
||||
- A "picture-in-picture" SubViewport showing the ship's main camera view.
|
||||
- Zoom-to-cursor and click-and-drag panning.
|
||||
- Predictive orbital path drawing for all objects.
|
||||
- Icon culling at a distance to reduce clutter.
|
||||
- Custom hover effects and detailed tooltips with "sensor data."
|
||||
- A "picture-in-picture" SubViewport showing the ship's main camera view.
|
||||
|
||||
### 5. Multi-Species Crew (Player Classes)
|
||||
|
||||
@ -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.
|
||||
@ -110,11 +180,12 @@ You mention "emergent events" in the gameplay loop. It would be beneficial to de
|
||||
|
||||
## 7. Crew Interaction & Ship Interior
|
||||
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
8
Init_Prompt.md
Normal 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 we’re 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)`)
|
||||
96
README.md
Normal file
96
README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# 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.
|
||||
23
main.tscn
23
main.tscn
@ -1,23 +0,0 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j3j483itissq" path="res://scripts/star_system_generator.gd" id="1_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://5uqp4amjj7ww" path="res://scenes/celestial_bodies/star.tscn" id="2_7mycd"]
|
||||
[ext_resource type="PackedScene" uid="uid://clt4qlsjcfgln" path="res://scenes/celestial_bodies/planet.tscn" id="3_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://74ppvxcw8an4" path="res://scenes/celestial_bodies/moon.tscn" id="4_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dm3s33o4xhqfv" path="res://scenes/celestial_bodies/station.tscn" id="5_kek77"]
|
||||
[ext_resource type="PackedScene" uid="uid://bawsujtlpmh5r" path="res://scenes/celestial_bodies/asteroid.tscn" id="6_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://didt2nsdtbmra" path="res://modules/Tube.tscn" id="7_272bh"]
|
||||
|
||||
[node name="StarSystem" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_planets = 1
|
||||
max_planets = 4
|
||||
max_moons = 10
|
||||
max_asteroid_belts = 2
|
||||
max_star_stations = 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")
|
||||
spaceship_scene = ExtResource("7_272bh")
|
||||
@ -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("..")
|
||||
@ -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="."]
|
||||
@ -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="."]
|
||||
@ -1,129 +0,0 @@
|
||||
[gd_scene load_steps=17 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://osk1l75vlikn" path="res://scenes/ship/computer/databank.gd" id="6_ft4kn"]
|
||||
[ext_resource type="Script" uid="uid://cskf26i7vnxug" path="res://scenes/ship/computer/control_panel.gd" id="6_oqcn4"]
|
||||
[ext_resource type="Resource" uid="uid://dghg3pbws42yu" path="res://scenes/ship/computer/shards/helm_logic_databank.tres" id="7_dmrms"]
|
||||
[ext_resource type="Resource" uid="uid://c4wyouanvf86c" path="res://scenes/ship/computer/panels/button_panel.tres" id="7_vmx8o"]
|
||||
[ext_resource type="Resource" uid="uid://57y6igb07e10" path="res://scenes/ship/computer/panels/readout_screen.tres" id="8_83bu1"]
|
||||
[ext_resource type="Resource" uid="uid://dcyr6utrk376h" path="res://scenes/ship/computer/panels/throttle_lever.tres" id="9_ixntg"]
|
||||
[ext_resource type="Resource" uid="uid://dl7g67mtqkfx2" path="res://scenes/ship/computer/panels/sensor_panel.tres" id="9_xwy4s"]
|
||||
[ext_resource type="Resource" uid="uid://bx7wgunvy5hfa" path="res://scenes/ship/computer/shards/helm_ship_status.tres" id="11_83bu1"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0bb77rmyatr0" path="res://scenes/ship/components/hardware/thruster.tscn" id="12_vmx8o"]
|
||||
[ext_resource type="Resource" uid="uid://g4ho63f30vjm" path="res://scenes/ship/computer/shards/nav_selection_databank.tres" id="12_wkxbw"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="13_83bu1"]
|
||||
[ext_resource type="Resource" uid="uid://b0suy3sxjwhtv" path="res://scenes/ship/computer/shards/sensor_databank.tres" id="13_xwy4s"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
script = ExtResource("1_nqe0s")
|
||||
mass = 13.0
|
||||
inertia = 1200.92
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, -150)
|
||||
rotation = 1.5708
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, 150)
|
||||
rotation = 4.71239
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
mass = 1.0
|
||||
|
||||
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
|
||||
position = Vector2(0, -10)
|
||||
panels = Array[ExtResource("6_oqcn4")]([ExtResource("7_vmx8o"), ExtResource("8_83bu1"), ExtResource("9_ixntg"), ExtResource("9_xwy4s")])
|
||||
installed_databanks = Array[ExtResource("6_ft4kn")]([ExtResource("7_dmrms"), ExtResource("11_83bu1"), ExtResource("12_wkxbw"), ExtResource("13_xwy4s"), null])
|
||||
|
||||
[node name="Thruster" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(-95, -130)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
|
||||
[node name="Thruster2" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(-95, 130)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
|
||||
[node name="Thruster3" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(95, 130)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
|
||||
[node name="Thruster4" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(95, -130)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
|
||||
[node name="MainEngine" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(0, 195)
|
||||
max_thrust = 10.0
|
||||
|
||||
[node name="Spawner" parent="." instance=ExtResource("13_83bu1")]
|
||||
position = Vector2(0, 27)
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Asteroid
|
||||
extends CelestialBody
|
||||
|
||||
# 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()
|
||||
@ -1 +0,0 @@
|
||||
uid://c816xae77cbmq
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bawsujtlpmh5r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c816xae77cbmq" path="res://scenes/celestial_bodies/asteroid.gd" id="1_akfqu"]
|
||||
|
||||
[node name="Asteroid" type="RigidBody2D"]
|
||||
script = ExtResource("1_akfqu")
|
||||
metadata/_custom_type_script = "uid://c816xae77cbmq"
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Moon
|
||||
extends CelestialBody
|
||||
|
||||
# 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.
|
||||
#mass = 100.0
|
||||
radius = 5.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/moon_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://b1xsx7er22nxn
|
||||
@ -1,7 +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="RigidBody2D"]
|
||||
script = ExtResource("1_530pw")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Planet
|
||||
extends CelestialBody
|
||||
|
||||
# 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:
|
||||
# A Planet has a smaller mass than a star.
|
||||
#mass = 1000.0
|
||||
radius = 10.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/planet_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://5f6ipgu65urb
|
||||
@ -1,7 +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="RigidBody2D"]
|
||||
script = ExtResource("1_cktii")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,16 +0,0 @@
|
||||
class_name Star
|
||||
extends CelestialBody
|
||||
|
||||
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.
|
||||
primary = null
|
||||
radius = 100.0
|
||||
|
||||
# You can set a default texture here, or assign it in the Inspector.
|
||||
# texture = preload("res://assets/star_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://um2sfghmii42
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://5uqp4amjj7ww"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://um2sfghmii42" path="res://scripts/star.gd" id="1_mcqwg"]
|
||||
|
||||
[node name="Star" type="RigidBody2D"]
|
||||
script = ExtResource("1_mcqwg")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Station
|
||||
extends CelestialBody
|
||||
|
||||
# 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.
|
||||
#mass = 0.001
|
||||
radius = 1.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/station_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://ulw61oxppwdu
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dm3s33o4xhqfv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ulw61oxppwdu" path="res://scripts/station.gd" id="1_rod8h"]
|
||||
|
||||
[node name="Station" type="RigidBody2D"]
|
||||
script = ExtResource("1_rod8h")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,213 +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()
|
||||
|
||||
|
||||
# --- Ladder Input and Launch Logic ---
|
||||
|
||||
# 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
|
||||
|
||||
# If we just occupied a station this frame, do nothing more.
|
||||
# This prevents the same input from instantly disengaging.
|
||||
|
||||
# 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
|
||||
@ -1 +0,0 @@
|
||||
uid://dxngvoommn5f1
|
||||
@ -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(2, 2)
|
||||
|
||||
[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
|
||||
@ -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)
|
||||
@ -1 +0,0 @@
|
||||
uid://dmhwqmbwk0t8k
|
||||
@ -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")
|
||||
@ -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)
|
||||
@ -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="."]
|
||||
@ -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.
|
||||
@ -1 +0,0 @@
|
||||
uid://b4g288mje38nj
|
||||
@ -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)
|
||||
@ -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.
|
||||
@ -1 +0,0 @@
|
||||
uid://crmwm623rh1ps
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -1 +0,0 @@
|
||||
uid://b7f8x2qimvn37
|
||||
@ -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
|
||||
@ -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"
|
||||
@ -1,164 +0,0 @@
|
||||
@tool
|
||||
class_name SystemStation
|
||||
extends Component
|
||||
|
||||
signal occupancy_changed(is_occupied: bool)
|
||||
|
||||
# --- Configuration ---
|
||||
@export var panels: Array[ControlPanel]
|
||||
@export var installed_databanks: Array[Databank]
|
||||
|
||||
# --- State ---
|
||||
var occupants: Array[PilotBall] = []
|
||||
var active_shard_instances: Array[Node] = []
|
||||
# NEW: We now track which panels belong to which occupant
|
||||
var occupant_panel_map: Dictionary = {}
|
||||
|
||||
# --- LIFECYCLE ---
|
||||
|
||||
func _ready():
|
||||
super()
|
||||
# --- FIX: Instantiate and initialize shards ONCE at startup ---
|
||||
# The logic is now persistent and runs as long as the station exists.
|
||||
var root_module = get_root_module()
|
||||
if not is_instance_valid(root_module):
|
||||
push_error("Station could not find its root module!")
|
||||
return
|
||||
|
||||
for shard_resource in installed_databanks:
|
||||
if not shard_resource or not shard_resource.logic_script: continue
|
||||
|
||||
var shard_instance = Node.new()
|
||||
shard_instance.set_script(shard_resource.logic_script)
|
||||
add_child(shard_instance) # Add as a permanent child
|
||||
active_shard_instances.append(shard_instance)
|
||||
|
||||
if shard_instance.has_method("initialize"):
|
||||
shard_instance.initialize(root_module)
|
||||
|
||||
# Future: Connections wbetween shards and other hardware would be made here.
|
||||
|
||||
func _process(_delta):
|
||||
# Check for disengagement for every occupant
|
||||
for character in occupants:
|
||||
# Note: This simple check won't work for multiple local players.
|
||||
# A real implementation would need to check input for a specific player ID.
|
||||
if is_instance_valid(character) and Input.is_action_just_pressed("interact"):
|
||||
disengage(character)
|
||||
return # Avoid modifying the array while iterating
|
||||
|
||||
# --- INTERACTION ---
|
||||
|
||||
func is_occupied() -> bool:
|
||||
return not occupants.is_empty()
|
||||
|
||||
func occupy(character: PilotBall):
|
||||
if character in occupants: return
|
||||
|
||||
occupants.append(character)
|
||||
character.enter_station_state()
|
||||
character.global_position = global_position # Move character to the station
|
||||
|
||||
# --- FIX: Launch UI for THIS character only ---
|
||||
launch_interfaces_for_occupant(character)
|
||||
|
||||
occupancy_changed.emit(true)
|
||||
|
||||
func disengage(character: PilotBall):
|
||||
if not character in occupants: return
|
||||
|
||||
# --- FIX: Close UI for THIS character only ---
|
||||
close_interfaces_for_occupant(character)
|
||||
|
||||
character.exit_station_state()
|
||||
occupants.erase(character)
|
||||
|
||||
if not is_occupied():
|
||||
occupancy_changed.emit(false)
|
||||
|
||||
# --- UI MANAGEMENT ---
|
||||
|
||||
func launch_interfaces_for_occupant(character: PilotBall):
|
||||
var ui_container = character.get_ui_container()
|
||||
print(character)
|
||||
if not ui_container: return
|
||||
|
||||
var panel_instances: Array[Control] = []
|
||||
for panel_resource in panels:
|
||||
if not panel_resource or not panel_resource.ui_scene: continue
|
||||
var panel_instance = panel_resource.ui_scene.instantiate()
|
||||
ui_container.add_child(panel_instance)
|
||||
panel_instance.owner = ui_container
|
||||
panel_instances.append(panel_instance)
|
||||
|
||||
# Store the panels created for this specific user
|
||||
occupant_panel_map[character] = panel_instances
|
||||
|
||||
# --- FIX: Connect the new panels to the PERSISTENT shards ---
|
||||
_connect_internals(panel_instances, active_shard_instances)
|
||||
|
||||
func close_interfaces_for_occupant(character: PilotBall):
|
||||
if occupant_panel_map.has(character):
|
||||
for panel in occupant_panel_map[character]:
|
||||
if is_instance_valid(panel):
|
||||
panel.queue_free()
|
||||
occupant_panel_map.erase(character)
|
||||
|
||||
# --- WIRING LOGIC (Now connects temporary panels to persistent shards) ---
|
||||
|
||||
func _connect_internals(panel_instances: Array, shard_instances: Array):
|
||||
# This logic remains the same, but it's now called with the relevant instances
|
||||
# every time a user sits down.
|
||||
var lever_panel
|
||||
var button_panel
|
||||
var readout_screen
|
||||
var map_panel
|
||||
var sensor_shard: SensorSystemShard
|
||||
var helm_shard: HelmLogicShard
|
||||
var status_shard: ShipStatusShard
|
||||
|
||||
for panel in panel_instances:
|
||||
if panel is ThrottleLeverUI:
|
||||
lever_panel = panel
|
||||
if panel is ButtonPanelUI:
|
||||
button_panel = panel
|
||||
if panel is ReadoutScreenUI:
|
||||
print("Panel is ReadoutScreen: %s" % panel)
|
||||
readout_screen = panel
|
||||
if panel is SensorPanel: # Look for the new map panel class
|
||||
map_panel = panel
|
||||
|
||||
for shard in shard_instances:
|
||||
print(shard)
|
||||
if shard is HelmLogicShard:
|
||||
helm_shard = shard
|
||||
|
||||
if shard is ShipStatusShard:
|
||||
status_shard = shard
|
||||
|
||||
if shard is SensorSystemShard: # Look for the new sensor shard class
|
||||
sensor_shard = shard
|
||||
|
||||
|
||||
if lever_panel and helm_shard:
|
||||
lever_panel.lever_value_changed.connect(helm_shard.set_throttle_input)
|
||||
print("Wired: Lever -> Helm Shard (Throttle)")
|
||||
|
||||
# You would do the same for the button panel, connecting its signals
|
||||
# to a set_rotation_input or similar function on the helm_shard.
|
||||
if button_panel and helm_shard:
|
||||
button_panel.connect("button_1_pressed", func(): helm_shard.set_rotation_input(1.0))
|
||||
button_panel.connect("button_2_pressed", func(): helm_shard.set_rotation_input(-1.0))
|
||||
button_panel.connect("button_3_pressed", helm_shard.shutdown_rcs)
|
||||
button_panel.connect("button_4_pressed", helm_shard.calibrate_rcs_performance)
|
||||
|
||||
if readout_screen and status_shard:
|
||||
print("Wired: Status Shard -> Readout Screen")
|
||||
|
||||
status_shard.connect("status_updated", readout_screen.update_display)
|
||||
|
||||
if map_panel and sensor_shard:
|
||||
# Connect the shard's "sensor_feed_updated" signal (blue wire)
|
||||
# to the map's "update_sensor_feed" socket.
|
||||
sensor_shard.connect("sensor_feed_updated", map_panel.update_sensor_feed)
|
||||
print("Wired: Sensor Shard -> Map Panel (Sensor Feed)")
|
||||
@ -1,20 +0,0 @@
|
||||
extends Control
|
||||
|
||||
## This UI provides three output signals, one for each button.
|
||||
class_name ButtonPanelUI
|
||||
|
||||
signal button_1_pressed
|
||||
signal button_2_pressed
|
||||
signal button_3_pressed
|
||||
signal button_4_pressed
|
||||
|
||||
@onready var RCSPosBtn: Button = $RCSPos
|
||||
@onready var RCSNegBtn: Button = $RCSNeg
|
||||
@onready var ShutdownBtn: Button = $RCSShutdown
|
||||
@onready var CalibrateRcsBtn: Button = $CalibrateRCS
|
||||
|
||||
func _ready():
|
||||
RCSPosBtn.pressed.connect(func(): emit_signal("button_1_pressed"))
|
||||
RCSNegBtn.pressed.connect(func(): emit_signal("button_2_pressed"))
|
||||
ShutdownBtn.pressed.connect(func(): emit_signal("button_3_pressed"))
|
||||
CalibrateRcsBtn.pressed.connect(func(): emit_signal("button_4_pressed"))
|
||||
@ -1,14 +0,0 @@
|
||||
@tool
|
||||
class_name Databank
|
||||
extends Resource
|
||||
|
||||
## The script containing the logic for this shard.
|
||||
@export var logic_script: Script
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Dictionary:
|
||||
return {}
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_signals() -> Dictionary:
|
||||
return {}
|
||||
@ -1,217 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name HelmLogicShard
|
||||
|
||||
# --- References ---
|
||||
var root_module: Module
|
||||
var thrusters: Array[Thruster] = []
|
||||
|
||||
# --- Ship Performance Data (calibrated) ---
|
||||
var max_positive_torque: float = 1.0
|
||||
var max_negative_torque: float = 1.0
|
||||
|
||||
# --- PD Controller Constants ---
|
||||
@export var HOLD_KP: float = 8000.0 # Proportional gain
|
||||
@export var HOLD_KD: float = 1200.0 # Derivative gain
|
||||
|
||||
var target_rotation_rad: float = 0.0
|
||||
var attitude_hold_enabled: bool = false
|
||||
|
||||
# The Station calls this after instantiating the shard.
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
if not is_instance_valid(root_module):
|
||||
push_error("Helm Shard initialized without a valid ship root!")
|
||||
return
|
||||
|
||||
thrusters = _find_all_thrusters(root_module)
|
||||
# You can add logic here to listen for parts being added/removed to re-scan.
|
||||
|
||||
# Default to holding the initial attitude.
|
||||
target_rotation_rad = root_module.rotation
|
||||
|
||||
|
||||
func _physics_process(_delta):
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# If attitude hold is on, run the PD controller.
|
||||
if attitude_hold_enabled:
|
||||
_perform_manual_hold()
|
||||
|
||||
# --- INPUT SOCKETS (Called by Panels or other Shards) ---
|
||||
|
||||
## This is an "input socket" for rotational control.
|
||||
## It takes a value from -1.0 to 1.0.
|
||||
func set_rotation_input(value: float):
|
||||
if abs(value) > 0.1:
|
||||
# Manual input overrides attitude hold.
|
||||
attitude_hold_enabled = false
|
||||
var desired_torque = (max_positive_torque if value > 0 else max_negative_torque) * value
|
||||
apply_rotational_thrust(desired_torque)
|
||||
else:
|
||||
# When input stops, re-engage hold at the current rotation.
|
||||
if not attitude_hold_enabled:
|
||||
attitude_hold_enabled = true
|
||||
target_rotation_rad = root_module.rotation
|
||||
|
||||
## This is an "input socket" for translational control (main thrusters).
|
||||
## It takes a value from 0.0 to 1.0.
|
||||
# --- REFACTORED: This is the key change ---
|
||||
func set_throttle_input(value: float):
|
||||
# This function now works with the simple on/off thrusters.
|
||||
for thruster in thrusters:
|
||||
if thruster.main_thruster:
|
||||
if value > 0.1:
|
||||
thruster.turn_on()
|
||||
else:
|
||||
thruster.turn_off()
|
||||
|
||||
# --- LOGIC (Migrated from ThrusterController.gd) ---
|
||||
|
||||
func _perform_manual_hold():
|
||||
var error = shortest_angle_between(root_module.rotation, target_rotation_rad)
|
||||
var desired_torque = (error * HOLD_KP) - (root_module.angular_velocity * HOLD_KD)
|
||||
|
||||
apply_rotational_thrust(desired_torque)
|
||||
|
||||
# --- REFACTORED: This is the other key change ---
|
||||
func apply_rotational_thrust(desired_torque: float):
|
||||
if not is_instance_valid(root_module):
|
||||
return
|
||||
|
||||
# Iterate through all available RCS thrusters that have been calibrated
|
||||
for thruster in thruster_data_map:
|
||||
var thruster_data: ThrusterData = thruster_data_map[thruster]
|
||||
|
||||
# If this thruster can help apply the desired torque, turn it on.
|
||||
# Otherwise, explicitly turn it off to ensure it's not firing incorrectly.
|
||||
if sign(thruster_data.measured_torque) == sign(desired_torque) and desired_torque != 0:
|
||||
thruster.turn_on()
|
||||
else:
|
||||
thruster.turn_off()
|
||||
|
||||
func shutdown_rcs():
|
||||
for thruster in thrusters:
|
||||
if not thruster.main_thruster:
|
||||
thruster.turn_off()
|
||||
|
||||
func _find_all_thrusters(node: Node) -> Array[Thruster]:
|
||||
var thrusters: Array[Thruster] = []
|
||||
for child in node.get_children():
|
||||
if child is Thruster:
|
||||
thrusters.append(child)
|
||||
if child.get_child_count() > 0:
|
||||
thrusters.append_array(_find_all_thrusters(child))
|
||||
|
||||
return thrusters
|
||||
|
||||
|
||||
func shortest_angle_between(from_angle: float, to_angle: float) -> float:
|
||||
var difference = fposmod(to_angle - from_angle, TAU)
|
||||
if difference > PI:
|
||||
return difference - TAU
|
||||
else:
|
||||
return difference
|
||||
|
||||
|
||||
var thruster_data_map: Dictionary = {}
|
||||
|
||||
# --- CALIBRATION LOGIC (Migrated from ThrusterController.gd) ---
|
||||
|
||||
# Inner class to store calibrated data for each thruster
|
||||
class ThrusterData:
|
||||
var thruster_node: Thruster
|
||||
var measured_torque: float # The rotational force it provides
|
||||
|
||||
## Manages the calibration sequence for all non-main thrusters.
|
||||
func calibrate_rcs_performance():
|
||||
print("Helm Shard: Beginning RCS calibration protocol...")
|
||||
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# --- THE FIX: Disable attitude hold during calibration ---
|
||||
var original_attitude_hold_state = attitude_hold_enabled
|
||||
attitude_hold_enabled = false
|
||||
shutdown_rcs() # Ensure all thrusters are off before we start
|
||||
await get_tree().physics_frame
|
||||
|
||||
print("Helm Shard: Attitude hold protocol: %s" % ("enabled" if attitude_hold_enabled else "disabled"))
|
||||
|
||||
thruster_data_map.clear()
|
||||
|
||||
for thruster in thrusters:
|
||||
if thruster.main_thruster: continue # Skip main engines
|
||||
|
||||
var data: ThrusterData = await _calibrate_single_thruster(thruster)
|
||||
thruster_data_map[thruster] = data
|
||||
print(" - Calibrated %s: Torque(%.3f)" % [thruster.name, data.measured_torque])
|
||||
|
||||
print(thruster_data_map)
|
||||
# Now that we have the data, calculate the ship's max torque values
|
||||
max_positive_torque = 0.0
|
||||
max_negative_torque = 0.0
|
||||
for data in thruster_data_map.values():
|
||||
if data.measured_torque > 0:
|
||||
max_positive_torque += data.measured_torque
|
||||
else:
|
||||
max_negative_torque += abs(data.measured_torque)
|
||||
|
||||
print("RCS Calibration Complete: Max Pos Torque: %.2f, Max Neg Torque: %.2f" % [max_positive_torque, max_negative_torque])
|
||||
|
||||
# Auto-tune the PD controller with the new values
|
||||
if max_positive_torque > 0 and max_negative_torque > 0:
|
||||
var average_max_torque = (max_positive_torque + max_negative_torque) / 2.0
|
||||
HOLD_KP = average_max_torque * 0.1
|
||||
HOLD_KD = HOLD_KP * 1 # You can tune this multiplier
|
||||
print("PD Controller Auto-Tuned: Kp set to %.2f, Kd set to %.2f" % [HOLD_KP, HOLD_KD])
|
||||
|
||||
attitude_hold_enabled = original_attitude_hold_state
|
||||
print("Helm Shard: Calibration complete. Attitude hold is now %s." % ("enabled" if attitude_hold_enabled else "disabled"))
|
||||
|
||||
## Performs a test fire of a single thruster and measures the resulting change in angular velocity.
|
||||
func _calibrate_single_thruster(thruster: Thruster) -> ThrusterData:
|
||||
var data = ThrusterData.new()
|
||||
data.thruster_node = thruster
|
||||
|
||||
# Prepare for test: save initial state
|
||||
var initial_angular_velocity = root_module.angular_velocity
|
||||
|
||||
var test_burn_duration = 0.5 # A very short burst
|
||||
|
||||
# --- Perform Test Fire ---
|
||||
thruster.turn_on()
|
||||
await get_tree().create_timer(test_burn_duration).timeout
|
||||
thruster.turn_off()
|
||||
|
||||
# Let the physics engine settle for one frame to ensure the velocity update is complete
|
||||
await get_tree().physics_frame
|
||||
|
||||
# --- Measure Results ---
|
||||
var delta_angular_velocity = root_module.angular_velocity - initial_angular_velocity
|
||||
|
||||
# --- Calculate Performance ---
|
||||
# Torque = inertia * angular_acceleration (alpha = dw/dt)
|
||||
if root_module.inertia > 0:
|
||||
data.measured_torque = root_module.inertia * (delta_angular_velocity / test_burn_duration)
|
||||
else:
|
||||
data.measured_torque = 0.0
|
||||
push_warning("Root module inertia is 0. Cannot calibrate torque.")
|
||||
|
||||
# --- Cleanup: Counter the spin from the test fire ---
|
||||
if abs(data.measured_torque) > 0.001:
|
||||
var counter_torque = -data.measured_torque
|
||||
var counter_burn_duration = (root_module.inertia * root_module.angular_velocity) / counter_torque
|
||||
|
||||
# Find a thruster that can apply the counter-torque
|
||||
for other_thruster in thrusters:
|
||||
var other_data = thruster_data_map.get(other_thruster)
|
||||
if other_data and sign(other_data.measured_torque) == sign(counter_torque):
|
||||
other_thruster.turn_on()
|
||||
await get_tree().create_timer(abs(counter_burn_duration)).timeout
|
||||
other_thruster.turn_off()
|
||||
break # Use the first one we find
|
||||
|
||||
await get_tree().physics_frame
|
||||
root_module.angular_velocity = 0 # Final reset for safety
|
||||
|
||||
return data
|
||||
@ -1,31 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name ShipStatusShard
|
||||
|
||||
## This shard emits a signal with the formatted ship status text.
|
||||
signal status_updated(text: String)
|
||||
|
||||
var root_module: Module
|
||||
|
||||
# Called by the Station when it's created.
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
|
||||
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.: %.3f deg/s[/font_size]
|
||||
[font_size=18]Velocity: %.2f m/s[/font_size]
|
||||
""" % [rotation_deg, angular_vel_dps, linear_vel_mps]
|
||||
|
||||
# 3. Emit the signal with the formatted text.
|
||||
status_updated.emit(status_text)
|
||||
@ -1,12 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name NavLogicShard
|
||||
|
||||
# --- INPUT SOCKETS ---
|
||||
# This function will be connected to the map's "body_selected_for_planning" signal.
|
||||
func on_body_selected(body: Node2D):
|
||||
if not is_instance_valid(body): return
|
||||
|
||||
print("NAV SHARD: Target selected - ", body.name)
|
||||
# Later, this is where we would enable maneuver calculation buttons
|
||||
# and feed this target data to other datashards.
|
||||
@ -1,12 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name SensorSystemShard
|
||||
|
||||
## This shard emits all trackable bodies as a "sensor feed" every frame.
|
||||
signal sensor_feed_updated(bodies: Array)
|
||||
|
||||
func _physics_process(delta):
|
||||
# In a more advanced game, this shard might have its own power requirements
|
||||
# or could be affected by radiation, etc. For now, it just gets all bodies.
|
||||
var all_bodies = GameManager.get_all_trackable_bodies()
|
||||
sensor_feed_updated.emit(all_bodies)
|
||||
@ -1,18 +0,0 @@
|
||||
class_name ShipSignalBus
|
||||
extends Node
|
||||
|
||||
# --- Navigation & Maneuvering Events ---
|
||||
# Emitted when a maneuver plan is calculated.
|
||||
signal maneuver_planned(plan)
|
||||
|
||||
# Emitted to command the start of a timed rotation.
|
||||
signal timed_rotation_commanded(target_rotation_rad, time_window)
|
||||
|
||||
# Emitted to command the start of a timed main engine burn.
|
||||
signal timed_burn_commanded(duration)
|
||||
|
||||
# --- Thruster & Ship Status Events ---
|
||||
# Emitted when the main engine starts or stops firing.
|
||||
signal main_engine_state_changed(is_firing: bool, total_thrust: float)
|
||||
# Emitted when RCS thrusters are fired.
|
||||
signal rcs_state_changed(is_firing: bool, torque: float)
|
||||
@ -1 +0,0 @@
|
||||
uid://w1546qtaupd2
|
||||
@ -1,85 +0,0 @@
|
||||
# Spaceship.gd
|
||||
class_name Spaceship
|
||||
extends OrbitalBody2D
|
||||
|
||||
@export var ship_name: String = "Stardust Drifter"
|
||||
@export var dry_mass: float = 1000.0 # Mass of the ship without fuel/cargo (in kg)
|
||||
@export var hull_integrity: float = 100.0
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
|
||||
@export_category("Camera")
|
||||
@export var zoom_speed: float = 1.0
|
||||
var current_time_scale: float = Engine.time_scale
|
||||
|
||||
# --- Node References to Modular Systems ---
|
||||
@onready var signal_bus: ShipSignalBus = $SignalBus
|
||||
@onready var thruster_controller: ThrusterController = $ThrusterController
|
||||
@onready var fuel_system = $FuelSystem
|
||||
@onready var life_support = $LifeSupport
|
||||
@onready var navigation_computer = $NavigationComputer
|
||||
# @onready var weapon_system = $WeaponSystem
|
||||
# @onready var power_grid = $PowerGrid
|
||||
|
||||
func _ready() -> void:
|
||||
super()
|
||||
GameManager.register_ship(self)
|
||||
|
||||
# Give the navigation computer a reference to this ship
|
||||
if navigation_computer:
|
||||
navigation_computer.ship = self
|
||||
|
||||
camera.make_current()
|
||||
|
||||
# This function will now handle all non-UI input for the player-controlled ship.
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
# --- Map Toggling ---
|
||||
if event.is_action_pressed("ui_map_mode"):
|
||||
# Instead of showing/hiding a node directly, we broadcast our intent.
|
||||
# The NavigationComputer will be listening for this global signal.
|
||||
SignalBus.emit_signal("map_mode_toggled")
|
||||
|
||||
# --- Time Scale Controls ---
|
||||
if event.is_action_pressed("time_increase"):
|
||||
var new_value = min(current_time_scale * 1.2, 1000)
|
||||
current_time_scale = clamp(new_value, 0.5, 1000)
|
||||
Engine.time_scale = current_time_scale
|
||||
|
||||
elif event.is_action_pressed("time_decrease"):
|
||||
var new_value = max(current_time_scale * 0.833, 0.1)
|
||||
current_time_scale = clamp(new_value, 0.5, 1000)
|
||||
Engine.time_scale = current_time_scale
|
||||
|
||||
elif event.is_action_pressed("time_reset"):
|
||||
Engine.time_scale = 1.0
|
||||
|
||||
# --- Public API for Ship Management ---
|
||||
# Call this to take damage. Damage can have a position for breach effects.
|
||||
func take_damage(amount: float, damage_position: Vector2):
|
||||
hull_integrity -= amount
|
||||
print("%s hull integrity at %.1f%%" % [ship_name, hull_integrity])
|
||||
|
||||
if hull_integrity <= 0:
|
||||
destroy_ship()
|
||||
else:
|
||||
# Check if the hit caused a hull breach
|
||||
life_support.check_for_breach(damage_position)
|
||||
|
||||
func destroy_ship():
|
||||
print("%s has been destroyed!" % ship_name)
|
||||
queue_free()
|
||||
|
||||
# --- Signal Handlers ---
|
||||
#func _on_fuel_mass_changed():
|
||||
# Update the ship's total mass when fuel is consumed or added
|
||||
#update_total_mass()
|
||||
|
||||
func _on_hull_breach(breach_position: Vector2, force_vector: Vector2):
|
||||
pass
|
||||
# A hull breach applies a continuous force at a specific point
|
||||
# For simplicity, we can apply it as a central force and torque here
|
||||
#var force = force_vector * 100 # Scale the force
|
||||
#
|
||||
## Calculate torque: Torque = r x F (cross product of position vector and force)
|
||||
#var position_relative_to_center = breach_position - self.global_position
|
||||
#var torque = position_relative_to_center.cross(force)
|
||||
@ -1 +0,0 @@
|
||||
uid://dyqbk4lcx3mhq
|
||||
@ -1,63 +0,0 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://dlck1lyrn1xvp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dyqbk4lcx3mhq" path="res://scenes/ship/spaceship.gd" id="1_ae4p7"]
|
||||
[ext_resource type="Script" uid="uid://c0bx113ifxyh8" path="res://scenes/ship/systems/thruster_controller.gd" id="2_xs8u7"]
|
||||
[ext_resource type="PackedScene" uid="uid://c77wxeb7gpplw" path="res://scenes/modules/test_module.tscn" id="2_y58gg"]
|
||||
[ext_resource type="Script" uid="uid://dx3uerblskj5r" path="res://scenes/ship/systems/fuel_system.gd" id="3_xs8u7"]
|
||||
[ext_resource type="Script" uid="uid://buyp6t5cppitw" path="res://scenes/ship/systems/life_support.gd" id="4_v0rat"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxpjm8tp3l1j7" path="res://scenes/ship/systems/navigation_computer.tscn" id="5_6nyhl"]
|
||||
[ext_resource type="Script" uid="uid://w1546qtaupd2" path="res://scenes/ship/ship_signal_bus.gd" id="7_yl4tl"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0bb77rmyatr0" path="res://scenes/ship/components/hardware/thruster.tscn" id="8_oedjh"]
|
||||
|
||||
[node name="Spaceship" type="Node2D"]
|
||||
script = ExtResource("1_ae4p7")
|
||||
base_mass = 2000.0
|
||||
inertia = 500.0
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="Module" parent="." instance=ExtResource("2_y58gg")]
|
||||
|
||||
[node name="FuelSystem" type="Node" parent="."]
|
||||
script = ExtResource("3_xs8u7")
|
||||
|
||||
[node name="LifeSupport" type="Node" parent="."]
|
||||
script = ExtResource("4_v0rat")
|
||||
|
||||
[node name="NavigationComputer" parent="." instance=ExtResource("5_6nyhl")]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
|
||||
[node name="ThrusterController" type="Node2D" parent="."]
|
||||
script = ExtResource("2_xs8u7")
|
||||
|
||||
[node name="RCS1" parent="." instance=ExtResource("8_oedjh")]
|
||||
position = Vector2(-125, 200)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
inertia = 0.0
|
||||
|
||||
[node name="RCS2" parent="." instance=ExtResource("8_oedjh")]
|
||||
position = Vector2(-125, -200)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
inertia = 0.0
|
||||
|
||||
[node name="RCS3" parent="." instance=ExtResource("8_oedjh")]
|
||||
position = Vector2(125, -200)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
inertia = 0.0
|
||||
|
||||
[node name="RCS4" parent="." instance=ExtResource("8_oedjh")]
|
||||
position = Vector2(125, 200)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
inertia = 0.0
|
||||
|
||||
[node name="MainEngine" parent="." instance=ExtResource("8_oedjh")]
|
||||
position = Vector2(-1, 226)
|
||||
max_thrust = 100.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="SignalBus" type="Node" parent="."]
|
||||
script = ExtResource("7_yl4tl")
|
||||
@ -1,184 +0,0 @@
|
||||
# space_simulation/scripts/map_controller.gd
|
||||
class_name MapController
|
||||
extends Control
|
||||
|
||||
signal body_selected_for_planning(body: RigidBody2D)
|
||||
|
||||
@export var map_icon_scene: PackedScene
|
||||
|
||||
const LABEL_CULLING_PIXEL_THRESHOLD = 65.0
|
||||
const ICON_CULLING_PIXEL_THRESHOLD = 40.0
|
||||
|
||||
var map_scale: float = 0.001
|
||||
var map_offset: Vector2 = Vector2.ZERO
|
||||
var focal_body: RigidBody2D
|
||||
|
||||
var icon_map: Dictionary = {}
|
||||
|
||||
var followed_body: RigidBody2D = null
|
||||
var map_tween: Tween
|
||||
|
||||
# The starting point for our lerp animation.
|
||||
var follow_start_offset: Vector2
|
||||
# The progress of the follow animation (0.0 to 1.0), animated by a tween.
|
||||
var follow_progress: float = 0.0:
|
||||
set(value):
|
||||
follow_progress = value
|
||||
# We must redraw every time the progress changes.
|
||||
queue_redraw()
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().physics_frame
|
||||
var star_system = GameManager.current_star_system
|
||||
if is_instance_valid(star_system):
|
||||
focal_body = star_system.get_system_data().star
|
||||
_populate_map()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_update_icon_positions()
|
||||
|
||||
func _draw() -> void:
|
||||
var map_center = get_rect().size / 2.0
|
||||
|
||||
var system_data = GameManager.get_system_data()
|
||||
if system_data and system_data.belts:
|
||||
for belt in system_data.belts:
|
||||
var radius = belt.centered_radius * map_scale
|
||||
draw_circle(map_center + map_offset, radius, Color(Color.WHITE, 0.1), false)
|
||||
|
||||
for body in icon_map:
|
||||
if body is Asteroid: continue
|
||||
var icon = icon_map[body]
|
||||
if not icon.visible: continue
|
||||
|
||||
var path_points = []
|
||||
if body is CelestialBody: path_points = OrbitalMechanics._calculate_relative_orbital_path(body)
|
||||
elif body is OrbitalBody2D: path_points = OrbitalMechanics._calculate_n_body_orbital_path(body)
|
||||
else: continue
|
||||
var scaled_path_points = PackedVector2Array()
|
||||
|
||||
for point in path_points:
|
||||
# Ensure path is drawn relative to the main focal body (the star)
|
||||
var path_world_pos = point + focal_body.global_position
|
||||
var relative_pos = path_world_pos - focal_body.global_position
|
||||
var scaled_pos = (relative_pos * map_scale) + map_offset + map_center
|
||||
scaled_path_points.append(scaled_pos)
|
||||
|
||||
if scaled_path_points.size() > 1:
|
||||
draw_polyline(scaled_path_points, Color(Color.WHITE, 0.2), 1.0, true)
|
||||
|
||||
func _populate_map():
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
icon_map.clear()
|
||||
|
||||
var all_bodies = GameManager.get_all_trackable_bodies()
|
||||
for body in all_bodies:
|
||||
if not is_instance_valid(body): continue
|
||||
var icon = map_icon_scene.instantiate() as MapIcon
|
||||
add_child(icon)
|
||||
icon.initialize(body)
|
||||
icon_map[body] = icon
|
||||
icon.selected.connect(_on_map_icon_selected)
|
||||
icon.follow_requested.connect(_on_follow_requested)
|
||||
|
||||
func _update_icon_positions():
|
||||
if not is_instance_valid(focal_body): return
|
||||
|
||||
var map_center = get_rect().size / 2.0
|
||||
|
||||
# --- MODIFIED: Continuous follow logic ---
|
||||
if is_instance_valid(followed_body):
|
||||
# Calculate the ideal offset to center the followed body.
|
||||
var relative_target_pos = followed_body.global_position - focal_body.global_position
|
||||
var target_offset = -relative_target_pos * map_scale
|
||||
|
||||
# During the initial pan, interpolate from the start to the target.
|
||||
# When follow_progress reaches 1.0, this just becomes target_offset.
|
||||
map_offset = follow_start_offset.lerp(target_offset, follow_progress)
|
||||
|
||||
# It will now use the dynamically updated map_offset.
|
||||
var icon_data_for_frame = []
|
||||
for body in icon_map:
|
||||
var icon = icon_map[body]
|
||||
|
||||
icon.visible = true
|
||||
icon.name_label.visible = true
|
||||
|
||||
var relative_pos = body.global_position - focal_body.global_position
|
||||
var final_screen_pos = (relative_pos * map_scale) + map_offset + map_center
|
||||
|
||||
icon_data_for_frame.append({
|
||||
"screen_pos": final_screen_pos,
|
||||
"body": body,
|
||||
"icon": icon
|
||||
})
|
||||
|
||||
icon.position = final_screen_pos - (icon.size / 2)
|
||||
|
||||
for i in range(icon_data_for_frame.size()):
|
||||
var data_a = icon_data_for_frame[i]
|
||||
|
||||
if not data_a.icon.visible:
|
||||
continue
|
||||
|
||||
for j in range(i + 1, icon_data_for_frame.size()):
|
||||
var data_b = icon_data_for_frame[j]
|
||||
if not data_b.icon.visible: continue
|
||||
|
||||
var distance = data_a.screen_pos.distance_to(data_b.screen_pos)
|
||||
|
||||
if distance < ICON_CULLING_PIXEL_THRESHOLD:
|
||||
if data_a.body.mass > data_b.body.mass:
|
||||
data_b.icon.visible = false
|
||||
else:
|
||||
data_a.icon.visible = false
|
||||
elif distance < LABEL_CULLING_PIXEL_THRESHOLD:
|
||||
if data_a.body.mass > data_b.body.mass:
|
||||
data_b.icon.name_label.visible = false
|
||||
else:
|
||||
data_a.icon.name_label.visible = false
|
||||
|
||||
# Request a redraw at the end of the update
|
||||
queue_redraw()
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP or event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
var zoom_factor = 1.25 if event.button_index == MOUSE_BUTTON_WHEEL_UP else 1 / 1.25
|
||||
var mouse_pos = get_local_mouse_position()
|
||||
var map_center = get_rect().size / 2.0
|
||||
|
||||
var point_under_mouse_world = (mouse_pos - map_center - map_offset) / map_scale
|
||||
map_scale *= zoom_factor
|
||||
var point_under_mouse_new_screen = (point_under_mouse_world * map_scale) + map_center + map_offset
|
||||
map_offset += mouse_pos - point_under_mouse_new_screen
|
||||
|
||||
if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_LEFT:
|
||||
# --- MODIFIED: Break the lock and reset progress ---
|
||||
if is_instance_valid(followed_body):
|
||||
print("Map lock disengaged by manual pan.")
|
||||
followed_body = null
|
||||
follow_progress = 0.0 # Reset progress
|
||||
if map_tween:
|
||||
map_tween.kill()
|
||||
|
||||
map_offset += event.relative
|
||||
|
||||
func _on_map_icon_selected(body: RigidBody2D):
|
||||
emit_signal("body_selected_for_planning", body)
|
||||
|
||||
func _on_follow_requested(body: RigidBody2D):
|
||||
print("Map view locking on to: ", body.name)
|
||||
follow_progress = 0.0
|
||||
followed_body = body
|
||||
|
||||
if map_tween:
|
||||
map_tween.kill()
|
||||
|
||||
# Store the offset at the exact moment the follow begins.
|
||||
follow_start_offset = map_offset
|
||||
|
||||
# --- REVISED: We now tween the 'follow_progress' property instead of 'map_offset' ---
|
||||
map_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
|
||||
map_tween.tween_property(self, "follow_progress", 1.0, 1.5)
|
||||
@ -1 +0,0 @@
|
||||
uid://b74hxlsox8ldo
|
||||
@ -1,402 +0,0 @@
|
||||
# NavigationComputer.gd
|
||||
extends Node
|
||||
|
||||
@onready var ship: Spaceship = %Spaceship
|
||||
var ship_signal_bus: ShipSignalBus
|
||||
|
||||
# --- Node References ---
|
||||
@onready var navigation_ui: CanvasLayer = %NavigationUI
|
||||
@onready var map_controller: MapController = %MapController
|
||||
@onready var target_label: Label = %TargetLabel
|
||||
@onready var info_label: Label = %InfoLabel
|
||||
@onready var ship_status_label: Label = %ShipStatusLabel
|
||||
@onready var torque_indicator: ColorRect = %TorqueIndicator
|
||||
@onready var sub_viewport: SubViewport = %SubViewport
|
||||
@onready var ship_view_camera: Camera2D = %ShipViewCamera # Add this reference
|
||||
|
||||
# Buttons for different maneuvers
|
||||
@onready var plan_hohmann_button: Button = %PlanHohmannButton
|
||||
@onready var plan_fast_button: Button = %PlanFastButton
|
||||
@onready var plan_torchship_button: Button = %PlanTorchshipButton
|
||||
@onready var plan_gravity_assist_button: Button = %PlanGravityAssistButton
|
||||
@onready var execute_plan_button: Button = %ExecutePlanButton
|
||||
|
||||
# How many seconds before a burn we should start orienting the ship.
|
||||
const PRE_BURN_ORIENTATION_SECONDS = 30.0 # Give a larger window for the new logic
|
||||
const ROTATION_SAFETY_BUFFER = 10.0 # Seconds to ensure rotation finishes before burn
|
||||
|
||||
# A flag to make sure we only send the signal once per maneuver
|
||||
var rotation_plan_sent = false
|
||||
|
||||
# --- State Management ---
|
||||
enum State { IDLE, PLANNING, WAITING, EXECUTING }
|
||||
var current_state: State = State.IDLE
|
||||
|
||||
# --- Navigation Data ---
|
||||
var source_body: CelestialBody
|
||||
var target_body: CelestialBody
|
||||
|
||||
var current_plan # Can be an array of ImpulsiveBurn or a ContinuousBurnPlan
|
||||
|
||||
# --- Inner classes to hold maneuver data ---
|
||||
class ImpulsiveBurn:
|
||||
var delta_v_magnitude: float
|
||||
var wait_time: float = 0.0
|
||||
var burn_duration: float
|
||||
var desired_rotation_rad: float # The world rotation the ship needs to be in
|
||||
|
||||
class ContinuousBurnPlan:
|
||||
var total_travel_time: float
|
||||
var acceleration_time: float
|
||||
var initial_burn_direction: Vector2 # The world-space direction vector for the burn
|
||||
|
||||
func _ready() -> void:
|
||||
# tell our SubViewport to render the same world as main viewport.
|
||||
if is_instance_valid(sub_viewport):
|
||||
print("NAV COMP: Sub viewport found")
|
||||
sub_viewport.world_2d = get_viewport().world_2d
|
||||
else:
|
||||
print("NAV COMP: Sub viewport not found")
|
||||
|
||||
# Connect to the global signal from the SignalBus
|
||||
SignalBus.map_mode_toggled.connect(on_map_mode_toggled)
|
||||
|
||||
if is_instance_valid(ship):
|
||||
ship_signal_bus = ship.signal_bus
|
||||
|
||||
# Ensure the UI starts hidden
|
||||
if navigation_ui:
|
||||
navigation_ui.hide()
|
||||
|
||||
# Connect UI signals
|
||||
map_controller.body_selected_for_planning.connect(_on_target_selected)
|
||||
plan_hohmann_button.pressed.connect(_on_plan_hohmann_pressed)
|
||||
plan_fast_button.pressed.connect(_on_plan_fast_pressed)
|
||||
plan_torchship_button.pressed.connect(_on_plan_torchship_pressed)
|
||||
plan_gravity_assist_button.pressed.connect(_on_plan_gravity_assist_pressed)
|
||||
execute_plan_button.pressed.connect(_on_execute_plan_pressed)
|
||||
|
||||
ship_status_label.text = ""
|
||||
|
||||
update_ui()
|
||||
|
||||
# Add a background to the UIm
|
||||
_setup_background()
|
||||
|
||||
|
||||
func _setup_background():
|
||||
# --- FIX #1: Add a black, opaque background ---
|
||||
var bg = ColorRect.new()
|
||||
bg.color = Color.BLACK
|
||||
# Add it as the very first child so it's behind everything else.
|
||||
navigation_ui.add_child(bg)
|
||||
navigation_ui.move_child(bg, 0)
|
||||
# Make the background cover the entire screen.
|
||||
bg.anchor_right = 1.0
|
||||
bg.anchor_bottom = 1.0
|
||||
|
||||
# This function is called whenever any node in the game emits the "map_mode_toggled" signal.
|
||||
func on_map_mode_toggled():
|
||||
if navigation_ui:
|
||||
# Toggle the visibility of the UI screen
|
||||
navigation_ui.visible = not navigation_ui.visible
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# This ensures the viewport camera always mirrors the ship's main camera.
|
||||
if is_instance_valid(ship) and is_instance_valid(ship.camera):
|
||||
if is_instance_valid(ship_view_camera):
|
||||
ship_view_camera.global_transform = ship.camera.global_transform
|
||||
ship_view_camera.zoom = ship.camera.zoom
|
||||
|
||||
_update_ship_status_label()
|
||||
|
||||
if current_state == State.PLANNING and current_plan:
|
||||
if current_plan is Array and not current_plan.is_empty():
|
||||
var first_burn = current_plan[0]
|
||||
# The plan is not locked in, but we can see the window approaching.
|
||||
first_burn.wait_time -= delta
|
||||
var time_str = _format_seconds_to_mmss(first_burn.wait_time)
|
||||
info_label.text = "Optimal window in: %s.\nPress Execute to lock in plan." % time_str
|
||||
if first_burn.wait_time < 0:
|
||||
# If the window is missed during planning, mark the plan as stale.
|
||||
info_label.text = "Transfer window missed. Please plan a new maneuver."
|
||||
current_plan = null
|
||||
update_ui()
|
||||
|
||||
if current_state == State.WAITING and current_plan:
|
||||
if current_plan is Array and not current_plan.is_empty():
|
||||
var next_burn: ImpulsiveBurn = current_plan[0]
|
||||
next_burn.wait_time -= delta
|
||||
var time_str = _format_seconds_to_mmss(next_burn.wait_time)
|
||||
info_label.text = "Time to burn: %s" % time_str
|
||||
|
||||
# --- NEW: Emit the rotation plan ---
|
||||
# When we are inside the orientation window, and haven't sent the plan yet...
|
||||
if not rotation_plan_sent:
|
||||
# The time allowed for rotation is the time we have left, minus a safety buffer.
|
||||
var time_for_rotation = next_burn.wait_time - ROTATION_SAFETY_BUFFER
|
||||
|
||||
# Tell the thruster controller to handle it.
|
||||
ship.signal_bus.emit_signal("timed_rotation_commanded", next_burn.desired_rotation_rad, time_for_rotation)
|
||||
|
||||
rotation_plan_sent = true # Mark the plan as sent
|
||||
|
||||
if next_burn.wait_time <= 0:
|
||||
_execute_maneuver()
|
||||
|
||||
# The EXECUTING state would be handled by a dedicated autopilot script/node
|
||||
|
||||
func update_ui():
|
||||
execute_plan_button.disabled = (current_plan == null or current_state != State.PLANNING)
|
||||
|
||||
if current_state == State.PLANNING:
|
||||
# Show available plans based on engine type
|
||||
|
||||
# TODO change UI to show recommendations based on thruster type
|
||||
#if installed_engine is ChemicalThruster:
|
||||
plan_hohmann_button.show()
|
||||
plan_fast_button.show()
|
||||
plan_gravity_assist_button.show()
|
||||
#elif installed_engine is IonDrive:
|
||||
plan_torchship_button.show()
|
||||
|
||||
# --- Signal Handlers ---
|
||||
|
||||
func _on_target_selected(body: CelestialBody):
|
||||
if current_state == State.IDLE or current_state == State.PLANNING:
|
||||
target_body = body
|
||||
|
||||
# Assume ship is orbiting the target's primary (e.g., the star)
|
||||
current_plan = null
|
||||
current_state = State.PLANNING
|
||||
target_label.text = "Target: %s." % target_body.name
|
||||
info_label.text = "Select a maneuver."
|
||||
update_ui()
|
||||
|
||||
func _on_execute_plan_pressed():
|
||||
if current_plan:
|
||||
current_state = State.WAITING
|
||||
update_ui()
|
||||
|
||||
# --- Planning Function Calls ---
|
||||
func _on_plan_hohmann_pressed():
|
||||
current_plan = _calculate_hohmann_transfer(source_body, target_body)
|
||||
# TODO: map_controller.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_fast_pressed():
|
||||
# For simplicity, a "fast" transfer is a Hohmann with a 25% larger transfer orbit
|
||||
current_plan = _calculate_hohmann_transfer(source_body, target_body, 1.25)
|
||||
# TODO: map_controller.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_torchship_pressed():
|
||||
current_plan = _calculate_brachistochrone_transfer()
|
||||
# TODO: map_controller.draw_planned_trajectory(...)
|
||||
update_ui()
|
||||
|
||||
func _on_plan_gravity_assist_pressed():
|
||||
info_label.text = "Gravity Assist calculation is highly complex and not yet implemented."
|
||||
print("Placeholder for Gravity Assist logic.")
|
||||
|
||||
# --- Calculation and Execution ---
|
||||
func _calculate_hohmann_transfer(source_planet: CelestialBody, target_planet: CelestialBody, transfer_boost_factor: float = 1.0) -> Array:
|
||||
# Get the central star safely from the GameManager.
|
||||
#var ship_current_primary = ship.primary
|
||||
var star = GameManager.get_system_data().star
|
||||
if not is_instance_valid(star):
|
||||
print("Hohmann Error: Could not find star in GameManager.")
|
||||
return []
|
||||
|
||||
# This maneuver requires the ship and target to orbit the same star.
|
||||
if not target_planet:
|
||||
info_label.text = "Invalid Transfer: No target object for intersect."
|
||||
return []
|
||||
|
||||
# mu (μ): The Standard Gravitational Parameter of the star. It's G * M, a constant that simplifies calculations.
|
||||
var mu = OrbitalMechanics.G * star.mass
|
||||
|
||||
# r1: The ship's current orbital radius (distance from the star).
|
||||
var r1 = ship.global_position.distance_to(star.global_position)
|
||||
# r2: The target planet's orbital radius.
|
||||
var r2 = target_planet.global_position.distance_to(star.global_position)
|
||||
|
||||
# --- Hohmann Transfer Calculations ---
|
||||
|
||||
# v_source_orbit: The ship's current circular orbital speed.
|
||||
var v_source_orbit = sqrt(mu / r1)
|
||||
# v_target_orbit: The target planet's circular orbital speed.
|
||||
var v_target_orbit = sqrt(mu / r2)
|
||||
|
||||
# a_transfer: The semi-major axis (average radius) of the elliptical transfer orbit.
|
||||
var a_transfer = (r1 + r2) / 2.0 * transfer_boost_factor
|
||||
|
||||
# v_transfer_periapsis: The required speed at the start of the transfer (periapsis) to get onto the ellipse.
|
||||
var v_transfer_periapsis = sqrt(mu * ((2.0 / r1) - (1.0 / a_transfer)))
|
||||
# v_transfer_apoapsis: The speed the ship will have when it arrives at the end of the transfer (apoapsis).
|
||||
var v_transfer_apoapsis = sqrt(mu * ((2.0 / r2) - (1.0 / a_transfer)))
|
||||
|
||||
# delta_v1: The first burn. The change in speed needed to go from the source orbit to the transfer orbit.
|
||||
var delta_v1 = v_transfer_periapsis - v_source_orbit
|
||||
# delta_v2: The second burn. The change in speed needed to go from the transfer orbit to the target orbit.
|
||||
var delta_v2 = v_target_orbit - v_transfer_apoapsis
|
||||
|
||||
# time_of_flight: Half the period of the elliptical transfer orbit (Kepler's 3rd Law).
|
||||
var time_of_flight = PI * sqrt(pow(a_transfer, 3) / mu)
|
||||
|
||||
# --- Launch Window (Phase Angle) Calculations ---
|
||||
|
||||
# ang_vel_target: The angular speed of the target planet (in radians per second).
|
||||
var ang_vel_target = sqrt(mu / pow(r2, 3))
|
||||
# travel_angle: The angle the target planet will travel through during the ship's flight time.
|
||||
var travel_angle = ang_vel_target * time_of_flight
|
||||
# required_phase_angle: The starting angle needed between the ship and target for a successful intercept.
|
||||
var required_phase_angle = PI - travel_angle
|
||||
|
||||
# vec_to_ship/target: Direction vectors from the star to the ship and target.
|
||||
var vec_to_ship = (ship.global_position - star.global_position).normalized()
|
||||
var vec_to_target = (target_planet.global_position - star.global_position).normalized()
|
||||
# current_phase_angle: The angle between the ship and target right now.
|
||||
var current_phase_angle = vec_to_ship.angle_to(vec_to_target)
|
||||
|
||||
# ang_vel_ship: The ship's current angular speed.
|
||||
var ang_vel_ship = sqrt(mu / pow(r1, 3))
|
||||
# relative_ang_vel: How quickly the ship is catching up to (or falling behind) the target.
|
||||
var relative_ang_vel = ang_vel_ship - ang_vel_target
|
||||
# angle_to_wait: The angular distance the ship needs to "wait" for alignment.
|
||||
var angle_to_wait = current_phase_angle - required_phase_angle
|
||||
# wait_time: The final calculated time in seconds to wait for the optimal launch window.
|
||||
var wait_time = abs(angle_to_wait / relative_ang_vel)
|
||||
|
||||
# --- Final Plan Assembly ---
|
||||
|
||||
# Calculate burn durations
|
||||
var acceleration = ship.thruster_controller.main_engine_max_thrust() / ship.mass
|
||||
var burn_duration1 = delta_v1 / acceleration
|
||||
var burn_duration2 = delta_v2 / acceleration
|
||||
|
||||
var plan = []
|
||||
var burn1 = ImpulsiveBurn.new()
|
||||
burn1.delta_v_magnitude = delta_v1
|
||||
burn1.wait_time = wait_time
|
||||
burn1.burn_duration = burn_duration1
|
||||
# The desired rotation is the angle of the ship's prograde (tangential) velocity vector.
|
||||
burn1.desired_rotation_rad = ship.linear_velocity.angle()
|
||||
plan.append(burn1)
|
||||
|
||||
var burn2 = ImpulsiveBurn.new()
|
||||
burn2.delta_v_magnitude = delta_v2
|
||||
burn2.wait_time = time_of_flight - burn_duration1
|
||||
burn2.burn_duration = burn_duration2
|
||||
# The desired rotation for burn 2 is the tangential direction at the target orbit.
|
||||
burn2.desired_rotation_rad = (target_planet.global_position - star.global_position).orthogonal().angle()
|
||||
plan.append(burn2)
|
||||
|
||||
info_label.text = "Hohmann Plan:\nWait: %d s\nBurn 1: %.1f m/s (%.1f s)" % [wait_time, delta_v1, burn_duration1]
|
||||
return plan
|
||||
|
||||
func _calculate_brachistochrone_transfer() -> ContinuousBurnPlan:
|
||||
var distance = ship.global_position.distance_to(target_body.global_position)
|
||||
var acceleration = ship.thruster_controller.main_engine_max_thrust() / ship.mass
|
||||
if acceleration == 0: return null
|
||||
|
||||
# d = 1/2 * a * t^2 => t = sqrt(2d/a). We do this twice (accel/decel).
|
||||
var time_for_half_journey = sqrt(distance / acceleration)
|
||||
|
||||
var plan = ContinuousBurnPlan.new()
|
||||
plan.total_travel_time = 2 * time_for_half_journey
|
||||
plan.acceleration_time = time_for_half_journey
|
||||
plan.required_acceleration = acceleration
|
||||
|
||||
info_label.text = "Torchship Trajectory Calculated.\nTravel Time: %d s" % plan.total_travel_time
|
||||
return plan
|
||||
|
||||
func _execute_maneuver():
|
||||
if current_state != State.WAITING or not current_plan: return
|
||||
|
||||
current_state = State.EXECUTING
|
||||
var burn: ImpulsiveBurn = current_plan.pop_front()
|
||||
|
||||
# Tell the controller to start burning. Orientation is already handled.
|
||||
ship.signal_bus.emit_signal("timed_burn_commanded", burn.burn_duration)
|
||||
|
||||
# Set up for the next leg of the journey or finish
|
||||
if not current_plan.is_empty():
|
||||
# The next "wait_time" is the coasting period between burns.
|
||||
current_state = State.WAITING
|
||||
|
||||
# Reset the flag here, preparing the system for the *next* burn's rotation command.
|
||||
rotation_plan_sent = false
|
||||
else:
|
||||
current_state = State.IDLE
|
||||
current_plan = null
|
||||
update_ui()
|
||||
|
||||
var _previous_angular_velocity: float = 0.0
|
||||
var actual_applied_torque: float = 0.0
|
||||
|
||||
func _update_ship_status_label():
|
||||
if not is_instance_valid(ship):
|
||||
ship_status_label.text = "NO SHIP DATA"
|
||||
return
|
||||
|
||||
# Build an array of strings for each line of the display
|
||||
var status_lines = []
|
||||
|
||||
var assume_torque = ship.thruster_controller.current_applied_torque
|
||||
|
||||
# Get rotation data from the ship
|
||||
var rotation_deg = rad_to_deg(ship.rotation)
|
||||
var current_angular_velocity = ship.angular_velocity
|
||||
var angular_vel_dps = rad_to_deg(ship.angular_velocity)
|
||||
var delta_omega = ship.angular_velocity - _previous_angular_velocity
|
||||
var delta = get_physics_process_delta_time()
|
||||
|
||||
if delta > 0:
|
||||
var angular_acceleration = delta_omega / delta
|
||||
#actual_applied_torque = ship.accumulated_torque # ship.inertia * angular_acceleration
|
||||
# Update the indicator color based on the comparison.
|
||||
|
||||
_previous_angular_velocity = current_angular_velocity
|
||||
|
||||
# Get the sign of each torque value.
|
||||
var calc_sign = sign(assume_torque)
|
||||
var actual_sign = sign(actual_applied_torque)
|
||||
|
||||
if calc_sign != 0 and calc_sign == actual_sign:
|
||||
# Success: We are commanding a turn, and the physics engine agrees.
|
||||
torque_indicator.color = Color.GREEN
|
||||
elif calc_sign != 0 and actual_sign == 0:
|
||||
# Mismatch: We are commanding a turn, but the physics engine reports no torque.
|
||||
# This is the flicker you are seeing.
|
||||
torque_indicator.color = Color.RED
|
||||
else:
|
||||
# Idle: No torque is being commanded, and none is being applied.
|
||||
torque_indicator.color = Color.DARK_GRAY
|
||||
|
||||
var sensor_torque_sign = "+" if signf(actual_applied_torque) > 0.0 else "-" if signf(actual_applied_torque) < 0.0 else "0"
|
||||
|
||||
|
||||
status_lines.append("Rotation: %.1f deg" % rotation_deg)
|
||||
status_lines.append("Ang. Vel.: %.5f deg/s" % angular_vel_dps)
|
||||
status_lines.append("Assumed Torque: %.2f N·m" % assume_torque)
|
||||
status_lines.append("Sensed Torque: %.2f N·m" % actual_applied_torque)
|
||||
|
||||
# Get burn data from the thruster controller
|
||||
var burn_time = ship.thruster_controller.current_burn_time_remaining
|
||||
var thrust_force = ship.thruster_controller.current_thrust_force
|
||||
status_lines.append("Burn Time: %.1f s" % burn_time)
|
||||
status_lines.append("Thrust: %.5f N" % thrust_force)
|
||||
|
||||
# Join the lines with a newline character and update the label
|
||||
ship_status_label.text = "\n".join(status_lines)
|
||||
|
||||
# Helper function to format a float of seconds into a M:SS string
|
||||
func _format_seconds_to_mmss(seconds_float: float) -> String:
|
||||
if seconds_float < 0:
|
||||
seconds_float = 0
|
||||
var total_seconds = int(seconds_float)
|
||||
var minutes = total_seconds / 60
|
||||
var seconds = total_seconds % 60
|
||||
# "%02d" formats the seconds with a leading zero if needed (e.g., 05)
|
||||
return "%d:%02d" % [minutes, seconds]
|
||||
@ -1 +0,0 @@
|
||||
uid://cq2sgw12uj4jl
|
||||
@ -1,103 +0,0 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cxpjm8tp3l1j7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cq2sgw12uj4jl" path="res://scenes/ship/systems/navigation_computer.gd" id="1_ys00n"]
|
||||
[ext_resource type="Script" uid="uid://b74hxlsox8ldo" path="res://scenes/ship/systems/map_controller.gd" id="2_ys00n"]
|
||||
[ext_resource type="PackedScene" uid="uid://c2imrmgjthfdm" path="res://scenes/UI/MapIcon.tscn" id="3_378us"]
|
||||
|
||||
[node name="NavigationComputer" type="Node"]
|
||||
script = ExtResource("1_ys00n")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
|
||||
[node name="NavigationUI" type="CanvasLayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="NavigationScreen" type="MarginContainer" parent="NavigationUI"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="NavigationUI/NavigationScreen"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="NavigationUI/NavigationScreen/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
|
||||
[node name="TargetLabel" type="Label" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
clip_text = true
|
||||
|
||||
[node name="InfoLabel" type="Label" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TorqueIndicator" type="ColorRect" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(20, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PlanHohmannButton" type="Button" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Hohmann Transfer"
|
||||
|
||||
[node name="PlanFastButton" type="Button" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Fast Impulse"
|
||||
|
||||
[node name="PlanTorchshipButton" type="Button" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Torchship Burn"
|
||||
|
||||
[node name="PlanGravityAssistButton" type="Button" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Grav Assist"
|
||||
|
||||
[node name="ExecutePlanButton" type="Button" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Launch"
|
||||
|
||||
[node name="MapController" type="Control" parent="NavigationUI/NavigationScreen/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 3.0
|
||||
script = ExtResource("2_ys00n")
|
||||
map_icon_scene = ExtResource("3_378us")
|
||||
|
||||
[node name="RightHandPanel" type="VBoxContainer" parent="NavigationUI/NavigationScreen/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 300)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
|
||||
[node name="ShipStatusLabel" type="Label" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="SubViewportContainer" type="SubViewportContainer" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SubViewport" type="SubViewport" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel/SubViewportContainer"]
|
||||
unique_name_in_owner = true
|
||||
transparent_bg = true
|
||||
handle_input_locally = false
|
||||
size = Vector2i(320, 180)
|
||||
render_target_update_mode = 4
|
||||
|
||||
[node name="ShipViewCamera" type="Camera2D" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel/SubViewportContainer/SubViewport"]
|
||||
unique_name_in_owner = true
|
||||
@ -1,371 +0,0 @@
|
||||
# ThrusterController.gd
|
||||
class_name ThrusterController
|
||||
extends Node2D
|
||||
|
||||
# --- References ---
|
||||
@onready var ship: Spaceship = get_parent()
|
||||
var ship_signal_bus: ShipSignalBus
|
||||
|
||||
@onready var main_engines: Array[Thruster] = []
|
||||
@onready var rcs_thrusters: Array[Thruster] = []
|
||||
|
||||
# --- Public Status Variables ---
|
||||
var current_burn_time_remaining: float = 0.0
|
||||
var current_thrust_force: float = 0.0
|
||||
var current_applied_torque: float = 0.0
|
||||
|
||||
# --- Autopilot & Manual Control State ---
|
||||
enum RotationMode { NONE, AUTOPILOT_TURN, MANUAL_HOLD }
|
||||
var rotation_mode: RotationMode = RotationMode.NONE
|
||||
const ATTITUDE_TOLERANCE_RAD = 0.005 # ~1 degree. If error is > this, we correct.
|
||||
|
||||
|
||||
# --- NEW: Inner class to store calibrated data for each thruster ---
|
||||
class ThrusterData:
|
||||
var thruster_node: Thruster
|
||||
var measured_force_vector: Vector2 # The linear push it provides
|
||||
var measured_torque: float # The rotational force it provides
|
||||
var primary_axis: String # e.g., "Linear", "Yaw", "Surge"
|
||||
|
||||
# --- NEW: A dictionary to map thruster nodes to their performance data ---
|
||||
var thruster_data_map: Dictionary[Thruster, ThrusterData] = {}
|
||||
|
||||
var target_rotation_rad: float = 0.0
|
||||
var is_burning: bool = false
|
||||
|
||||
# --- Ship Performance Data (calibrated) ---
|
||||
var max_positive_torque: float = 10000.0 # Default value, will be calibrated
|
||||
var max_negative_torque: float = 10000.0 # Default value, will be calibrated
|
||||
|
||||
# --- Manual Hold Constants (PD Controller) ---
|
||||
@export var HOLD_KP: float = 8000.0 # Proportional gain for stability
|
||||
@export var HOLD_KD: float = 1200.0 # Derivative gain for damping
|
||||
|
||||
# --- Crew Comfort Levels ---
|
||||
const COMFORTABLE_ANGULAR_VELOCITY = 0.5 # radians/sec
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
|
||||
if is_instance_valid(ship):
|
||||
ship_signal_bus = ship.signal_bus
|
||||
ship_signal_bus.timed_rotation_commanded.connect(_on_autopilot_rotation_commanded)
|
||||
ship_signal_bus.timed_burn_commanded.connect(_on_autopilot_main_engine_burn_commanded)
|
||||
|
||||
# Register thrusters
|
||||
var thruster_nodes = get_tree().get_nodes_in_group("ship_thrusters")
|
||||
for thruster: Thruster in thruster_nodes:
|
||||
if thruster.get_parent() == ship:
|
||||
if thruster.main_thruster: main_engines.append(thruster)
|
||||
else: rcs_thrusters.append(thruster)
|
||||
|
||||
await get_tree().physics_frame
|
||||
|
||||
# Calibrate thrusters on startup
|
||||
await calibrate_rcs_performance()
|
||||
command_hold_attitude(0.0)
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
match rotation_mode:
|
||||
RotationMode.MANUAL_HOLD:
|
||||
#if _is_attitude_misaligned() or true:
|
||||
_perform_manual_hold()
|
||||
#if ship: print("AUTOPILOT: Holding attitude.")
|
||||
|
||||
# --- PUBLIC API ---
|
||||
|
||||
# The Navigation Computer calls this.
|
||||
func _on_autopilot_rotation_commanded(new_target_rot: float, time_window: float):
|
||||
rotation_mode = RotationMode.AUTOPILOT_TURN
|
||||
# Start the asynchronous autopilot maneuver
|
||||
_execute_autopilot_rotation(new_target_rot, time_window)
|
||||
|
||||
func _on_autopilot_main_engine_burn_commanded(duration: float):
|
||||
while _is_attitude_misaligned():
|
||||
await get_tree().physics_frame
|
||||
|
||||
_fire_main_engine(duration)
|
||||
|
||||
# The player/manual input would call this.
|
||||
func command_hold_attitude(rotation_rad: float):
|
||||
if rotation_mode != RotationMode.MANUAL_HOLD:
|
||||
print("AUTOPILOT: Engaging manual attitude hold at %.2f deg." % rad_to_deg(rotation_rad))
|
||||
rotation_mode = RotationMode.MANUAL_HOLD
|
||||
target_rotation_rad = rotation_rad
|
||||
|
||||
func command_stop_rotation():
|
||||
if rotation_mode == RotationMode.MANUAL_HOLD:
|
||||
command_hold_attitude(ship.rotation)
|
||||
|
||||
func command_disable_rcs():
|
||||
rotation_mode = RotationMode.NONE
|
||||
shutdown_rcs()
|
||||
print("AUTOPILOT: RCS disabled.")
|
||||
|
||||
# --- Helper for Misalignment Check ---
|
||||
func _is_attitude_misaligned() -> bool:
|
||||
var error = shortest_angle_between(ship.rotation, target_rotation_rad)
|
||||
return abs(error) > ATTITUDE_TOLERANCE_RAD
|
||||
|
||||
func calibrate_rcs_performance() -> void:
|
||||
print("AUTOPILOT: Beginning full RCS calibration protocol...")
|
||||
thruster_data_map.clear()
|
||||
|
||||
# Create a temporary copy as the original arrays may change
|
||||
var all_thrusters_to_test = main_engines + rcs_thrusters
|
||||
|
||||
for thruster in all_thrusters_to_test:
|
||||
var data: ThrusterData = await _calibrate_single_thruster(thruster)
|
||||
thruster_data_map[thruster] = data
|
||||
print(" - Calibrated %s: Force(%.3f, %.3f), Torque(%.3f)" % [thruster.name, data.measured_force_vector.x, data.measured_force_vector.y, data.measured_torque])
|
||||
|
||||
# Now that we have the data, we can update the ship's max torque values
|
||||
max_positive_torque = 0
|
||||
max_negative_torque = 0
|
||||
for data in thruster_data_map.values():
|
||||
if data.measured_torque > 0:
|
||||
max_positive_torque += data.measured_torque
|
||||
else:
|
||||
max_negative_torque += abs(data.measured_torque)
|
||||
|
||||
print("RCS Calibration Complete: Max Pos Torque: %.2f, Max Neg Torque: %.2f" % [max_positive_torque, max_negative_torque])
|
||||
|
||||
# --- 3. Cleanup: Perform a final burn to stop the ship ---
|
||||
# We use the newly calibrated torque to calculate the stop burn time.
|
||||
await _reset_ship_rotation()
|
||||
|
||||
shutdown_rcs()
|
||||
|
||||
# Auto-tune the PD controller with the new values
|
||||
var average_max_torque = (max_positive_torque + max_negative_torque) / 2.0
|
||||
HOLD_KP = average_max_torque * 0.1
|
||||
HOLD_KD = HOLD_KP * 1
|
||||
print("PD Controller Auto-Tuned: Kp set to %.4f, Kd set to %.4f" % [HOLD_KP, HOLD_KD])
|
||||
|
||||
|
||||
func _calibrate_single_thruster(thruster: Thruster) -> ThrusterData:
|
||||
var data = ThrusterData.new()
|
||||
data.thruster_node = thruster
|
||||
|
||||
# Prepare for test: save state, disable damping
|
||||
var initial_velocity = ship.linear_velocity
|
||||
var initial_angular_velocity = ship.angular_velocity
|
||||
|
||||
var test_burn_duration = 0.5 if thruster.main_thruster else 0.2 # A very short burst
|
||||
|
||||
# --- Perform Test Fire ---
|
||||
thruster.turn_on()
|
||||
await get_tree().create_timer(test_burn_duration).timeout
|
||||
thruster.turn_off()
|
||||
|
||||
# Let the physics engine settle for one frame
|
||||
await get_tree().physics_frame
|
||||
|
||||
# --- Measure Results ---
|
||||
var delta_velocity = ship.linear_velocity - initial_velocity
|
||||
var delta_angular_velocity = ship.angular_velocity - initial_angular_velocity
|
||||
|
||||
# --- Calculate Performance ---
|
||||
# Force = mass * acceleration (a = dv/dt)
|
||||
data.measured_force_vector = ship.mass * (delta_velocity / test_burn_duration)
|
||||
# Torque = inertia * angular_acceleration (alpha = dw/dt)
|
||||
data.measured_torque = ship.inertia * (delta_angular_velocity / test_burn_duration)
|
||||
|
||||
return data
|
||||
|
||||
# Resets the ship's rotation and angular velocity to zero.
|
||||
func _reset_ship_rotation() -> void:
|
||||
# Use the calibrated torque to calculate the final stop burn time.
|
||||
if abs(ship.angular_velocity) > 0.01:
|
||||
var stop_torque_dir = -sign(ship.angular_velocity)
|
||||
var stop_torque_mag = max_positive_torque if stop_torque_dir > 0 else max_negative_torque
|
||||
var stop_burn_time = abs((ship.inertia * ship.angular_velocity) / stop_torque_mag)
|
||||
|
||||
var stop_timer = get_tree().create_timer(stop_burn_time)
|
||||
while stop_timer.get_time_left() > 0.0:
|
||||
apply_rotational_thrust(stop_torque_dir * stop_torque_mag)
|
||||
await get_tree().physics_frame
|
||||
|
||||
shutdown_rcs()
|
||||
print("AUTOPILOT: Ship rotation and velocity reset.")
|
||||
|
||||
# --- AUTOPILOT "BANG-COAST-BANG" LOGIC (REFACTORED) ---
|
||||
func _execute_autopilot_rotation(new_target_rot: float, time_window: float):
|
||||
var angle_to_turn = shortest_angle_between(ship.rotation, new_target_rot)
|
||||
|
||||
if abs(angle_to_turn) < 0.01:
|
||||
command_hold_attitude(new_target_rot)
|
||||
return
|
||||
|
||||
# --- NEW: Get the specific torque values for each phase ---
|
||||
var accel_torque = max_positive_torque if angle_to_turn > 0 else max_negative_torque
|
||||
var decel_torque = max_negative_torque if angle_to_turn > 0 else max_positive_torque
|
||||
|
||||
if accel_torque == 0 or decel_torque == 0:
|
||||
print("AUTOPILOT ERROR: Missing thrusters for a full rotation.")
|
||||
return
|
||||
|
||||
# --- NEW: Asymmetrical Burn Calculation ---
|
||||
# This is a more complex kinematic problem. We solve for the peak velocity and individual times.
|
||||
var accel_angular_accel = accel_torque / ship.inertia
|
||||
var decel_angular_accel = decel_torque / ship.inertia
|
||||
|
||||
# Solve for peak angular velocity (ω_peak) and times (t1, t2)
|
||||
var peak_angular_velocity = (2 * angle_to_turn * accel_angular_accel * decel_angular_accel) / (accel_angular_accel + decel_angular_accel)
|
||||
peak_angular_velocity = sqrt(abs(peak_angular_velocity)) * sign(angle_to_turn)
|
||||
|
||||
var accel_burn_time = abs(peak_angular_velocity / accel_angular_accel)
|
||||
var decel_burn_time = abs(peak_angular_velocity / decel_angular_accel)
|
||||
|
||||
var total_maneuver_time = accel_burn_time + decel_burn_time
|
||||
|
||||
if total_maneuver_time > time_window:
|
||||
print("AUTOPILOT WARNING: Maneuver is impossible in the given time window. Performing max-power turn.")
|
||||
# Fallback to a simple 50/50 burn if time is too short.
|
||||
accel_burn_time = time_window / 2.0
|
||||
decel_burn_time = time_window / 2.0
|
||||
|
||||
# No coast time in this simplified model, but it could be added back with more complex math.
|
||||
|
||||
print("AUTOPILOT: Asymmetrical Plan: Accel Burn %.2fs, Decel Burn %.2fs" % [accel_burn_time, decel_burn_time])
|
||||
|
||||
# --- Execute Maneuver ---
|
||||
|
||||
# ACCELERATION BURN
|
||||
var accel_timer = get_tree().create_timer(accel_burn_time)
|
||||
while accel_timer.get_time_left() > 0.0:
|
||||
apply_rotational_thrust(accel_torque * sign(angle_to_turn))
|
||||
await get_tree().physics_frame
|
||||
|
||||
# DECELERATION BURN
|
||||
print("AUTOPILOT: Acceleration complete, executing deceleration burn.")
|
||||
var decel_timer = get_tree().create_timer(decel_burn_time)
|
||||
while decel_timer.get_time_left() > 0.0:
|
||||
apply_rotational_thrust(-accel_torque * sign(angle_to_turn)) # Apply opposite torque
|
||||
await get_tree().physics_frame
|
||||
|
||||
shutdown_rcs()
|
||||
|
||||
print("AUTOPILOT: Rotation maneuver complete.")
|
||||
command_hold_attitude(new_target_rot)
|
||||
|
||||
|
||||
# --- MANUAL HOLD & STABILIZATION LOGIC (REFACTORED) ---
|
||||
func _perform_manual_hold():
|
||||
if not is_instance_valid(ship):
|
||||
rotation_mode = RotationMode.NONE # Safety check
|
||||
return
|
||||
|
||||
# 1. Calculate the error (how far we have to go).
|
||||
var error = shortest_angle_between(ship.rotation, target_rotation_rad)
|
||||
|
||||
# 2. Calculate the required torque using the PD formula.
|
||||
# This value will be positive, negative, or zero depending on our state.
|
||||
var desired_torque = (error * HOLD_KP) - (ship.angular_velocity * HOLD_KD)
|
||||
|
||||
# 3. Continuously command the thrusters based on the calculated torque.
|
||||
# This function will turn thrusters ON if they match the desired_torque's sign
|
||||
# and OFF if they do not. It must be called every frame to work correctly.
|
||||
if not _is_attitude_misaligned() and abs(ship.angular_velocity) < 0.01 and abs(desired_torque) < 0.0001:
|
||||
ship.angular_velocity = 0.0
|
||||
ship.rotation = target_rotation_rad
|
||||
|
||||
shutdown_rcs()
|
||||
else:
|
||||
apply_rotational_thrust(desired_torque)
|
||||
|
||||
|
||||
# --- WORKER FUNCTIONS ---
|
||||
|
||||
func apply_rotational_thrust(desired_torque: float):
|
||||
if not is_instance_valid(ship):
|
||||
return
|
||||
|
||||
var delta_torque = 0.0
|
||||
|
||||
# Iterate through all available RCS thrusters
|
||||
for thruster in rcs_thrusters:
|
||||
# ... (your existing calculation for produced_torque is correct)
|
||||
var thruster_local_pos = thruster.position
|
||||
var thruster_data: ThrusterData = thruster_data_map.get(thruster)
|
||||
|
||||
# If this thruster can help, turn it on. Otherwise, explicitly turn it off.
|
||||
if sign(thruster_data.measured_torque) == sign(desired_torque) and desired_torque != 0:
|
||||
thruster.turn_on()
|
||||
delta_torque += thruster_data.measured_torque
|
||||
|
||||
else:
|
||||
thruster.turn_off()
|
||||
|
||||
current_applied_torque = delta_torque
|
||||
|
||||
## Applies forces from the correct thrusters to achieve a desired torque.
|
||||
#func apply_rotational_thrust(desired_torque: float):
|
||||
#if not is_instance_valid(ship):
|
||||
#return
|
||||
#
|
||||
##print("AUTOPILOT: Applying rotational thrust %f" % desired_torque)
|
||||
## Iterate through all available RCS thrusters
|
||||
#var delta_torque = 0.0
|
||||
#for thruster in rcs_thrusters:
|
||||
## 1. Get the thruster's position relative to the ship's center. This is its local position.
|
||||
#var thruster_local_pos = thruster.position
|
||||
#
|
||||
## 2. Calculate the force this thruster produces, also in LOCAL space.
|
||||
#var force_local_vec = thruster.thrust_direction * thruster.max_thrust
|
||||
#
|
||||
## 3. Calculate the torque in LOCAL space. This is now a valid calculation.
|
||||
#var produced_torque = thruster_local_pos.cross(force_local_vec)
|
||||
## 4. Decide whether to fire the thruster. This check will now work correctly.
|
||||
#if sign(produced_torque) == sign(desired_torque):
|
||||
#thruster.turn_on()
|
||||
#delta_torque += produced_torque
|
||||
#
|
||||
#else:
|
||||
#thruster.turn_off()
|
||||
#
|
||||
#current_applied_torque = delta_torque
|
||||
|
||||
|
||||
func shutdown_rcs():
|
||||
for thruster in rcs_thrusters: thruster.turn_off()
|
||||
current_applied_torque = 0.0
|
||||
|
||||
# Calculates the shortest angle between two angles (in radians).
|
||||
# The result will be between -PI and +PI. The sign indicates the direction.
|
||||
func shortest_angle_between(from_angle: float, to_angle: float) -> float:
|
||||
var difference = fposmod(to_angle - from_angle, TAU)
|
||||
if difference > PI:
|
||||
return difference - TAU
|
||||
else:
|
||||
return difference
|
||||
|
||||
# Calculates the total torque available from all thrusters for a given direction.
|
||||
func _get_total_possible_torque(direction: int) -> float:
|
||||
var total_torque: float = 0.0
|
||||
for thruster in rcs_thrusters:
|
||||
var r = thruster.position
|
||||
var force_local_vec = thruster.thrust_direction * thruster.max_thrust
|
||||
var produced_torque = r.cross(force_local_vec)
|
||||
if sign(produced_torque) == direction:
|
||||
total_torque += abs(produced_torque)
|
||||
return total_torque
|
||||
|
||||
# Fires all main engines for a specified duration.
|
||||
func _fire_main_engine(duration: float):
|
||||
print("AUTOPILOT: Firing main engine for %.2f seconds." % duration)
|
||||
|
||||
for engine in main_engines:
|
||||
engine.turn_on()
|
||||
|
||||
await get_tree().create_timer(duration).timeout
|
||||
|
||||
for engine in main_engines:
|
||||
engine.turn_off()
|
||||
|
||||
is_burning = false
|
||||
print("AUTOPILOT: Main engine burn complete.")
|
||||
|
||||
func main_engine_max_thrust():
|
||||
return main_engines.reduce(func(thrust, engine : Thruster): return thrust + engine.max_thrust, 0.0)
|
||||
@ -1 +0,0 @@
|
||||
uid://c0bx113ifxyh8
|
||||
@ -1,67 +0,0 @@
|
||||
class_name CelestialBody
|
||||
extends RigidBody2D
|
||||
|
||||
# The celestial body that this body orbits.
|
||||
@export var primary: CelestialBody
|
||||
|
||||
# This is a placeholder for your pixel art texture.
|
||||
@export var texture: Texture2D
|
||||
|
||||
# The radius of the body, used for drawing and future collision detection.
|
||||
@export var radius: float = 10.0
|
||||
|
||||
# Default color based on body type for visualization.
|
||||
var body_color: Color = Color.ORANGE_RED
|
||||
|
||||
var orbit_radius_real : float = 0.0
|
||||
|
||||
var direction_to_primary : Vector2 = Vector2.ZERO
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "CelestialBody"
|
||||
|
||||
func _ready() -> void:
|
||||
# We will handle gravity manually, so we set the built-in gravity scale to 0.
|
||||
gravity_scale = 0.0
|
||||
|
||||
# To make the simulation work without drag, we must set linear damping to 0.
|
||||
linear_damp = 0.0
|
||||
angular_damp = 0.0
|
||||
|
||||
can_sleep = false
|
||||
|
||||
# Set the color based on the class name for easy differentiation.
|
||||
match get_class_name():
|
||||
"Star":
|
||||
body_color = Color.GOLD
|
||||
"Planet":
|
||||
body_color = Color.BLUE
|
||||
"Moon":
|
||||
body_color = Color.PURPLE
|
||||
"Station":
|
||||
body_color = Color.WHITE
|
||||
"Asteroid":
|
||||
body_color = Color.BROWN
|
||||
_:
|
||||
body_color = Color.ORANGE_RED
|
||||
|
||||
# This callback is the correct place to apply custom forces to a RigidBody2D.
|
||||
func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
|
||||
if primary and is_instance_valid(primary):
|
||||
var force = OrbitalMechanics.simple_n_body_grav(self, primary)
|
||||
state.apply_central_force(force)
|
||||
|
||||
# We force a redraw here to update the body's visual representation.
|
||||
queue_redraw()
|
||||
|
||||
# Override the default drawing function to draw the body.
|
||||
# This is useful for debugging and visualization.
|
||||
func _draw() -> void:
|
||||
if texture:
|
||||
# If a texture is assigned, draw it.
|
||||
var size = Vector2(radius * 2, radius * 2)
|
||||
var offset = -size / 2.0
|
||||
draw_texture_rect(texture, Rect2(offset, size), false)
|
||||
else:
|
||||
# Otherwise, draw a simple placeholder circle.
|
||||
draw_circle(Vector2.ZERO, radius, body_color)
|
||||
@ -1 +0,0 @@
|
||||
uid://bn1u2xood3vs6
|
||||
@ -1,165 +0,0 @@
|
||||
extends Node2D
|
||||
class_name OrbitalBody2D
|
||||
|
||||
# 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
|
||||
|
||||
# Determine if this body is the root of a physics simulation
|
||||
#@onready var is_sim_root: bool = true
|
||||
@onready var is_sim_root: bool = true:
|
||||
set(value):
|
||||
is_sim_root = value
|
||||
get:
|
||||
# The first time this variable is accessed after being ready,
|
||||
# we perform our essential setup checks.
|
||||
if not sim_check_complete:
|
||||
var p = get_parent()
|
||||
var found_parent = false
|
||||
while p:
|
||||
if p is OrbitalBody2D:
|
||||
found_parent = true
|
||||
break
|
||||
p = p.get_parent()
|
||||
is_sim_root = not found_parent
|
||||
|
||||
return is_sim_root
|
||||
|
||||
var sim_check_complete: bool = false
|
||||
|
||||
func _ready():
|
||||
# Ensure mass update runs immediately before the firfst _physics_process.
|
||||
recalculate_physical_properties()
|
||||
# FIX: Enable _physics_process for ALL OrbitalBody2D nodes (including Thrusters).
|
||||
#The 'if is_sim_root' inside _physics_process will prevent integration for children.
|
||||
set_physics_process(not Engine.is_editor_hint())
|
||||
|
||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||
# This method is called by a component (like Thruster) at its global position.
|
||||
func apply_force(force: Vector2, position: Vector2 = Vector2.ZERO):
|
||||
# This is the force routing logic.
|
||||
if is_sim_root:
|
||||
# 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
|
||||
else:
|
||||
# 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, position)
|
||||
return # Stop at the first OrbitalBody2D parent
|
||||
p = p.get_parent()
|
||||
|
||||
push_error("OrbitalBody2D tried to apply force but could not find a parent OrbitalBody2D.")
|
||||
|
||||
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 is_sim_root and not Engine.is_editor_hint():
|
||||
|
||||
# FIX 2: 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 + accumulated_force
|
||||
|
||||
# 3. Apply Linear Physics (F = ma)
|
||||
var linear_acceleration = total_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():
|
||||
if not is_sim_root:
|
||||
return
|
||||
|
||||
var all_parts: Array[OrbitalBody2D] = []
|
||||
_collect_all_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_all_parts(parts_array: Array):
|
||||
# Add self to the list
|
||||
parts_array.append(self)
|
||||
|
||||
# Recurse into children
|
||||
# TODO: this assumes that all OrbitalBody2D that are attached are done in a clean chain without breaks, which may not be the case
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
child._collect_all_parts(parts_array)
|
||||
@ -1 +0,0 @@
|
||||
uid://0isnsk356que
|
||||
@ -1,11 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="GameConfig" load_steps=4 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://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")
|
||||
metadata/_custom_type_script = "uid://bfc6u1f8sigxj"
|
||||
@ -1,157 +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 : StarSystemGenerator = null
|
||||
var ships_in_system : Array[OrbitalBody2D]
|
||||
|
||||
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: Node2D):
|
||||
if not ships_in_system.has(ship):
|
||||
ships_in_system.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():
|
||||
if current_star_system:
|
||||
return current_star_system.get_system_data()
|
||||
return null
|
||||
|
||||
func get_system_bodies() -> Array[CelestialBody]:
|
||||
if current_star_system:
|
||||
return current_star_system.get_all_bodies()
|
||||
|
||||
return []
|
||||
|
||||
func get_all_trackable_bodies() -> Array:
|
||||
var all_bodies: Array = []
|
||||
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.
|
||||
all_bodies.append_array(ships_in_system)
|
||||
|
||||
return all_bodies
|
||||
|
||||
# Traverses up the scene tree to find the first parent that is a Spaceship.
|
||||
func _find_parent_ship(node: Node) -> Spaceship:
|
||||
var current_node = node.get_parent()
|
||||
while is_instance_valid(current_node):
|
||||
if current_node is Spaceship:
|
||||
return current_node
|
||||
current_node = current_node.get_parent()
|
||||
return null # Return null if no spaceship parent was found
|
||||
@ -1,166 +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 = 10.0 # Adjust this to control the "speed" of your simulation
|
||||
|
||||
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 force of gravity exerted by a primary on an orbiter.
|
||||
func calculate_gravitational_force(orbiter: Node2D, primary: RigidBody2D) -> Vector2:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var direction = orbiter.global_position.direction_to(primary.global_position)
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
|
||||
if distance_sq < 1.0: # Avoid division by zero
|
||||
return Vector2.ZERO
|
||||
|
||||
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
|
||||
return direction * force_magnitude
|
||||
|
||||
# Simplified n-body gravitational pull on orbiters.
|
||||
# Station orbiting a moon will for instance calculate forces to the moon, the planet, and the star.
|
||||
func simple_n_body_grav(primary: RigidBody2D, orbiter : RigidBody2D) -> Vector2:
|
||||
var pull = calculate_gravitational_force(primary, orbiter)
|
||||
var inner_primary : CelestialBody = primary
|
||||
|
||||
while (inner_primary.primary is CelestialBody):
|
||||
pull = pull + calculate_gravitational_force(inner_primary.primary, orbiter)
|
||||
inner_primary = inner_primary.primary
|
||||
|
||||
return pull
|
||||
|
||||
# Calculates the perfect initial velocity for a stable circular orbit.
|
||||
func calculate_circular_orbit_velocity(orbiter: Node2D, primary: RigidBody2D) -> Vector2:
|
||||
if not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
if distance == 0:
|
||||
return Vector2.ZERO
|
||||
|
||||
# v = sqrt(G * M / r)
|
||||
var speed_magnitude = sqrt(G * primary.mass / distance)
|
||||
|
||||
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
|
||||
|
||||
# The final velocity must include the primary's own velocity.
|
||||
return (perpendicular_direction * speed_magnitude) + primary.linear_velocity
|
||||
|
||||
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: CelestialBody) -> PackedVector2Array:
|
||||
if not is_instance_valid(body_to_trace) or not is_instance_valid(body_to_trace.primary):
|
||||
return PackedVector2Array()
|
||||
|
||||
# --- Initial State ---
|
||||
var primary = body_to_trace.primary
|
||||
var primary_mass = primary.mass
|
||||
var body_mass = body_to_trace.mass
|
||||
|
||||
# The position of the body relative to its parent.
|
||||
var ghost_relative_pos = body_to_trace.global_position - primary.global_position
|
||||
# The velocity of the body relative to its parent's velocity.
|
||||
var ghost_relative_vel = body_to_trace.linear_velocity - primary.linear_velocity
|
||||
|
||||
# --- NEW: Dynamically Calculate Simulation Time from Orbital Period ---
|
||||
var r_magnitude = ghost_relative_pos.length()
|
||||
if r_magnitude == 0:
|
||||
return PackedVector2Array()
|
||||
|
||||
var v_sq = ghost_relative_vel.length_squared()
|
||||
var mu = OrbitalMechanics.G * primary_mass # Standard Gravitational Parameter
|
||||
|
||||
# 1. Calculate the specific orbital energy. Negative energy means it's a stable orbit.
|
||||
var specific_energy = v_sq / 2.0 - mu / r_magnitude
|
||||
|
||||
# --- Simulation Parameters ---
|
||||
var num_steps = 200 # The desired number of points for the orbit line's smoothness.
|
||||
var time_step: float # The duration of each step, which we will now calculate.
|
||||
|
||||
if specific_energy >= 0:
|
||||
# Escape trajectory (parabolic or hyperbolic). The period is infinite.
|
||||
# We'll just draw a segment of its path using a fixed time_step.
|
||||
time_step = 0.1
|
||||
else:
|
||||
# Stable elliptical orbit.
|
||||
# 2. Calculate the semi-major axis from the energy.
|
||||
var semi_major_axis = -mu / (2.0 * specific_energy)
|
||||
|
||||
# 3. Calculate the orbital period using Kepler's Third Law: T = 2π * sqrt(a³/μ)
|
||||
var orbital_period = 2.0 * PI * sqrt(pow(semi_major_axis, 3) / mu)
|
||||
|
||||
# 4. Calculate the time_step needed to complete one period in num_steps.
|
||||
time_step = orbital_period / float(num_steps)
|
||||
|
||||
var path_points = PackedVector2Array()
|
||||
|
||||
for i in range(num_steps):
|
||||
# --- Physics Calculation (Primary is now at the origin (0,0)) ---
|
||||
var distance_sq = ghost_relative_pos.length_squared()
|
||||
|
||||
if distance_sq < 1.0:
|
||||
break
|
||||
|
||||
# Direction is simply towards the origin.
|
||||
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
|
||||
|
||||
# --- Integration (Update ghost's relative state) ---
|
||||
ghost_relative_vel += acceleration * time_step
|
||||
ghost_relative_pos += ghost_relative_vel * time_step
|
||||
|
||||
path_points.append(ghost_relative_pos)
|
||||
|
||||
return path_points
|
||||
@ -1,11 +0,0 @@
|
||||
extends Node
|
||||
|
||||
# Signal emitted when the player requests to toggle the map view.
|
||||
signal map_mode_toggled
|
||||
|
||||
# Signal emitted from the map when a body is selected to be followed.
|
||||
# It passes the selected CelestialBody as an argument.
|
||||
signal follow_target_selected(body: CelestialBody)
|
||||
|
||||
# Emitted by the NavComputer to command a timed rotation.
|
||||
signal rotation_maneuver_planned(target_rotation_rad: float, time_window_seconds: float)
|
||||
@ -1 +0,0 @@
|
||||
uid://duu8vu31yyt7r
|
||||
@ -1,346 +0,0 @@
|
||||
class_name StarSystemGenerator
|
||||
extends Node2D
|
||||
|
||||
@export_group("System Generation")
|
||||
# Minimum and maximum number of planets to generate.
|
||||
@export var min_planets: int = 2
|
||||
@export var max_planets: int = 7
|
||||
|
||||
# Minimum and maximum number of moons to generate per planet.
|
||||
@export var min_moons: int = 0
|
||||
@export var max_moons: int = 4
|
||||
|
||||
# Minimum and maximum number of asteroid belts to generate.
|
||||
@export var min_asteroid_belts: int = 1
|
||||
@export var max_asteroid_belts: int = 3
|
||||
|
||||
# Minimum and maximum number of asteroids per belt.
|
||||
@export var min_asteroids_per_belt: int = 20
|
||||
@export var max_asteroids_per_belt: int = 100
|
||||
|
||||
# Minimum and maximum number of star-orbiting stations.
|
||||
@export var min_star_stations: int = 0
|
||||
@export var max_star_stations: int = 2
|
||||
|
||||
@export var min_planetary_stations : int = 0
|
||||
@export var max_planetary_stations: int = 2
|
||||
|
||||
@export var min_moon_stations : int = 0
|
||||
@export var max_moon_stations: int = 1
|
||||
|
||||
# References to the PackedScene resources for each celestial body.
|
||||
@export var star_scene: PackedScene
|
||||
@export var planet_scene: PackedScene
|
||||
@export var moon_scene: PackedScene
|
||||
@export var station_scene: PackedScene
|
||||
@export var asteroid_scene: PackedScene
|
||||
@export var spaceship_scene: PackedScene
|
||||
|
||||
# A factor to scale the initial orbital distances to make the system visually compelling.
|
||||
@export var min_ring_distance: float = 10000
|
||||
@export var min_planetary_orbit_radius : float = 200
|
||||
|
||||
# A factor to determine the buffer between orbits based on mass.
|
||||
@export var orbit_buffer_factor: float = 1
|
||||
const orbit_buffer_constant : float = 1000
|
||||
var orbit_buffer : float = orbit_buffer_constant * orbit_buffer_factor
|
||||
|
||||
# A flag to check if the system has been generated.
|
||||
var has_generated: bool = false
|
||||
|
||||
# Constants for real-world physics calculations.
|
||||
#const G = 6.67430e-11 # Gravitational constant (N·m²/kg²)
|
||||
#const SUN_MASS = 1.989e30 # Mass of the sun (kg)
|
||||
const SUN_MASS: float = 10000000.0
|
||||
const PLANETARY_MASS: float = SUN_MASS / 300000.0
|
||||
const MOON_MASS: float = PLANETARY_MASS / 100.0
|
||||
const ASTEROID_MASS: float = MOON_MASS / 5.0
|
||||
const STATION_MASS: float = MOON_MASS / 1000.0
|
||||
|
||||
var system_data : SystemData
|
||||
|
||||
func _ready() -> void:
|
||||
if not has_generated:
|
||||
system_data = SystemData.new()
|
||||
generate_star_system()
|
||||
has_generated = true
|
||||
|
||||
# --- THE CHANGE ---
|
||||
# After the world is built, tell the GameManager to start the game.
|
||||
GameManager.start_game()
|
||||
|
||||
# The main function to generate the entire star system.
|
||||
func generate_star_system() -> void:
|
||||
# Create the star at the center of the system.
|
||||
var star_instance = _create_star()
|
||||
|
||||
add_child(star_instance)
|
||||
|
||||
# Generate and place all celestial bodies in randomized order.
|
||||
_generate_and_place_bodies(star_instance)
|
||||
|
||||
# Creates a single star instance.
|
||||
func _create_star() -> RigidBody2D:
|
||||
var star_instance = star_scene.instantiate() as Star
|
||||
system_data.star = star_instance
|
||||
star_instance.name = "Star"
|
||||
|
||||
# Set the star's properties.
|
||||
star_instance.orbit_radius_real = 0.0
|
||||
star_instance.mass = SUN_MASS
|
||||
star_instance.modulate = Color("ffe066") # Yellow
|
||||
|
||||
return star_instance
|
||||
|
||||
# Generates and places all bodies in randomized order.
|
||||
func _generate_and_place_bodies(primary: RigidBody2D) -> void:
|
||||
# 1. Randomize the number of each type of body.
|
||||
var num_planets = randi_range(min_planets, max_planets)
|
||||
var num_belts = randi_range(min_asteroid_belts, max_asteroid_belts)
|
||||
var num_star_stations = randi_range(min_star_stations, max_star_stations)
|
||||
|
||||
# 2. Create an "inventory" of bodies to be placed.
|
||||
var bodies_to_place = []
|
||||
bodies_to_place.append({"type": "wormhole_exit", "num": 0})
|
||||
for i in range(num_planets):
|
||||
bodies_to_place.append({"type": "planet"})
|
||||
for i in range(num_belts):
|
||||
bodies_to_place.append({"type": "asteroid_belt"})
|
||||
for i in range(num_star_stations):
|
||||
bodies_to_place.append({"type": "station"})
|
||||
|
||||
# 3. Randomize the order of placement.
|
||||
bodies_to_place.shuffle()
|
||||
|
||||
var current_orbit_radius = min_ring_distance
|
||||
|
||||
# 4. Place bodies sequentially based on the randomized order.
|
||||
for body_data in bodies_to_place:
|
||||
var body_type = body_data["type"]
|
||||
|
||||
match body_type:
|
||||
"planet":
|
||||
var planet_instance = planet_scene.instantiate() as CelestialBody
|
||||
system_data.planets.append(planet_instance)
|
||||
planet_instance.name = "Planet " + str(current_orbit_radius)
|
||||
planet_instance.primary = primary
|
||||
planet_instance.mass = PLANETARY_MASS # randf_range(1e24, 1e25)
|
||||
|
||||
# Calculate orbit based on the last placed body.
|
||||
planet_instance.orbit_radius_real = current_orbit_radius + (planet_instance.mass * orbit_buffer)
|
||||
|
||||
_create_body_in_ring(primary, planet_instance)
|
||||
_create_moons_and_stations(planet_instance)
|
||||
|
||||
current_orbit_radius = planet_instance.orbit_radius_real + (planet_instance.mass * orbit_buffer)
|
||||
|
||||
"asteroid_belt":
|
||||
var belt = _create_asteroid_belt(primary, current_orbit_radius)
|
||||
system_data.belts.append(belt)
|
||||
|
||||
current_orbit_radius = current_orbit_radius + (belt.mass * orbit_buffer) * 2
|
||||
|
||||
"station":
|
||||
var station_instance = station_scene.instantiate() as CelestialBody
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Star Station " + str(current_orbit_radius)
|
||||
station_instance.primary = primary
|
||||
station_instance.mass = STATION_MASS # A very small mass
|
||||
|
||||
station_instance.orbit_radius_real = current_orbit_radius + (station_instance.mass * orbit_buffer)
|
||||
|
||||
_create_body_in_ring(primary, station_instance)
|
||||
|
||||
current_orbit_radius = station_instance.orbit_radius_real + (station_instance.mass * orbit_buffer)
|
||||
"wormhole_exit":
|
||||
if not spaceship_scene:
|
||||
print("Spaceship scene not set in StarSystemGenerator!")
|
||||
continue
|
||||
|
||||
print("Spawning spaceship...")
|
||||
var spaceship_instance = spaceship_scene.instantiate() as Module
|
||||
|
||||
# 3. Position it in a stable orbit
|
||||
var orbit_radius = current_orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
var initial_position_vector = Vector2(orbit_radius, 0).rotated(randf() * TAU)
|
||||
spaceship_instance.global_position = primary.global_position + initial_position_vector
|
||||
|
||||
# 4. Use the OrbitalMechanics library to calculate its initial velocity
|
||||
spaceship_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(spaceship_instance, primary)
|
||||
|
||||
add_child(spaceship_instance)
|
||||
|
||||
current_orbit_radius = orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
|
||||
print("Star system generation complete.")
|
||||
|
||||
GameManager.register_star_system(self)
|
||||
# Creates moons and stations around a primary body.
|
||||
func _create_moons_and_stations(primary: RigidBody2D) -> void:
|
||||
var num_moons = randi_range(min_moons, max_moons)
|
||||
var num_planetary_stations = randi_range(min_planetary_stations, max_planetary_stations)
|
||||
|
||||
# Start with a base orbital radius around the parent planet.
|
||||
var next_local_orbit_radius = min_planetary_orbit_radius
|
||||
|
||||
# Create an "inventory" of bodies to be placed.
|
||||
var bodies_to_place = []
|
||||
for i in range(num_planetary_stations):
|
||||
bodies_to_place.append({"type": "station", "num": i})
|
||||
for i in range(num_moons):
|
||||
bodies_to_place.append({"type": "moon", "num": i})
|
||||
|
||||
# Randomize the order of placement.
|
||||
bodies_to_place.shuffle()
|
||||
|
||||
for body in bodies_to_place:
|
||||
match body.type:
|
||||
"station":
|
||||
var station_instance = station_scene.instantiate() as CelestialBody
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Station " + str(body.num + 1)
|
||||
station_instance.primary = primary
|
||||
station_instance.mass = STATION_MASS
|
||||
|
||||
# Use the local orbit radius for this station.
|
||||
station_instance.orbit_radius_real = next_local_orbit_radius
|
||||
_create_body_in_ring(primary, station_instance)
|
||||
|
||||
# Increment the *local* orbit radius for the next body.
|
||||
next_local_orbit_radius += randf_range(40, 100)
|
||||
|
||||
"moon":
|
||||
var moon_instance = moon_scene.instantiate() as CelestialBody
|
||||
system_data.moons.append(moon_instance)
|
||||
moon_instance.name = "Moon " + str(body.num + 1)
|
||||
moon_instance.primary = primary
|
||||
moon_instance.mass = MOON_MASS
|
||||
|
||||
# Use the local orbit radius for this moon.
|
||||
moon_instance.orbit_radius_real = next_local_orbit_radius
|
||||
_create_body_in_ring(primary, moon_instance)
|
||||
|
||||
# Increment the *local* orbit radius for the next body.
|
||||
next_local_orbit_radius += randf_range(40, 100)
|
||||
|
||||
# --- FIX for stations orbiting moons ---
|
||||
var num_moon_stations = randi_range(min_moon_stations, max_moon_stations)
|
||||
var next_moon_station_orbit = randf_range(20, 50) # Start closer for moon stations
|
||||
|
||||
for i in range(num_moon_stations):
|
||||
var station_instance = station_scene.instantiate() as CelestialBody
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Station " + str(i + 1)
|
||||
station_instance.primary = moon_instance
|
||||
station_instance.mass = STATION_MASS
|
||||
|
||||
# Set the orbit relative to the moon.
|
||||
station_instance.orbit_radius_real = next_moon_station_orbit
|
||||
_create_body_in_ring(moon_instance, station_instance)
|
||||
|
||||
# Increment for the next station around this *same* moon.
|
||||
next_moon_station_orbit += randf_range(20, 50)
|
||||
|
||||
#var num_moon_stations = randi_range(min_moon_stations, max_moon_stations)
|
||||
#
|
||||
#var moon_inner_orbit = randf_range(2, 5)
|
||||
#
|
||||
#for i in range(num_moon_stations):
|
||||
## Generate a space station.
|
||||
#var station_instance = station_scene.instantiate() as CelestialBody
|
||||
#system_data.stations.append(station_instance)
|
||||
#station_instance.name = "Station " + str(i + 1)
|
||||
#station_instance.primary = moon_instance
|
||||
#station_instance.mass = STATION_MASS
|
||||
#station_instance.orbit_radius_real = moon_inner_orbit
|
||||
#_create_body_in_ring(moon_instance, station_instance)
|
||||
#
|
||||
#moon_inner_orbit = moon_inner_orbit + randf_range(2, 5)
|
||||
|
||||
|
||||
# Creates a single asteroid belt.
|
||||
func _create_asteroid_belt(primary: RigidBody2D, initial_offset: float) -> AsteroidBelt:
|
||||
var num_asteroids = randi_range(min_asteroids_per_belt, max_asteroids_per_belt)
|
||||
var belt = AsteroidBelt.new()
|
||||
|
||||
for i in range(num_asteroids):
|
||||
var asteroid_instance = asteroid_scene.instantiate() as CelestialBody
|
||||
asteroid_instance.name = "Asteroid " + str(i + 1)
|
||||
asteroid_instance.primary = primary
|
||||
asteroid_instance.mass = ASTEROID_MASS # randf_range(1e10, 1e23)
|
||||
|
||||
belt.asteroids.append(asteroid_instance)
|
||||
|
||||
belt.mass = belt.asteroids.reduce(func(accum, asteroid : CelestialBody): return accum + asteroid.mass, 0.0)
|
||||
var offset = belt.mass * orbit_buffer
|
||||
belt.centered_radius = initial_offset + offset
|
||||
|
||||
for asteroid in belt.asteroids:
|
||||
# Asteroids within a belt have a random orbit radius within a certain range.
|
||||
asteroid.orbit_radius_real = belt.centered_radius + randf_range(-offset / 2, offset / 2)
|
||||
|
||||
_create_body_in_ring(primary, asteroid)
|
||||
|
||||
return belt
|
||||
|
||||
# Helper function to instantiate and place a body in a ring.
|
||||
func _create_body_in_ring(primary: CelestialBody, body_instance: CelestialBody) -> void:
|
||||
# 1. Parent the new body to its primary FIRST.
|
||||
# This establishes the correct local coordinate space.
|
||||
primary.add_child(body_instance)
|
||||
|
||||
# 2. Calculate the position vector relative to the parent.
|
||||
var initial_position_vector = Vector2(body_instance.orbit_radius_real, 0).rotated(randf() * TAU)
|
||||
|
||||
# 3. Set the LOCAL position of the body.
|
||||
# Godot will now correctly calculate its global_position based on the parent's position.
|
||||
body_instance.position = initial_position_vector
|
||||
|
||||
# 4. Now that the global position is correct, calculate the velocity for a stable orbit.
|
||||
# This part remains the same, as physics calculations need the final global positions.
|
||||
body_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(body_instance, primary)
|
||||
|
||||
print("Created " + body_instance.name + " with radius " + str(body_instance.orbit_radius_real) + " and mass " + str(body_instance.mass))
|
||||
print("Initial orbital velocity is: " + str(body_instance.linear_velocity))
|
||||
|
||||
# Recursively finds all celestial bodies in the scene.
|
||||
func get_all_bodies() -> Array:
|
||||
var bodies = []
|
||||
for child in get_children():
|
||||
if child is CelestialBody:
|
||||
bodies.append(child)
|
||||
# Recursively add children if they are also celestial bodies.
|
||||
for sub_child in child.get_children():
|
||||
if sub_child is CelestialBody:
|
||||
bodies.append(sub_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[CelestialBody]
|
||||
|
||||
class SystemData:
|
||||
var star : CelestialBody
|
||||
var planets : Array[CelestialBody]
|
||||
var moons: Array[CelestialBody]
|
||||
var stations : Array[CelestialBody]
|
||||
var belts : Array[AsteroidBelt]
|
||||
|
||||
func all_bodies() -> Array[CelestialBody]:
|
||||
var bodies : Array[CelestialBody] = [star]
|
||||
bodies.append_array(planets)
|
||||
bodies.append_array(stations)
|
||||
|
||||
bodies.append_array(moons)
|
||||
|
||||
var all_asteroids : Array[CelestialBody] = []
|
||||
for belt in belts:
|
||||
all_asteroids.append_array(belt.asteroids)
|
||||
|
||||
bodies.append_array(all_asteroids)
|
||||
return bodies
|
||||
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 994 B |
6
src/main.tscn
Normal file
6
src/main.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bkcouefvi7iup" path="res://scripts/star_system.gd" id="1_ig7tw"]
|
||||
|
||||
[node name="StarSystem" type="Node3D"]
|
||||
script = ExtResource("1_ig7tw")
|
||||
566
src/modules/3d_test_ship.tscn
Normal file
566
src/modules/3d_test_ship.tscn
Normal file
@ -0,0 +1,566 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bkwogkfqk2uxo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ktv2t"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_shb7f"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_ism2t"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ism2t"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[node name="3dTestShip" type="RigidBody3D" unique_id=246037729]
|
||||
script = ExtResource("1_ktv2t")
|
||||
physics_mode = 1
|
||||
base_mass = 10000.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate7" parent="." unique_id=1182121679 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate8" parent="." unique_id=294855274 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate9" parent="." unique_id=130054924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate4" parent="." unique_id=2133064539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate5" parent="." unique_id=1436331513 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate6" parent="." unique_id=1249365999 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate11" parent="." unique_id=1656979163 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate13" parent="." unique_id=1426276711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate14" parent="." unique_id=1212526811 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate15" parent="." unique_id=403515873 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate16" parent="." unique_id=145935239 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate17" parent="." unique_id=1662804653 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate18" parent="." unique_id=741829932 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate21" parent="." unique_id=31417961 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate22" parent="." unique_id=1845702661 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate23" parent="." unique_id=1747432968 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate24" parent="." unique_id=1486518216 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate25" parent="." unique_id=1880158566 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate26" parent="." unique_id=1506445603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate27" parent="." unique_id=1749302489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate31" parent="." unique_id=1965678834 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate32" parent="." unique_id=515940324 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate33" parent="." unique_id=313389603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate34" parent="." unique_id=363616195 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate35" parent="." unique_id=568985619 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate36" parent="." unique_id=193191417 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate38" parent="." unique_id=1152815429 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate40" parent="." unique_id=1303768723 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate41" parent="." unique_id=1489680526 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate42" parent="." unique_id=1454642421 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate43" parent="." unique_id=1322280114 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate44" parent="." unique_id=1380061102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate45" parent="." unique_id=1740305308 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate48" parent="." unique_id=587023569 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate49" parent="." unique_id=1103858035 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate50" parent="." unique_id=916625356 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate51" parent="." unique_id=2115734988 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate52" parent="." unique_id=1715698306 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate53" parent="." unique_id=369018899 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate54" parent="." unique_id=1618415296 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate57" parent="." unique_id=1148292814 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 0, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate58" parent="." unique_id=1183219370 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate59" parent="." unique_id=95522376 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate60" parent="." unique_id=960534764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, -1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate61" parent="." unique_id=1862079328 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate62" parent="." unique_id=876185578 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate64" parent="." unique_id=622302151 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate65" parent="." unique_id=2027647666 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate66" parent="." unique_id=335333911 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate63" parent="." unique_id=779321466 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate69" parent="." unique_id=391423682 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate70" parent="." unique_id=1436426809 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate71" parent="." unique_id=1045660804 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate72" parent="." unique_id=1696784058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate73" parent="." unique_id=1709873058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate74" parent="." unique_id=1071906843 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate75" parent="." unique_id=413542580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate76" parent="." unique_id=448578032 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate77" parent="." unique_id=1162322851 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate78" parent="." unique_id=790206161 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate79" parent="." unique_id=1019136641 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate80" parent="." unique_id=152922175 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate81" parent="." unique_id=771888008 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate82" parent="." unique_id=816092557 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate83" parent="." unique_id=1871920861 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate84" parent="." unique_id=103727539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate85" parent="." unique_id=1457444620 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate86" parent="." unique_id=1402217859 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate87" parent="." unique_id=293240152 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate88" parent="." unique_id=158231735 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate89" parent="." unique_id=2017317978 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate90" parent="." unique_id=1810711362 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate91" parent="." unique_id=648502427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate92" parent="." unique_id=1280848561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate93" parent="." unique_id=1000182357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate94" parent="." unique_id=663755561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate95" parent="." unique_id=977211031 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate96" parent="." unique_id=1017704164 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate97" parent="." unique_id=2095269489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate98" parent="." unique_id=615154295 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate99" parent="." unique_id=1435686924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate100" parent="." unique_id=361501534 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate101" parent="." unique_id=776176100 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate102" parent="." unique_id=1146417492 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate103" parent="." unique_id=1413321748 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate104" parent="." unique_id=1044980803 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate105" parent="." unique_id=1804409489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate106" parent="." unique_id=1076107521 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate107" parent="." unique_id=1190510681 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate108" parent="." unique_id=855909591 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate109" parent="." unique_id=946006990 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate110" parent="." unique_id=1957722835 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate111" parent="." unique_id=1708941560 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate112" parent="." unique_id=598393913 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate113" parent="." unique_id=629535431 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate114" parent="." unique_id=1483594858 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate115" parent="." unique_id=1186769437 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate116" parent="." unique_id=752889015 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate117" parent="." unique_id=175698677 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate118" parent="." unique_id=670641245 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate119" parent="." unique_id=988678524 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate120" parent="." unique_id=896262764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate121" parent="." unique_id=1336630931 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate122" parent="." unique_id=101919359 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate123" parent="." unique_id=1356736016 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate124" parent="." unique_id=742815341 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate125" parent="." unique_id=1651537246 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate126" parent="." unique_id=1253078352 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate127" parent="." unique_id=519787812 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate128" parent="." unique_id=629828036 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate129" parent="." unique_id=2010663580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate130" parent="." unique_id=1705163002 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate131" parent="." unique_id=1635599014 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate132" parent="." unique_id=789401102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate133" parent="." unique_id=1671040057 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate134" parent="." unique_id=2118015321 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate135" parent="." unique_id=1970124357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate136" parent="." unique_id=2129372302 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate137" parent="." unique_id=543355427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate138" parent="." unique_id=1885736043 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate139" parent="." unique_id=654209436 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate140" parent="." unique_id=1938132143 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate141" parent="." unique_id=486424951 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate142" parent="." unique_id=910140496 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate143" parent="." unique_id=515293159 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate144" parent="." unique_id=890871001 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate145" parent="." unique_id=1626468827 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate146" parent="." unique_id=578516444 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate147" parent="." unique_id=402255852 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate148" parent="." unique_id=1631434711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate149" parent="." unique_id=726702930 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate150" parent="." unique_id=1001521061 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Spawner" parent="." unique_id=6714366 instance=ExtResource("3_ism2t")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1071155008]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, -3)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="." unique_id=151820223]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, -3)
|
||||
|
||||
[node name="OmniLight3D3" type="OmniLight3D" parent="." unique_id=390575041]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, 4)
|
||||
|
||||
[node name="OmniLight3D4" type="OmniLight3D" parent="." unique_id=1659652061]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, 4)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1905582997]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2096937457]
|
||||
replication_config = SubResource("SceneReplicationConfig_ism2t")
|
||||
26
src/modules/physics_testing_ship.tscn
Normal file
26
src/modules/physics_testing_ship.tscn
Normal file
@ -0,0 +1,26 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://xcgmicfdqqb1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ogx5r"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_nyqc6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_3bya3"]
|
||||
|
||||
[node name="PhysicsTestingShip" type="RigidBody3D"]
|
||||
script = ExtResource("1_ogx5r")
|
||||
base_mass = 200.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_nyqc6")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, -1, 0)
|
||||
|
||||
[node name="Spawner" parent="." instance=ExtResource("3_3bya3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.021089494, 0)
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -2)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, -2)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
7
src/modules/test_ship.tscn
Normal file
7
src/modules/test_ship.tscn
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cwblg6q5qse6c"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="1_obkto"]
|
||||
|
||||
[node name="TestShip" type="Node3D"]
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("1_obkto")]
|
||||
@ -12,14 +12,24 @@ config_version=5
|
||||
|
||||
config/name="space_simulation"
|
||||
run/main_scene="uid://dogqi2c58qdc0"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/features=PackedStringArray("4.6", "Double Precision", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
OrbitalMechanics="*res://scripts/singletons/orbital_mechanics.gd"
|
||||
SignalBus="*res://scripts/singletons/signal_bus.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]
|
||||
|
||||
@ -82,19 +92,90 @@ interact={
|
||||
"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)
|
||||
]
|
||||
}
|
||||
toggle_wiring_panel={
|
||||
"deadzone": 0.2,
|
||||
"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)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
2d_render/layer_2="UI_Panels"
|
||||
2d_physics/layer_1="hullplates"
|
||||
2d_physics/layer_2="ship_components"
|
||||
2d_physics/layer_3="celestial_bodies"
|
||||
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)
|
||||
@ -7,7 +7,7 @@ signal follow_requested(body: Node2D)
|
||||
|
||||
@onready var name_label: Label = $NameLabel
|
||||
|
||||
var body_reference: Node2D
|
||||
var body_reference: OrbitalBody3D
|
||||
var dot_color: Color = Color.WHITE
|
||||
|
||||
var hover_tween: Tween
|
||||
@ -27,17 +27,11 @@ func _ready() -> void:
|
||||
mouse_entered.connect(_on_mouse_entered)
|
||||
mouse_exited.connect(_on_mouse_exited)
|
||||
|
||||
func initialize(body: Node2D):
|
||||
func initialize(body: OrbitalBody3D):
|
||||
body_reference = body
|
||||
name_label.text = body.name
|
||||
|
||||
if body is OrbitalBody2D:
|
||||
dot_color = Color.CYAN
|
||||
elif body is CelestialBody:
|
||||
match body.get_class_name():
|
||||
"Star": dot_color = Color.GOLD
|
||||
"Planet": dot_color = Color.DODGER_BLUE
|
||||
"Moon": dot_color = Color.PURPLE
|
||||
dot_color = Color.CYAN
|
||||
|
||||
self.tooltip_text = _generate_tooltip_text()
|
||||
|
||||
@ -79,7 +73,8 @@ func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
emit_signal("selected", body_reference)
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
emit_signal("follow_requested", body_reference)
|
||||
print(body_reference)
|
||||
follow_requested.emit(body_reference)
|
||||
|
||||
|
||||
# No changes are needed here; the tweens will automatically use the new setter function.
|
||||
@ -97,28 +92,26 @@ func _generate_tooltip_text() -> String:
|
||||
var info = [body_reference.name]
|
||||
|
||||
if body_reference is CelestialBody:
|
||||
var celestial = body_reference as CelestialBody
|
||||
if is_instance_valid(celestial.primary):
|
||||
var mu = OrbitalMechanics.G * celestial.primary.mass
|
||||
var r = celestial.global_position.distance_to(celestial.primary.global_position)
|
||||
var period_seconds = TAU * sqrt(pow(r, 3) / mu)
|
||||
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
|
||||
var planet_system = body_reference.get_parent() as Barycenter
|
||||
var period_seconds = OrbitalMechanics.get_orbital_time_in_seconds(planet_system, GameManager.get_system_data().star)
|
||||
|
||||
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
|
||||
|
||||
var moon_count = 0
|
||||
for child in celestial.get_children():
|
||||
if child is CelestialBody and child.get_class_name() == "Moon":
|
||||
for child in planet_system.get_internal_attractors():
|
||||
if child is CelestialBody:
|
||||
moon_count += 1
|
||||
if moon_count > 0:
|
||||
info.append("Moons: %d" % moon_count)
|
||||
|
||||
if body_reference is Spaceship:
|
||||
if body_reference is Module:
|
||||
info.append("Class: Player Vessel")
|
||||
info.append("Mass: %.0f kg" % body_reference.mass)
|
||||
|
||||
return "\n".join(info)
|
||||
|
||||
func _format_seconds_to_mmss(seconds: float) -> String:
|
||||
var total_seconds = int(seconds)
|
||||
var minutes = total_seconds / 60
|
||||
var total_seconds: int = int(seconds)
|
||||
var minutes: int = total_seconds / 60
|
||||
var seconds_rem = total_seconds % 60
|
||||
return "%d min, %d sec" % [minutes, seconds_rem]
|
||||
45
src/scenes/UI/ui_window.gd
Normal file
45
src/scenes/UI/ui_window.gd
Normal file
@ -0,0 +1,45 @@
|
||||
# CustomWindow.gd
|
||||
extends VBoxContainer
|
||||
class_name UiWindow
|
||||
|
||||
## Emitted when the custom "Flip" button is pressed.
|
||||
signal flip_button_pressed
|
||||
signal close_requested(c: Control)
|
||||
|
||||
@onready var title_bar: PanelContainer = $TitleBar
|
||||
@onready var title_label: Label = %TitleLabel
|
||||
@onready var flip_button: Button = %FlipButton
|
||||
@onready var close_button: Button = %CloseButton
|
||||
@onready var content_container: MarginContainer = %ContentContainer
|
||||
|
||||
var is_dragging: bool = false
|
||||
var title: String = ""
|
||||
|
||||
func _ready():
|
||||
# Connect the buttons to their functions
|
||||
close_button.pressed.connect(_close) # Or emit_signal("close_requested")
|
||||
flip_button.pressed.connect(flip_button_pressed.emit)
|
||||
|
||||
# Connect the title bar's input signal to handle dragging
|
||||
title_bar.gui_input.connect(_on_title_bar_gui_input)
|
||||
|
||||
# Set the window title from the property
|
||||
title_label.text = title
|
||||
|
||||
func _close():
|
||||
close_requested.emit(self)
|
||||
|
||||
# This function adds your main content (like the PanelFrame) into the window.
|
||||
func set_content(content_node: Node):
|
||||
for child in content_container.get_children():
|
||||
content_container.remove_child(child)
|
||||
|
||||
content_container.add_child(content_node)
|
||||
|
||||
func _on_title_bar_gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
is_dragging = event.is_pressed()
|
||||
|
||||
if event is InputEventMouseMotion and is_dragging:
|
||||
# When dragging, move the entire window by the mouse's relative motion.
|
||||
self.position += event.relative
|
||||
1
src/scenes/UI/ui_window.gd.uid
Normal file
1
src/scenes/UI/ui_window.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://d3g84xgbh8nlp
|
||||
44
src/scenes/UI/ui_window.tscn
Normal file
44
src/scenes/UI/ui_window.tscn
Normal file
@ -0,0 +1,44 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cdnowhkg5cq88"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d3g84xgbh8nlp" path="res://scenes/UI/ui_window.gd" id="1_11aw0"]
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_11aw0"]
|
||||
size = Vector2(16, 16)
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_ishqf"]
|
||||
size = Vector2(16, 16)
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer"]
|
||||
offset_right = 196.0
|
||||
offset_bottom = 36.0
|
||||
script = ExtResource("1_11aw0")
|
||||
|
||||
[node name="TitleBar" type="PanelContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="TitleBar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Placeholder Title"
|
||||
|
||||
[node name="FlipButton" type="Button" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
icon = SubResource("PlaceholderTexture2D_11aw0")
|
||||
|
||||
[node name="CloseButton" type="Button" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
icon = SubResource("PlaceholderTexture2D_ishqf")
|
||||
|
||||
[node name="ContentContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
15
src/scenes/celestial_bodies/celestial_body.gd
Normal file
15
src/scenes/celestial_bodies/celestial_body.gd
Normal file
@ -0,0 +1,15 @@
|
||||
class_name CelestialBody extends OrbitalBody3D
|
||||
|
||||
# --- Set in corresponding scene ---
|
||||
# var auto_proxy_gravity = false
|
||||
@export var radius: float = 100.0
|
||||
|
||||
func set_radius(value: float):
|
||||
radius = value
|
||||
|
||||
if $Surface.mesh is SphereMesh:
|
||||
$Surface.mesh.radius = radius
|
||||
$Surface.mesh.height = radius * 2.0
|
||||
|
||||
if $CollisionShape3D.shape is SphereShape3D:
|
||||
$CollisionShape3D.shape.radius = radius
|
||||
1
src/scenes/celestial_bodies/celestial_body.gd.uid
Normal file
1
src/scenes/celestial_bodies/celestial_body.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dok35h0q4pseh
|
||||
28
src/scenes/celestial_bodies/celestial_body.tscn
Normal file
28
src/scenes/celestial_bodies/celestial_body.tscn
Normal file
@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://dv18eg4xrlefe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dok35h0q4pseh" path="res://scenes/celestial_bodies/celestial_body.gd" id="1_uxu4s"]
|
||||
[ext_resource type="Material" uid="uid://de0xnmjf12ted" path="res://scenes/celestial_bodies/materials/sun_mat.tres" id="2_vi0nt"]
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_vi0nt"]
|
||||
resource_local_to_scene = true
|
||||
material = ExtResource("2_vi0nt")
|
||||
radius = 2000.0
|
||||
height = 4000.0
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_uxu4s"]
|
||||
|
||||
[node name="CelestialBody" type="RigidBody3D"]
|
||||
script = ExtResource("1_uxu4s")
|
||||
auto_proxy_gravity = false
|
||||
metadata/_custom_type_script = "uid://dok35h0q4pseh"
|
||||
|
||||
[node name="Surface" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("SphereMesh_vi0nt")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("SphereShape3D_uxu4s")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
light_color = Color(0.958646, 0.7997282, 0.55087835, 1)
|
||||
omni_range = 200000.0
|
||||
omni_attenuation = 2.0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user