Randoloot/scripts/planets/expedition_controller.gd
2026-10-20 19:53:03 +02:00

283 lines
11 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends Node
const UI := preload("res://scripts/planets/expedition_ui.gd")
const CONTACT := preload("res://scripts/ui/orbital_contact_style.gd")
const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
const FREIGHTER_SIZE := 285.0
const CONTACT_RANGE := preload("res://scripts/planets/orbital_contact_range.gd")
var game: Node2D
var player: CharacterBody2D
var freighter: Node2D
var screen: Control
var _layer: CanvasLayer
var _prompt: PanelContainer
var _text: Label
var _contact_label: Label
var _prompt_target: Node2D
var _freighter_half_size := Vector2.ZERO
var _scan_button: Button
var _land_button: Button
var _near_planet: Node2D
var _message := ""
var _message_time := 0.0
var _previous_pause := false
var _previous_docked := false
var _hud_was_visible := true
var _prompt_refresh_time := 0.0
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
name = "PlanetExpeditions"
get_tree().auto_accept_quit = false
freighter = Node2D.new()
freighter.name = "Freighter"
freighter.z_index = -3 # Keep the player's hull, engines and shield above the frigate.
freighter.position = Vector2(480,-270)
game.add_child(freighter)
var sprite := Sprite2D.new()
sprite.texture = preload("res://assets/planets/freighter.png")
sprite.scale = Vector2.ONE * FREIGHTER_SIZE / maxf(sprite.texture.get_width(),sprite.texture.get_height())
_freighter_half_size = Vector2(sprite.texture.get_image().get_used_rect().size)*sprite.scale*0.5
freighter.add_child(sprite)
_layer = CanvasLayer.new()
_layer.layer = 60
add_child(_layer)
_prompt = UI.panel(_layer,Vector2(300,0))
_prompt.add_theme_stylebox_override("panel",CONTACT.panel_style(UI.CYAN))
var column := VBoxContainer.new()
column.add_theme_constant_override("separation",7)
_prompt.add_child(column)
_contact_label = UI.label(column,"● ŁĄCZE ORBITALNE",10,UI.CYAN)
_text = UI.label(column,"",11)
_text.add_theme_constant_override("line_spacing",3)
_text.custom_minimum_size.x = 276
_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
var row := HBoxContainer.new()
row.add_theme_constant_override("separation",7)
column.add_child(row)
_scan_button = UI.button(row,"SKANUJ [F]",scan_nearby)
_land_button = UI.button(row,"LĄDUJ [L]",land_nearby)
for button in [_scan_button,_land_button]:
CONTACT.compact_button(button,UI.CYAN)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_prompt.hide()
get_viewport().size_changed.connect(_layout_prompt)
func combat_blocked() -> bool:
if float(player.get("landing_combat_lock")) > 0:
return true
for enemy in get_tree().get_nodes_in_group("enemies"):
if enemy is Node2D and player.global_position.distance_to(enemy.global_position) < 650:
return true
return false
func _process(delta: float) -> void:
_message_time = maxf(0, _message_time - delta)
if is_instance_valid(screen) or get_tree().paused or bool(player.get("_is_destroyed")):
_prompt.hide()
return
if _prompt.visible: _layout_prompt()
_prompt_refresh_time += delta
# Proximity hints do not need to rebuild at the rendering frame rate.
if delta > 0 and _prompt_refresh_time < 0.1:
return
_prompt_refresh_time = 0
_refresh_prompt()
func _refresh_prompt() -> void:
_near_planet = null
var best := INF
for planet in get_tree().get_nodes_in_group("scanner_planets"):
if not bool(planet.get("earthlike")):
continue
var distance: float = player.global_position.distance_to(planet.global_position) - float(planet.get("radius"))
if CONTACT_RANGE.at_planet(player,planet) and distance < best:
best = distance
_near_planet = planet
var nearby_freighter := CONTACT_RANGE.at_freighter(player,freighter)
if nearby_freighter: _prompt_target = freighter
elif is_instance_valid(_near_planet): _prompt_target = _near_planet
else: _prompt_target = player
var has_destination := nearby_freighter or is_instance_valid(_near_planet)
if not has_destination and _message_time <= 0:
_prompt.hide()
# Never accumulate combat text while the hint is hidden.
if not _text.text.is_empty():
_text.text = ""
return
var blocked := combat_blocked()
if blocked:
_prompt.hide()
return
var text := ""
if _message_time > 0:
text = _message
_contact_label.text = "● RAPORT WYPRAWY"
_prompt.tooltip_text = ""
elif nearby_freighter:
_contact_label.text = "● FREGATA / POŁĄCZENIE GOTOWE"
text = "FRACHTOWIEC\nHangar · Warsztat · Magazyn"
_prompt.tooltip_text = "Zadokuj, aby naprawić lądownik, rozbudować statek i przenieść wyposażenie."
else:
var profile: Dictionary = _near_planet.planet_profile
_contact_label.text = "● TELEMETRIA / "+("SKAN GOTOWY" if GameState.captain.expedition.scans.has(profile.id) else "NIEZBADANA PLANETA")
if GameState.captain.expedition.scans.has(profile.id):
text = _compact_scan(profile)
_prompt.tooltip_text = PROFILES.scan_description(profile,GameState.captain)
else:
text = "%s / EARTHLIKE\nZeskanuj warunki przed lądowaniem." % profile.name
_prompt.tooltip_text = "Skan ujawni warunki, zagrożenie i szanse powodzenia POI."
if blocked and _message_time <= 0:
text += "\nDokowanie i lądowanie zablokowane podczas walki."
var changed := _text.text != text or _scan_button.visible != (is_instance_valid(_near_planet) and not nearby_freighter) or _land_button.visible != has_destination
if _text.text != text:
_text.text = text
_scan_button.visible = is_instance_valid(_near_planet) and not nearby_freighter
_land_button.visible = has_destination
_land_button.text = "DOKUJ [L]" if nearby_freighter else "LĄDUJ [L]"
_land_button.disabled = blocked
_prompt.show()
if changed:
# A standalone PanelContainer otherwise keeps its previous large height.
_layout_prompt.call_deferred()
func _layout_prompt() -> void:
if not is_instance_valid(_prompt):
return
_prompt.size = Vector2(300, _prompt.get_combined_minimum_size().y)
var viewport_size := get_viewport().get_visible_rect().size
var target: Node2D = _prompt_target if is_instance_valid(_prompt_target) else player
var canvas := target.get_global_transform_with_canvas()
var radius := _freighter_half_size.x if target == freighter else float(target.get("radius")) if target != player else 30.0
var vertical := _freighter_half_size.y if target == freighter else radius
var obstacles: Array[Rect2] = CONTACT.hud_obstacles(game.get_node_or_null("HUD/HUDRoot"))
var services: Control = game.get("services_menu")
if is_instance_valid(services) and services.is_visible_in_tree(): obstacles.append(services.get_global_rect().grow(8))
_prompt.position = CONTACT.beside(canvas.origin,radius*canvas.x.length(),_prompt.size,viewport_size,obstacles,true,vertical*canvas.y.length())
func _compact_scan(profile: Dictionary) -> String:
var captain: Resource = GameState.captain
var report: Dictionary = PROFILES.threat_report(profile,captain)
return "%s · %s\nTLEN %d%% · TEMP %d°C · BIO %d/50\nZAGROŻENIE: %.1f%% / 300%%\nŚREDNIA SZANSA POI: %.1f%%\nEtapy: %.1f%% / %.1f%% / %.1f%%\nLądowanie −%d HP · Lądownik %d%%" % [profile.name,PROFILES.biome_name(profile),profile.oxygen,profile.temperature,profile.flora,report.total,PROFILES.average_success_chance(profile,captain)*100,PROFILES.success_chance(profile,captain,0)*100,PROFILES.success_chance(profile,captain,1)*100,PROFILES.success_chance(profile,captain,2)*100,profile.landing_damage,ceili(captain.expedition.lander_hp)]
func _unhandled_key_input(event: InputEvent) -> void:
if is_instance_valid(screen) or get_tree().paused or not event.pressed or event.echo:
return
if event.keycode == KEY_F:
scan_nearby()
elif event.keycode == KEY_L:
land_nearby()
else:
return
get_viewport().set_input_as_handled()
func scan_nearby() -> void:
_refresh_prompt()
if not is_instance_valid(_near_planet):
return
GameState.captain.expedition.scan(_near_planet.planet_profile)
_message_time = 0
_refresh_prompt()
func land_nearby() -> void:
_refresh_prompt()
if is_instance_valid(screen) or bool(player.get("_is_destroyed")):
return
if combat_blocked():
_notice("Odsuń się od przeciwników i zakończ walkę.")
return
if CONTACT_RANGE.at_freighter(player,freighter):
open_freighter()
return
if not is_instance_valid(_near_planet):
return
var progress: Resource = GameState.captain.expedition
if not progress.scans.has(_near_planet.planet_profile.id):
scan_nearby()
return
if not progress.begin_visit(_near_planet.planet_profile):
_notice("Lądownik wymaga naprawy do 100% we frachtowcu.")
return
_enter()
screen = Control.new()
screen.set_script(preload("res://scripts/planets/surface_screen.gd"))
screen.captain = GameState.captain
screen.progress = progress
screen.flight_ship = player
screen.planet_texture = _near_planet.body_texture
screen.finished.connect(finish_surface)
_layer.add_child(screen)
func open_freighter() -> void:
if is_instance_valid(screen):
return
_enter()
screen = preload("res://scenes/planets/freighter_interior.tscn").instantiate()
screen.captain = GameState.captain
screen.inventory = player.get_node("ShipInventory")
screen.closed.connect(_leave)
_layer.add_child(screen)
func _enter() -> void:
_previous_pause = get_tree().paused
_previous_docked = player.is_docked
_hud_was_visible = game.get_node("HUD/HUDRoot").visible
game.get_node("HUD/HUDRoot").hide()
player.is_docked = true
player.velocity = Vector2.ZERO
get_tree().paused = true
_prompt.hide()
func finish_surface(success: bool) -> void:
var progress: Resource = GameState.captain.expedition
var result: Dictionary = progress.finish_visit(GameState.captain,success)
progress.deliver_modules(player.get_node("ShipInventory"))
_leave()
var returned: Dictionary = result.get("materials",{}).duplicate()
if int(result.get("parts",0)) > 0: returned.parts = result.parts
if int(result.get("missiles",0)) > 0: returned.missile = result.missiles
if int(result.get("credits",0)) > 0: returned.credits = int(returned.get("credits",0))+int(result.credits)
var cargo_text := UI.material_text(returned)
_notice(("POWRÓT Z WYPRAWY · " + (cargo_text if not cargo_text.is_empty() else "brak łupu")) if result.get("success",false) else "LĄDOWNIK ZNISZCZONY · Utracono łup bieżącej wyprawy.")
if not progress.delivered_modules.is_empty():
_message += "\nBrak miejsca: %d modułów czeka we frachtowcu." % progress.delivered_modules.size()
func _leave() -> void:
if is_instance_valid(screen):
_layer.remove_child(screen)
screen.queue_free()
screen = null
player.is_docked = _previous_docked
game.get_node("HUD/HUDRoot").visible = _hud_was_visible
get_tree().paused = _previous_pause
func _notice(message: String) -> void:
_message = message
_message_time = 7
_prompt_refresh_time = 0.1
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST:
if GameState.captain.expedition.active:
GameState.captain.expedition.finish_visit(GameState.captain,true)
get_tree().quit()
func _exit_tree() -> void:
if is_instance_valid(GameState.captain) and GameState.captain.expedition.active:
GameState.captain.expedition.finish_visit(GameState.captain,true)
if is_instance_valid(screen):
get_tree().paused = _previous_pause
get_tree().auto_accept_quit = true
func request_distant_travel(map_id: String) -> bool:
if not is_instance_valid(screen) or GameState.captain.expedition.active: return false
if not game.travel_to_distant(map_id): return false
_leave()
return true
func request_system_travel(level: int) -> bool:
if not is_instance_valid(screen) or GameState.captain.expedition.active or level == GameState.system_level or not GameState.can_visit_system(level): return false
if not game.travel_to_system(level): return false
_leave()
return true