spaceship/SpeedFader.gd
2026-08-16 21:38:07 +02:00

125 lines
4.6 KiB
GDScript

class_name SpeedFader
extends Control
# Vertical drag control for GameState.fleet_speed_multiplier. Same raw-touch
# idiom as TouchJoystick (handle_touch/handle_drag offered first by
# GameController, not _gui_input) and the same NeonDraw look, but a fader
# rather than a stick: the knob's Y position along a fixed vertical track IS
# the value, no spring-back to centre.
#
# Self-contained: writes straight to GameState.fleet_speed_multiplier rather
# than reporting a value for something else to apply, since the multiplier
# already lives in the one place (the autoload) that needs to survive scene
# swaps.
@export var glow_color: Color = Color(0.7, 0.3, 1.0)
@export var core_color: Color = Color(1.0, 1.0, 1.0)
@export var track_margin: float = 28.0
@onready var fleet: Node2D = get_node("../../Fleet")
@onready var label: Label = get_node("Label")
var active_touch_index: int = -1
# 0 = bottom of the track (1.0x), 1 = top (GameState.MAX_FLEET_SPEED_MULTIPLIER).
var knob_t: float = 0.0
var status_message_until: int = 0
func _ready() -> void:
visible = false
queue_redraw()
func _process(_delta: float) -> void:
var has_distorder: bool = fleet.has_capability("distorder")
if has_distorder != visible:
visible = has_distorder
if not has_distorder:
# Fleet owns persistent composition state. This outgoing UI node can
# briefly observe an empty fleet during a scene swap, so it must only
# clear its own interaction state here.
active_touch_index = -1
knob_t = 0.0
return
knob_t = (GameState.fleet_speed_multiplier - 1.0) \
/ maxf(GameState.MAX_FLEET_SPEED_MULTIPLIER - 1.0, 0.001)
# #62: GameState._process() (not this node's own _process()) is what
# drains energy and force-resets the multiplier, since it has to keep
# running across the space<->surface swap and while this control might
# not even be in the current scene tree. It leaves a one-shot flag for
# whichever SpeedFader picks it up next.
if GameState.boost_depleted_notice:
GameState.boost_depleted_notice = false
# Still holding the drag at the moment energy ran out would
# otherwise re-boost on the very next drag event, from a knob
# position the finger never actually moved away from.
active_touch_index = -1
label.text = "Out of energy!"
status_message_until = Time.get_ticks_msec() + 3000
elif Time.get_ticks_msec() >= status_message_until:
label.text = "x%.1f" % GameState.fleet_speed_multiplier
queue_redraw()
func is_active() -> bool:
return active_touch_index != -1
func _blocked_by_modal() -> bool:
# Same check as IconButton._blocked_by_modal() - panels stay transparent
# to raw input (the skill tree/formation editor need _unhandled_input),
# so every raw-touch control checks for itself instead.
for menu in get_tree().get_nodes_in_group("modal_menus"):
if menu.is_visible_in_tree() and not menu.is_ancestor_of(self):
return true
return false
func handle_touch(event: InputEventScreenTouch) -> bool:
var local: Vector2 = event.position - global_position
if event.pressed:
if not visible or active_touch_index != -1 or _blocked_by_modal():
return false
if not Rect2(Vector2.ZERO, size).has_point(local):
return false
active_touch_index = event.index
_update_from_local(local)
return true
if event.index != active_touch_index:
return false
active_touch_index = -1
queue_redraw()
return true
func handle_drag(event: InputEventScreenDrag) -> bool:
if event.index != active_touch_index:
return false
_update_from_local(event.position - global_position)
return true
func _update_from_local(local: Vector2) -> void:
var usable: float = maxf(size.y - track_margin * 2.0, 1.0)
# Up is 1.0 (top of the track), matching how a real fader/throttle reads.
var t: float = 1.0 - clampf((local.y - track_margin) / usable, 0.0, 1.0)
knob_t = t
GameState.set_fleet_speed_multiplier(1.0 + t * (GameState.MAX_FLEET_SPEED_MULTIPLIER - 1.0))
label.text = "x%.1f" % GameState.fleet_speed_multiplier
queue_redraw()
func _draw() -> void:
var top: Vector2 = Vector2(size.x * 0.5, track_margin)
var bottom: Vector2 = Vector2(size.x * 0.5, size.y - track_margin)
NeonDraw.draw_glowing_line(self, bottom, top, glow_color, 0.55, 0.5, core_color)
# Tick marks at the extremes read as "min" and "max" at a glance.
for t in [0.0, 1.0]:
var y: float = lerpf(bottom.y, top.y, t)
NeonDraw.draw_glowing_line(self, Vector2(size.x * 0.5 - 12.0, y),
Vector2(size.x * 0.5 + 12.0, y), glow_color, 0.4, 0.35, core_color)
var knob_pos: Vector2 = bottom.lerp(top, clampf(knob_t, 0.0, 1.0))
NeonDraw.draw_glowing_circle(self, knob_pos, 16.0, glow_color,
1.0 if is_active() else 0.8, core_color)