extends Control signal finished(success: bool) const UI := preload("res://scripts/planets/expedition_ui.gd") const GENERATOR := preload("res://scripts/planets/surface_generator.gd") var captain: Resource var progress: Resource var world: Node2D var lander: Sprite2D var camera: Camera2D var _hp: ProgressBar var _cargo: Label var _status: Label var _dialog: PanelContainer var _choices: VBoxContainer var _poi: Dictionary = {} var _rng := RandomNumberGenerator.new() var _motion := preload("res://scripts/planets/lander_motion.gd").new() var flight_ship: Node var _ending := false var _hud_refresh_time := 0.0 var _previous_position := Vector2.ZERO var _active_event: Dictionary = {} var _event_armed := true const SURFACE_RENDER_SCALE := 1.5 const SURFACE_CAMERA_ZOOM := 0.8 var _surface_viewport: SubViewport func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_STOP _rng.seed = progress.visit_seed ^ 90317 if is_instance_valid(flight_ship): _motion.configure(flight_ship) var container := TextureRect.new() container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) container.expand_mode = TextureRect.EXPAND_IGNORE_SIZE container.stretch_mode = TextureRect.STRETCH_SCALE container.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR container.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(container) var viewport := SubViewport.new() _surface_viewport = viewport viewport.size = Vector2i(get_viewport_rect().size * SURFACE_RENDER_SCALE) viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS viewport.world_2d = World2D.new() container.add_child(viewport) container.texture = viewport.get_texture() world = Node2D.new() world.set_script(preload("res://scripts/planets/surface_world.gd")) world.data = GENERATOR.generate(progress.visit_seed, progress.active_profile) viewport.add_child(world) lander = Sprite2D.new() lander.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON lander.texture = preload("res://assets/planets/lander.png") lander.scale = Vector2.ONE * 100.0 / lander.texture.get_width() lander.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS var sprite_material := ShaderMaterial.new() sprite_material.shader = preload("res://resources/effects/lander_detail.gdshader") lander.material = sprite_material lander.z_index = 30 viewport.add_child(lander) lander.reset_physics_interpolation() var engine := Node2D.new() engine.name = "EngineVisual" engine.set_script(preload("res://scripts/planets/lander_engine_visual.gd")) engine.source = self engine.scale = Vector2.ONE / lander.scale engine.z_index = -1 lander.add_child(engine) var audio := AudioStreamPlayer.new() audio.name = "EngineAudio" audio.set_script(preload("res://scripts/game/ship_engine_audio.gd")) audio.thrust_source = self add_child(audio) camera = Camera2D.new() camera.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF camera.process_callback = Camera2D.CAMERA2D_PROCESS_IDLE camera.zoom = Vector2.ONE * SURFACE_CAMERA_ZOOM * SURFACE_RENDER_SCALE camera.position_smoothing_enabled = true camera.position_smoothing_speed = 6 camera.limit_left = -1920 camera.limit_top = -1920 camera.limit_right = 1920 camera.limit_bottom = 1920 viewport.add_child(camera) resized.connect(_resize_surface_viewport) var info := UI.panel(self, Vector2(440, 0)) info.position = Vector2(24, 24) info.mouse_filter = Control.MOUSE_FILTER_IGNORE var column := VBoxContainer.new() info.add_child(column) UI.label(column, str(progress.active_profile.name).to_upper(), 25, UI.CYAN) UI.label(column,str(world.data.landscape.name),14,Color("e4cc9d")) UI.label(column, "%s / TLEN %d%% / %d°C / FLORA %d" % [ preload("res://scripts/planets/planet_profiles.gd").biome_name(progress.active_profile), progress.active_profile.oxygen, progress.active_profile.temperature, progress.active_profile.flora], 13) _hp = ProgressBar.new() _hp.custom_minimum_size = Vector2(0, 14) _hp.show_percentage = false var hp_style := StyleBoxFlat.new() hp_style.bg_color = Color("5bc9b5") hp_style.set_corner_radius_all(5) _hp.add_theme_stylebox_override("fill",hp_style) column.add_child(_hp) _status = UI.label(column, "", 13) _cargo = UI.label(column, "", 13) _cargo.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _cargo.custom_minimum_size.x = 410 var back := UI.button(self, "POWRÓT NA STATEK [L]", func(): _return(true)) back.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) back.position = Vector2(get_viewport_rect().size.x-285, 24) back.size.x = 260 var help := UI.panel(self) help.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) help.position = Vector2(24, get_viewport_rect().size.y-84) UI.label(help, "W — ciąg · S — hamuj / wstecz · A/D — obrót\nQ/E — ruch boczny · TAB — dopalanie · F — zbadaj", 14) var map_frame := UI.panel(self, Vector2(195,195)) map_frame.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) map_frame.position = get_viewport_rect().size-Vector2(220,220) var minimap := Control.new() minimap.set_script(preload("res://scripts/planets/surface_minimap.gd")) minimap.world = world minimap.lander = lander map_frame.add_child(minimap) _dialog = UI.panel(self, Vector2(460,0)) _dialog.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) _dialog.position = Vector2(get_viewport_rect().size.x-490,get_viewport_rect().size.y*0.5-100) _choices = VBoxContainer.new() _choices.add_theme_constant_override("separation", 12) _dialog.add_child(_choices) _dialog.hide() _update_hud() func _physics_process(delta: float) -> void: if _ending: return _previous_position = lander.position if _dialog.visible: _motion.velocity = Vector2.ZERO _motion.angular_velocity = 0 return _motion.step(delta, Input.get_axis("ship_turn_left","ship_turn_right"), Input.is_action_pressed("ship_thrust"), Input.is_action_pressed("ship_brake"), Input.get_axis("ship_strafe_left","ship_strafe_right"), Input.is_action_pressed("ship_mini_boost")) lander.rotation = _motion.rotation var next := lander.position + _motion.velocity * delta var bounded := next.clamp(Vector2(-1780,-1780),Vector2(1780,1780)) if next.x != bounded.x: _motion.velocity.x = 0 if next.y != bounded.y: _motion.velocity.y = 0 lander.position = bounded func _process(delta: float) -> void: if _ending: return # Camera and sprite must follow the same interpolated pose between physics ticks. camera.position = _previous_position.lerp(lander.position, Engine.get_physics_interpolation_fraction()) _hud_refresh_time += delta if _hud_refresh_time >= 0.1: _hud_refresh_time = 0 world.refresh_pois(lander.position) _update_hud() _check_events() func _input(event: InputEvent) -> void: if not event is InputEventKey or not event.pressed or event.echo or _ending: return if event.is_action("ship_mini_boost"): get_viewport().set_input_as_handled() return if event.keycode == KEY_L: _return(true) elif event.keycode == KEY_ESCAPE: if _dialog.visible: _dialog.hide() else: _return(true) elif event.keycode == KEY_F and not _dialog.visible: for encounter in world.data.events: if not encounter.complete and lander.position.distance_to(encounter.position) <= 180: _event_armed = false _show_event(encounter) get_viewport().set_input_as_handled() return for poi in world.data.pois: if not poi.complete and lander.position.distance_to(poi.position) <= 150: _poi = poi _show_poi() break else: return get_viewport().set_input_as_handled() func _show_poi(message := "") -> void: for child in _choices.get_children(): _choices.remove_child(child) child.queue_free() _dialog.show() var names := {"mine":"OPUSZCZONA KOPALNIA", "ruins":"STAROŻYTNE RUINY", "deposit":"ZŁOŻE MINERAŁÓW"} UI.label(_choices, names[_poi.kind], 22, UI.CYAN) var description := UI.label(_choices, "Każdy kolejny etap daje więcej łupu, ale zwiększa ryzyko.\nNieudana próba uszkadza lądownik i kończy badanie miejsca.", 14) description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART description.custom_minimum_size.x = 410 if not message.is_empty(): var result := UI.label(_choices, message, 15, Color("#efca8a")) result.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART result.custom_minimum_size.x = 410 if not _poi.complete: var chance: float = preload("res://scripts/planets/planet_profiles.gd").success_chance(progress.active_profile, captain, _poi.stage) UI.button(_choices, "%s · %d%% szans" % [{"mine":["Poziom 1 — wejście","Poziom 2 — głębszy szyb","Poziom 3 — najgłębsza komora"],"ruins":["Zbadaj dziedziniec","Otwórz komorę","Uruchom rdzeń"],"deposit":["Zbierz powierzchniowe złoże","Wykonaj odwiert","Wydobądź głęboką żyłę"]}[_poi.kind][int(_poi.stage)],roundi(chance*100)], _attempt) UI.button(_choices, "Zakończ badanie tego miejsca", func(): _poi.complete = true; _dialog.hide()) UI.button(_choices, "Wróć do lotu", func(): _dialog.hide()) func _attempt() -> void: if _ending: return var result: Dictionary = progress.resolve_poi(captain, _poi, _rng) if result.is_empty(): return var message := "" if result.success: message = "SUKCES · " + UI.material_text(result.materials) if result.module != null: message += "\nZnaleziono moduł statku, poziom %d." % result.module.item_level if _poi.complete: message += "\nMiejsce w pełni zbadane." else: message = "NIEPOWODZENIE · utrata %d HP.\nDalsze badanie tego miejsca jest niemożliwe." % result.damage _update_hud() _show_poi(message) if progress.lander_hp <= 0: _ending = true _status.text = "LĄDOWNIK ZNISZCZONY — utracono łup wyprawy" _dialog.hide() var explosion := Node2D.new() explosion.set_script(preload("res://scripts/game/player_destruction_effect.gd")) explosion.configure(lander,Vector2.ZERO,camera) lander.get_parent().add_child(explosion) lander.hide() await explosion.finished finished.emit(false) func _update_hud() -> void: _hp.value = progress.lander_hp if not _ending: _status.text = "LĄDOWNIK %d / 100 HP · ENERGIA %d%%" % [ceili(progress.lander_hp),roundi(_motion.energy)] var loot := UI.material_text(progress.cargo) _cargo.text = "ŁUP WYPRAWY: " + (loot if not loot.is_empty() else "brak") + "\nModuły: %d" % progress.cargo_modules.size() func _return(success: bool) -> void: if _ending: return _ending = true finished.emit(success) func engine_thrust_active() -> bool: return not _ending and not _dialog.visible and (_motion.boost_active or (Input.is_action_pressed("ship_thrust") and not Input.is_action_pressed("ship_brake"))) func _check_events() -> void: for event in world.data.events: if event.complete: continue var distance: float = lander.position.distance_to(event.position) if distance > 260: _event_armed = true elif distance <= 180 and _event_armed and not _dialog.visible: _event_armed = false _show_event(event) return func _show_event(event: Dictionary, result: Dictionary = {}) -> void: _active_event = event for child in _choices.get_children(): _choices.remove_child(child) child.queue_free() _dialog.show() UI.label(_choices,"SPOTKANIE / INŻYNIER",23,UI.CYAN) var text := "Znalazłeś miejscowego inżyniera. Za 100 kredytów sprzeda Ci moduł. Nie jesteś pewien, czy będzie coś warty." if not result.is_empty(): if result.get("success",false): var item: ItemInstance = result.item text = "Kupiono: %s\nPoziom modułu: %d\n\nModuł trafił do ładowni wyprawy. Wróć bezpiecznie na statek, aby go zachować." % [item.definition.display_name,item.item_level] var icon := TextureRect.new() icon.texture = item.definition.icon icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.custom_minimum_size = Vector2(70,70) _choices.add_child(icon) else: text = str(result.get("reason","Oferta niedostępna.")) var description := UI.label(_choices,text,16) description.custom_minimum_size.x = 410 description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART if not event.complete: UI.label(_choices,"Twoje kredyty: %d" % captain.credits,15,Color("efca8a")) var buy := UI.button(_choices,"KUP NIEZNANY MODUŁ · 100 KREDYTÓW",_buy_engineer) buy.disabled = captain.credits < 100 UI.button(_choices,"Może później",func(): _dialog.hide()) else: UI.button(_choices,"Kontynuuj wyprawę",func(): _dialog.hide()) func _buy_engineer() -> void: if _ending: return var result: Dictionary = progress.buy_engineer_offer(captain,_active_event,_rng) _show_event(_active_event,result) world.refresh_pois(lander.position) _update_hud() func _resize_surface_viewport() -> void: if is_instance_valid(_surface_viewport): _surface_viewport.size = Vector2i(Vector2(maxf(1,size.x),maxf(1,size.y))*SURFACE_RENDER_SCALE)