Files
Brachistochrone/naf_resolute_godot/naf_resolute.gd
T
scorpion21211-cmd 72ca74c67f Initial commit
2026-10-03 23:29:41 -04:00

549 lines
17 KiB
GDScript

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()