extends CharacterBody3D ## NAF Resolute CVT-07 — Roci-style torch + attitude. ## This script on the CharacterBody3D. Hull child named "Resolute" wears resolute_driver.gd. ## Sibling of SolarSystem. Walk-to-helm later; F enter is the demo stub. ## ## Throttle: Alt + mouse wheel → commanded G, 0..5. Cruise ~0.8 G. ## Nose +Y. Drive plume follows throttle. RCS puffs on attitude input. ## Plugs: pdc_aim_dir, radar_contacts, combat_ui — fill when Nebulous layer lands. @onready var interaction_area: Area3D = get_node_or_null("InteractionArea") @onready var hull: Node3D = get_node_or_null("Resolute") @onready var third_person_camera: Camera3D = get_node_or_null("ThirdPersonCamera") @onready var interact_prompt: Label = get_node_or_null("/root/Node3D/HUD/InteractPrompt") @onready var pause_menu: Control = get_node_or_null("/root/Node3D/HUD/PauseMenu") @onready var flight_label: Label = get_node_or_null("/root/Node3D/HUD/FlightLabel") @onready var body_collision: CollisionShape3D = get_node_or_null("CollisionShape3D") var player_inside: bool = false var player_ref: CharacterBody3D = null var using_third_person: bool = false var f_was_down: bool = false var _v_was: bool = false var mass: float = 280000.0 const G0: float = 9.81 const G_MAX: float = 5.0 const G_CRUISE: float = 0.80 const G_STEP: float = 0.10 var g_cmd: float = 0.0 var drive_armed: bool = true var ang_vel: Vector3 = Vector3.ZERO @export var ang_accel: float = 0.48 @export var ang_max: float = 0.95 @export var ang_damp: float = 2.2 var surface_friction: float = 4.0 const MOON_RADIUS: float = 173700.0 const MOON_G: float = 1.62 const MOON_SOI: float = 400000.0 const EARTH_RADIUS: float = 637100.0 const EARTH_G: float = 9.81 const EARTH_SOI: float = 20000000.0 const CAM_FAR: float = 1000000.0 const NEAR_SURFACE_MARGIN: float = 400.0 var free_look: bool = false var look_sensitivity: float = 0.003 var orbit_yaw: float = 0.0 var orbit_pitch: float = 0.35 var orbit_distance: float = 48.0 var orbit_pitch_min: float = -1.15 var orbit_pitch_max: float = 1.15 var helm_camera: Camera3D = null var _gear_down: bool = true var _rads_out: bool = true var _airlock_open: bool = false var _dock_open: bool = false var _cargo_open: bool = false var _deck_open: bool = false var _tubes_open: bool = false var _battle: bool = false var _chaff_t: float = 0.0 ## Combat plugs — empty until radar / track file exists. var pdc_target: Node3D = null var radar_contacts: Array = [] var combat_ui_open: bool = false var telem_body: String = "—" var telem_alt: float = 0.0 var telem_speed: float = 0.0 var telem_vspeed: float = 0.0 var telem_hspeed: float = 0.0 var telem_vcirc: float = 0.0 var telem_has_alt: bool = false func _ready() -> void: _ensure_actions() if hull == null or hull == self: hull = get_node_or_null("Resolute") as Node3D if hull == null or hull == self: hull = find_child("NAF_Resolute", true, false) as Node3D if hull == null or hull == self: hull = find_child("Resolute", true, false) as Node3D if interaction_area: interaction_area.monitoring = true interaction_area.monitorable = true interaction_area.body_entered.connect(_on_body_entered) interaction_area.body_exited.connect(_on_body_exited) helm_camera = _find_helm_cam() if helm_camera: helm_camera.near = 0.05 helm_camera.far = CAM_FAR helm_camera.current = false if third_person_camera: third_person_camera.near = 0.2 third_person_camera.far = CAM_FAR third_person_camera.current = false if flight_label: flight_label.visible = false _set_hull("gear_deploy", 1.0) _set_hull("radiators_deploy", 1.0) print("Resolute ready hull=", hull, " helm=", helm_camera) func _find_helm_cam() -> Camera3D: var root: Node = hull if hull else self var n := root.find_child("HelmCamera", true, false) if n is Camera3D: return n as Camera3D n = root.find_child("SeatCamera", true, false) if n is Camera3D: return n as Camera3D return get_node_or_null("CameraPivot/CapsuleCamera") as Camera3D func _set_hull(prop: String, value: Variant) -> void: if hull and prop in hull: hull.set(prop, value) func _moon() -> Node3D: return get_node_or_null("/root/Node3D/SolarSystem/Moon") as Node3D func _earth() -> Node3D: return get_node_or_null("/root/Node3D/SolarSystem/Earth") as Node3D func _active_gravity() -> Dictionary: var moon := _moon() if moon: var d_m: float = global_position.distance_to(moon.global_position) if d_m >= 1.0 and d_m <= MOON_SOI: var to_c: Vector3 = moon.global_position - global_position return {"name": "Luna", "center": moon.global_position, "radius": MOON_RADIUS, "g": MOON_G, "accel": to_c.normalized() * MOON_G, "dist": d_m} var earth := _earth() if earth: var d_e: float = global_position.distance_to(earth.global_position) if d_e >= 1.0 and d_e <= EARTH_SOI: var to_c2: Vector3 = earth.global_position - global_position return {"name": "Earth", "center": earth.global_position, "radius": EARTH_RADIUS, "g": EARTH_G, "accel": to_c2.normalized() * EARTH_G, "dist": d_e} return {"name": "—", "center": Vector3.ZERO, "radius": 0.0, "g": 0.0, "accel": Vector3.ZERO, "dist": 0.0} func _on_body_entered(body: Node3D) -> void: if body.is_in_group("player"): player_ref = body if interact_prompt and not player_inside: interact_prompt.text = "Press F to Board Resolute" interact_prompt.visible = true func _on_body_exited(body: Node3D) -> void: if body.is_in_group("player") and not player_inside: player_ref = null if interact_prompt: interact_prompt.visible = false func _unhandled_input(event: InputEvent) -> void: if not player_inside: return if event.is_action_pressed("ui_cancel"): if pause_menu and pause_menu.has_method("open_pause") and player_ref: pause_menu.open_pause(player_ref) get_viewport().set_input_as_handled() return if event is InputEventMouseButton and Input.is_key_pressed(KEY_ALT): if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed: g_cmd = minf(G_MAX, g_cmd + G_STEP) get_viewport().set_input_as_handled() elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed: g_cmd = maxf(0.0, g_cmd - G_STEP) get_viewport().set_input_as_handled() if _tap(event, KEY_G): _gear_down = not _gear_down _set_hull("gear_deploy", 1.0 if _gear_down else 0.0) elif _tap(event, KEY_R): _rads_out = not _rads_out _set_hull("radiators_deploy", 1.0 if _rads_out else 0.0) elif _tap(event, KEY_H): _airlock_open = not _airlock_open _set_hull("hatch_airlock_open", 1.0 if _airlock_open else 0.0) elif _tap(event, KEY_J): _dock_open = not _dock_open _set_hull("hatch_dock_open", 1.0 if _dock_open else 0.0) elif _tap(event, KEY_U): _cargo_open = not _cargo_open _set_hull("hatch_cargo_open", 1.0 if _cargo_open else 0.0) elif _tap(event, KEY_M): _deck_open = not _deck_open _set_hull("hatch_deck_open", 1.0 if _deck_open else 0.0) elif _tap(event, KEY_O): _tubes_open = not _tubes_open _set_hull("torpedo_doors_open", 1.0 if _tubes_open else 0.0) elif _tap(event, KEY_B): _battle = not _battle _set_hull("battle_lights", _battle) elif _tap(event, KEY_X): _chaff_t = 0.9 elif _tap(event, KEY_T): drive_armed = not drive_armed elif _tap(event, KEY_C): if g_cmd < 0.05: g_cmd = G_CRUISE else: g_cmd = 0.0 func _ensure_actions() -> void: var binds := { "interact": KEY_F, "toggle_first_person": KEY_V, } for action in binds.keys(): if InputMap.has_action(action): continue InputMap.add_action(action) var ev := InputEventKey.new() ev.physical_keycode = binds[action] InputMap.action_add_event(action, ev) func _act_pressed(action: String) -> bool: return InputMap.has_action(action) and Input.is_action_pressed(action) func _act_just(action: String) -> bool: return InputMap.has_action(action) and Input.is_action_just_pressed(action) func _tap(event: InputEvent, key: Key) -> bool: if event is InputEventKey and event.pressed and not event.echo and event.physical_keycode == key: get_viewport().set_input_as_handled() return true return false func _process(delta: float) -> void: var interact_now := _act_just("interact") or (Input.is_physical_key_pressed(KEY_F) and not f_was_down) if not player_inside and player_ref and interact_now: f_was_down = true enter_ship() return if not player_inside: f_was_down = Input.is_physical_key_pressed(KEY_F) return if interact_now and not f_was_down: f_was_down = true exit_ship() return f_was_down = _act_pressed("interact") or Input.is_physical_key_pressed(KEY_F) if _act_just("toggle_first_person") or _just_v(): toggle_camera() free_look = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) if Input.is_key_pressed(KEY_ALT): Input.mouse_mode = Input.MOUSE_MODE_VISIBLE else: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED handle_flight(delta) _update_telemetry() _update_flight_hud() _push_combat_plugs() if using_third_person: update_third_person_camera() if third_person_camera and not third_person_camera.current: third_person_camera.make_current() elif helm_camera and not helm_camera.current: helm_camera.make_current() func _just_v() -> bool: var down := Input.is_physical_key_pressed(KEY_V) var edge := down and not _v_was _v_was = down return edge func _input(event: InputEvent) -> void: if not player_inside or not free_look or not using_third_person: return if event is InputEventMouseMotion: orbit_yaw -= event.relative.x * look_sensitivity orbit_pitch += event.relative.y * look_sensitivity orbit_pitch = clampf(orbit_pitch, orbit_pitch_min, orbit_pitch_max) func handle_flight(delta: float) -> void: var pitch_in := 0.0 var yaw_in := 0.0 var roll_in := 0.0 if Input.is_key_pressed(KEY_W): pitch_in -= 1.0 if Input.is_key_pressed(KEY_S): pitch_in += 1.0 if Input.is_key_pressed(KEY_A): yaw_in += 1.0 if Input.is_key_pressed(KEY_D): yaw_in -= 1.0 if Input.is_key_pressed(KEY_Q): roll_in -= 1.0 if Input.is_key_pressed(KEY_E): roll_in += 1.0 var torque := Vector3(pitch_in, roll_in, yaw_in) * ang_accel if torque.length_squared() < 0.0001: ang_vel = ang_vel.move_toward(Vector3.ZERO, ang_damp * delta) else: ang_vel += torque * delta if ang_vel.length() > ang_max: ang_vel = ang_vel.normalized() * ang_max if ang_vel.length_squared() > 0.000001: rotate_object_local(Vector3.RIGHT, ang_vel.x * delta) rotate_object_local(Vector3.UP, ang_vel.y * delta) rotate_object_local(Vector3.FORWARD, -ang_vel.z * delta) var thrusting := drive_armed and g_cmd > 0.04 _set_hull("drive_on", thrusting) _set_hull("throttle", clampf(g_cmd / G_MAX, 0.0, 1.0) if thrusting else 0.0) _set_hull("rcs_attitude", pitch_in != 0.0 or yaw_in != 0.0 or roll_in != 0.0) _set_hull("pdc_fire", Input.is_physical_key_pressed(KEY_P)) if _chaff_t > 0.0: _chaff_t -= delta _set_hull("chaff_fire", true) else: _set_hull("chaff_fire", false) var acceleration: Vector3 = _active_gravity()["accel"] if thrusting: acceleration += global_transform.basis.y * (g_cmd * G0) velocity += acceleration * delta var near_surface := is_on_floor() if not near_surface: var moon := _moon() if moon and global_position.distance_to(moon.global_position) < MOON_RADIUS + NEAR_SURFACE_MARGIN: near_surface = true var earth := _earth() if earth and global_position.distance_to(earth.global_position) < EARTH_RADIUS + NEAR_SURFACE_MARGIN: near_surface = true if near_surface: if body_collision: body_collision.disabled = false move_and_slide() if is_on_floor(): velocity = velocity.move_toward(Vector3.ZERO, surface_friction * delta) else: if body_collision: body_collision.disabled = true global_position += velocity * delta PhysicsServer3D.body_set_state(get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, global_transform) PhysicsServer3D.body_set_state(get_rid(), PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY, velocity) func _update_telemetry() -> void: telem_speed = velocity.length() var body: Dictionary = _active_gravity() telem_body = str(body["name"]) if telem_body == "—": telem_has_alt = false telem_alt = 0.0 telem_vspeed = 0.0 telem_hspeed = telem_speed telem_vcirc = 0.0 return telem_has_alt = true var r: float = float(body["dist"]) var rad: float = float(body["radius"]) var g: float = float(body["g"]) telem_alt = r - rad var radial: Vector3 = (global_position - body["center"]).normalized() telem_vspeed = velocity.dot(radial) telem_hspeed = (velocity - radial * telem_vspeed).length() telem_vcirc = sqrt(g * rad * rad / r) if r > 1.0 else 0.0 func _update_flight_hud() -> void: if flight_label == null or not player_inside: return flight_label.visible = true var mode := "3P" if using_third_person else "1P" var vs_sign := "+" if telem_vspeed >= 0.0 else "" var alt_txt := "ALT %8.0f m" % telem_alt if telem_has_alt else "ALT —" flight_label.text = ( "[%s] RESOLUTE | BODY %s\n" % [mode, telem_body] + "G %4.2f / %.0f %s\n" % [g_cmd, G_MAX, "DRIVE" if drive_armed and g_cmd > 0.04 else "COAST"] + alt_txt + "\n" + "SPD %8.1f m/s\n" % telem_speed + "VS %s%7.1f m/s\n" % [vs_sign, telem_vspeed] + "H %8.1f m/s" % telem_hspeed ) func _push_combat_plugs() -> void: if hull == null: return if "pdc_aim_dir" in hull: if pdc_target and is_instance_valid(pdc_target): hull.pdc_aim_dir = (pdc_target.global_position - global_position).normalized() else: hull.pdc_aim_dir = global_transform.basis.y if "radar_contacts" in hull: hull.radar_contacts = radar_contacts func set_pdc_target(node: Node3D) -> void: pdc_target = node func set_radar_contacts(list: Array) -> void: radar_contacts = list func enter_ship() -> void: if player_inside or player_ref == null: return player_inside = true if interact_prompt: interact_prompt.visible = false player_ref.reparent(self) var spawn := _player_spawn() if spawn: player_ref.global_transform = spawn.global_transform else: player_ref.position = Vector3(0, 1.2, 0) player_ref.rotation = Vector3.ZERO player_ref.set_physics_process(false) player_ref.set_process_input(false) player_ref.set_process_unhandled_input(false) if player_ref.has_method("set_in_vehicle"): player_ref.set_in_vehicle(true) var player_col = player_ref.get_node_or_null("CollisionShape3D") if player_col: player_col.disabled = true var player_cam = player_ref.get_node_or_null("CameraPivot/Camera3D") if player_cam: player_cam.current = false player_ref.visible = false using_third_person = false f_was_down = true if helm_camera: helm_camera.current = true helm_camera.make_current() FloatingOrigin.set_focus(self) Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if flight_label: flight_label.visible = true func exit_ship() -> void: if not player_inside or player_ref == null: return var exiting: CharacterBody3D = player_ref player_inside = false g_cmd = 0.0 drive_armed = true ang_vel = Vector3.ZERO _set_hull("drive_on", false) _set_hull("throttle", 0.0) _set_hull("rcs_attitude", false) _set_hull("pdc_fire", false) _set_hull("chaff_fire", false) if flight_label: flight_label.visible = false if helm_camera: helm_camera.current = false if third_person_camera: third_person_camera.current = false var station := get_node_or_null("/root/Node3D/SolarSystem/Moon/LovellAnchor/Station") var dest: Node = station if station else get_tree().current_scene exiting.reparent(dest) var hatch := _airlock_exit() if hatch: exiting.global_position = hatch.global_position + hatch.global_transform.basis.y * 2.0 else: exiting.global_position = global_position + global_transform.basis.x * 8.0 exiting.visible = true exiting.set_physics_process(true) exiting.set_process_input(true) exiting.set_process_unhandled_input(true) var player_col = exiting.get_node_or_null("CollisionShape3D") if player_col: player_col.disabled = false if exiting.has_method("set_in_vehicle"): exiting.set_in_vehicle(false) var player_cam = exiting.get_node_or_null("CameraPivot/Camera3D") if player_cam: player_cam.current = true player_cam.make_current() if exiting.has_method("restore_from_vehicle"): exiting.restore_from_vehicle() FloatingOrigin.set_focus(exiting) player_ref = null func _player_spawn() -> Node3D: var root: Node = hull if hull else self return root.find_child("PlayerSpawn", true, false) as Node3D func _airlock_exit() -> Node3D: var root: Node = hull if hull else self var n := root.find_child("hatch_airlock_outer", true, false) return n as Node3D func toggle_camera() -> void: using_third_person = not using_third_person if using_third_person: if helm_camera: helm_camera.current = false if third_person_camera: third_person_camera.make_current() else: if third_person_camera: third_person_camera.current = false if helm_camera: helm_camera.make_current() func update_third_person_camera() -> void: if not third_person_camera: return var cy := cos(orbit_yaw) var sy := sin(orbit_yaw) var cp := cos(orbit_pitch) var sp := sin(orbit_pitch) var offset := Vector3(sy * cp, sp, cy * cp) * orbit_distance var local_off := Vector3(offset.x, -offset.z, offset.y) var cam_pos := global_position + global_transform.basis * local_off third_person_camera.global_position = cam_pos var target := global_position + global_transform.basis.y * 4.0 var forward := target - cam_pos if forward.length_squared() < 0.0001: return forward = forward.normalized() var ref_up := global_transform.basis.z if abs(forward.dot(ref_up)) > 0.92: ref_up = global_transform.basis.x var right := ref_up.cross(forward) if right.length_squared() < 0.0001: return right = right.normalized() var up := forward.cross(right).normalized() third_person_camera.global_basis = Basis(-right, up, -forward).orthonormalized()