class_name Boat extends FloatingBody ## All values multiplied by mass, handling the same regardless of it @export var engine_power := 12.0 # forward thrust, m/s² at full throttle @export var reverse_ratio := 0.4 # reverse is weaker, like a real prop @export var throttle_response := 1.5 # how fast the engine spools up/down @export var rudder_strength := 2.5 # turning torque at speed @export var keel_grip := 3.0 # resistance to sliding sideways @export var loadout: Dictionary[StringName, WeaponDef] = {} var desired_throttle := 0.0 # move_toward target for actual throttle var steer_input := 0.0 var throttle := 0.0 # current throttle state of ship var _slots: Array[WeaponSlot] = [] func _ready() -> void: super() for child in get_children(): if child is WeaponSlot: _slots.append(child) var def: WeaponDef = loadout.get(child.name) if def != null: child.mount(def) func _physics_process(delta: float) -> void: super(delta) throttle = move_toward(throttle, desired_throttle, throttle_response * delta) if submerged_ratio <= 0.0: return # airborne: no thrust, no rudder, no keel # boat's forward direction, flattened onto the water plane var forward := -global_basis.z forward.y = 0.0 forward = forward.normalized() # --- engine --- var power := engine_power * (reverse_ratio if throttle < 0.0 else 1.0) apply_central_force(forward * throttle * power * mass * submerged_ratio) # --- rudder: only bites when water flows past it --- var forward_speed := linear_velocity.dot(forward) var rudder_bite := clampf(forward_speed / 3.0, -1.0, 1.0) apply_torque(Vector3.UP * steer_input * rudder_strength * rudder_bite * mass * submerged_ratio) # --- keel: kill sideways sliding --- var right := global_basis.x right.y = 0.0 right = right.normalized() var lateral_speed := linear_velocity.dot(right) apply_central_force(-right * lateral_speed * keel_grip * mass * submerged_ratio) func fire_weapons() -> void: for slot in _slots: if slot.weapon != null: slot.weapon.try_fire()