diff --git a/assets/sounds/music/menu_music2.mp3 b/assets/sounds/music/menu_music2.mp3 new file mode 100644 index 0000000..b0f88bf Binary files /dev/null and b/assets/sounds/music/menu_music2.mp3 differ diff --git a/assets/sounds/music/menu_music2.mp3.import b/assets/sounds/music/menu_music2.mp3.import new file mode 100644 index 0000000..6182951 --- /dev/null +++ b/assets/sounds/music/menu_music2.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://nscqyrjfn7u4" +path="res://.godot/imported/menu_music2.mp3-7efdba0b08e1c22b65cc3d25ef9b8f90.mp3str" + +[deps] + +source_file="res://assets/sounds/music/menu_music2.mp3" +dest_files=["res://.godot/imported/menu_music2.mp3-7efdba0b08e1c22b65cc3d25ef9b8f90.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/resources/items/afterburner.tres b/resources/items/afterburner.tres index 6a6dc8f..580e753 100644 --- a/resources/items/afterburner.tres +++ b/resources/items/afterburner.tres @@ -2,8 +2,7 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bpaiuqwsxswii" path="res://scripts/abilities/afterburner_ability.gd" id="2"] - -[ext_resource type="Texture2D" path="res://assets/icons/afterburner.png" id="icon"] +[ext_resource type="Texture2D" uid="uid://kukr8bhgqa7p" path="res://assets/icons/afterburner.png" id="icon"] [sub_resource type="Resource" id="Ability"] script = ExtResource("2") @@ -12,9 +11,9 @@ description = "Szybko rozpędza do 300% zwykłej prędkości (+200%), następnie [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "afterburner" display_name = "Dopalacz" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie silnika." active_ability = SubResource("Ability") diff --git a/resources/items/cloak.tres b/resources/items/cloak.tres index 0cc607d..2d0f90d 100644 --- a/resources/items/cloak.tres +++ b/resources/items/cloak.tres @@ -1,19 +1,22 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/cloak_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://drdg44cvejr2n"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://ddqhcasuuecgc" path="res://assets/icons/extensions/cloak_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") +kind = 1 ability_id = "cloak" cooldown = 60.0 display_name = "Kamuflaż" description = "Przez 10 s statek staje się niemal niewidoczny. Przeciwnicy nie wykrywają go ani nie śledzą. Oddanie strzału przerywa kamuflaż. Nie zapewnia odporności na już wystrzelone pociski. Odnowienie: 60 s." -kind = 1 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "cloak" display_name = "Kamuflaż" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/items/combat_drones.tres b/resources/items/combat_drones.tres index a92c5a4..fa912dc 100644 --- a/resources/items/combat_drones.tres +++ b/resources/items/combat_drones.tres @@ -1,19 +1,22 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/combat_drones_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cvauqvsl4x5gk"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://huejfa7inar0" path="res://assets/icons/extensions/combat_drones_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") +kind = 5 ability_id = "combat_drones" cooldown = 30.0 display_name = "Drony bojowe" description = "Wypuszcza 3 drony na 10 s. Strzelają działkami mechanicznymi; łącznie zadają 50% DPS statku w momencie startu. Koszt: 50 szt. zwykłej amunicji. Odnowienie: 30 s." -kind = 5 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "combat_drones" display_name = "Drony bojowe" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/items/flares.tres b/resources/items/flares.tres index 8efb3df..b5af891 100644 --- a/resources/items/flares.tres +++ b/resources/items/flares.tres @@ -1,19 +1,22 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/flares_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://demqfp0to5pnx"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://ccdxl4xyjwyg3" path="res://assets/icons/extensions/flares_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") +kind = 3 ability_id = "flares" cooldown = 30.0 display_name = "Flary" description = "Wypuszcza trzy flary na 10 s. Wrogowie w zasięgu obierają je za cel zamiast gracza. Odnowienie: 30 s." -kind = 3 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "flares" display_name = "Flary" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/items/mine_layer.tres b/resources/items/mine_layer.tres index 982ce9d..5686410 100644 --- a/resources/items/mine_layer.tres +++ b/resources/items/mine_layer.tres @@ -1,19 +1,22 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/mine_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cnlcstlicfthn"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://dwv0fw3dyupj3" path="res://assets/icons/extensions/mine_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") +kind = 2 ability_id = "mine_layer" cooldown = 5.0 display_name = "Miny" description = "Zostawia minę na 60 s. Uzbraja się po 0,7 s. Trafiony wróg otrzymuje obrażenia równe 5% maksymalnego kadłuba (bez redukcji absorpcji) i zwalnia o 50% na 5 s. Zrzut co 5 s." -kind = 2 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "mine_layer" display_name = "Miny" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/items/repair_drones.tres b/resources/items/repair_drones.tres index 1dc7b77..d295099 100644 --- a/resources/items/repair_drones.tres +++ b/resources/items/repair_drones.tres @@ -2,8 +2,7 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bgofku10gduus" path="res://scripts/abilities/repair_drones_ability.gd" id="2"] - -[ext_resource type="Texture2D" path="res://assets/icons/repair_drones.png" id="icon"] +[ext_resource type="Texture2D" uid="uid://b1v55dh3vgl78" path="res://assets/icons/repair_drones.png" id="icon"] [sub_resource type="Resource" id="Ability"] script = ExtResource("2") @@ -12,9 +11,9 @@ description = "Naprawiają tyle kadłuba, ile pozwalają części i brakujące p [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "repair_drones" display_name = "Drony naprawcze" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie naprawcze." active_ability = SubResource("Ability") diff --git a/resources/items/shield_charge.tres b/resources/items/shield_charge.tres index 2779dee..f7df7ab 100644 --- a/resources/items/shield_charge.tres +++ b/resources/items/shield_charge.tres @@ -2,8 +2,7 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bwka1dwxq64sl" path="res://scripts/abilities/shield_charge_ability.gd" id="2"] - -[ext_resource type="Texture2D" path="res://assets/icons/shield_charge.png" id="icon"] +[ext_resource type="Texture2D" uid="uid://bckqak06f6qxc" path="res://assets/icons/shield_charge.png" id="icon"] [sub_resource type="Resource" id="Ability"] script = ExtResource("2") @@ -12,9 +11,9 @@ description = "Przywraca 100 punktów tarcz, do ich maksimum. Wymaga i zużywa 1 [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "shield_charge" display_name = "Ładowanie tarcz" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie tarcz." active_ability = SubResource("Ability") diff --git a/resources/items/solar_shield_charge.tres b/resources/items/solar_shield_charge.tres index dad059a..60aab2c 100644 --- a/resources/items/solar_shield_charge.tres +++ b/resources/items/solar_shield_charge.tres @@ -1,19 +1,22 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/solar_shield_charge_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://x5cduw4c4tes"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://jemfbex10v14" path="res://assets/icons/extensions/solar_shield_charge_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") +kind = 4 ability_id = "solar_shield_charge" cooldown = 30.0 display_name = "Doładowanie tarcz" description = "W zasięgu gwiazdy przywraca 20% maksymalnych tarcz na sekundę przez maksymalnie 5 s. Opuszczenie zasięgu kończy efekt. Odnowienie: 30 s." -kind = 4 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "solar_shield_charge" display_name = "Doładowanie tarcz" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/items/teleport.tres b/resources/items/teleport.tres index 0404782..c456e2c 100644 --- a/resources/items/teleport.tres +++ b/resources/items/teleport.tres @@ -1,19 +1,21 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] -[ext_resource type="Texture2D" path="res://assets/icons/extensions/teleport_art.png" id="icon"] +[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://dt406oym4bd6n"] + +[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://c8emovr704ky4" path="res://assets/icons/extensions/teleport_art.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") ability_id = "teleport" cooldown = 60.0 display_name = "Teleport" description = "Teleportuje w losowe, bezpieczne miejsce systemu. Zużywa całą aktualną energię (wymaga dodatniej energii). Odnowienie: 60 s." -kind = 0 + [resource] script = ExtResource("1") -icon = ExtResource("icon") item_id = "teleport" display_name = "Teleport" +icon = ExtResource("icon") category = 5 description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." active_ability = SubResource("Ability") diff --git a/resources/ui/game_theme.tres b/resources/ui/game_theme.tres index 57d22f5..9f167f2 100644 --- a/resources/ui/game_theme.tres +++ b/resources/ui/game_theme.tres @@ -1,16 +1,15 @@ -[gd_resource type="Theme" load_steps=3 format=3] +[gd_resource type="Theme" format=3 uid="uid://c0tot0b54dn2e"] -[ext_resource type="FontFile" path="res://assets/fonts/font2.ttf" id="1_font"] +[ext_resource type="FontFile" uid="uid://drp85tpfncq5m" path="res://assets/fonts/font2.ttf" id="1_font"] [sub_resource type="StyleBoxFlat" id="TooltipShell"] -bg_color = Color(0.035, 0.055, 0.085, 1) content_margin_left = 2.0 content_margin_top = 2.0 content_margin_right = 2.0 content_margin_bottom = 2.0 -shadow_size = 0 +bg_color = Color(0.035, 0.055, 0.085, 1) [resource] default_font = ExtResource("1_font") -TooltipPanel/styles/panel = SubResource("TooltipShell") TooltipLabel/font_sizes/font_size = 11 +TooltipPanel/styles/panel = SubResource("TooltipShell") diff --git a/scenes/game/game.tscn b/scenes/game/game.tscn index 0c9cb37..7bac0e1 100644 --- a/scenes/game/game.tscn +++ b/scenes/game/game.tscn @@ -93,10 +93,7 @@ ENERGIA: 100 / 100" [node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")] layout_mode = 1 offset_left = -270.0 -offset_top = 244.0 -offset_right = -10.0 offset_bottom = 422.0 -grow_vertical = 1 [node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")] layout_mode = 1 diff --git a/scenes/ui/main_menu.tscn b/scenes/ui/main_menu.tscn index 04b3d14..5b188fc 100644 --- a/scenes/ui/main_menu.tscn +++ b/scenes/ui/main_menu.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://csjojorlptcs1" path="res://scripts/ui/main_menu.gd" id="1_script"] [ext_resource type="Texture2D" uid="uid://bfc6l14s8iwhp" path="res://assets/background/background_menu.jpg" id="2_background"] -[ext_resource type="Script" path="res://scripts/game/scene_music_player.gd" id="3_scene_music"] +[ext_resource type="Script" uid="uid://dvokq8qdccw37" path="res://scripts/game/scene_music_player.gd" id="3_scene_music"] [sub_resource type="StyleBoxFlat" id="StyleBox_primary"] content_margin_left = 22.0 @@ -67,7 +67,7 @@ script = ExtResource("1_script") [node name="MenuMusic" type="AudioStreamPlayer" parent="." unique_id=784369201] script = ExtResource("3_scene_music") -track_path = "res://assets/sounds/music/menu_music.mp3" +track_path = "res://assets/sounds/music/menu_music2.mp3" playback_volume_db = -13.0 [node name="Background" type="TextureRect" parent="." unique_id=1339534440] diff --git a/scripts/game/asteroid_destruction_effect.gd b/scripts/game/asteroid_destruction_effect.gd index ba74a48..d99c7ef 100644 --- a/scripts/game/asteroid_destruction_effect.gd +++ b/scripts/game/asteroid_destruction_effect.gd @@ -1,116 +1,88 @@ extends Node2D -const SETTLE_TIME := 8.0 -var _texture: Texture2D -var _fragments: Array[Dictionary] = [] -var _dust: Array[Dictionary] = [] +const PICKUP := preload("res://scripts/items/mineral_pickup.gd") var _elapsed := 0.0 -var _rng := RandomNumberGenerator.new() -var _dust_color := Color("b4a99a") +var _dust: Array[Dictionary] = [] -func configure(sprite: Sprite2D) -> void: - _texture = sprite.texture +func configure(sprite: Sprite2D, count: int = 6, mineral: String = "iron") -> void: transform = sprite.global_transform - var sprite_scale := global_scale + var source_scale := scale scale = Vector2.ONE - _rng.randomize() - var dimensions := _texture.get_size() - var grid: Array[Vector2] = [] - for y in 5: - for x in 5: - var vertex := Vector2(x, y) * dimensions / 4.0 - if x > 0 and x < 4: - vertex.x += _rng.randf_range(-0.3, 0.3) * dimensions.x / 4.0 - if y > 0 and y < 4: - vertex.y += _rng.randf_range(-0.3, 0.3) * dimensions.y / 4.0 - grid.append(vertex) - # Shared jagged cracks keep neighbouring pieces fitted together. - var horizontal: Array[PackedVector2Array] = [] - var vertical: Array[PackedVector2Array] = [] - for y in 5: - for x in 4: - horizontal.append(_fracture_edge(grid[y * 5 + x], grid[y * 5 + x + 1], y > 0 and y < 4)) - for y in 4: - for x in 5: - vertical.append(_fracture_edge(grid[y * 5 + x], grid[(y + 1) * 5 + x], x > 0 and x < 4)) - for y in 4: - for x in 4: - var outline := PackedVector2Array() - outline.append_array(_edge_vertices(horizontal[y * 4 + x], false)) - outline.append_array(_edge_vertices(vertical[y * 5 + x + 1], false)) - outline.append_array(_edge_vertices(horizontal[(y + 1) * 4 + x], true)) - outline.append_array(_edge_vertices(vertical[y * 5 + x], true)) - # Rare jagged cracks can cross at a corner. Repair the silhouette once, - # rather than submitting an invalid polygon every rendered frame. - if Geometry2D.triangulate_polygon(outline).is_empty(): - outline = Geometry2D.convex_hull(outline) - if outline.size() > 1 and outline[0].is_equal_approx(outline[outline.size() - 1]): - outline.remove_at(outline.size() - 1) - if outline.size() < 3 or Geometry2D.triangulate_polygon(outline).is_empty(): - continue - var center := Vector2.ZERO - for vertex in outline: - center += vertex - center /= outline.size() - var origin := (center - dimensions * 0.5) * sprite_scale - var points := PackedVector2Array() - var uv := PackedVector2Array() - for vertex in outline: - points.append((vertex - center) * sprite_scale) - uv.append(vertex / dimensions) - var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT - _fragments.append({"points": points, "uv": uv, "position": origin, - "velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(40.0, 95.0), - "angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)}) - var mineral := _texture.resource_path.get_file().get_basename() - _dust_color = {"iron": Color("be9b75"), "titanium": Color("9ecbe9"), "uranium": Color("a9db71"), "aluminium": Color("d3dce0"), "sulfur": Color("e8cd65")}.get(mineral, Color("b4a99a")) + var dimensions := sprite.texture.get_size() + var offset := randf() + var cracks: Array[PackedVector2Array] = [] + for index in count: + var endpoint := _edge_point(offset + float(index) * 4.0 / count) * dimensions + cracks.append(_fracture_edge(endpoint, count)) + for index in count: + # Neighbours share the same jagged fracture, so the pieces fit together. + var start := offset + float(index) * 4.0 / count + var finish := offset + float(index + 1) * 4.0 / count + var vertices := cracks[index].duplicate() + for corner in range(int(floor(start)) + 1, int(ceil(finish))): + vertices.append(_edge_point(float(corner)) * dimensions) + var next_crack := cracks[(index + 1) % count] + for vertex_index in range(next_crack.size() - 1, 0, -1): + vertices.append(next_crack[vertex_index]) + var center := Vector2.ZERO + for vertex in vertices: + center += vertex + center /= vertices.size() + var fragment_points := PackedVector2Array() + var fragment_uv := PackedVector2Array() + for vertex in vertices: + fragment_points.append((vertex - center) * source_scale) + fragment_uv.append(vertex / dimensions + Vector2(0.5, 0.5)) + var fragment := Node2D.new() + fragment.set_script(PICKUP) + fragment.set("mineral", mineral) + fragment.set("texture", sprite.texture) + fragment.set("points", fragment_points) + fragment.set("uv", fragment_uv) + fragment.position = center * source_scale + fragment.set("drift", center.normalized() * randf_range(65.0, 115.0)) + fragment.set("spin", randf_range(-2.5, 2.5)) + add_child(fragment) for index in 28: - _dust.append({"direction": Vector2.RIGHT.rotated(_rng.randf_range(0.0, TAU)), - "speed": _rng.randf_range(45.0, 210.0), "size": _rng.randf_range(1.0, 3.0)}) - z_index = 35 + _dust.append({"velocity": Vector2.from_angle(randf() * TAU) * randf_range(45.0, 180.0), "size": randf_range(1.0, 3.0)}) light_mask = 4 -func _fracture_edge(start: Vector2, end: Vector2, jagged: bool) -> PackedVector2Array: - var edge := PackedVector2Array([start]) - if jagged: - var normal := (end - start).orthogonal().normalized() - var roughness := start.distance_to(end) * 0.13 - var side := 1.0 if _rng.randf() > 0.5 else -1.0 - for index in 3: - var along := (index + 1.0) / 4.0 + _rng.randf_range(-0.05, 0.05) - edge.append(start.lerp(end, along) + normal * roughness * side * _rng.randf_range(0.5, 1.0)) - side *= -1.0 - edge.append(end) +func _fracture_edge(endpoint: Vector2, count: int) -> PackedVector2Array: + var edge := PackedVector2Array([Vector2.ZERO]) + var normal := endpoint.orthogonal().normalized() + var roughness := endpoint.length() * minf(0.14, 0.65 / count) + var side := -1.0 if randf() < 0.5 else 1.0 + for index in 5: + var along := float(index + 1) / 6.0 + var offset := roughness * sin(along * PI) * side * randf_range(0.6, 1.0) + edge.append(endpoint * along + normal * offset) + side *= -1.0 + edge.append(endpoint) return edge -func _edge_vertices(edge: PackedVector2Array, backwards: bool) -> PackedVector2Array: - var vertices := PackedVector2Array() - # The next edge supplies the end vertex, avoiding duplicate polygon points. - for index in edge.size() - 1: - vertices.append(edge[edge.size() - 1 - index] if backwards else edge[index]) - return vertices +func _edge_point(distance: float) -> Vector2: + var t := fposmod(distance, 4.0) + if t < 1.0: + return Vector2(-0.5 + t, -0.5) + if t < 2.0: + return Vector2(0.5, -0.5 + t - 1.0) + if t < 3.0: + return Vector2(0.5 - (t - 2.0), 0.5) + return Vector2(-0.5, 0.5 - (t - 3.0)) func _process(delta: float) -> void: _elapsed += delta - for fragment in _fragments: - fragment.position += fragment.velocity * delta - fragment.velocity *= exp(-1.2 * delta) - fragment.angle += fragment.spin * delta - fragment.spin *= exp(-0.7 * delta) - queue_redraw() - # Leave the settled debris visible without updating it every frame. - if _elapsed >= SETTLE_TIME: + if _elapsed <= 1.0: + queue_redraw() + elif not _dust.is_empty(): _dust.clear() - set_process(false) + queue_redraw() + if _elapsed > 1.0 and get_child_count() == 0: + queue_free() func _draw() -> void: - for fragment in _fragments: - draw_set_transform(fragment.position, fragment.angle) - draw_polygon(fragment.points, PackedColorArray([Color.WHITE]), fragment.uv, _texture) - draw_set_transform(Vector2.ZERO) - var dust_fade := maxf(0.0, 1.0 - _elapsed / 0.8) + var fade := maxf(0.0, 1.0 - _elapsed) for mote in _dust: - var travel: Vector2 = mote.direction * mote.speed * (1.0 - exp(-2.0 * _elapsed)) / 2.0 - draw_circle(travel, mote.size * (1.0 + _elapsed), Color(_dust_color, dust_fade * 0.65)) + draw_circle(mote.velocity * (1.0 - exp(-2.0 * _elapsed)) * 0.5, mote.size, Color(0.7, 0.75, 0.8, fade * 0.65)) if _elapsed < 0.18: - draw_circle(Vector2.ZERO, 16.0 * (1.0 - _elapsed / 0.18), Color(0.85, 0.9, 1.0, (1.0 - _elapsed / 0.18) * 0.55)) + draw_circle(Vector2.ZERO, 16.0 * (1.0 - _elapsed / 0.18), Color(0.85, 0.9, 1.0, fade * 0.5)) diff --git a/scripts/game/deployed_mine.gd b/scripts/game/deployed_mine.gd index bf8e8b4..29828aa 100644 --- a/scripts/game/deployed_mine.gd +++ b/scripts/game/deployed_mine.gd @@ -4,7 +4,8 @@ const VFX := preload("res://scripts/game/extension_vfx.gd") const MINE_TEXTURE := preload("res://assets/icons/extensions/mine_art.png") const LIFETIME := 60.0 const ARM_TIME := 0.7 -const TRIGGER_RADIUS := 34.0 +const TRIGGER_RADIUS := 60.0 +const BLAST_RADIUS := 140.0 var drift := Vector2.ZERO var _age := 0.0 var _triggered := false @@ -27,17 +28,30 @@ func _physics_process(delta: float) -> void: if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0: continue if global_position.distance_to(enemy.global_position) <= TRIGGER_RADIUS + float(enemy.call("get_visual_size")) * 0.35: - _triggered = true - enemy.call("apply_mine_hit") - var explosion := Node2D.new() - explosion.set_script(preload("res://scripts/game/deployment_flash.gd")) - explosion.set("color", Color("ffbc75")) - explosion.set("blast", true) - explosion.position = position - get_parent().add_child(explosion) - queue_free() + _detonate() return +func _detonate() -> void: + if _triggered: + return + _triggered = true + # Resolve the entire blast once; each nearby enemy receives one hit. + for enemy in get_tree().get_nodes_in_group("enemies"): + if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0: + continue + var hull_radius := float(enemy.call("get_visual_size")) * 0.35 + if global_position.distance_to(enemy.global_position) <= BLAST_RADIUS + hull_radius: + enemy.call("apply_mine_hit") + var explosion := Node2D.new() + explosion.set_script(preload("res://scripts/game/deployment_flash.gd")) + explosion.set("color", Color("ffbc75")) + explosion.set("blast", true) + # The shockwave reaches the same radius as the area damage. + explosion.scale = Vector2.ONE * (BLAST_RADIUS / 88.0) + explosion.position = position + get_parent().add_child(explosion) + queue_free() + func _draw() -> void: var armed := _age >= ARM_TIME var deployment := smoothstep(0.0, 0.35, _age) diff --git a/scripts/game/enemy_mine.gd b/scripts/game/enemy_mine.gd new file mode 100644 index 0000000..7e51853 --- /dev/null +++ b/scripts/game/enemy_mine.gd @@ -0,0 +1,55 @@ +extends Node2D + +const ARM_TIME := 0.7 +const LIFETIME := 60.0 +const TRIGGER_RADIUS := 54.0 +var player: CharacterBody2D +var drift := Vector2.ZERO +var _age := 0.0 +var _triggered := false + +func _ready() -> void: + add_to_group("enemy_mines") + z_index = 12 + +func _physics_process(delta: float) -> void: + _age += delta + position += drift * delta + drift = drift.move_toward(Vector2.ZERO, 100.0 * delta) + queue_redraw() + if _age >= LIFETIME: + queue_free() + return + if _triggered or _age < ARM_TIME or not is_instance_valid(player) or bool(player.get("_is_destroyed")): + return + if global_position.distance_to(player.global_position) <= TRIGGER_RADIUS: + _triggered = true + player.call("take_damage", float(player.get("max_hull")) * 0.05, false, DamageTypes.Type.KINETIC) + var explosion := Node2D.new() + explosion.set_script(preload("res://scripts/game/deployment_flash.gd")) + explosion.set("color", Color("ff4265")) + explosion.set("blast", true) + explosion.position = position + get_parent().add_child(explosion) + queue_free() + +func _draw() -> void: + var armed := _age >= ARM_TIME + var pulse := 0.65 + 0.35 * sin(_age * 7.0) + var accent := Color("ff4265") if armed else Color("ffb45c") + var fade := clampf(LIFETIME - _age, 0.0, 1.0) + draw_circle(Vector2.ZERO, 30.0, Color(accent, 0.08 * pulse * fade)) + var shell := PackedVector2Array() + for index in 16: + var radius := 24.0 if index % 2 == 0 else 13.0 + shell.append(Vector2.from_angle(TAU * index / 16.0) * radius) + draw_colored_polygon(shell, Color("292236") * Color(1, 1, 1, fade)) + var outline := shell.duplicate() + outline.append(shell[0]) + draw_polyline(outline, Color(accent, 0.9 * fade), 1.5, true) + draw_circle(Vector2.ZERO, 10.0, Color("13101e") * Color(1, 1, 1, fade)) + draw_arc(Vector2.ZERO, 11.0, 0.0, TAU, 32, Color(accent, fade), 2.0, true) + for index in 3: + var direction := Vector2.from_angle(-PI * 0.5 + TAU * index / 3.0) + draw_line(direction * 4.0, direction * 8.0, Color(accent, pulse * fade), 2.5, true) + draw_circle(Vector2.ZERO, 3.0, Color(accent, pulse * fade)) diff --git a/scripts/game/enemy_mine.gd.uid b/scripts/game/enemy_mine.gd.uid new file mode 100644 index 0000000..3a5aee5 --- /dev/null +++ b/scripts/game/enemy_mine.gd.uid @@ -0,0 +1 @@ +uid://dgo884psqgnsd diff --git a/scripts/game/enemy_ship.gd b/scripts/game/enemy_ship.gd index a97ef91..b841e4b 100644 --- a/scripts/game/enemy_ship.gd +++ b/scripts/game/enemy_ship.gd @@ -35,6 +35,7 @@ var _shield_visual: Node2D var hull := 50.0 var _player: CharacterBody2D var _activated := false +var _retreat_mine_rolled := false var _sprite: Sprite2D var _weapon_timer := 0.0 var _slow_remaining := 0.0 @@ -115,10 +116,14 @@ func _physics_process(delta: float) -> void: if _activated and to_player.length_squared() > 1.0: var direction := to_player.normalized() var fleeing := is_retreating() + if fleeing and not _retreat_mine_rolled: + _retreat_mine_rolled = true + if randf() < 0.15: + _drop_retreat_mine(to_player.normalized()) var movement := combat_movement(to_player) var desired_rotation := (movement if fleeing else direction).angle() + PI * 0.5 rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta) - velocity = velocity.move_toward(movement * approach_speed * (1.35 if fleeing else 1.0) * get_slow_multiplier(), approach_acceleration * get_slow_multiplier() * delta) + velocity = velocity.move_toward(movement * approach_speed * (0.5 if fleeing else 1.0) * get_slow_multiplier(), approach_acceleration * get_slow_multiplier() * delta) if not fleeing and to_player.length() <= weapon_range and _weapon_timer <= 0.0: _fire_plasma(direction) _weapon_timer = get_fire_interval() @@ -129,7 +134,15 @@ func _physics_process(delta: float) -> void: _check_player_collision() func is_retreating() -> bool: - return hull < max_hull * 0.5 + return hull > 0.0 and hull < max_hull * 0.1 + +func _drop_retreat_mine(toward_target: Vector2) -> void: + var mine := Node2D.new() + mine.set_script(preload("res://scripts/game/enemy_mine.gd")) + mine.set("player", _player) + mine.set("drift", toward_target * 36.0) + mine.position = position + toward_target * (get_visual_size() * 0.5 + 16.0) + get_parent().add_child.call_deferred(mine) func combat_movement(to_target: Vector2) -> Vector2: var distance := to_target.length() @@ -175,7 +188,7 @@ func apply_mine_hit() -> void: return _slow_remaining = 5.0 velocity = velocity.limit_length(approach_speed * 0.5) - take_damage(max_hull * 0.05, false, DamageTypes.Type.KINETIC, true) + take_damage(hull * 0.5, false, DamageTypes.Type.KINETIC, true) func _check_player_collision() -> void: for collision_index in get_slide_collision_count(): diff --git a/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index 5bac64b..e77cf69 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -148,6 +148,11 @@ func _ready() -> void: _window_manager.call("register_window", _inventory_clip, inventory_panel, inventory_panel.get_node("Contents/Header")) $HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred()) _create_ability_bar() + var objectives := PanelContainer.new() + objectives.name = "MissionObjectives" + objectives.set_script(preload("res://scripts/ui/mission_objectives.gd")) + objectives.set("game", get_parent()) + $HUDRoot.add_child(objectives) debug_toggle.pressed.connect(_toggle_debug) inventory_button.pressed.connect(_toggle_inventory) _update_debug_toggle() diff --git a/scripts/game/ship_controller.gd b/scripts/game/ship_controller.gd index fb4b53f..9d225ac 100644 --- a/scripts/game/ship_controller.gd +++ b/scripts/game/ship_controller.gd @@ -400,7 +400,7 @@ func start_boost(speed_bonus: float, acceleration_time: float) -> void: func get_active_ability(slot_index: int) -> ActiveAbility: var item: ItemInstance = $ShipInventory.get_item("extension", slot_index) - return item.definition.active_ability if item != null and item.definition != null else null + return item.get_active_ability() if item != null and item.definition != null else null func get_luck_chance() -> float: return GameState.captain.get_luck_chance(luck_percent) diff --git a/scripts/game/wormhole.gd b/scripts/game/wormhole.gd index fcb79a9..d780229 100644 --- a/scripts/game/wormhole.gd +++ b/scripts/game/wormhole.gd @@ -1,33 +1,35 @@ extends Node2D +const VORTEX := preload("res://scripts/game/wormhole_vortex.gdshader") var _time := 0.0 +var _vortex_material: ShaderMaterial func _ready() -> void: z_index = -2 + light_mask = 4 + var vortex := ColorRect.new() + vortex.name = "Vortex" + vortex.position = Vector2(-144, -144) + vortex.size = Vector2(288, 288) + vortex.mouse_filter = Control.MOUSE_FILTER_IGNORE + _vortex_material = ShaderMaterial.new() + _vortex_material.shader = VORTEX + vortex.material = _vortex_material + add_child(vortex) var label := Label.new() + label.name = "EntryLabel" label.text = "WORMHOLE\nWleć, aby przejść do następnego układu" - label.position = Vector2(-180, 72) - label.size = Vector2(360, 44) + label.position = Vector2(-200, 106) + label.size = Vector2(400, 48) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.add_theme_font_size_override("font_size", 13) - label.add_theme_color_override("font_color", Color("89e8ff")) + label.add_theme_color_override("font_color", Color("a0e7ff")) + label.add_theme_color_override("font_outline_color", Color("030916")) + label.add_theme_constant_override("outline_size", 5) add_child(label) func _process(delta: float) -> void: _time += delta - queue_redraw() - -func _draw() -> void: - var pulse := 1.0 + sin(_time * 2.5) * 0.06 - for ring in range(6, 0, -1): - draw_circle(Vector2.ZERO, (48.0 + ring * 4.0) * pulse, Color(0.28, 0.35, 0.95, 0.025 * (7 - ring))) - draw_circle(Vector2.ZERO, 43.0, Color("040917")) - for arm in 3: - var points := PackedVector2Array() - for segment in 65: - var progress := segment / 64.0 - var angle := progress * TAU * 1.3 + _time * 1.6 + TAU * arm / 3.0 - points.append(Vector2.RIGHT.rotated(angle) * (8.0 + progress * 44.0) * pulse) - draw_polyline(points, Color(0.35 + arm * 0.12, 0.55, 1.0, 0.8), 3.0, true) - draw_arc(Vector2.ZERO, 49.0 * pulse, 0, TAU, 96, Color("8beaff"), 2.0, true) + if _vortex_material != null: + _vortex_material.set_shader_parameter("elapsed", _time) diff --git a/scripts/game/wormhole_vortex.gdshader b/scripts/game/wormhole_vortex.gdshader new file mode 100644 index 0000000..460cbdc --- /dev/null +++ b/scripts/game/wormhole_vortex.gdshader @@ -0,0 +1,62 @@ +shader_type canvas_item; +render_mode unshaded; + +uniform float elapsed = 0.0; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +float turbulence(vec2 p) { + return noise(p) * 0.55 + noise(p * 2.07) * 0.28 + noise(p * 4.13) * 0.17; +} +void fragment() { + vec2 p = (UV - 0.5) * 2.0; + float r = length(p); + float a = atan(p.y, p.x); + float t = elapsed; + float reveal = smoothstep(0.0, 1.2, t); + float pulse = 1.0 + 0.012 * sin(t * 2.1); + float radius = r / pulse; + // Use circular coordinates for seamless turbulent filaments. + float twist = a + 2.6 * log(max(radius, 0.035)) - t * 0.65; + vec2 flow = vec2(cos(twist), sin(twist)); + float clouds = turbulence(flow * 5.0 + vec2(radius * 13.0, -t * 0.3)); + float filaments = pow(max(0.0, sin(twist * 7.0 + clouds * 5.0)), 5.0); + float disc = exp(-pow((radius - 0.46) / 0.125, 2.0)); + float rim_r = 0.432 + 0.008 * sin(a * 6.0 - t * 1.4) + (clouds - 0.5) * 0.017; + float rim = exp(-abs(radius - rim_r) * 170.0); + float bloom = exp(-abs(radius - rim_r) * 25.0); + float halo = exp(-pow(radius / 0.66, 4.0)); + vec3 cyan = vec3(0.16, 0.73, 1.0); + vec3 violet = vec3(0.40, 0.15, 0.93); + vec3 tint = mix(violet, cyan, 0.5 + 0.5 * sin(twist * 2.0 + t * 0.3)); + vec3 color = tint * disc * (0.18 + clouds * 0.55 + filaments * 0.85); + color += cyan * bloom * 0.3 + vec3(0.63, 0.92, 1.0) * rim * 1.5; + color += violet * halo * 0.055; + // A dark throat with contracting spiral bands suggests a deep tunnel. + float inside = 1.0 - smoothstep(0.37, 0.425, radius); + float depth = -log(max(radius / 0.425, 0.015)); + float tunnel = pow(max(0.0, sin(depth * 16.0 - t * 3.3 + a * 2.0)), 12.0); + float throat_fade = smoothstep(0.035, 0.3, radius); + vec3 throat = vec3(0.003, 0.006, 0.026); + throat += mix(violet, cyan, radius / 0.425) * tunnel * throat_fade * 0.22; + throat += cyan * pow(max(0.0, sin(twist * 5.0 + clouds * 2.0)), 14.0) * throat_fade * 0.11; + color = mix(color, throat, inside); + // Orbiting pinpoints and their short luminous trails, outside the event horizon. + float orbit_angle = a - t * 0.24; + vec2 orbit = vec2(cos(orbit_angle), sin(orbit_angle)) * radius * 43.0; + float sparks = pow(turbulence(orbit), 18.0) * 32.0; + sparks *= smoothstep(0.45, 0.52, radius) * (1.0 - smoothstep(0.62, 0.86, radius)); + color += vec3(0.56, 0.83, 1.0) * sparks; + float alpha = max(inside, max(disc * (0.5 + clouds * 0.5), bloom * 0.8)); + alpha = max(alpha, halo * 0.1 + sparks); + alpha *= (1.0 - smoothstep(0.84, 0.98, radius)) * reveal; + COLOR = vec4(color, clamp(alpha, 0.0, 1.0)); +} diff --git a/scripts/game/wormhole_vortex.gdshader.uid b/scripts/game/wormhole_vortex.gdshader.uid new file mode 100644 index 0000000..6bc2dc4 --- /dev/null +++ b/scripts/game/wormhole_vortex.gdshader.uid @@ -0,0 +1 @@ +uid://cintxpcnpffcx diff --git a/scripts/items/crafting_recipes.gd b/scripts/items/crafting_recipes.gd index a251a3d..8adae3e 100644 --- a/scripts/items/crafting_recipes.gd +++ b/scripts/items/crafting_recipes.gd @@ -4,12 +4,14 @@ const MINERALS := ["iron", "titanium", "uranium", "aluminium", "sulfur"] const NAMES := {"iron": "Żelazo", "titanium": "Tytan", "uranium": "Uran", "aluminium": "Aluminium", "sulfur": "Siarka"} const COLORS := {"iron": Color("c5b5a0"), "titanium": Color("9ecbe9"), "uranium": Color("a9ef83"), "aluminium": Color("dce8ef"), "sulfur": Color("f4d466")} const IDS := ["parts", "ammunition", "missile", "energy_bank", "irradiated_ammunition"] +# Base asteroid yield increased from mean 2 (1-3) to mean 6 (5-7). +# Triple mineral costs to preserve base crafts per asteroid; miner remains a bonus. const DATA := { - "parts": {"name": "Części", "amount": 1, "cost": {"titanium": 1, "iron": 1, "aluminium": 1}}, - "ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 1, "sulfur": 1}}, - "missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 1, "sulfur": 1, "uranium": 1}}, - "energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 1, "aluminium": 1}}, - "irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 1, "uranium": 1}}, + "parts": {"name": "Części", "amount": 1, "cost": {"titanium": 3, "iron": 3, "aluminium": 3}}, + "ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 3, "sulfur": 3}}, + "missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 3, "sulfur": 3, "uranium": 3}}, + "energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 3, "aluminium": 3}}, + "irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 3, "uranium": 3}}, } static func icon(id: String) -> Texture2D: diff --git a/scripts/items/extension_cooldowns.gd b/scripts/items/extension_cooldowns.gd new file mode 100644 index 0000000..805a6a1 --- /dev/null +++ b/scripts/items/extension_cooldowns.gd @@ -0,0 +1,27 @@ +extends RefCounted + +const MIN_SECONDS := 5 +const BASE_SECONDS := 30 +# Cumulative P(cooldown <= seconds); interpolate exponentially between anchors. +const SECONDS := [5, 10, 15, 20, 25, 30] +const CHANCES := [0.0002, 0.0015, 0.01, 0.05, 0.25, 1.0] + +static func cumulative_chance(seconds: int) -> float: + if seconds < MIN_SECONDS: + return 0.0 + if seconds >= BASE_SECONDS: + return 1.0 + for index in SECONDS.size() - 1: + if seconds <= SECONDS[index + 1]: + var progress := float(seconds - SECONDS[index]) / float(SECONDS[index + 1] - SECONDS[index]) + return exp(lerpf(log(CHANCES[index]), log(CHANCES[index + 1]), progress)) + return 1.0 + +static func from_roll(roll: float) -> int: + for seconds in range(MIN_SECONDS, BASE_SECONDS): + if roll < cumulative_chance(seconds): + return seconds + return BASE_SECONDS + +static func roll_seconds() -> int: + return from_roll(randf()) diff --git a/scripts/items/extension_cooldowns.gd.uid b/scripts/items/extension_cooldowns.gd.uid new file mode 100644 index 0000000..41b0b88 --- /dev/null +++ b/scripts/items/extension_cooldowns.gd.uid @@ -0,0 +1 @@ +uid://4nswtu72lqfp diff --git a/scripts/items/item_generator.gd b/scripts/items/item_generator.gd index 18b2716..5ffae18 100644 --- a/scripts/items/item_generator.gd +++ b/scripts/items/item_generator.gd @@ -49,6 +49,7 @@ static func generate_for_definition(definition: ItemDefinition, level: int, enem item.primary_value_override = (item.damage_min + item.damage_max) * 0.5 item.energy_regeneration_override = item.definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2) if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + item.extension_cooldown = ItemInstance.EXTENSION_COOLDOWNS.roll_seconds() return item var roll := randf() var count := 1 if roll < 0.5 else (2 if roll < 0.75 else (3 if roll < 0.89 else (4 if roll < 0.97 else 5))) diff --git a/scripts/items/item_instance.gd b/scripts/items/item_instance.gd index 5d0be15..b6ee057 100644 --- a/scripts/items/item_instance.gd +++ b/scripts/items/item_instance.gd @@ -2,6 +2,30 @@ extends Resource class_name ItemInstance const ENERGY_REGENERATION_BONUS_MULTIPLIER := 0.5 +const EXTENSION_COOLDOWNS := preload("res://scripts/items/extension_cooldowns.gd") +# Exported with the item; old items default to 30 seconds without rerolling. +@export_range(5, 30, 1) var extension_cooldown: int = 30 +var _instance_ability: ActiveAbility + +func get_active_ability() -> ActiveAbility: + if definition == null or definition.active_ability == null: + return null + if definition.category != ItemDefinition.Category.ACTIVE_EXTENSION: + return definition.active_ability + if _instance_ability != null and _instance_ability.cooldown == clampi(extension_cooldown, 5, 30): + return _instance_ability + if _instance_ability == null: + # Never mutate the shared ability resource in the item definition. + _instance_ability = definition.active_ability.duplicate() as ActiveAbility + _instance_ability.cooldown = clampi(extension_cooldown, 5, 30) + var description := definition.active_ability.description + var fixed_cooldown := RegEx.new() + fixed_cooldown.compile(" (Odnowienie:|Zrzut co) [0-9]+ s\\.") + description = fixed_cooldown.sub(description, "", true) + if definition.item_id == "mine_layer": + description = "Zostawia minę na 60 s. Uzbraja się po 0,7 s. Wykrywa bliski przelot wroga. Wybuch w promieniu 140 jednostek zadaje każdemu wrogowi 50% aktualnego kadłuba bez redukcji absorpcji (tarcze pochłaniają obrażenia) i spowalnia o 50% na 5 s." + _instance_ability.description = description + "\nOdnowienie: %d s." % _instance_ability.cooldown + return _instance_ability @export var definition: ItemDefinition @export_range(1, 1000, 1) var item_level: int = 1 @@ -110,7 +134,7 @@ func get_tooltip_text() -> String: if definition.category == ItemDefinition.Category.WEAPON: primary_text = "Obrażenia: %s" % get_damage_range_text() elif definition.active_ability != null: - primary_text = definition.active_ability.description + primary_text = get_active_ability().description var lines: Array[String] = [definition.display_name, "%s • Poziom %d" % [get_rarity_name(), item_level], "", definition.description, "", "CECHA GŁÓWNA", primary_text] if definition.energy_regeneration_bonus > 0.0: lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER)) diff --git a/scripts/items/mineral_pickup.gd b/scripts/items/mineral_pickup.gd new file mode 100644 index 0000000..ed8af04 --- /dev/null +++ b/scripts/items/mineral_pickup.gd @@ -0,0 +1,48 @@ +extends Node2D + +const PICKUP_RADIUS := 48.0 +const ATTRACTION_RADIUS := 150.0 +var mineral := "iron" +var texture: Texture2D +var points := PackedVector2Array() +var uv := PackedVector2Array() +var drift := Vector2.ZERO +var spin := 0.0 +var _age := 0.0 +var _collected := false +var _player: CharacterBody2D + +func _ready() -> void: + add_to_group("loot") + add_to_group("mineral_pickups") + _player = get_tree().current_scene.get_node_or_null("PlayerShip") as CharacterBody2D + z_index = 18 + light_mask = 4 + +func _physics_process(delta: float) -> void: + _age += delta + position += drift * delta + drift *= exp(-1.8 * delta) + rotation += spin * delta + spin *= exp(-0.7 * delta) + queue_redraw() + if _collected or _age < 0.8 or not is_instance_valid(_player) or bool(_player.get("_is_destroyed")): + return + var distance := global_position.distance_to(_player.global_position) + if distance <= PICKUP_RADIUS: + _collected = true + GameState.captain.minerals[mineral] = int(GameState.captain.minerals.get(mineral, 0)) + 1 + GameState.captain.emit_changed() + var popup := preload("res://scenes/game/damage_popup.tscn").instantiate() as Label + popup.text = "+1 %s" % {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu", "sulfur": "siarki"}.get(mineral, mineral) + popup.set("text_color", Color("c9e8ff")) + popup.position = _player.position + Vector2(-40, -50) + get_tree().current_scene.add_child.call_deferred(popup) + queue_free() + elif distance <= ATTRACTION_RADIUS: + global_position = global_position.move_toward(_player.global_position, 180.0 * delta) + +func _draw() -> void: + if texture == null or points.size() < 3: + return + draw_polygon(points, PackedColorArray([Color.WHITE]), uv, texture) diff --git a/scripts/items/mineral_pickup.gd.uid b/scripts/items/mineral_pickup.gd.uid new file mode 100644 index 0000000..d0fd567 --- /dev/null +++ b/scripts/items/mineral_pickup.gd.uid @@ -0,0 +1 @@ +uid://cssyy83yo8lss diff --git a/scripts/levels/celestial_body.gd b/scripts/levels/celestial_body.gd index 935d9db..08b6a93 100644 --- a/scripts/levels/celestial_body.gd +++ b/scripts/levels/celestial_body.gd @@ -58,7 +58,7 @@ func _update_label() -> void: _label.text = display_name + "\nGwiazda centralna" _label.add_theme_color_override("font_color", Color("ffdb98")) elif is_protected: - _label.text = display_name + " — zamieszkana\n" + ("USŁUGI DOSTĘPNE" if is_safe else "POKONAJ WSZYSTKICH WROGÓW") + _label.text = display_name + " — zamieszkana" + ("\nUSŁUGI DOSTĘPNE" if is_safe else "") _label.add_theme_color_override("font_color", Color("77e8bc") if is_safe else Color("ffbd83")) else: _label.text = display_name diff --git a/scripts/levels/exploration_poi.gd b/scripts/levels/exploration_poi.gd index df650f2..efaee52 100644 --- a/scripts/levels/exploration_poi.gd +++ b/scripts/levels/exploration_poi.gd @@ -121,9 +121,15 @@ func claim() -> bool: var summary := "" var scale := reward_scale() if kind == "asteroid": - var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale)) + roundi(GameState.captain.get_skill_bonus("miner")) - GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount - summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu", "sulfur": "siarki"}[_mineral]] + var amount := _rng.randi_range(5, 7) + maxi(0, roundi(GameState.captain.get_skill_bonus("miner"))) + var debris := Node2D.new() + debris.set_script(ASTEROID_DESTRUCTION) + debris.name = "AsteroidDebris" + debris.call("configure", _asteroid_sprite, amount, _mineral) + get_parent().add_child.call_deferred(debris) + set_physics_process(false) + queue_free() + return true elif kind == "ruin": var xp := roundi(_rng.randi_range(10, 20) * scale) GameState.captain.add_rewards(xp, 0) @@ -156,13 +162,6 @@ func claim() -> bool: popup.position = position + Vector2(-80, -65) get_parent().add_child.call_deferred(popup) set_physics_process(false) - if kind == "asteroid" and is_instance_valid(_asteroid_sprite): - var debris := Node2D.new() - debris.set_script(ASTEROID_DESTRUCTION) - debris.name = "AsteroidDebris" - debris.call("configure", _asteroid_sprite) - # The POI may be destroyed during a physics query; add debris outside it. - get_parent().add_child.call_deferred(debris) queue_free() return true diff --git a/scripts/ui/captain_skills_ui.gd b/scripts/ui/captain_skills_ui.gd index e3605e4..72c3923 100644 --- a/scripts/ui/captain_skills_ui.gd +++ b/scripts/ui/captain_skills_ui.gd @@ -9,8 +9,6 @@ var _panel: PanelContainer var _contents: VBoxContainer var _bar: HBoxContainer var _buttons: Dictionary = {} -var _was_paused := false -var _owns_pause := false var _refresh_pending := false func _ready() -> void: @@ -78,19 +76,11 @@ func open_management() -> void: if bool(player.get("_is_destroyed")): return if not _screen.visible: - _was_paused = get_tree().paused - _owns_pause = true - get_tree().paused = true _screen.show() _build_management() func _close() -> void: - if not _captain.skill_offers.is_empty(): - return _screen.hide() - if _owns_pause: - get_tree().paused = _was_paused - _owns_pause = false func _build_management() -> void: for child in _contents.get_children(): @@ -103,13 +93,12 @@ func _build_management() -> void: var title := _label("AWANS KAPITANA" if choosing else "UMIEJĘTNOŚCI KAPITANA", 25, Color("91dbea")) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL heading.add_child(title) - if not choosing: - var close := Button.new() - close.name = "Close" - close.text = "WRÓĆ DO LOTU" - close.pressed.connect(_close) - heading.add_child(close) - _contents.add_child(_label("POZIOM %d · GRA WSTRZYMANA" % _captain.level, 13, Color("91aabd"))) + var close := Button.new() + close.name = "Close" + close.text = "WRÓĆ DO LOTU" + close.pressed.connect(_close) + heading.add_child(close) + _contents.add_child(_label("POZIOM %d" % _captain.level, 13, Color("91aabd"))) if choosing: _contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16)) var choices := HBoxContainer.new() @@ -327,6 +316,4 @@ func _layout() -> void: else: _bar.position = Vector2(16, viewport_size.y - 190) -func _exit_tree() -> void: - if _owns_pause and is_inside_tree(): - get_tree().paused = _was_paused + diff --git a/scripts/ui/combat_feedback.gd b/scripts/ui/combat_feedback.gd index 5ea490b..52de316 100644 --- a/scripts/ui/combat_feedback.gd +++ b/scripts/ui/combat_feedback.gd @@ -24,4 +24,10 @@ func _draw() -> void: var warning_color := Color(1.0, 0.32, 0.26, pulse) draw_rect(Rect2(Vector2(3, 3), size - Vector2(6, 6)), warning_color, false, 3) var warning := "NISKI KADŁUB — %d%%" % roundi(hull / max_hull * 100) - draw_string(FONT, Vector2(0, 34), warning, HORIZONTAL_ALIGNMENT_CENTER, size.x, 18, warning_color) + # Keep the warning below the navigation buttons, including after a resize. + var navigation := get_parent().get_node_or_null("TopNavigation") as Control + var warning_top := 104.0 + if is_instance_valid(navigation): + warning_top = navigation.get_global_rect().end.y - global_position.y + 12.0 + var baseline := warning_top + FONT.get_ascent(18) + draw_string(FONT, Vector2(0, baseline), warning, HORIZONTAL_ALIGNMENT_CENTER, size.x, 18, warning_color) diff --git a/scripts/ui/flight_cockpit.gd b/scripts/ui/flight_cockpit.gd index db2bafa..d08af94 100644 --- a/scripts/ui/flight_cockpit.gd +++ b/scripts/ui/flight_cockpit.gd @@ -35,7 +35,7 @@ func _draw() -> void: if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50: return var outline := PackedVector2Array([Vector2(24, 0), Vector2(size.x - 24, 0), Vector2(size.x, 24), Vector2(size.x, size.y - 12), Vector2(size.x - 12, size.y), Vector2(12, size.y), Vector2(0, size.y - 12), Vector2(0, 24)]) - draw_colored_polygon(outline, Color(0.025, 0.041, 0.063, 1.0)) + draw_colored_polygon(outline, Color(0.025, 0.041, 0.063, 0.78)) var closed := outline.duplicate() closed.append(outline[0]) draw_polyline(closed, Color("376779"), 1.5, true) diff --git a/scripts/ui/mission_objectives.gd b/scripts/ui/mission_objectives.gd new file mode 100644 index 0000000..70e83f6 --- /dev/null +++ b/scripts/ui/mission_objectives.gd @@ -0,0 +1,50 @@ +extends PanelContainer + +var game: Node2D +var _objective: Label + +func _ready() -> void: + mouse_filter = Control.MOUSE_FILTER_IGNORE + var style := StyleBoxFlat.new() + style.bg_color = Color(0.025, 0.041, 0.063, 0.72) + style.border_color = Color("376779") + style.set_border_width_all(1) + style.set_corner_radius_all(6) + style.set_content_margin_all(12) + add_theme_stylebox_override("panel", style) + var contents := VBoxContainer.new() + contents.mouse_filter = Control.MOUSE_FILTER_IGNORE + contents.add_theme_constant_override("separation", 6) + add_child(contents) + var title := Label.new() + title.text = "CELE" + title.mouse_filter = Control.MOUSE_FILTER_IGNORE + title.add_theme_font_size_override("font_size", 12) + title.add_theme_color_override("font_color", Color("91dbea")) + contents.add_child(title) + _objective = Label.new() + _objective.name = "CurrentObjective" + _objective.mouse_filter = Control.MOUSE_FILTER_IGNORE + _objective.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _objective.add_theme_font_size_override("font_size", 11) + _objective.add_theme_color_override("font_color", Color("efb568")) + contents.add_child(_objective) + +func _process(_delta: float) -> void: + if not is_instance_valid(game): + return + if bool(game.get("system_cleared")): + _objective.text = "Znajdź zamieszkaną planetę" + elif bool(game.get("_boss_spawned")): + _objective.text = "Pokonaj bossa" + else: + _objective.text = "Pokonaj wszystkich wrogów" + var cockpit := get_parent().get_node_or_null("Cockpit") as Control + var available_width := cockpit.position.x - 24.0 if is_instance_valid(cockpit) else 260.0 + var panel_width := clampf(available_width, 180.0, 260.0) + size.x = panel_width + size.y = get_combined_minimum_size().y + var bottom: float = (get_parent() as Control).size.y - 12.0 + if available_width < 180.0 and is_instance_valid(cockpit): + bottom = cockpit.position.y - 64.0 + position = Vector2(12.0, bottom - size.y) diff --git a/scripts/ui/mission_objectives.gd.uid b/scripts/ui/mission_objectives.gd.uid new file mode 100644 index 0000000..4cd3172 --- /dev/null +++ b/scripts/ui/mission_objectives.gd.uid @@ -0,0 +1 @@ +uid://bd8lju10fyxbr