295 lines
12 KiB
GDScript
295 lines
12 KiB
GDScript
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 _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
|
||
var _last_combat_blocked := false
|
||
var _needs_prompt_refresh := true
|
||
|
||
func _ready() -> void:
|
||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||
name = "PlanetExpeditions"
|
||
get_tree().auto_accept_quit = false
|
||
freighter = Node2D.new()
|
||
freighter.name = "Freighter"
|
||
freighter.add_to_group("scanner_freighters")
|
||
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)
|
||
_land_button = UI.button(row,"LĄDUJ [L]",land_nearby)
|
||
for button in [_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 not enemy.is_queued_for_deletion() and float(enemy.get("hull")) > 0.0 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()
|
||
_needs_prompt_refresh = true
|
||
return
|
||
var blocked := combat_blocked()
|
||
if blocked != _last_combat_blocked or _needs_prompt_refresh:
|
||
_prompt_refresh_time = 0.1
|
||
_last_combat_blocked = blocked
|
||
_needs_prompt_refresh = false
|
||
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
|
||
if is_instance_valid(_near_planet):
|
||
var profile: Dictionary = _near_planet.planet_profile
|
||
if not GameState.captain.expedition.scans.has(profile.id):
|
||
GameState.captain.expedition.scan(profile)
|
||
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 / DANE PLANETY"
|
||
text = _compact_scan(profile)
|
||
_prompt.tooltip_text = PROFILES.scan_description(profile,GameState.captain)
|
||
# A hidden card must be laid out again even if the target and text stayed the same.
|
||
var changed := not _prompt.visible or _text.text != text or _land_button.visible != has_destination
|
||
if _text.text != text:
|
||
_text.text = text
|
||
_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:
|
||
_layout_prompt()
|
||
# A standalone PanelContainer otherwise keeps its previous large height.
|
||
_layout_prompt.call_deferred()
|
||
|
||
func _layout_prompt() -> void:
|
||
if not is_instance_valid(_prompt):
|
||
return
|
||
# Do not invalidate the wrapped report's container layout every frame.
|
||
var desired_size := Vector2(300, _prompt.get_combined_minimum_size().y)
|
||
if not _prompt.size.is_equal_approx(desired_size):
|
||
_prompt.size = desired_size
|
||
var viewport_size := get_viewport().get_visible_rect().size
|
||
# Return reports belong to the screen, not to a moving planet or ship.
|
||
# Object avoidance can switch sides abruptly as the camera moves.
|
||
if _message_time > 0:
|
||
_prompt.position = Vector2(
|
||
maxf(10, (viewport_size.x - _prompt.size.x) * 0.5),
|
||
maxf(preload("res://scripts/ui/resource_bar.gd").HEIGHT + 12, viewport_size.y - _prompt.size.y - 160)
|
||
)
|
||
return
|
||
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_L:
|
||
land_nearby()
|
||
else:
|
||
return
|
||
get_viewport().set_input_as_handled()
|
||
|
||
func land_nearby() -> void:
|
||
_refresh_prompt()
|
||
if is_instance_valid(screen) or bool(player.get("_is_destroyed")):
|
||
return
|
||
if combat_blocked():
|
||
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.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:
|
||
_needs_prompt_refresh = true
|
||
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 GameState.active_distant_map.is_empty(): return false
|
||
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
|
||
|
||
func request_distant_return() -> bool:
|
||
if not is_instance_valid(screen) or GameState.active_distant_map.is_empty(): return false
|
||
var sector := game.get_node_or_null("DistantSector")
|
||
if sector == null or not sector._return_home(): return false
|
||
_leave()
|
||
return true
|