From d2bf034d983f69aa10ba8525ebb2cd218838f8f4 Mon Sep 17 00:00:00 2001 From: Tomasz Boruc Date: Thu, 15 Oct 2026 18:32:38 +0200 Subject: [PATCH] ZMianu HUD --- assets/icons/ui/captain_skills.svg | 6 ++ assets/icons/ui/captain_skills.svg.import | 43 ++++++++ assets/icons/ui/crafting.svg | 8 ++ assets/icons/ui/crafting.svg.import | 43 ++++++++ scripts/captain/captain_progress.gd | 27 ++--- scripts/game/asteroid_destruction_effect.gd | 77 ++++++++++---- scripts/game/flight_hud.gd | 108 +++++++++++++++++--- scripts/game/ship_energy_indicator.gd | 36 +++++-- scripts/levels/celestial_body.gd | 2 + scripts/levels/exploration_poi.gd | 37 +++---- scripts/ui/captain_panel.gd | 47 --------- scripts/ui/enemy_drawer.gd | 50 ++------- scripts/ui/flight_cockpit.gd | 12 ++- scripts/ui/planet_services_menu.gd | 2 +- scripts/ui/protected_planet_marker.gd | 3 - scripts/ui/scanner_overlay.gd | 81 ++++++++++----- scripts/ui/ship_crafting_panel.gd | 6 +- scripts/ui/ship_profile.gd | 56 ++++++++++ scripts/ui/ship_profile.gd.uid | 1 + tests/ships/flight_profile_test.gd | 40 ++++++++ tests/ships/flight_profile_test.gd.uid | 1 + tests/ships/flight_profile_test.tscn | 6 ++ 22 files changed, 488 insertions(+), 204 deletions(-) create mode 100644 assets/icons/ui/captain_skills.svg create mode 100644 assets/icons/ui/captain_skills.svg.import create mode 100644 assets/icons/ui/crafting.svg create mode 100644 assets/icons/ui/crafting.svg.import create mode 100644 scripts/ui/ship_profile.gd create mode 100644 scripts/ui/ship_profile.gd.uid create mode 100644 tests/ships/flight_profile_test.gd create mode 100644 tests/ships/flight_profile_test.gd.uid create mode 100644 tests/ships/flight_profile_test.tscn diff --git a/assets/icons/ui/captain_skills.svg b/assets/icons/ui/captain_skills.svg new file mode 100644 index 0000000..185b217 --- /dev/null +++ b/assets/icons/ui/captain_skills.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/ui/captain_skills.svg.import b/assets/icons/ui/captain_skills.svg.import new file mode 100644 index 0000000..b4de6ae --- /dev/null +++ b/assets/icons/ui/captain_skills.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cn3fyvgfbwqye" +path="res://.godot/imported/captain_skills.svg-cbdac3f3096be57c282840f44fa14935.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/ui/captain_skills.svg" +dest_files=["res://.godot/imported/captain_skills.svg-cbdac3f3096be57c282840f44fa14935.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/icons/ui/crafting.svg b/assets/icons/ui/crafting.svg new file mode 100644 index 0000000..c71ad63 --- /dev/null +++ b/assets/icons/ui/crafting.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/ui/crafting.svg.import b/assets/icons/ui/crafting.svg.import new file mode 100644 index 0000000..ea0dafc --- /dev/null +++ b/assets/icons/ui/crafting.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cn00i5tkhcxyw" +path="res://.godot/imported/crafting.svg-c0cc0c27f763904bea856d7dc30d600d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/ui/crafting.svg" +dest_files=["res://.godot/imported/crafting.svg-c0cc0c27f763904bea856d7dc30d600d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/scripts/captain/captain_progress.gd b/scripts/captain/captain_progress.gd index e7b290c..1b0f0ed 100644 --- a/scripts/captain/captain_progress.gd +++ b/scripts/captain/captain_progress.gd @@ -9,9 +9,7 @@ const BALANCE := preload("res://scripts/captain/captain_balance.gd") @export var parts: float = 0.0 @export var minerals: Dictionary = {"uranium": 0, "titanium": 0, "iron": 0, "aluminium": 0} @export var learning_points: int = 0 -@export var skills: Dictionary = { - "piloting": 0, "gunner": 0, "mechanic": 0, "sharp_eye": 0, -} +@export var skills: Dictionary = {} func get_xp_required() -> int: return BALANCE.xp_required(level) @@ -19,20 +17,17 @@ func get_xp_required() -> int: func get_skill_rank(skill: String) -> int: return clampi(int(skills.get(skill, 0)), 0, BALANCE.MAX_SKILL_RANK) -func get_skill_bonus(skill: String) -> float: - return BALANCE.skill_bonus(skill, get_skill_rank(skill)) +func get_skill_bonus(_skill: String) -> float: + return 0.0 # The old captain skill system has been retired. func normalize_skill_ranks() -> void: - var changed_ranks := false - for skill in BALANCE.SKILL_NAMES: - var previous := maxi(0, int(skills.get(skill, 0))) - var rank := mini(previous, BALANCE.MAX_SKILL_RANK) - learning_points += previous - rank - if int(skills.get(skill, 0)) != rank: - changed_ranks = true - skills[skill] = rank - if changed_ranks: + # Refund legacy ranks for the future captain skill system. + var refunded := get_spent_learning_points() + if refunded > 0: + learning_points += refunded + skills.clear() emit_changed() + return func get_spent_learning_points() -> int: var spent := 0 @@ -56,8 +51,8 @@ func reset_skills() -> bool: emit_changed() return true -func can_train(skill: String) -> bool: - return skills.has(skill) and learning_points > 0 and get_skill_rank(skill) < BALANCE.MAX_SKILL_RANK +func can_train(_skill: String) -> bool: + return false func train_skill(skill: String) -> bool: if not can_train(skill): diff --git a/scripts/game/asteroid_destruction_effect.gd b/scripts/game/asteroid_destruction_effect.gd index c739195..00a4c2a 100644 --- a/scripts/game/asteroid_destruction_effect.gd +++ b/scripts/game/asteroid_destruction_effect.gd @@ -1,6 +1,6 @@ extends Node2D -const DURATION := 1.6 +const SETTLE_TIME := 8.0 var _texture: Texture2D var _fragments: Array[Dictionary] = [] var _dust: Array[Dictionary] = [] @@ -24,24 +24,36 @@ func configure(sprite: Sprite2D) -> void: if y > 0 and y < 4: vertex.y += _rng.randf_range(-0.3, 0.3) * dimensions.y / 4.0 grid.append(vertex) + # Shared jagged cracks keep neighbouring pieces fitted together. + var horizontal: Array[PackedVector2Array] = [] + var vertical: Array[PackedVector2Array] = [] + for y in 5: + for x in 4: + horizontal.append(_fracture_edge(grid[y * 5 + x], grid[y * 5 + x + 1], y > 0 and y < 4)) + for y in 4: + for x in 5: + vertical.append(_fracture_edge(grid[y * 5 + x], grid[(y + 1) * 5 + x], x > 0 and x < 4)) for y in 4: for x in 4: - var a := grid[y * 5 + x] - var b := grid[y * 5 + x + 1] - var c := grid[(y + 1) * 5 + x + 1] - var d := grid[(y + 1) * 5 + x] - for triangle in [[a, b, c], [a, c, d]]: - var center: Vector2 = (triangle[0] + triangle[1] + triangle[2]) / 3.0 - var origin := (center - dimensions * 0.5) * sprite_scale - var points := PackedVector2Array() - var uv := PackedVector2Array() - for vertex: Vector2 in triangle: - points.append((vertex - center) * sprite_scale) - uv.append(vertex / dimensions) - var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT - _fragments.append({"points": points, "uv": uv, "position": origin, - "velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(70.0, 170.0), - "angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)}) + var outline := PackedVector2Array() + outline.append_array(_edge_vertices(horizontal[y * 4 + x], false)) + outline.append_array(_edge_vertices(vertical[y * 5 + x + 1], false)) + outline.append_array(_edge_vertices(horizontal[(y + 1) * 4 + x], true)) + outline.append_array(_edge_vertices(vertical[y * 5 + x], true)) + var center := Vector2.ZERO + for vertex in outline: + center += vertex + center /= outline.size() + var origin := (center - dimensions * 0.5) * sprite_scale + var points := PackedVector2Array() + var uv := PackedVector2Array() + for vertex in outline: + points.append((vertex - center) * sprite_scale) + uv.append(vertex / dimensions) + var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT + _fragments.append({"points": points, "uv": uv, "position": origin, + "velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(40.0, 95.0), + "angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)}) _dust_color = Color("a1c7df") if "ice" in _texture.resource_path else Color("b4a99a") for index in 28: _dust.append({"direction": Vector2.RIGHT.rotated(_rng.randf_range(0.0, TAU)), @@ -49,22 +61,43 @@ func configure(sprite: Sprite2D) -> void: z_index = 35 light_mask = 4 +func _fracture_edge(start: Vector2, end: Vector2, jagged: bool) -> PackedVector2Array: + var edge := PackedVector2Array([start]) + if jagged: + var normal := (end - start).orthogonal().normalized() + var roughness := start.distance_to(end) * 0.13 + var side := 1.0 if _rng.randf() > 0.5 else -1.0 + for index in 3: + var along := (index + 1.0) / 4.0 + _rng.randf_range(-0.05, 0.05) + edge.append(start.lerp(end, along) + normal * roughness * side * _rng.randf_range(0.5, 1.0)) + side *= -1.0 + edge.append(end) + return edge + +func _edge_vertices(edge: PackedVector2Array, backwards: bool) -> PackedVector2Array: + var vertices := PackedVector2Array() + # The next edge supplies the end vertex, avoiding duplicate polygon points. + for index in edge.size() - 1: + vertices.append(edge[edge.size() - 1 - index] if backwards else edge[index]) + return vertices + func _process(delta: float) -> void: _elapsed += delta - if _elapsed >= DURATION: - queue_free() - return for fragment in _fragments: fragment.position += fragment.velocity * delta fragment.velocity *= exp(-1.2 * delta) fragment.angle += fragment.spin * delta + fragment.spin *= exp(-0.7 * delta) queue_redraw() + # Leave the settled debris visible without updating it every frame. + if _elapsed >= SETTLE_TIME: + _dust.clear() + set_process(false) func _draw() -> void: - var fade := 1.0 - smoothstep(0.65, DURATION, _elapsed) for fragment in _fragments: draw_set_transform(fragment.position, fragment.angle) - draw_polygon(fragment.points, PackedColorArray([Color(1.0, 1.0, 1.0, fade)]), fragment.uv, _texture) + draw_polygon(fragment.points, PackedColorArray([Color.WHITE]), fragment.uv, _texture) draw_set_transform(Vector2.ZERO) var dust_fade := maxf(0.0, 1.0 - _elapsed / 0.8) for mote in _dust: diff --git a/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index a4d9710..886e20e 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -24,6 +24,89 @@ var _debug_item_button: Button var _debug_item_status: Label var _captain_panel: PanelContainer var _cockpit: Control +var _crafting_window: PanelContainer +var _skills_window: PanelContainer + +func _create_navigation() -> void: + var navigation := HBoxContainer.new() + navigation.name = "TopNavigation" + $HUDRoot.add_child(navigation) + navigation.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP) + navigation.offset_left = -60 + navigation.offset_right = 60 + navigation.offset_top = 12 + navigation.offset_bottom = 64 + navigation.add_theme_constant_override("separation", 8) + _crafting_window = _create_window("CraftingWindow", "TWÓRZ", Vector2(440, 250)) + var crafting := PanelContainer.new() + crafting.name = "CraftingPanel" + crafting.set_script(preload("res://scripts/ui/ship_crafting_panel.gd")) + crafting.set("standalone", true) + _crafting_window.get_node("Contents").add_child(crafting) + _skills_window = _create_window("CaptainSkillsWindow", "UMIEJĘTNOŚCI KAPITANA", Vector2(480, 300)) + var empty := Control.new() + empty.name = "SkillsContent" + empty.custom_minimum_size = Vector2(440, 230) + _skills_window.get_node("Contents").add_child(empty) + for entry in [["Twórz", _crafting_window, "res://assets/icons/ui/crafting.svg", Color("efb568")], ["Umiejętności kapitana", _skills_window, "res://assets/icons/ui/captain_skills.svg", Color("b9a0ef")]]: + var button := Button.new() + button.tooltip_text = entry[0] + button.icon = load(entry[2]) as Texture2D + button.expand_icon = true + button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER + button.custom_minimum_size = Vector2(56, 52) + button.add_theme_constant_override("icon_max_width", 36) + preload("res://scripts/ui/hud_button_style.gd").apply(button, entry[3]) + button.add_theme_constant_override("h_separation", 0) + for state in ["normal", "hover", "pressed", "disabled"]: + var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat + style.set_content_margin_all(8) + button.add_theme_stylebox_override(state, style) + navigation.add_child(button) + button.pressed.connect(_toggle_window.bind(entry[1])) + +func _create_window(node_name: String, title: String, dimensions: Vector2) -> PanelContainer: + var window := PanelContainer.new() + window.name = node_name + var style := StyleBoxFlat.new() + style.bg_color = Color("0b1520fa") + style.border_color = Color("376779") + style.set_border_width_all(1) + style.set_corner_radius_all(8) + style.set_content_margin_all(16) + window.add_theme_stylebox_override("panel", style) + $HUDRoot.add_child(window) + window.set_anchors_and_offsets_preset(Control.PRESET_CENTER) + window.offset_left = -dimensions.x * 0.5 + window.offset_right = dimensions.x * 0.5 + window.offset_top = -dimensions.y * 0.5 + window.offset_bottom = dimensions.y * 0.5 + var contents := VBoxContainer.new() + contents.name = "Contents" + contents.add_theme_constant_override("separation", 12) + window.add_child(contents) + var header := HBoxContainer.new() + contents.add_child(header) + var label := Label.new() + label.text = title + label.add_theme_font_size_override("font_size", 14) + label.add_theme_color_override("font_color", Color("91dbea")) + label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + header.add_child(label) + var close := Button.new() + close.text = "×" + close.tooltip_text = "Zamknij" + close.custom_minimum_size = Vector2(32, 28) + close.pressed.connect(window.hide) + header.add_child(close) + window.hide() + return window + +func _toggle_window(window: PanelContainer) -> void: + var opening := not window.visible + _crafting_window.hide() + _skills_window.hide() + window.visible = opening func _ready() -> void: _sidebar_open = bool(GameState.hud_panel_state.get("ship_open", false)) @@ -43,10 +126,6 @@ func _ready() -> void: # The display font has unusually tall vertical metrics; keep debug rows compact. debug_info.add_theme_constant_override("line_spacing", mini(0, 13 - ceili(debug_info.get_theme_font("font").get_height(10)))) _create_sidebar() - var enemy_panel := Control.new() - enemy_panel.name = "EnemyDrawer" - enemy_panel.set_script(preload("res://scripts/ui/enemy_drawer.gd")) - $HUDRoot.add_child(enemy_panel) _inventory_clip = Control.new() _inventory_clip.name = "InventoryDrawer" _inventory_clip.clip_contents = true @@ -121,23 +200,21 @@ func _create_sidebar() -> void: equipment.reparent(contents, false) equipment.size_flags_horizontal = Control.SIZE_EXPAND_FILL equipment.add_theme_stylebox_override("panel", StyleBoxEmpty.new()) - var crafting := PanelContainer.new() - crafting.name = "CraftingPanel" - crafting.set_script(preload("res://scripts/ui/ship_crafting_panel.gd")) - equipment.get_node("Contents").add_child(crafting) contents.minimum_size_changed.connect(func() -> void: _layout_sidebar.call_deferred()) _sidebar_toggle = Button.new() + _sidebar_toggle.set_script(preload("res://scripts/ui/ship_profile.gd")) + _sidebar_toggle.set("player", player) _sidebar_toggle.name = "ShipToggle" - _sidebar_toggle.text = "STATEK ▾" if _sidebar_open else "STATEK ◀" + _sidebar_toggle.tooltip_text = "Zamknij okno statku" if _sidebar_open else "Otwórz statek i sloty wyposażenia" _sidebar_toggle.add_theme_font_size_override("font_size", 12) preload("res://scripts/ui/hud_button_style.gd").apply(_sidebar_toggle) preload("res://scripts/ui/hud_button_style.gd").apply(inventory_button, Color("b9a0ef")) $HUDRoot.add_child(_sidebar_toggle) _sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT) - _sidebar_toggle.offset_left = -180 + _sidebar_toggle.offset_left = -352 _sidebar_toggle.offset_right = -12 _sidebar_toggle.offset_top = 12 - _sidebar_toggle.offset_bottom = 48 + _sidebar_toggle.offset_bottom = 122 _sidebar_toggle.pressed.connect(_toggle_sidebar) _cockpit = Control.new() _cockpit.name = "Cockpit" @@ -145,6 +222,7 @@ func _create_sidebar() -> void: _cockpit.set("player", player) _cockpit.set("game", get_parent()) $HUDRoot.add_child(_cockpit) + _create_navigation() _layout_debug() func _layout_debug() -> void: @@ -165,7 +243,7 @@ func _toggle_sidebar() -> void: GameState.hud_panel_state["ship_open"] = _sidebar_open if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() - _sidebar_toggle.text = "STATEK ▾" if _sidebar_open else "STATEK ◀" + _sidebar_toggle.tooltip_text = "Zamknij okno statku" if _sidebar_open else "Otwórz statek i sloty wyposażenia" if _sidebar_open: _sidebar.show() else: @@ -181,16 +259,16 @@ func _layout_sidebar() -> void: if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() _sidebar.scale = Vector2.ONE * EQUIPMENT_UI_SCALE - _sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 64.0) / EQUIPMENT_UI_SCALE))) + _sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 148.0) / EQUIPMENT_UI_SCALE))) _cockpit.scale = Vector2.ONE * 0.85 - _cockpit.size = Vector2(minf(640.0, ($HUDRoot.size.x - 24) / 0.85), 104) + _cockpit.size = Vector2(minf(850.0, ($HUDRoot.size.x - 24) / 0.85), 120) _cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12) if _ability_bar != null: var abilities := _ability_bar.get_parent() as Control abilities.offset_bottom = _cockpit.position.y - $HUDRoot.size.y - 14.0 abilities.offset_top = abilities.offset_bottom - 44.0 _layout_debug() - _sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8 if _sidebar_open else $HUDRoot.size.x, 56) + _sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8 if _sidebar_open else $HUDRoot.size.x, 136) _sidebar.visible = _sidebar_open _inventory_clip.size = inventory_panel.size * EQUIPMENT_UI_SCALE _layout_inventory() diff --git a/scripts/game/ship_energy_indicator.gd b/scripts/game/ship_energy_indicator.gd index 054eb3c..8880023 100644 --- a/scripts/game/ship_energy_indicator.gd +++ b/scripts/game/ship_energy_indicator.gd @@ -20,6 +20,9 @@ func _ready() -> void: _background.bg_color = Color("101925") _background.border_color = Color("456277") _background.set_border_width_all(1) + _background.set_corner_radius_all(4) + _background.shadow_color = Color(0, 0, 0, 0.5) + _background.shadow_size = 3 func _physics_process(_delta: float) -> void: if is_instance_valid(player): @@ -58,13 +61,26 @@ func _hull_ratio() -> float: func _draw() -> void: if not is_instance_valid(player): return - var color := Color("71e3ff") if _energy_ratio() >= 0.25 else Color("ffbd66") - var bar_position := Vector2(-44, 48) - draw_style_box(_background, Rect2(bar_position - Vector2(2, 2), Vector2(92, 10))) - draw_rect(Rect2(bar_position, Vector2(88 * _display_ratio, 6)), color) - var shield_bar_position := bar_position + Vector2(0, 14) - draw_style_box(_background, Rect2(shield_bar_position - Vector2(2, 2), Vector2(92, 10))) - draw_rect(Rect2(shield_bar_position, Vector2(88 * _display_shield_ratio, 6)), Color.WHITE) - var hull_bar_position := bar_position + Vector2(0, 28) - draw_style_box(_background, Rect2(hull_bar_position - Vector2(2, 2), Vector2(92, 10))) - draw_rect(Rect2(hull_bar_position, Vector2(88 * _display_hull_ratio, 6)), Color("ef5967")) + _draw_bar(Vector2(-46, 58), _display_ratio, Color("efbd62"), 0) + _draw_bar(Vector2(-46, 72), _display_shield_ratio, Color("66cfff"), 1) + _draw_bar(Vector2(-46, 86), _display_hull_ratio, Color("ef6875"), 2) + +func _draw_bar(position: Vector2, ratio: float, color: Color, kind: int) -> void: + draw_style_box(_background, Rect2(position - Vector2(14, 3), Vector2(110, 12))) + # Ten illuminated cells, with partial cells tracking the exact current value. + for index in 10: + var cell := Rect2(position + Vector2(index * 9.2, 0), Vector2(7.6, 6)) + draw_rect(cell, color.darkened(0.8)) + var fraction := clampf(ratio * 10.0 - index, 0.0, 1.0) + if fraction > 0: + cell.size.x *= fraction + draw_rect(cell, color) + draw_rect(Rect2(cell.position, Vector2(cell.size.x, 2)), color.lightened(0.4)) + var center := position + Vector2(-8, 3) + match kind: + 0: + draw_polyline(PackedVector2Array([center + Vector2(1, -4), center + Vector2(-2, 0), center + Vector2(2, 0), center + Vector2(-1, 4)]), color, 1.4, true) + 1: + draw_polyline(PackedVector2Array([center + Vector2(-3, -3), center + Vector2(3, -3), center + Vector2(2, 2), center + Vector2(0, 4), center + Vector2(-2, 2), center + Vector2(-3, -3)]), color, 1.2, true) + 2: + draw_polyline(PackedVector2Array([center + Vector2(0, -4), center + Vector2(3, 0), center + Vector2(2, 3), center + Vector2(-2, 3), center + Vector2(-3, 0), center + Vector2(0, -4)]), color, 1.2, true) diff --git a/scripts/levels/celestial_body.gd b/scripts/levels/celestial_body.gd index 52aabbb..935d9db 100644 --- a/scripts/levels/celestial_body.gd +++ b/scripts/levels/celestial_body.gd @@ -11,6 +11,8 @@ var _label: Label var _sprite: Sprite2D func _ready() -> void: + if not is_star: + add_to_group("scanner_planets") var sprite := Sprite2D.new() _sprite = sprite sprite.name = "BodySprite" diff --git a/scripts/levels/exploration_poi.gd b/scripts/levels/exploration_poi.gd index dc30adf..bd9acd4 100644 --- a/scripts/levels/exploration_poi.gd +++ b/scripts/levels/exploration_poi.gd @@ -119,8 +119,8 @@ func claim() -> bool: elif kind == "ruin": var xp := roundi(_rng.randi_range(10, 20) * scale) GameState.captain.add_rewards(xp, 0) - var module := _drop_module(true) - summary = "%d XP oraz %s (poziom %d)" % [xp, module.definition.display_name, module.item_level] + _drop_module(true) + summary = "+%d doświadczenia · +1 moduł" % xp _show_discovery(summary) else: match _rng.randi_range(0, 3): @@ -163,28 +163,29 @@ func _show_discovery(reward: String) -> void: layer.layer = 30 layer.name = "AncientDiscoveryMessage" get_parent().add_child(layer) - var panel := PanelContainer.new() - panel.mouse_filter = Control.MOUSE_FILTER_IGNORE - layer.add_child(panel) - panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP) - panel.offset_left = -300 - panel.offset_right = 300 - panel.offset_top = 70 var label := Label.new() - label.custom_minimum_size = Vector2(600, 95) - label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + layer.add_child(label) + label.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP) + label.offset_left = -300 + label.offset_right = 300 + label.offset_top = 145 + label.offset_bottom = 181 label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE - label.add_theme_font_size_override("font_size", 18) - label.add_theme_color_override("font_color", Color("ffd166")) - label.text = "Znalazłeś ślady starożytnej upadłej cywilizacji!\nZdobyłeś: " + reward + label.add_theme_font_size_override("font_size", 15) + label.add_theme_color_override("font_color", Color("d8d4b7")) + label.add_theme_color_override("font_shadow_color", Color(0.0, 0.0, 0.0, 0.85)) + label.add_theme_constant_override("shadow_offset_x", 1) + label.add_theme_constant_override("shadow_offset_y", 2) + label.text = reward if _module_left_outside: - label.text += "\nMagazyn pełny — moduł czeka obok planety." - panel.add_child(label) + label.text += " (obok planety)" + label.modulate.a = 0.0 var tween := layer.create_tween() - tween.tween_interval(7.0) - tween.tween_property(panel, "modulate:a", 0.0, 0.5) + tween.tween_property(label, "modulate:a", 1.0, 0.35) + tween.tween_interval(3.5) + tween.tween_property(label, "modulate:a", 0.0, 0.8) tween.tween_callback(layer.queue_free) func _drop_module(ancient: bool) -> ItemInstance: diff --git a/scripts/ui/captain_panel.gd b/scripts/ui/captain_panel.gd index 9a8d287..e669e59 100644 --- a/scripts/ui/captain_panel.gd +++ b/scripts/ui/captain_panel.gd @@ -12,55 +12,17 @@ const BALANCE := preload("res://scripts/captain/captain_balance.gd") var _collapsed := false var _collapse_button: Button var _captain: CaptainProgress -var _rank_labels: Dictionary = {} -var _effect_labels: Dictionary = {} -var _buttons: Dictionary = {} func _ready() -> void: _build_portrait() _captain = GameState.captain _captain.normalize_skill_ranks() - for skill in BALANCE.SKILL_NAMES: - var row := HBoxContainer.new() - row.name = skill - row.add_theme_constant_override("separation", 5) - var title := Label.new() - title.text = BALANCE.SKILL_NAMES[skill] - title.tooltip_text = BALANCE.SKILL_HELP[skill] - title.size_flags_horizontal = Control.SIZE_EXPAND_FILL - title.add_theme_font_size_override("font_size", 11) - row.add_child(title) - var rank := Label.new() - rank.custom_minimum_size.x = 40 - rank.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - rank.add_theme_font_size_override("font_size", 11) - row.add_child(rank) - _rank_labels[skill] = rank - var effect := Label.new() - effect.custom_minimum_size.x = 74 - effect.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - effect.add_theme_font_size_override("font_size", 10) - row.add_child(effect) - _effect_labels[skill] = effect - var button := Button.new() - button.name = "Train_" + skill - button.text = "+" - button.custom_minimum_size = Vector2(24, 22) - button.add_theme_font_size_override("font_size", 13) - button.tooltip_text = "Rozwiń umiejętność za 1 punkt nauki.\n" + BALANCE.SKILL_HELP[skill] - button.pressed.connect(_train.bind(skill)) - row.add_child(button) - _buttons[skill] = button - $Contents/Skills.add_child(row) _captain.changed.connect(_refresh) player.get_node("ShipInventory").connect("inventory_changed", _refresh) _refresh() if bool(GameState.hud_panel_state.get("captain_collapsed", true)): _toggle_collapsed() -func _train(skill: String) -> void: - _captain.train_skill(skill) - func _refresh() -> void: level_label.text = "POZ. %d" % _captain.level xp_label.text = "XP %d / %d" % [_captain.experience, _captain.get_xp_required()] @@ -68,15 +30,6 @@ func _refresh() -> void: xp_bar.value = _captain.experience credits_label.text = "Kredyty: %d" % _captain.credits points_label.text = "Punkty: %d" % _captain.learning_points - for skill in _buttons: - var rank := _captain.get_skill_rank(skill) - _rank_labels[skill].text = "%d/%d" % [rank, BALANCE.MAX_SKILL_RANK] - var suffix: String = {"piloting": "unik", "gunner": "DMG", "mechanic": "red.", "sharp_eye": "kryt"}[skill] - var bonus := _captain.get_skill_bonus(skill) - _effect_labels[skill].text = "%.1f%% %s" % [bonus * 100.0, suffix] - var next_bonus := BALANCE.skill_bonus(skill, rank + 1) - _buttons[skill].tooltip_text = BALANCE.SKILL_HELP[skill] + ("\nOsiągnięto limit 20 punktów." if rank >= BALANCE.MAX_SKILL_RANK else "\nNastępny punkt: %.1f%% → %.1f%%" % [bonus * 100.0, next_bonus * 100.0]) - _buttons[skill].disabled = not _captain.can_train(skill) func _build_portrait() -> void: custom_minimum_size = Vector2(320, 0) diff --git a/scripts/ui/enemy_drawer.gd b/scripts/ui/enemy_drawer.gd index f941204..9c3afec 100644 --- a/scripts/ui/enemy_drawer.gd +++ b/scripts/ui/enemy_drawer.gd @@ -1,49 +1,17 @@ extends Control -var _panel: PanelContainer var _label: Label -var _toggle: Button -var _open := false -var _tween: Tween func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE - set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) - _panel = PanelContainer.new() - var style := StyleBoxFlat.new() - style.bg_color = Color("0b1520f5") - style.border_color = Color("a54e61") - style.set_border_width_all(1) - style.set_content_margin_all(12) - _panel.add_theme_stylebox_override("panel", style) - add_child(_panel) _label = Label.new() - _label.add_theme_font_size_override("font_size", 12) - _panel.add_child(_label) - _toggle = Button.new() - _toggle.text = "WRÓG ▾" - preload("res://scripts/ui/hud_button_style.gd").apply(_toggle, Color("ff9aae")) - add_child(_toggle) - _toggle.pressed.connect(_toggle_drawer) - _open = bool(GameState.hud_panel_state.get("enemy_open", false)) - get_viewport().size_changed.connect(_layout) - _layout() - -func _layout() -> void: - position.x = (get_viewport_rect().size.x - 390.0) * 0.5 - _panel.size = Vector2(390, 150) - _panel.position.y = 56.0 if _open else -160.0 - _toggle.position = Vector2(130, 12) - _toggle.size = Vector2(130, 36) - -func _toggle_drawer() -> void: - _open = not _open - GameState.hud_panel_state["enemy_open"] = _open - _toggle.text = "WRÓG ▴" if _open else "WRÓG ▾" - if _tween != null: - _tween.kill() - _tween = create_tween() - _tween.tween_property(_panel, "position:y", 56.0 if _open else -160.0, 0.25).set_trans(Tween.TRANS_CUBIC) + _label.name = "EnemyDetails" + _label.mouse_filter = Control.MOUSE_FILTER_IGNORE + _label.add_theme_font_size_override("font_size", 9) + _label.add_theme_color_override("font_color", Color("c9acb5")) + _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(_label) func _process(_delta: float) -> void: var enemy: Node = null @@ -53,8 +21,8 @@ func _process(_delta: float) -> void: var profile := DamageTypes.enemy_profile(GameState.system_level, GameState.system_seed) var kind := int(profile["kind"]) var names := ["Działko plazmowe", "Działko maszynowe", "Laser", "Rakiety", "Działko gamma"] - _label.text = "WRÓG · UKŁAD %d\n%s · %s\n%s" % [GameState.system_level, names[kind], DamageTypes.NAMES[DamageTypes.for_weapon(kind)], DamageTypes.describe(profile["absorption"])] + _label.text = "%s\n%s" % [names[kind], DamageTypes.describe(profile["absorption"])] if enemy != null: - _label.text += "\nDPS: %.1f (%s)\nTarcze: %.0f / %.0f · bez regeneracji" % [enemy.get_weapon_dps(), ["zwykły", "elita", "boss"][enemy.loot_tier], enemy.shields, enemy.max_shields] + _label.text += "\nDPS: %.1f (%s)\nTarcze: %.0f / %.0f" % [enemy.get_weapon_dps(), ["zwykły", "elita", "boss"][enemy.loot_tier], enemy.shields, enemy.max_shields] else: _label.text += "\nUkład oczyszczony" diff --git a/scripts/ui/flight_cockpit.gd b/scripts/ui/flight_cockpit.gd index fa43c84..836eebc 100644 --- a/scripts/ui/flight_cockpit.gd +++ b/scripts/ui/flight_cockpit.gd @@ -6,11 +6,16 @@ const WEAPON_DISPLAY_ORDER := [0, 2, 1] const AMBER := Color("efb568") var player: CharacterBody2D var game: Node2D +var _enemy_details: Control var _inventory: ShipInventory func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE _inventory = player.get_node("ShipInventory") + _enemy_details = Control.new() + _enemy_details.name = "EnemyDetailsPanel" + _enemy_details.set_script(preload("res://scripts/ui/enemy_drawer.gd")) + add_child(_enemy_details) func _process(delta: float) -> void: if is_instance_valid(player): @@ -18,6 +23,9 @@ func _process(delta: float) -> void: var local := get_global_transform().affine_inverse() * screen var overlaps := Rect2(Vector2.ZERO, size).grow(36).has_point(local) modulate.a = move_toward(modulate.a, 0.18 if overlaps else 1.0, delta * 4.0) + var cell := (size.x - 238) / 3.0 + _enemy_details.position = Vector2(218.0 + 2.0 * cell, 57) + _enemy_details.size = Vector2(cell, 56) queue_redraw() func _text(value: String, point: Vector2, width: float, font_size: int, color: Color) -> void: @@ -34,7 +42,9 @@ func _draw() -> void: draw_line(Vector2(28, 2), Vector2(size.x - 28, 2), CYAN, 2, true) # Put weapon cards beside the counters rather than stacking three rows. _text("DPS: %.1f" % _inventory.get_total_dps(), Vector2(20, 81), 172, 12, CYAN) - _text(DamageTypes.describe(player.get("absorption")), Vector2(205, 81), size.x - 225, 11, Color("a9d69a")) + var absorption: Vector3 = player.get("absorption") + for index in 3: + _text("%s - %.1f%%" % [DamageTypes.NAMES[index], absorption[index]], Vector2(205, 77 + index * 15), 2.0 * (size.x - 238) / 3.0, 11, Color("a9d69a")) var cell := (size.x - 238) / 3.0 var values := [_inventory.missile_ammunition, _inventory.ammunition, int(game.call("get_remaining_enemies"))] var captions := ["RAKIETY", "AMUNICJA", "WROGOWIE"] diff --git a/scripts/ui/planet_services_menu.gd b/scripts/ui/planet_services_menu.gd index 91724c9..0a0f16c 100644 --- a/scripts/ui/planet_services_menu.gd +++ b/scripts/ui/planet_services_menu.gd @@ -26,7 +26,7 @@ func _ready() -> void: _gamma_buy.pressed.connect(func() -> void: _inventory.buy_irradiated_ammunition()) next_button.pressed.connect(func() -> void: next_level_requested.emit()) buy_ammunition_button.pressed.connect(_buy_ammunition) - reset_skills_button.pressed.connect(_reset_skills) + reset_skills_button.hide() _missile_button = Button.new() _missile_button.custom_minimum_size.y = 36 _missile_button.add_theme_font_size_override("font_size", 12) diff --git a/scripts/ui/protected_planet_marker.gd b/scripts/ui/protected_planet_marker.gd index aa2c2f4..630bd73 100644 --- a/scripts/ui/protected_planet_marker.gd +++ b/scripts/ui/protected_planet_marker.gd @@ -10,9 +10,6 @@ func _process(_delta: float) -> void: func _draw() -> void: if not is_instance_valid(game) or not is_instance_valid(game.get("protected_planet")): return - var planet: Node2D = game.get("protected_planet") - var color := Color("77e8bc") if bool(game.get("system_cleared")) else Color("ffbd83") - _draw_target(planet.global_position, str(planet.get("display_name")) + " — zamieszkana", color) var portal: Node2D = game.get("wormhole") if is_instance_valid(portal): _draw_target(portal.global_position, "WORMHOLE — następny układ", Color("89e8ff")) diff --git a/scripts/ui/scanner_overlay.gd b/scripts/ui/scanner_overlay.gd index ef45940..70534d6 100644 --- a/scripts/ui/scanner_overlay.gd +++ b/scripts/ui/scanner_overlay.gd @@ -1,21 +1,45 @@ extends Control -const FONT := preload("res://assets/fonts/font2.ttf") +const DOT_RADIUS := 3.0 +const SCAN_RING_RADIUS := 48.0 var player: CharacterBody2D var targets: Array[Node2D] = [] +var _elapsed := 0.0 +var _previous_player_position := Vector2.ZERO +var _player_position := Vector2.ZERO +var _positions: Dictionary = {} func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE + process_physics_priority = 10 + _player_position = player.global_position + _previous_player_position = _player_position -func _process(_delta: float) -> void: +func _process(delta: float) -> void: + _elapsed += delta + queue_redraw() + +func _physics_process(_delta: float) -> void: if not is_instance_valid(player): return + _previous_player_position = _player_position + _player_position = player.global_position + if _previous_player_position.distance_to(_player_position) > 600.0: + _previous_player_position = _player_position targets.clear() - for group in ["enemies", "exploration_pois"]: + var next_positions: Dictionary = {} + for group in ["enemies", "exploration_pois", "scanner_planets"]: for target in get_tree().get_nodes_in_group(group): - if player.get_parent().is_ancestor_of(target) and is_target_in_range(target): + if player.get_parent().is_ancestor_of(target) and not target.is_queued_for_deletion() and is_target_in_range(target): + if group == "scanner_planets" and (not bool(target.get("is_protected")) or not bool(player.get_parent().get("system_cleared"))): + continue targets.append(target) - queue_redraw() + var current: Vector2 = target.global_position + var previous: Vector2 = _positions.get(target.get_instance_id(), [current, current])[1] + if previous.distance_to(current) > 600.0: + previous = current + next_positions[target.get_instance_id()] = [previous, current] + _positions = next_positions func get_scan_range() -> float: var transform := get_viewport().get_canvas_transform() @@ -29,38 +53,39 @@ func is_target_in_range(target: Node2D) -> bool: func _draw() -> void: if not is_instance_valid(player) or bool(player.get("_is_destroyed")) or bool(player.get("is_docked")): return - var bounds := Rect2(Vector2(344, 70), Vector2(maxf(100, size.x - 680), maxf(100, size.y - 230))) - var origin := get_viewport().get_canvas_transform() * player.global_position - var anchor := Vector2(clampf(origin.x, bounds.position.x, bounds.end.x), clampf(origin.y, bounds.position.y, bounds.end.y)) + var canvas := get_viewport().get_canvas_transform() + var to_local := get_global_transform().affine_inverse() + # Match the rendered ship and targets, rather than their discrete physics positions. + var fraction := Engine.get_physics_interpolation_fraction() + var origin: Vector2 = to_local * (canvas * _previous_player_position.lerp(_player_position, fraction)) + var radius := SCAN_RING_RADIUS * canvas.x.length() + var pulse := (sin(_elapsed * TAU * 0.9) + 1.0) * 0.5 for target in targets: if not is_instance_valid(target) or target.is_queued_for_deletion(): continue - var color := Color("ff7777") - var caption := "WRÓG" + var color := Color("ff5555") if target.is_in_group("enemies"): if float(target.get("hull")) <= 0: continue + elif target.is_in_group("scanner_planets"): + color = Color("b68aff") else: + if bool(target.get("_claimed")): + continue match str(target.get("kind")): "asteroid": - color = Color("a6c8e0") - caption = "AST" + color = Color("a8adb5") "wreck": - color = Color("ffd166") - caption = "WRAK" + color = Color("b98050") "ruin": - color = Color("d2a4ff") - caption = "?" - var screen := get_viewport().get_canvas_transform() * target.global_position - if get_viewport_rect().has_point(screen): + color = Color("6be58b") + _: + continue + var positions: Array = _positions[target.get_instance_id()] + var target_position: Vector2 = positions[0].lerp(positions[1], fraction) + var offset: Vector2 = to_local * (canvas * target_position) - origin + if offset.length_squared() < 0.01: continue - var direction := (screen - anchor).normalized() - var point := screen - if not bounds.has_point(screen): - var dx := (bounds.end.x - anchor.x) if direction.x > 0 else (anchor.x - bounds.position.x) - var dy := (bounds.end.y - anchor.y) if direction.y > 0 else (anchor.y - bounds.position.y) - var distance := minf(dx / maxf(absf(direction.x), 0.001), dy / maxf(absf(direction.y), 0.001)) - point = anchor + direction * distance - var side := Vector2(-direction.y, direction.x) - draw_colored_polygon(PackedVector2Array([point + direction * 10, point - direction * 6 + side * 6, point - direction * 6 - side * 6]), color) - draw_string(FONT, point + Vector2(-18, 25), caption, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, color) + var point := origin + offset.normalized() * radius + draw_circle(point, DOT_RADIUS + 3.0 + pulse * 2.0, Color(color, 0.08 + pulse * 0.12), true, -1.0, true) + draw_circle(point, DOT_RADIUS, Color(color, 0.55 + pulse * 0.45), true, -1.0, true) diff --git a/scripts/ui/ship_crafting_panel.gd b/scripts/ui/ship_crafting_panel.gd index a9a06c3..61b625d 100644 --- a/scripts/ui/ship_crafting_panel.gd +++ b/scripts/ui/ship_crafting_panel.gd @@ -1,5 +1,6 @@ extends PanelContainer +@export var standalone := false var _inventory: ShipInventory var _expanded := false var _toggle: Button @@ -39,8 +40,9 @@ func _ready() -> void: _missile.pressed.connect(func() -> void: _inventory.craft_missile()) _gamma = _button("CraftGamma", "+10 NAPROMIENIOWANEJ · 1 URAN + 10 CZĘŚCI") _gamma.pressed.connect(func() -> void: _inventory.craft_irradiated_ammunition()) - _details.hide() - if bool(GameState.hud_panel_state.get("crafting_expanded", false)): + _details.visible = standalone + _toggle.visible = not standalone + if not standalone and bool(GameState.hud_panel_state.get("crafting_expanded", false)): _toggle_expanded() _inventory.ammunition_changed.connect(_refresh) GameState.captain.changed.connect(_refresh) diff --git a/scripts/ui/ship_profile.gd b/scripts/ui/ship_profile.gd new file mode 100644 index 0000000..7c4e9d1 --- /dev/null +++ b/scripts/ui/ship_profile.gd @@ -0,0 +1,56 @@ +extends Button + +const FONT := preload("res://assets/fonts/font2.ttf") +var player: CharacterBody2D +var _texture: Texture2D +var _ship_name := "" + +func _ready() -> void: + focus_mode = Control.FOCUS_NONE + text = "" + for state in ["normal", "hover", "pressed", "focus"]: + add_theme_stylebox_override(state, StyleBoxEmpty.new()) + var ship := ShipCatalog.for_path(GameState.selected_ship_path) + _ship_name = ship["name"] + _texture = load(ShipCatalog.texture_path(ship)) as Texture2D + +func _process(_delta: float) -> void: + queue_redraw() + +func _draw() -> void: + if not is_instance_valid(player): + return + var portrait_center := Vector2(size.x - 54, 55) + var color := Color("91dbea") if is_hovered() else Color("376779") + var style := StyleBoxFlat.new() + style.bg_color = Color("091522f5") + style.border_color = color + style.set_border_width_all(1) + style.set_corner_radius_all(12) + draw_style_box(style, Rect2(0, 9, size.x - 48, 94)) + draw_circle(portrait_center, 50, Color("0a1725")) + draw_arc(portrait_center, 50, 0, TAU, 64, color, 3, true) + draw_arc(portrait_center, 44, 0, TAU, 64, Color("efb568"), 1, true) + if _texture != null: + var dimensions := _texture.get_size() + var display_size := dimensions * (70.0 / maxf(dimensions.x, dimensions.y)) + draw_texture_rect(_texture, Rect2(portrait_center - display_size * 0.5, display_size), false) + draw_string(FONT, Vector2(14, 29), _ship_name.to_upper(), HORIZONTAL_ALIGNMENT_LEFT, size.x - 120, 13, Color("c9e8ff")) + _bar(Rect2(12, 38, size.x - 118, 24), float(player.get("hull")), float(player.get("max_hull")), Color("df7158"), "KADŁUB") + _bar(Rect2(12, 68, size.x - 118, 24), float(player.get("shields")), float(player.get("max_shields")), Color("478ddd"), "TARCZE") + +func _bar(rect: Rect2, value: float, maximum: float, color: Color, caption: String) -> void: + var background := StyleBoxFlat.new() + background.bg_color = Color("060e18") + background.border_color = color.darkened(0.4) + background.set_border_width_all(1) + background.set_corner_radius_all(7) + draw_style_box(background, rect) + var fill_rect := rect.grow(-3) + fill_rect.size.x *= clampf(value / maxf(1.0, maximum), 0.0, 1.0) + if fill_rect.size.x > 0: + var fill := StyleBoxFlat.new() + fill.bg_color = color + fill.set_corner_radius_all(4) + draw_style_box(fill, fill_rect) + draw_string(FONT, rect.position + Vector2(0, 16), "%s %.0f / %.0f" % [caption, value, maximum], HORIZONTAL_ALIGNMENT_CENTER, rect.size.x, 10, Color.WHITE) diff --git a/scripts/ui/ship_profile.gd.uid b/scripts/ui/ship_profile.gd.uid new file mode 100644 index 0000000..a4a619e --- /dev/null +++ b/scripts/ui/ship_profile.gd.uid @@ -0,0 +1 @@ +uid://dsxp2c0jn6qlh diff --git a/tests/ships/flight_profile_test.gd b/tests/ships/flight_profile_test.gd new file mode 100644 index 0000000..d5bc205 --- /dev/null +++ b/tests/ships/flight_profile_test.gd @@ -0,0 +1,40 @@ +extends Node + +func _ready() -> void: + GameState.start_new_run() + GameState.captain.skills = {"piloting": 4, "gunner": 3} + GameState.captain.learning_points = 2 + var game := preload("res://scenes/game/game.tscn").instantiate() + add_child(game) + await get_tree().process_frame + await get_tree().process_frame + assert(GameState.captain.learning_points == 9) + assert(GameState.captain.skills.is_empty()) + GameState.captain.normalize_skill_ranks() + assert(GameState.captain.learning_points == 9) + assert(GameState.captain.get_skill_bonus("piloting") == 0.0) + assert(not GameState.captain.train_skill("gunner")) + var root := game.get_node("HUD/HUDRoot") + assert(not root.has_node("EnemyDrawer")) + assert(root.get_node("CaptainPanel/Contents/Skills").get_child_count() == 0) + var drawer := root.get_node("ShipDrawer") as Control + assert(not drawer.visible) + root.get_node("ShipToggle").emit_signal("pressed") + assert(drawer.visible) + assert(drawer.has_node("Scroll/Contents/ShipEquipmentPanel/Contents/EquipmentLayout/Weapon1")) + assert(not drawer.has_node("Scroll/Contents/ShipEquipmentPanel/Contents/CraftingPanel")) + var navigation := root.get_node("TopNavigation") + navigation.get_child(0).emit_signal("pressed") + assert(root.get_node("CraftingWindow").visible) + assert(root.get_node("CraftingWindow/Contents/CraftingPanel/Contents/CraftingDetails").visible) + navigation.get_child(1).emit_signal("pressed") + assert(not root.get_node("CraftingWindow").visible) + assert(root.get_node("CaptainSkillsWindow").visible) + assert(root.get_node("CaptainSkillsWindow/Contents/SkillsContent").get_child_count() == 0) + root.get_node("CaptainSkillsWindow/Contents").get_child(0).get_child(1).emit_signal("pressed") + assert(not root.get_node("CaptainSkillsWindow").visible) + assert(root.has_node("Cockpit/EnemyDetailsPanel/EnemyDetails")) + print("FLIGHT_PROFILE_TEST: PASS navigation, ship slots, crafting, empty skills, enemy details and legacy point refund") + # Keep the result available briefly for the runtime debugger. + await get_tree().create_timer(15.0).timeout + get_tree().quit() diff --git a/tests/ships/flight_profile_test.gd.uid b/tests/ships/flight_profile_test.gd.uid new file mode 100644 index 0000000..4faa281 --- /dev/null +++ b/tests/ships/flight_profile_test.gd.uid @@ -0,0 +1 @@ +uid://da1s61ut1jgjg diff --git a/tests/ships/flight_profile_test.tscn b/tests/ships/flight_profile_test.tscn new file mode 100644 index 0000000..619df8f --- /dev/null +++ b/tests/ships/flight_profile_test.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/ships/flight_profile_test.gd" id="1"] + +[node name="FlightProfileTest" type="Node"] +script = ExtResource("1")