diff --git a/documentation.txt b/documentation.txt index 78de2c4..1fd42b7 100644 --- a/documentation.txt +++ b/documentation.txt @@ -226,3 +226,16 @@ w Inspectorze. Domyślnie pasma są neutralne (0 dB), szyny mają -6 dB i wysył WeaponAudio.BUS_NAMES przypisuje rodzaj broni do szyny. Głośność gracza na odtwarzaczu wynosi 0 dB, plazma wrogów -4 dB względem szyny. Zmiana szyny Plasma obejmuje oba źródła. Muzyka i inne dźwięki nie przechodzą przez szyny broni. + +MODELE STATKÓW + +Ekran wyboru pokazuje tylko grafiki 0, 2, 3, 7. Parametry bazowe są w +scripts/game/ship_catalog.gd (SHIPS), wspólnym dla wyboru i kontrolera statku. +Aegis (0): kadłub 100, tarcze 125, energia 100, prędkość 90 px/s. +Titan (2): kadłub 125, tarcze 100, energia 100, prędkość 90 px/s. +Eagle (3): kadłub 100, tarcze 100, energia 100, prędkość 108 px/s. +Phantom (7): kadłub 100, tarcze 100, energia 125, prędkość 90 px/s. +Bazowa regeneracja energii wszystkich modeli: 5 pkt/s. +Statystyki modelu ustawiane są przed przeliczeniem wyposażenia, także przy restarcie. +Brak lub nieaktualny wybór modelu powoduje użycie Aegis. Dopalacz i inne umiejętności +korzystają z faktycznych maksimów, więc Phantom wydaje całą swoją energię 125 pkt. diff --git a/project.godot b/project.godot index 6302188..f39289a 100644 --- a/project.godot +++ b/project.godot @@ -37,6 +37,10 @@ window/stretch/aspect="expand" enabled=PackedStringArray("res://addons/godot_mcp_toolkit/plugin.cfg") +[gui] + +theme/custom="res://resources/ui/game_theme.tres" + [input] ship_thrust={ diff --git a/resources/items/engine_extension.tres b/resources/items/engine_extension.tres index a16f8c0..b96a475 100644 --- a/resources/items/engine_extension.tres +++ b/resources/items/engine_extension.tres @@ -1,13 +1,12 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://5w8lekkq5okp"] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1_definition"] [resource] script = ExtResource("1_definition") item_id = "engine_extension" -display_name = "Rozszerzenie silnika" -category = 4 -description = "Pasywne rozszerzenie zwiększające prędkość maksymalną." +display_name = "Moduł silnika" +description = "Pasywny moduł zwiększający prędkość maksymalną." primary_stat = "max_speed" primary_value = 40.0 stat_suffix = " px/s" diff --git a/resources/ui/game_theme.tres b/resources/ui/game_theme.tres new file mode 100644 index 0000000..77d66eb --- /dev/null +++ b/resources/ui/game_theme.tres @@ -0,0 +1,6 @@ +[gd_resource type="Theme" load_steps=2 format=3] + +[ext_resource type="FontFile" path="res://assets/fonts/font2.ttf" id="1_font"] + +[resource] +default_font = ExtResource("1_font") diff --git a/scenes/game/game.tscn b/scenes/game/game.tscn index fcde2dd..aa2b0e1 100644 --- a/scenes/game/game.tscn +++ b/scenes/game/game.tscn @@ -90,30 +90,19 @@ TARCZE: 100 / 100 ENERGIA: 100 / 100" [node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")] -layout_mode = 0 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 +layout_mode = 1 offset_left = -270.0 -offset_top = 224.0 +offset_top = 244.0 offset_right = -10.0 -offset_bottom = 504.0 -grow_horizontal = 0 +offset_bottom = 422.0 grow_vertical = 1 [node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")] -layout_mode = 0 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 +layout_mode = 1 offset_left = -230.0 offset_top = -128.0 offset_right = 230.0 offset_bottom = 128.0 -grow_horizontal = 2 -grow_vertical = 2 [node name="CaptainPanel" parent="HUD/HUDRoot" unique_id=424797965 instance=ExtResource("9_captain")] layout_mode = 0 diff --git a/scenes/ui/ship_selection.tscn b/scenes/ui/ship_selection.tscn index 313b381..f389d35 100644 --- a/scenes/ui/ship_selection.tscn +++ b/scenes/ui/ship_selection.tscn @@ -52,10 +52,10 @@ size_flags_vertical = 3 unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 +size_flags_vertical = 0 theme_override_constants/h_separation = 14 theme_override_constants/v_separation = 14 -columns = 6 +columns = 4 [node name="Footer" type="HBoxContainer" parent="Layout"] layout_mode = 2 @@ -74,4 +74,3 @@ custom_minimum_size = Vector2(220, 52) layout_mode = 2 theme_override_font_sizes/font_size = 20 text = "Rozpocznij grę" - diff --git a/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index 2c7d1d4..8243675 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -11,6 +11,20 @@ var _ability_buttons: Dictionary = {} var _ability_bar: HBoxContainer func _ready() -> void: + debug_info.add_theme_font_size_override("font_size", 9) + # Establish anchor layout before applying offsets; absolute layout resets anchors. + var equipment := $HUDRoot/ShipEquipmentPanel as Control + equipment.set("layout_mode", 1) + equipment.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT) + equipment.offset_left = -270.0 + equipment.offset_right = -10.0 + inventory_panel.set("layout_mode", 1) + inventory_panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER_BOTTOM) + inventory_panel.grow_vertical = Control.GROW_DIRECTION_BEGIN + inventory_panel.offset_left = -390.0 + inventory_panel.offset_right = 390.0 + inventory_panel.offset_top = -330.0 + inventory_panel.offset_bottom = -62.0 _create_ability_bar() debug_toggle.pressed.connect(_toggle_debug) inventory_button.pressed.connect(_toggle_inventory) @@ -72,6 +86,7 @@ OBRÓT: %.2f rad/s""" % [ ship_velocity.length(), ship_velocity.x, ship_velocity.y, ship_position.x, ship_position.y, rad_to_deg(player.rotation), angular_speed, ] + debug_info.text += "\n\n" + _get_bonus_summary() func _toggle_debug() -> void: _debug_expanded = not _debug_expanded @@ -84,6 +99,42 @@ func _update_debug_toggle() -> void: func _toggle_inventory() -> void: inventory_panel.visible = not inventory_panel.visible +func _get_bonus_summary() -> String: + # Read final ship values so gear and captain multipliers match actual combat. + return """BONUSY ŁĄCZNE (MODUŁY + KAPITAN) +Obrażenia broni: %+.1f%% (×%.3f) +Szansa na krytyk: %.1f%% +Obrażenia krytyczne: ×%.2f (%+.1f%% bonusu) +Szansa na unik: %.1f%% +Redukcja obrażeń: %.1f%% + +Regeneracja energii: %.1f pkt/s (%+.1f) +Regeneracja tarcz: %.1f pkt/s (%+.1f) +Maks. kadłub: %.1f (%+.1f pkt) +Maks. tarcze: %.1f (%+.1f pkt) +Maks. energia: %.1f (%+.1f pkt) +Maks. prędkość: %.1f px/s (%+.1f%%) +Zdobywane XP: %+.1f%% +Zdobywane kredyty: %+.1f%%""" % [ + (float(player.call("get_weapon_damage_multiplier")) - 1.0) * 100.0, + float(player.call("get_weapon_damage_multiplier")), + float(player.call("get_critical_chance")) * 100.0, + float(player.call("get_critical_multiplier")), + float(player.get("critical_damage_bonus")) * 100.0, + float(player.call("get_evasion_chance")) * 100.0, + float(player.call("get_damage_reduction")) * 100.0, + float(player.get("energy_regeneration")), + float(player.get("energy_regeneration")) - float(player.get("base_energy_regeneration")), + float(player.get("shield_regeneration")), + float(player.get("shield_regeneration")) - float(player.get("base_shield_regeneration")), + float(player.get("max_hull")), float(player.get("max_hull")) - float(player.get("base_max_hull")), + float(player.get("max_shields")), float(player.get("max_shields")) - float(player.get("base_max_shields")), + float(player.get("max_energy")), float(player.get("max_energy")) - float(player.get("base_max_energy")), + float(player.get("max_speed")), (float(player.get("max_speed")) / float(player.get("base_max_speed")) - 1.0) * 100.0, + (float(player.get("experience_multiplier")) - 1.0) * 100.0, + (float(player.get("credits_multiplier")) - 1.0) * 100.0, + ] + func _create_ability_bar() -> void: var panel := PanelContainer.new() panel.name = "ActiveAbilities" diff --git a/scripts/game/game.gd b/scripts/game/game.gd index 4b17213..64fb656 100644 --- a/scripts/game/game.gd +++ b/scripts/game/game.gd @@ -66,6 +66,7 @@ func _spawn_celestial_bodies() -> void: var star := Node2D.new() star.set_script(CELESTIAL_BODY) star.name = "CentralStar" + star.set("display_name", system_data["star_name"]) star.set("body_texture", system_data["star_texture"]) star.set("radius", 140.0) star.set("is_star", true) @@ -75,6 +76,7 @@ func _spawn_celestial_bodies() -> void: var planet := Node2D.new() planet.set_script(CELESTIAL_BODY) planet.name = "Planet%d" % (index + 1) + planet.set("display_name", data["name"]) planet.position = data["position"] planet.set("body_texture", data["texture"]) planet.set("radius", data["radius"]) diff --git a/scripts/game/ship_catalog.gd b/scripts/game/ship_catalog.gd new file mode 100644 index 0000000..86779d2 --- /dev/null +++ b/scripts/game/ship_catalog.gd @@ -0,0 +1,22 @@ +extends RefCounted +class_name ShipCatalog + +# Base hull values, before equipment and captain bonuses. +const SHIPS := [ + {"number": 0, "name": "Aegis", "role": "Wzmocnione tarcze", "hull": 100.0, "shields": 125.0, "energy": 100.0, "speed": 90.0}, + {"number": 2, "name": "Titan", "role": "Wzmocniony kadłub", "hull": 125.0, "shields": 100.0, "energy": 100.0, "speed": 90.0}, + {"number": 3, "name": "Eagle", "role": "Szybki myśliwiec", "hull": 100.0, "shields": 100.0, "energy": 100.0, "speed": 108.0}, + {"number": 7, "name": "Phantom", "role": "Większy zapas energii", "hull": 100.0, "shields": 100.0, "energy": 125.0, "speed": 90.0}, +] + +static func texture_path(ship: Dictionary) -> String: + return "res://assets/Ships/Spaceship_%d.png" % int(ship["number"]) + +static func for_path(path: String) -> Dictionary: + for ship in SHIPS: + if texture_path(ship) == path: + return ship + return SHIPS[0] + +static func stats_text(ship: Dictionary) -> String: + return "Kadłub: %d · Tarcze: %d\nEnergia: %d · Prędkość: %d px/s" % [ship["hull"], ship["shields"], ship["energy"], ship["speed"]] diff --git a/scripts/game/ship_catalog.gd.uid b/scripts/game/ship_catalog.gd.uid new file mode 100644 index 0000000..60466b7 --- /dev/null +++ b/scripts/game/ship_catalog.gd.uid @@ -0,0 +1 @@ +uid://bx36ksa4vir5x diff --git a/scripts/game/ship_controller.gd b/scripts/game/ship_controller.gd index 36adcf3..7e8927d 100644 --- a/scripts/game/ship_controller.gd +++ b/scripts/game/ship_controller.gd @@ -5,6 +5,7 @@ const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn") const DAMAGE_POPUP_SCENE := preload("res://scenes/game/damage_popup.tscn") const COMBAT_ROLLS := preload("res://scripts/game/combat_rolls.gd") const CAPTAIN_BALANCE := preload("res://scripts/captain/captain_balance.gd") +const SHIP_CATALOG := preload("res://scripts/game/ship_catalog.gd") @export var max_speed := 76.0 @export var reverse_max_speed := 210.0 @@ -47,6 +48,14 @@ var _boost_acceleration := 0.0 var _boost_velocity_acceleration := 0.0 @onready var _shield_visual: Node2D = $ShieldVisual +func _enter_tree() -> void: + # Set the chassis before inventory children calculate equipment bonuses. + var chassis := SHIP_CATALOG.for_path(GameState.selected_ship_path) + base_max_hull = float(chassis["hull"]) + base_max_shields = float(chassis["shields"]) + base_max_energy = float(chassis["energy"]) + base_max_speed = float(chassis["speed"]) + func apply_equipment_bonuses(bonuses: Dictionary) -> void: max_hull = (base_max_hull + float(bonuses.get("max_hull", 0.0))) * (1.0 + float(bonuses.get("hull_percent", 0.0)) / 100.0) max_shields = (base_max_shields + float(bonuses.get("max_shields", 0.0))) * (1.0 + float(bonuses.get("shield_percent", 0.0)) / 100.0) @@ -77,7 +86,7 @@ func _ready() -> void: _ensure_input_action(&"ship_strafe_right", KEY_E) _sprite = $Sprite2D - var texture_path := GameState.selected_ship_path + var texture_path := SHIP_CATALOG.texture_path(SHIP_CATALOG.for_path(GameState.selected_ship_path)) if texture_path.is_empty() or not ResourceLoader.exists(texture_path): texture_path = DEFAULT_SHIP_TEXTURE diff --git a/scripts/items/item_instance.gd b/scripts/items/item_instance.gd index 1f50295..8dd6a9c 100644 --- a/scripts/items/item_instance.gd +++ b/scripts/items/item_instance.gd @@ -61,7 +61,27 @@ func get_rarity_name() -> String: return ["Zwykły", "Niepospolity", "Rzadki", "Epicki", "Legendarny"][clampi(rarity, 0, 4)] func get_rarity_color() -> Color: - return [Color("b9c5d4"), Color("63df91"), Color("65b4ff"), Color("bd83ff"), Color("ffca63")][clampi(rarity, 0, 4)] + return get_background_color().lightened(0.4) + +func get_addon_count() -> int: + var count := 0 + for modifier in additional_modifiers: + if modifier != null: + count += 1 + return count + +func get_background_color() -> Color: + return [Color("101925"), Color("414b58"), Color("65432d"), Color("244c79"), Color("796025")][mini(get_addon_count(), 4)] + +func get_type_color() -> Color: + if definition == null: + return Color("354455") + match definition.category: + ItemDefinition.Category.WEAPON: + return Color("ae74ef") + ItemDefinition.Category.ACTIVE_EXTENSION: + return Color("f39c4d") + return Color("65ce8b") func get_tooltip_text() -> String: if definition == null: diff --git a/scripts/items/loot_pickup.gd b/scripts/items/loot_pickup.gd index c9b2492..700f54a 100644 --- a/scripts/items/loot_pickup.gd +++ b/scripts/items/loot_pickup.gd @@ -14,7 +14,7 @@ func _ready() -> void: _label.size = Vector2(220, 44) _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _label.add_theme_font_size_override("font_size", 11) - _label.add_theme_color_override("font_color", item.get_rarity_color()) + _label.add_theme_color_override("font_color", Color("f1f4f8")) _label.text = item.definition.display_name add_child(_label) queue_redraw() @@ -22,10 +22,11 @@ func _ready() -> void: func _draw() -> void: if item == null: return - var color := item.get_rarity_color() - draw_circle(Vector2.ZERO, 15.0, Color(color, 0.16)) - draw_arc(Vector2.ZERO, 15.0, 0.0, TAU, 32, color, 2.0, true) - draw_colored_polygon(PackedVector2Array([Vector2(0, -9), Vector2(8, 0), Vector2(0, 9), Vector2(-8, 0)]), color) + var background := item.get_background_color() + var border := item.get_type_color() + draw_circle(Vector2.ZERO, 15.0, background) + draw_arc(Vector2.ZERO, 15.0, 0.0, TAU, 32, border, 2.0, true) + draw_polyline(PackedVector2Array([Vector2(0, -8), Vector2(7, 0), Vector2(0, 8), Vector2(-7, 0), Vector2(0, -8)]), Color("f1f4f8"), 1.0, true) func _physics_process(delta: float) -> void: if not is_instance_valid(_player) or bool(_player.get("_is_destroyed")): diff --git a/scripts/levels/celestial_body.gd b/scripts/levels/celestial_body.gd index cc90494..62d211f 100644 --- a/scripts/levels/celestial_body.gd +++ b/scripts/levels/celestial_body.gd @@ -1,6 +1,7 @@ extends Node2D var body_texture: Texture2D +var display_name: String = "" var radius := 100.0 var is_star := false var is_protected := false @@ -31,13 +32,13 @@ func _update_label() -> void: if _label.get_parent() == null: add_child(_label) if is_star: - _label.text = "GWIAZDA CENTRALNA" + _label.text = display_name + "\nGwiazda centralna" _label.add_theme_color_override("font_color", Color("ffdb98")) elif is_protected: - _label.text = "PLANETA CHRONIONEJ RASY\n" + ("USŁUGI DOSTĘPNE" if is_safe else "POKONAJ WSZYSTKICH WROGÓW") + _label.text = display_name + " — zamieszkana\n" + ("USŁUGI DOSTĘPNE" if is_safe else "POKONAJ WSZYSTKICH WROGÓW") _label.add_theme_color_override("font_color", Color("77e8bc") if is_safe else Color("ffbd83")) else: - _label.text = "PLANETA" + _label.text = display_name _label.add_theme_color_override("font_color", Color("9baec6")) func _draw() -> void: diff --git a/scripts/levels/celestial_names.gd b/scripts/levels/celestial_names.gd new file mode 100644 index 0000000..fad9818 --- /dev/null +++ b/scripts/levels/celestial_names.gd @@ -0,0 +1,39 @@ +extends RefCounted + +# Expand these lists to add names. The generator draws without repeats per system. +const PLANETS := [ + "Aeloria", "Aetheris", "Akara", "Almara", "Altessa", "Amaris", "Ankora", "Ardessa", "Arkanis", "Arluna", + "Ashara", "Astraya", "Aurelia", "Avalora", "Axion", "Azura", "Belara", "Borealis", "Bravora", "Caelora", + "Caldera", "Calyptra", "Carthis", "Celestia", "Cerion", "Cindara", "Corthis", "Cyrenia", "Damaris", "Danora", + "Delyra", "Demeris", "Doria", "Dravona", "Draxia", "Elaris", "Elatha", "Eldora", "Elysara", "Emberia", + "Enara", "Erevia", "Esterra", "Ethora", "Evoria", "Faleris", "Farona", "Fenoria", "Ferros", "Florana", + "Galara", "Galdora", "Ganyra", "Gareth", "Gorath", "Halara", "Heloria", "Hespera", "Hydrion", "Icarra", + "Ilmara", "Ilyria", "Imeris", "Inara", "Ionia", "Iridia", "Iskora", "Ixara", "Janara", "Jovara", + "Kaelith", "Kalara", "Kandora", "Karthis", "Kelora", "Kharon", "Korelia", "Krythia", "Lamara", "Lanthea", + "Larion", "Lethara", "Lioris", "Lumara", "Luneth", "Lyssara", "Maelora", "Malthis", "Maravia", "Meridia", + "Mireth", "Morana", "Myrris", "Nalara", "Neridia", "Nethora", "Nivalis", "Noctara", "Novara", "Nyxara", + "Oceara", "Olmara", "Ondaris", "Orenth", "Orilia", "Othara", "Palaris", "Peloria", "Peridia", "Phaedra", + "Pyrrha", "Qalara", "Quenora", "Ralith", "Ravena", "Relora", "Rhyona", "Rivara", "Saelora", "Salthis", + "Seloria", "Serenia", "Shalara", "Solara", "Sylvara", "Talara", "Tamaris", "Telora", "Teravia", "Thalara", + "Thyra", "Toralis", "Trivora", "Uloria", "Umbria", "Vaelora", "Valthis", "Velara", "Vesperia", "Virelia", + "Volara", "Weylara", "Xandria", "Xelora", "Yalara", "Ydris", "Zaelora", "Zareth", "Zeridia", "Zyphora", +] + +const STARS := [ + "Aldorin", "Althar", "Anteros", "Arcthel", "Asterion", "Astraeus", "Aurion", "Azareth", "Baelor", "Balthir", + "Belthar", "Caelion", "Caldrin", "Caphiron", "Celion", "Cerath", "Corvian", "Cygnar", "Daelion", "Damarion", + "Denaris", "Doranth", "Dracon", "Elarion", "Elthar", "Endrion", "Erethon", "Esterion", "Falrion", "Fendris", + "Feron", "Galath", "Galdrian", "Gethar", "Haelion", "Haldor", "Heliar", "Hesperion", "Hyron", "Ildar", + "Ilthion", "Indrath", "Iriath", "Iskar", "Jareth", "Jovion", "Kaelor", "Kalthar", "Keldrin", "Kharion", + "Korath", "Kyron", "Laerion", "Lantor", "Larath", "Lethion", "Lorath", "Lumion", "Lyrian", "Maelthar", + "Maldrin", "Mendros", "Merath", "Mithrion", "Morion", "Naelor", "Neldrin", "Nerath", "Nivalon", "Nocturion", + "Oberion", "Olthar", "Orath", "Orionis", "Othrin", "Paelor", "Pelion", "Perath", "Pyron", "Quenthar", + "Raelion", "Raldor", "Rethar", "Saelion", "Seldrin", "Serath", "Solion", "Sylthar", "Taelor", "Teldrin", + "Tharion", "Theron", "Ulthar", "Vaelion", "Valdrin", "Velthar", "Veyron", "Xanthor", "Yldar", "Zerion", +] + +static func take_name(pool: Array, rng: RandomNumberGenerator) -> String: + var index := rng.randi_range(0, pool.size() - 1) + var result: String = pool[index] + pool.remove_at(index) + return result diff --git a/scripts/levels/celestial_names.gd.uid b/scripts/levels/celestial_names.gd.uid new file mode 100644 index 0000000..80d08be --- /dev/null +++ b/scripts/levels/celestial_names.gd.uid @@ -0,0 +1 @@ +uid://cvt3efku1cddc diff --git a/scripts/levels/system_generator.gd b/scripts/levels/system_generator.gd index 6f694ca..dc16e8a 100644 --- a/scripts/levels/system_generator.gd +++ b/scripts/levels/system_generator.gd @@ -1,6 +1,7 @@ extends RefCounted const BALANCE := preload("res://scripts/levels/level_balance.gd") +const NAMES := preload("res://scripts/levels/celestial_names.gd") const WORLD_SIZE := Vector2(5200, 3600) const PLAYER_START := Vector2(180, 200) const SAFE_ZONE_RADIUS := 1200.0 @@ -39,6 +40,12 @@ const PLANET_TEXTURES: Array[Texture2D] = [ static func generate(level: int, seed_value: int) -> Dictionary: var rng := RandomNumberGenerator.new() rng.seed = seed_value + # Separate stream keeps names stable without changing existing system geometry. + var name_rng := RandomNumberGenerator.new() + name_rng.seed = seed_value ^ 0x4e414d45 + var planet_names := NAMES.PLANETS.duplicate() + var star_names := NAMES.STARS.duplicate() + var star_name := NAMES.take_name(star_names, name_rng) var star_texture := STAR_TEXTURES[rng.randi_range(0, STAR_TEXTURES.size() - 1)] var planet_count := rng.randi_range(1, 5) var protected_index := rng.randi_range(0, planet_count - 1) @@ -48,6 +55,7 @@ static func generate(level: int, seed_value: int) -> Dictionary: var angle := angle_offset + TAU * index / planet_count + rng.randf_range(-0.12, 0.12) var orbit := 1000.0 + 110.0 * index + rng.randf_range(-40.0, 40.0) planets.append({ + "name": NAMES.take_name(planet_names, name_rng), "position": Vector2.RIGHT.rotated(angle) * orbit, "radius": rng.randf_range(70.0, 105.0), "texture": PLANET_TEXTURES[rng.randi_range(0, PLANET_TEXTURES.size() - 1)], @@ -71,6 +79,7 @@ static func generate(level: int, seed_value: int) -> Dictionary: }) cursor += 1 return { + "star_name": star_name, "star_texture": star_texture, "star_position": Vector2.ZERO, "player_start": PLAYER_START, diff --git a/scripts/ui/equipment_slot_view.gd b/scripts/ui/equipment_slot_view.gd index 0024e31..dfb54d0 100644 --- a/scripts/ui/equipment_slot_view.gd +++ b/scripts/ui/equipment_slot_view.gd @@ -14,17 +14,19 @@ func _ready() -> void: overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(overlay) _delete_button = Button.new() - _delete_button.text = "×" + _delete_button.text = "x" _delete_button.tooltip_text = "Rozłóż przedmiot: +10 części" _delete_button.flat = true _delete_button.add_theme_font_size_override("font_size", 13) _delete_button.add_theme_color_override("font_color", Color("ff8989")) overlay.add_child(_delete_button) _delete_button.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT) - _delete_button.offset_left = -20 - _delete_button.offset_right = 0 - _delete_button.offset_top = 0 - _delete_button.offset_bottom = 18 + # Overlay follows the panel's content rect (5 px top, 18 px right padding). + _delete_button.offset_left = -4 + _delete_button.offset_right = 16 + _delete_button.offset_top = -3 + _delete_button.offset_bottom = 17 + _delete_button.custom_minimum_size = Vector2(20, 20) _delete_button.pressed.connect(_delete_item) _inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory if _inventory: @@ -35,31 +37,43 @@ func _ready() -> void: "module": custom_minimum_size = Vector2(104, 36) _: - custom_minimum_size = Vector2(84, 40) + custom_minimum_size = Vector2(148, 56) + slot_label.add_theme_font_size_override("font_size", 10) + slot_label.custom_minimum_size.y = 36 + slot_label.size_flags_vertical = Control.SIZE_EXPAND_FILL + slot_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + slot_label.text_overrun_behavior = TextServer.OVERRUN_NO_TRIMMING _refresh() func _refresh() -> void: if not is_node_ready() or not _inventory: return var item: ItemInstance = _inventory.get_item(slot_kind, slot_index) + _apply_slot_style(item) tooltip_text = item.get_tooltip_text() if item != null else "" if _delete_button: _delete_button.visible = item != null if item == null or item.definition == null: slot_label.add_theme_color_override("font_color", Color("82909e")) - slot_label.text = _empty_slot_text() + slot_label.text = "" return var definition := item.definition - slot_label.add_theme_color_override("font_color", item.get_rarity_color()) - var category_name := _category_name(definition.category) - var value_text := _stat_text(item) - var slot_title := category_name - if slot_kind == "weapon": - var weapon_binding := "LPM" if slot_index == 0 else "PPM" - slot_title = "BROŃ %d [%s]" % [slot_index + 1, weapon_binding] - elif slot_kind == "module": - slot_title = "MODUŁ %d [%d]" % [slot_index + 1, slot_index + 1] - slot_label.text = "%s\n%s\n%s" % [slot_title, definition.display_name, value_text] + slot_label.add_theme_color_override("font_color", Color("f1f4f8")) + slot_label.text = definition.display_name + +func _apply_slot_style(item: ItemInstance) -> void: + var style := StyleBoxFlat.new() + style.bg_color = Color("101925") + style.border_color = Color("354455") + style.set_border_width_all(1) + style.set_content_margin_all(5) + if slot_kind == "cargo": + style.content_margin_right = 18 + if item != null and item.definition != null: + style.border_color = item.get_type_color() + style.bg_color = item.get_background_color() + style.set_border_width_all(2) + add_theme_stylebox_override("panel", style) func _delete_item() -> void: if _inventory: @@ -80,7 +94,9 @@ func _make_custom_tooltip(for_text: String) -> Object: label.add_theme_font_size_override("font_size", 13) var item := _inventory.get_item(slot_kind, slot_index) if item: - label.add_theme_color_override("font_color", item.get_rarity_color()) + background.bg_color = item.get_background_color() + background.border_color = item.get_type_color() + label.add_theme_color_override("font_color", Color("f1f4f8")) panel.add_child(label) return panel @@ -135,9 +151,9 @@ func _category_name(category: int) -> String: ItemDefinition.Category.GENERATOR: return "GENERATOR" ItemDefinition.Category.EXTENSION: - return "ROZSZERZENIE" + return "MODUŁ PASYWNY" ItemDefinition.Category.ACTIVE_EXTENSION: - return "ROZSZERZENIE AKTYWNE" + return "ROZSZERZENIE" return "PRZEDMIOT" func _stat_text(item: ItemInstance) -> String: diff --git a/scripts/ui/protected_planet_marker.gd b/scripts/ui/protected_planet_marker.gd index d5332cd..4b39252 100644 --- a/scripts/ui/protected_planet_marker.gd +++ b/scripts/ui/protected_planet_marker.gd @@ -1,5 +1,7 @@ extends Control +const GAME_FONT := preload("res://assets/fonts/font2.ttf") + var game: Node2D func _process(_delta: float) -> void: @@ -22,5 +24,7 @@ func _draw() -> void: var side := Vector2(-direction.y, direction.x) var color := Color("77e8bc") if bool(game.get("system_cleared")) else Color("ffbd83") draw_colored_polygon(PackedVector2Array([point + direction * 10, point - direction * 7 + side * 7, point - direction * 7 - side * 7]), color) - var label_position := Vector2(clampf(point.x - 45, 8, size.x - 120), clampf(point.y + 25, 20, size.y - 12)) - draw_string(ThemeDB.fallback_font, label_position, "Planeta rasy", HORIZONTAL_ALIGNMENT_LEFT, -1, 12, color) + var label_text := str(planet.get("display_name")) + " — zamieszkana" + var text_width := GAME_FONT.get_string_size(label_text, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x + var label_position := Vector2(clampf(point.x - text_width * 0.5, 8, maxf(8, size.x - text_width - 8)), clampf(point.y + 25, 20, size.y - 12)) + draw_string(GAME_FONT, label_position, label_text, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, color) diff --git a/scripts/ui/ship_selection.gd b/scripts/ui/ship_selection.gd index c6b401f..38f39cd 100644 --- a/scripts/ui/ship_selection.gd +++ b/scripts/ui/ship_selection.gd @@ -1,7 +1,7 @@ extends Control const GAME_SCENE := "res://scenes/game/game.tscn" -const SHIP_DIRECTORY := "res://assets/Ships" +const CATALOG := preload("res://scripts/game/ship_catalog.gd") @onready var ship_grid: GridContainer = %ShipGrid @onready var selected_label: Label = %SelectedLabel @@ -15,49 +15,41 @@ func _ready() -> void: _load_ships() func _load_ships() -> void: - var directory := DirAccess.open(SHIP_DIRECTORY) - if directory == null: - selected_label.text = "Nie można znaleźć katalogu statków." - return - - var ship_files: Array[String] = [] - for file_name in directory.get_files(): - if file_name.get_extension().to_lower() == "png": - ship_files.append(file_name) - ship_files.sort_custom(func(a: String, b: String) -> bool: - return _ship_number(a) < _ship_number(b) - ) - - for file_name in ship_files: - var ship_path := SHIP_DIRECTORY.path_join(file_name) + var selected_ship := CATALOG.for_path(GameState.selected_ship_path) + for chassis in CATALOG.SHIPS: + var ship_path := CATALOG.texture_path(chassis) var texture := load(ship_path) as Texture2D if texture == null: continue - var ship_number := _ship_number(file_name) var button := Button.new() - button.custom_minimum_size = Vector2(150, 125) + button.name = "Ship%d" % int(chassis["number"]) + button.custom_minimum_size = Vector2(250, 260) + button.size_flags_horizontal = Control.SIZE_EXPAND_FILL button.toggle_mode = true button.button_group = button_group - button.icon = texture - button.expand_icon = true - button.add_theme_constant_override("icon_max_width", 92) - button.vertical_icon_alignment = VERTICAL_ALIGNMENT_TOP - button.text = "Statek %d" % ship_number - button.add_theme_font_size_override("font_size", 16) - button.pressed.connect(_select_ship.bind(ship_path, ship_number, button)) + var preview := TextureRect.new() + preview.texture = texture + preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + preview.mouse_filter = Control.MOUSE_FILTER_IGNORE + button.add_child(preview) + preview.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP) + preview.offset_left = -50.0 + preview.offset_right = 50.0 + preview.offset_top = 12.0 + preview.offset_bottom = 102.0 + button.text = "%s\n%s\n\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)] + button.add_theme_font_size_override("font_size", 13) + button.tooltip_text = "%s — %s\nStatystyki bazowe przed bonusami modułów.\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)] + button.pressed.connect(_select_ship.bind(chassis, button)) ship_grid.add_child(button) + if chassis["number"] == selected_ship["number"]: + _select_ship(chassis, button) - if GameState.selected_ship_path == ship_path or GameState.selected_ship_path.is_empty(): - _select_ship(ship_path, ship_number, button) - -func _ship_number(file_name: String) -> int: - var digits := file_name.get_basename().trim_prefix("Spaceship_") - return int(digits) if digits.is_valid_int() else 9999 - -func _select_ship(ship_path: String, ship_number: int, button: Button) -> void: - GameState.selected_ship_path = ship_path +func _select_ship(chassis: Dictionary, button: Button) -> void: + GameState.selected_ship_path = CATALOG.texture_path(chassis) button.button_pressed = true - selected_label.text = "Wybrano: Statek %d" % ship_number + selected_label.text = "Wybrano: %s · %s" % [chassis["name"], chassis["role"]] continue_button.disabled = false func _start_game() -> void: diff --git a/tests/ships/ship_selection_test.gd b/tests/ships/ship_selection_test.gd new file mode 100644 index 0000000..74bd0d1 --- /dev/null +++ b/tests/ships/ship_selection_test.gd @@ -0,0 +1,39 @@ +extends Node + +const CATALOG := preload("res://scripts/game/ship_catalog.gd") +const SELECTION := preload("res://scenes/ui/ship_selection.tscn") +const PLAYER := preload("res://scenes/game/player_ship.tscn") + +func _ready() -> void: + GameState.selected_ship_path = "res://assets/Ships/Spaceship_9.png" + var selection := SELECTION.instantiate() + add_child(selection) + var grid: GridContainer = selection.get_node("Layout/ShipScroll/ShipGrid") + assert(grid.get_child_count() == 4) + assert(GameState.selected_ship_path == CATALOG.texture_path(CATALOG.SHIPS[0])) + for chassis in CATALOG.SHIPS: + grid.get_node("Ship%d" % chassis["number"]).emit_signal("pressed") + assert(GameState.selected_ship_path == CATALOG.texture_path(chassis)) + GameState.start_new_run() + var ship := PLAYER.instantiate() as CharacterBody2D + add_child(ship) + assert(ship.get("max_hull") == chassis["hull"] and ship.get("hull") == chassis["hull"]) + assert(ship.get("max_shields") == chassis["shields"] and ship.get("shields") == chassis["shields"]) + assert(ship.get("max_energy") == chassis["energy"] and ship.get("energy") == chassis["energy"]) + assert(ship.get("max_speed") == chassis["speed"] and ship.get("energy_regeneration") == 5.0) + assert(ship.get_node("Sprite2D").texture.resource_path == CATALOG.texture_path(chassis)) + var inventory: ShipInventory = ship.get_node("ShipInventory") + assert(inventory.move_item("cargo", 2, "module", 0)) + var equipped := inventory.get_item("module", 0) + assert(ship.get("max_shields") == chassis["shields"] + equipped.get_stat_total("max_shields")) + inventory.preserve_for_restart() + ship.queue_free() + await get_tree().process_frame + var restored := PLAYER.instantiate() as CharacterBody2D + add_child(restored) + assert(restored.get("base_max_shields") == chassis["shields"]) + assert(restored.get("shields") == restored.get("max_shields")) + assert(restored.get_node("ShipInventory").get_item("module", 0) == equipped) + restored.queue_free() + await get_tree().process_frame + print("SHIP_SELECTION_TEST: PASS — four choices, fallback, base stats, full resources, equipment and restart") diff --git a/tests/ships/ship_selection_test.gd.uid b/tests/ships/ship_selection_test.gd.uid new file mode 100644 index 0000000..0f9b2ac --- /dev/null +++ b/tests/ships/ship_selection_test.gd.uid @@ -0,0 +1 @@ +uid://dnxknxvb27xo7 diff --git a/tests/ships/ship_selection_test.tscn b/tests/ships/ship_selection_test.tscn new file mode 100644 index 0000000..f542fee --- /dev/null +++ b/tests/ships/ship_selection_test.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/ships/ship_selection_test.gd" id="1"] + +[node name="ShipSelectionTest" type="Node"] +script = ExtResource("1")