zminy
This commit is contained in:
parent
c04e08e406
commit
bf74111d72
@ -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.
|
||||
|
||||
@ -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={
|
||||
|
||||
@ -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"
|
||||
|
||||
6
resources/ui/game_theme.tres
Normal file
6
resources/ui/game_theme.tres
Normal 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")
|
||||
@ -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
|
||||
|
||||
@ -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ę"
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"])
|
||||
|
||||
22
scripts/game/ship_catalog.gd
Normal file
22
scripts/game/ship_catalog.gd
Normal 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"]]
|
||||
1
scripts/game/ship_catalog.gd.uid
Normal file
1
scripts/game/ship_catalog.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bx36ksa4vir5x
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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")):
|
||||
|
||||
@ -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:
|
||||
|
||||
39
scripts/levels/celestial_names.gd
Normal file
39
scripts/levels/celestial_names.gd
Normal 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
|
||||
1
scripts/levels/celestial_names.gd.uid
Normal file
1
scripts/levels/celestial_names.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cvt3efku1cddc
|
||||
@ -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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
39
tests/ships/ship_selection_test.gd
Normal file
39
tests/ships/ship_selection_test.gd
Normal 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")
|
||||
1
tests/ships/ship_selection_test.gd.uid
Normal file
1
tests/ships/ship_selection_test.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dnxknxvb27xo7
|
||||
6
tests/ships/ship_selection_test.tscn
Normal file
6
tests/ships/ship_selection_test.tscn
Normal 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")
|
||||
Loading…
x
Reference in New Issue
Block a user