Files
millimeters-of-aluminum/scenes/character/character_pawn_3d.gd
2025-12-05 15:50:48 +01:00

139 lines
5.6 KiB
GDScript

# CharacterPawn.gd
extends OrbitalBody3D
class_name CharacterPawn3D
## Core Parameters
@export var collision_energy_loss: float = 0.3
@export var base_inertia: float = 1.0 # Pawn's inertia without suit
## Input Buffers
var _move_input: Vector2 = Vector2.ZERO
var _roll_input: float = 0.0
var _vertical_input: float = 0.0
var _interact_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
var _l_click_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
var _r_click_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
var _pitch_yaw_input: Vector2 = Vector2.ZERO
## Rotation Variables
@onready var camera_anchor: Marker3D = $CameraAnchor
@onready var camera_pivot: Node3D = $CameraAnchor/CameraPivot
@onready var camera: Camera3D = $CameraAnchor/CameraPivot/SpringArm/Camera3D
@export_range(0.1, PI / 2.0) var max_yaw_rad: float = deg_to_rad(80.0)
@export_range(-PI / 2.0 + 0.01, 0) var min_pitch_rad: float = deg_to_rad(-75.0)
@export_range(0, PI / 2.0 - 0.01) var max_pitch_rad: float = deg_to_rad(60.0)
@export var head_turn_lerp_speed: float = 15.0
## Movement Components
@onready var eva_suit_component: EVAMovementComponent = $EVAMovementComponent
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
## Physics State (Managed by Pawn)
@export var angular_damping: float = 0.95 # Base damping
## Other State Variables
var current_gravity: Vector3 = Vector3.ZERO # TODO: Implement gravity detection
var overlapping_ladder_area: Area3D = null
@onready var grip_detector: Area3D = $GripDetector
# Constants for State Checks
const WALKABLE_GRAVITY_THRESHOLD: float = 1.0
func _ready():
# find movement components
if eva_suit_component: print("Found EVA Suit Controller")
if zero_g_movemement_component: print("Found Zero-G Movement Controller")
# Connect grip detector signals
if grip_detector and zero_g_movemement_component:
print("GripDetector Area3D node found")
grip_detector.area_entered.connect(zero_g_movemement_component.on_grip_area_entered)
grip_detector.area_exited.connect(zero_g_movemement_component.on_grip_area_exited)
else:
printerr("GripDetector Area3D node not found on CharacterPawn!")
if name == str(multiplayer.get_unique_id()):
camera.make_current()
camera.process_mode = Node.PROCESS_MODE_ALWAYS
func _process(_delta: float) -> void:
camera_pivot.global_transform = camera_anchor.get_global_transform_interpolated()
func _physics_process(_delta: float):
_apply_mouse_rotation()
_reset_inputs()
func _integrate_forces(state: PhysicsDirectBodyState3D):
if not is_multiplayer_authority(): return
super (state)
# print("Integrating forces for pawn %s" % name)
# print(" Move Input: %s, Vertical Input: %f, Roll Input: %f" % [_move_input, _vertical_input, _roll_input])
# Zero-G Movement
if zero_g_movemement_component:
# We pass the physics state
zero_g_movemement_component.process_movement(state, _move_input, _vertical_input, _roll_input, _l_click_input, _r_click_input)
# EVA Suit Movement
if eva_suit_component and zero_g_movemement_component.movement_state == ZeroGMovementComponent.MovementState.IDLE:
eva_suit_component.process_eva_movement(state, _move_input, _vertical_input, _roll_input, _r_click_input)
# --- Universal Rotation ---
func _apply_mouse_rotation():
if _pitch_yaw_input != Vector2.ZERO:
camera_anchor.rotate_y(-_pitch_yaw_input.x)
# Apply Pitch LOCALLY to pivot
camera_anchor.rotate_object_local(Vector3.RIGHT, _pitch_yaw_input.y)
camera_anchor.rotation.x = clamp(camera_anchor.rotation.x, min_pitch_rad, max_pitch_rad)
_pitch_yaw_input = Vector2.ZERO
camera_anchor.rotation.z = 0.0
# --- Universal Integration & Collision ---
func _integrate_angular_velocity(delta: float):
# (Same integration logic as before using Basis or Quaternions)
if angular_velocity.length_squared() > 0.0001:
rotate(angular_velocity.normalized(), angular_velocity.length() * delta)
# Prevent drift if velocity is very small
if angular_velocity.length_squared() < 0.0001:
angular_velocity = Vector3.ZERO
# --- Input Setters/Resets (Add vertical to set_movement_input) ---
func set_movement_input(move: Vector2, roll: float, vertical: float): _move_input = move; _roll_input = roll; _vertical_input = vertical
func set_interaction_input(interact_input: PlayerController3D.KeyInput): _interact_input = interact_input
func set_rotation_input(pitch_yaw_input: Vector2): _pitch_yaw_input += pitch_yaw_input
func set_click_input(l_action: PlayerController3D.KeyInput, r_action: PlayerController3D.KeyInput):
_l_click_input = l_action
_r_click_input = r_action
func _reset_inputs():
_move_input = Vector2.ZERO
_roll_input = 0.0
_vertical_input = 0.0
_interact_input = PlayerController3D.KeyInput.new(false, false, false)
_l_click_input = PlayerController3D.KeyInput.new(false, false, false)
_r_click_input = PlayerController3D.KeyInput.new(false, false, false)
_pitch_yaw_input = Vector2.ZERO # Keep _r_held
# --- Helper Functions ---
func _on_ladder_area_entered(area: Area3D): if area.is_in_group("Ladders"): overlapping_ladder_area = area
func _on_ladder_area_exited(area: Area3D): if area == overlapping_ladder_area: overlapping_ladder_area = null
func _reset_head_yaw(delta: float):
# Smoothly apply the reset target to the actual pivot rotation
camera_anchor.rotation.y = lerpf(camera_anchor.rotation.y, 0.0, delta * head_turn_lerp_speed)
# TODO: Re-enable when multiplayer authority per pawn is functional
# func _notification(what: int) -> void:
# match what:
# NOTIFICATION_ENTER_TREE:
# set_multiplayer_authority(int(name))