This commit is contained in:
Tomasz Boruc 2026-10-10 22:38:40 +02:00
parent c04e08e406
commit bf74111d72
23 changed files with 314 additions and 90 deletions

View File

@ -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.

View File

@ -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={

View File

@ -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"

View File

@ -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")

View File

@ -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

View File

@ -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ę"

View File

@ -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"

View File

@ -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"])

View File

@ -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"]]

View File

@ -0,0 +1 @@
uid://bx36ksa4vir5x

View File

@ -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

View File

@ -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:

View File

@ -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")):

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1 @@
uid://cvt3efku1cddc

View File

@ -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,

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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")

View File

@ -0,0 +1 @@
uid://dnxknxvb27xo7

View File

@ -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")