This commit is contained in:
Tomasz Boruc 2026-10-17 14:28:20 +02:00
parent 458db974c0
commit 9aa2dfebd6
38 changed files with 524 additions and 223 deletions

Binary file not shown.

View File

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

View File

@ -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://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="Script" uid="uid://bpaiuqwsxswii" path="res://scripts/abilities/afterburner_ability.gd" id="2"]
[ext_resource type="Texture2D" uid="uid://kukr8bhgqa7p" path="res://assets/icons/afterburner.png" id="icon"]
[ext_resource type="Texture2D" path="res://assets/icons/afterburner.png" id="icon"]
[sub_resource type="Resource" id="Ability"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
@ -12,9 +11,9 @@ description = "Szybko rozpędza do 300% zwykłej prędkości (+200%), następnie
[resource] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "afterburner" item_id = "afterburner"
display_name = "Dopalacz" display_name = "Dopalacz"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie silnika." description = "Aktywne rozszerzenie silnika."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,22 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://drdg44cvejr2n"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/cloak_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
kind = 1
ability_id = "cloak" ability_id = "cloak"
cooldown = 60.0 cooldown = 60.0
display_name = "Kamuflaż" 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." 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] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "cloak" item_id = "cloak"
display_name = "Kamuflaż" display_name = "Kamuflaż"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,22 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cvauqvsl4x5gk"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/combat_drones_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
kind = 5
ability_id = "combat_drones" ability_id = "combat_drones"
cooldown = 30.0 cooldown = 30.0
display_name = "Drony bojowe" 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." 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] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "combat_drones" item_id = "combat_drones"
display_name = "Drony bojowe" display_name = "Drony bojowe"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,22 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://demqfp0to5pnx"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/flares_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
kind = 3
ability_id = "flares" ability_id = "flares"
cooldown = 30.0 cooldown = 30.0
display_name = "Flary" display_name = "Flary"
description = "Wypuszcza trzy flary na 10 s. Wrogowie w zasięgu obierają je za cel zamiast gracza. Odnowienie: 30 s." description = "Wypuszcza trzy flary na 10 s. Wrogowie w zasięgu obierają je za cel zamiast gracza. Odnowienie: 30 s."
kind = 3
[resource] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "flares" item_id = "flares"
display_name = "Flary" display_name = "Flary"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,22 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cnlcstlicfthn"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/mine_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
kind = 2
ability_id = "mine_layer" ability_id = "mine_layer"
cooldown = 5.0 cooldown = 5.0
display_name = "Miny" 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." 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] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "mine_layer" item_id = "mine_layer"
display_name = "Miny" display_name = "Miny"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -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://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="Script" uid="uid://bgofku10gduus" path="res://scripts/abilities/repair_drones_ability.gd" id="2"]
[ext_resource type="Texture2D" uid="uid://b1v55dh3vgl78" path="res://assets/icons/repair_drones.png" id="icon"]
[ext_resource type="Texture2D" path="res://assets/icons/repair_drones.png" id="icon"]
[sub_resource type="Resource" id="Ability"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
@ -12,9 +11,9 @@ description = "Naprawiają tyle kadłuba, ile pozwalają części i brakujące p
[resource] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "repair_drones" item_id = "repair_drones"
display_name = "Drony naprawcze" display_name = "Drony naprawcze"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie naprawcze." description = "Aktywne rozszerzenie naprawcze."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -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://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="Script" uid="uid://bwka1dwxq64sl" path="res://scripts/abilities/shield_charge_ability.gd" id="2"]
[ext_resource type="Texture2D" uid="uid://bckqak06f6qxc" path="res://assets/icons/shield_charge.png" id="icon"]
[ext_resource type="Texture2D" path="res://assets/icons/shield_charge.png" id="icon"]
[sub_resource type="Resource" id="Ability"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
@ -12,9 +11,9 @@ description = "Przywraca 100 punktów tarcz, do ich maksimum. Wymaga i zużywa 1
[resource] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "shield_charge" item_id = "shield_charge"
display_name = "Ładowanie tarcz" display_name = "Ładowanie tarcz"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie tarcz." description = "Aktywne rozszerzenie tarcz."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,22 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://x5cduw4c4tes"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/solar_shield_charge_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
kind = 4
ability_id = "solar_shield_charge" ability_id = "solar_shield_charge"
cooldown = 30.0 cooldown = 30.0
display_name = "Doładowanie tarcz" 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." 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] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "solar_shield_charge" item_id = "solar_shield_charge"
display_name = "Doładowanie tarcz" display_name = "Doładowanie tarcz"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -1,19 +1,21 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3] [gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://dt406oym4bd6n"]
[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="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Texture2D" path="res://assets/icons/extensions/teleport_art.png" id="icon"] [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"] [sub_resource type="Resource" id="Ability"]
script = ExtResource("2") script = ExtResource("2")
ability_id = "teleport" ability_id = "teleport"
cooldown = 60.0 cooldown = 60.0
display_name = "Teleport" display_name = "Teleport"
description = "Teleportuje w losowe, bezpieczne miejsce systemu. Zużywa całą aktualną energię (wymaga dodatniej energii). Odnowienie: 60 s." description = "Teleportuje w losowe, bezpieczne miejsce systemu. Zużywa całą aktualną energię (wymaga dodatniej energii). Odnowienie: 60 s."
kind = 0
[resource] [resource]
script = ExtResource("1") script = ExtResource("1")
icon = ExtResource("icon")
item_id = "teleport" item_id = "teleport"
display_name = "Teleport" display_name = "Teleport"
icon = ExtResource("icon")
category = 5 category = 5
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5." description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
active_ability = SubResource("Ability") active_ability = SubResource("Ability")

View File

@ -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"] [sub_resource type="StyleBoxFlat" id="TooltipShell"]
bg_color = Color(0.035, 0.055, 0.085, 1)
content_margin_left = 2.0 content_margin_left = 2.0
content_margin_top = 2.0 content_margin_top = 2.0
content_margin_right = 2.0 content_margin_right = 2.0
content_margin_bottom = 2.0 content_margin_bottom = 2.0
shadow_size = 0 bg_color = Color(0.035, 0.055, 0.085, 1)
[resource] [resource]
default_font = ExtResource("1_font") default_font = ExtResource("1_font")
TooltipPanel/styles/panel = SubResource("TooltipShell")
TooltipLabel/font_sizes/font_size = 11 TooltipLabel/font_sizes/font_size = 11
TooltipPanel/styles/panel = SubResource("TooltipShell")

View File

@ -93,10 +93,7 @@ ENERGIA: 100 / 100"
[node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")] [node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")]
layout_mode = 1 layout_mode = 1
offset_left = -270.0 offset_left = -270.0
offset_top = 244.0
offset_right = -10.0
offset_bottom = 422.0 offset_bottom = 422.0
grow_vertical = 1
[node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")] [node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")]
layout_mode = 1 layout_mode = 1

View File

@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://csjojorlptcs1" path="res://scripts/ui/main_menu.gd" id="1_script"] [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="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"] [sub_resource type="StyleBoxFlat" id="StyleBox_primary"]
content_margin_left = 22.0 content_margin_left = 22.0
@ -67,7 +67,7 @@ script = ExtResource("1_script")
[node name="MenuMusic" type="AudioStreamPlayer" parent="." unique_id=784369201] [node name="MenuMusic" type="AudioStreamPlayer" parent="." unique_id=784369201]
script = ExtResource("3_scene_music") 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 playback_volume_db = -13.0
[node name="Background" type="TextureRect" parent="." unique_id=1339534440] [node name="Background" type="TextureRect" parent="." unique_id=1339534440]

View File

@ -1,116 +1,88 @@
extends Node2D extends Node2D
const SETTLE_TIME := 8.0 const PICKUP := preload("res://scripts/items/mineral_pickup.gd")
var _texture: Texture2D
var _fragments: Array[Dictionary] = []
var _dust: Array[Dictionary] = []
var _elapsed := 0.0 var _elapsed := 0.0
var _rng := RandomNumberGenerator.new() var _dust: Array[Dictionary] = []
var _dust_color := Color("b4a99a")
func configure(sprite: Sprite2D) -> void: func configure(sprite: Sprite2D, count: int = 6, mineral: String = "iron") -> void:
_texture = sprite.texture
transform = sprite.global_transform transform = sprite.global_transform
var sprite_scale := global_scale var source_scale := scale
scale = Vector2.ONE scale = Vector2.ONE
_rng.randomize() var dimensions := sprite.texture.get_size()
var dimensions := _texture.get_size() var offset := randf()
var grid: Array[Vector2] = [] var cracks: Array[PackedVector2Array] = []
for y in 5: for index in count:
for x in 5: var endpoint := _edge_point(offset + float(index) * 4.0 / count) * dimensions
var vertex := Vector2(x, y) * dimensions / 4.0 cracks.append(_fracture_edge(endpoint, count))
if x > 0 and x < 4: for index in count:
vertex.x += _rng.randf_range(-0.3, 0.3) * dimensions.x / 4.0 # Neighbours share the same jagged fracture, so the pieces fit together.
if y > 0 and y < 4: var start := offset + float(index) * 4.0 / count
vertex.y += _rng.randf_range(-0.3, 0.3) * dimensions.y / 4.0 var finish := offset + float(index + 1) * 4.0 / count
grid.append(vertex) var vertices := cracks[index].duplicate()
# Shared jagged cracks keep neighbouring pieces fitted together. for corner in range(int(floor(start)) + 1, int(ceil(finish))):
var horizontal: Array[PackedVector2Array] = [] vertices.append(_edge_point(float(corner)) * dimensions)
var vertical: Array[PackedVector2Array] = [] var next_crack := cracks[(index + 1) % count]
for y in 5: for vertex_index in range(next_crack.size() - 1, 0, -1):
for x in 4: vertices.append(next_crack[vertex_index])
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 var center := Vector2.ZERO
for vertex in outline: for vertex in vertices:
center += vertex center += vertex
center /= outline.size() center /= vertices.size()
var origin := (center - dimensions * 0.5) * sprite_scale var fragment_points := PackedVector2Array()
var points := PackedVector2Array() var fragment_uv := PackedVector2Array()
var uv := PackedVector2Array() for vertex in vertices:
for vertex in outline: fragment_points.append((vertex - center) * source_scale)
points.append((vertex - center) * sprite_scale) fragment_uv.append(vertex / dimensions + Vector2(0.5, 0.5))
uv.append(vertex / dimensions) var fragment := Node2D.new()
var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT fragment.set_script(PICKUP)
_fragments.append({"points": points, "uv": uv, "position": origin, fragment.set("mineral", mineral)
"velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(40.0, 95.0), fragment.set("texture", sprite.texture)
"angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)}) fragment.set("points", fragment_points)
var mineral := _texture.resource_path.get_file().get_basename() fragment.set("uv", fragment_uv)
_dust_color = {"iron": Color("be9b75"), "titanium": Color("9ecbe9"), "uranium": Color("a9db71"), "aluminium": Color("d3dce0"), "sulfur": Color("e8cd65")}.get(mineral, Color("b4a99a")) 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: for index in 28:
_dust.append({"direction": Vector2.RIGHT.rotated(_rng.randf_range(0.0, TAU)), _dust.append({"velocity": Vector2.from_angle(randf() * TAU) * randf_range(45.0, 180.0), "size": randf_range(1.0, 3.0)})
"speed": _rng.randf_range(45.0, 210.0), "size": _rng.randf_range(1.0, 3.0)})
z_index = 35
light_mask = 4 light_mask = 4
func _fracture_edge(start: Vector2, end: Vector2, jagged: bool) -> PackedVector2Array: func _fracture_edge(endpoint: Vector2, count: int) -> PackedVector2Array:
var edge := PackedVector2Array([start]) var edge := PackedVector2Array([Vector2.ZERO])
if jagged: var normal := endpoint.orthogonal().normalized()
var normal := (end - start).orthogonal().normalized() var roughness := endpoint.length() * minf(0.14, 0.65 / count)
var roughness := start.distance_to(end) * 0.13 var side := -1.0 if randf() < 0.5 else 1.0
var side := 1.0 if _rng.randf() > 0.5 else -1.0 for index in 5:
for index in 3: var along := float(index + 1) / 6.0
var along := (index + 1.0) / 4.0 + _rng.randf_range(-0.05, 0.05) var offset := roughness * sin(along * PI) * side * randf_range(0.6, 1.0)
edge.append(start.lerp(end, along) + normal * roughness * side * _rng.randf_range(0.5, 1.0)) edge.append(endpoint * along + normal * offset)
side *= -1.0 side *= -1.0
edge.append(end) edge.append(endpoint)
return edge return edge
func _edge_vertices(edge: PackedVector2Array, backwards: bool) -> PackedVector2Array: func _edge_point(distance: float) -> Vector2:
var vertices := PackedVector2Array() var t := fposmod(distance, 4.0)
# The next edge supplies the end vertex, avoiding duplicate polygon points. if t < 1.0:
for index in edge.size() - 1: return Vector2(-0.5 + t, -0.5)
vertices.append(edge[edge.size() - 1 - index] if backwards else edge[index]) if t < 2.0:
return vertices 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: func _process(delta: float) -> void:
_elapsed += delta _elapsed += delta
for fragment in _fragments: if _elapsed <= 1.0:
fragment.position += fragment.velocity * delta
fragment.velocity *= exp(-1.2 * delta)
fragment.angle += fragment.spin * delta
fragment.spin *= exp(-0.7 * delta)
queue_redraw() queue_redraw()
# Leave the settled debris visible without updating it every frame. elif not _dust.is_empty():
if _elapsed >= SETTLE_TIME:
_dust.clear() _dust.clear()
set_process(false) queue_redraw()
if _elapsed > 1.0 and get_child_count() == 0:
queue_free()
func _draw() -> void: func _draw() -> void:
for fragment in _fragments: var fade := maxf(0.0, 1.0 - _elapsed)
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)
for mote in _dust: for mote in _dust:
var travel: Vector2 = mote.direction * mote.speed * (1.0 - exp(-2.0 * _elapsed)) / 2.0 draw_circle(mote.velocity * (1.0 - exp(-2.0 * _elapsed)) * 0.5, mote.size, Color(0.7, 0.75, 0.8, fade * 0.65))
draw_circle(travel, mote.size * (1.0 + _elapsed), Color(_dust_color, dust_fade * 0.65))
if _elapsed < 0.18: 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))

View File

@ -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 MINE_TEXTURE := preload("res://assets/icons/extensions/mine_art.png")
const LIFETIME := 60.0 const LIFETIME := 60.0
const ARM_TIME := 0.7 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 drift := Vector2.ZERO
var _age := 0.0 var _age := 0.0
var _triggered := false var _triggered := false
@ -27,16 +28,29 @@ func _physics_process(delta: float) -> void:
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0: if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
continue continue
if global_position.distance_to(enemy.global_position) <= TRIGGER_RADIUS + float(enemy.call("get_visual_size")) * 0.35: if global_position.distance_to(enemy.global_position) <= TRIGGER_RADIUS + float(enemy.call("get_visual_size")) * 0.35:
_detonate()
return
func _detonate() -> void:
if _triggered:
return
_triggered = true _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") enemy.call("apply_mine_hit")
var explosion := Node2D.new() var explosion := Node2D.new()
explosion.set_script(preload("res://scripts/game/deployment_flash.gd")) explosion.set_script(preload("res://scripts/game/deployment_flash.gd"))
explosion.set("color", Color("ffbc75")) explosion.set("color", Color("ffbc75"))
explosion.set("blast", true) 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 explosion.position = position
get_parent().add_child(explosion) get_parent().add_child(explosion)
queue_free() queue_free()
return
func _draw() -> void: func _draw() -> void:
var armed := _age >= ARM_TIME var armed := _age >= ARM_TIME

View File

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

View File

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

View File

@ -35,6 +35,7 @@ var _shield_visual: Node2D
var hull := 50.0 var hull := 50.0
var _player: CharacterBody2D var _player: CharacterBody2D
var _activated := false var _activated := false
var _retreat_mine_rolled := false
var _sprite: Sprite2D var _sprite: Sprite2D
var _weapon_timer := 0.0 var _weapon_timer := 0.0
var _slow_remaining := 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: if _activated and to_player.length_squared() > 1.0:
var direction := to_player.normalized() var direction := to_player.normalized()
var fleeing := is_retreating() 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 movement := combat_movement(to_player)
var desired_rotation := (movement if fleeing else direction).angle() + PI * 0.5 var desired_rotation := (movement if fleeing else direction).angle() + PI * 0.5
rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta) 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: if not fleeing and to_player.length() <= weapon_range and _weapon_timer <= 0.0:
_fire_plasma(direction) _fire_plasma(direction)
_weapon_timer = get_fire_interval() _weapon_timer = get_fire_interval()
@ -129,7 +134,15 @@ func _physics_process(delta: float) -> void:
_check_player_collision() _check_player_collision()
func is_retreating() -> bool: 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: func combat_movement(to_target: Vector2) -> Vector2:
var distance := to_target.length() var distance := to_target.length()
@ -175,7 +188,7 @@ func apply_mine_hit() -> void:
return return
_slow_remaining = 5.0 _slow_remaining = 5.0
velocity = velocity.limit_length(approach_speed * 0.5) 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: func _check_player_collision() -> void:
for collision_index in get_slide_collision_count(): for collision_index in get_slide_collision_count():

View File

@ -148,6 +148,11 @@ func _ready() -> void:
_window_manager.call("register_window", _inventory_clip, inventory_panel, inventory_panel.get_node("Contents/Header")) _window_manager.call("register_window", _inventory_clip, inventory_panel, inventory_panel.get_node("Contents/Header"))
$HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred()) $HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred())
_create_ability_bar() _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) debug_toggle.pressed.connect(_toggle_debug)
inventory_button.pressed.connect(_toggle_inventory) inventory_button.pressed.connect(_toggle_inventory)
_update_debug_toggle() _update_debug_toggle()

View File

@ -400,7 +400,7 @@ func start_boost(speed_bonus: float, acceleration_time: float) -> void:
func get_active_ability(slot_index: int) -> ActiveAbility: func get_active_ability(slot_index: int) -> ActiveAbility:
var item: ItemInstance = $ShipInventory.get_item("extension", slot_index) 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: func get_luck_chance() -> float:
return GameState.captain.get_luck_chance(luck_percent) return GameState.captain.get_luck_chance(luck_percent)

View File

@ -1,33 +1,35 @@
extends Node2D extends Node2D
const VORTEX := preload("res://scripts/game/wormhole_vortex.gdshader")
var _time := 0.0 var _time := 0.0
var _vortex_material: ShaderMaterial
func _ready() -> void: func _ready() -> void:
z_index = -2 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() var label := Label.new()
label.name = "EntryLabel"
label.text = "WORMHOLE\nWleć, aby przejść do następnego układu" label.text = "WORMHOLE\nWleć, aby przejść do następnego układu"
label.position = Vector2(-180, 72) label.position = Vector2(-200, 106)
label.size = Vector2(360, 44) label.size = Vector2(400, 48)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.mouse_filter = Control.MOUSE_FILTER_IGNORE
label.add_theme_font_size_override("font_size", 13) 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) add_child(label)
func _process(delta: float) -> void: func _process(delta: float) -> void:
_time += delta _time += delta
queue_redraw() if _vortex_material != null:
_vortex_material.set_shader_parameter("elapsed", _time)
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)

View File

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

View File

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

View File

@ -4,12 +4,14 @@ const MINERALS := ["iron", "titanium", "uranium", "aluminium", "sulfur"]
const NAMES := {"iron": "Żelazo", "titanium": "Tytan", "uranium": "Uran", "aluminium": "Aluminium", "sulfur": "Siarka"} 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 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"] 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 := { const DATA := {
"parts": {"name": "Części", "amount": 1, "cost": {"titanium": 1, "iron": 1, "aluminium": 1}}, "parts": {"name": "Części", "amount": 1, "cost": {"titanium": 3, "iron": 3, "aluminium": 3}},
"ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 1, "sulfur": 1}}, "ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 3, "sulfur": 3}},
"missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 1, "sulfur": 1, "uranium": 1}}, "missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 3, "sulfur": 3, "uranium": 3}},
"energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 1, "aluminium": 1}}, "energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 3, "aluminium": 3}},
"irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 1, "uranium": 1}}, "irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 3, "uranium": 3}},
} }
static func icon(id: String) -> Texture2D: static func icon(id: String) -> Texture2D:

View File

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

View File

@ -0,0 +1 @@
uid://4nswtu72lqfp

View File

@ -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.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) item.energy_regeneration_override = item.definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2)
if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION:
item.extension_cooldown = ItemInstance.EXTENSION_COOLDOWNS.roll_seconds()
return item return item
var roll := randf() 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))) 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)))

View File

@ -2,6 +2,30 @@ extends Resource
class_name ItemInstance class_name ItemInstance
const ENERGY_REGENERATION_BONUS_MULTIPLIER := 0.5 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 var definition: ItemDefinition
@export_range(1, 1000, 1) var item_level: int = 1 @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: if definition.category == ItemDefinition.Category.WEAPON:
primary_text = "Obrażenia: %s" % get_damage_range_text() primary_text = "Obrażenia: %s" % get_damage_range_text()
elif definition.active_ability != null: 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] 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: 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)) lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))

View File

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

View File

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

View File

@ -58,7 +58,7 @@ func _update_label() -> void:
_label.text = display_name + "\nGwiazda centralna" _label.text = display_name + "\nGwiazda centralna"
_label.add_theme_color_override("font_color", Color("ffdb98")) _label.add_theme_color_override("font_color", Color("ffdb98"))
elif is_protected: 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")) _label.add_theme_color_override("font_color", Color("77e8bc") if is_safe else Color("ffbd83"))
else: else:
_label.text = display_name _label.text = display_name

View File

@ -121,9 +121,15 @@ func claim() -> bool:
var summary := "" var summary := ""
var scale := reward_scale() var scale := reward_scale()
if kind == "asteroid": if kind == "asteroid":
var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale)) + roundi(GameState.captain.get_skill_bonus("miner")) var amount := _rng.randi_range(5, 7) + maxi(0, roundi(GameState.captain.get_skill_bonus("miner")))
GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount var debris := Node2D.new()
summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu", "sulfur": "siarki"}[_mineral]] 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": elif kind == "ruin":
var xp := roundi(_rng.randi_range(10, 20) * scale) var xp := roundi(_rng.randi_range(10, 20) * scale)
GameState.captain.add_rewards(xp, 0) GameState.captain.add_rewards(xp, 0)
@ -156,13 +162,6 @@ func claim() -> bool:
popup.position = position + Vector2(-80, -65) popup.position = position + Vector2(-80, -65)
get_parent().add_child.call_deferred(popup) get_parent().add_child.call_deferred(popup)
set_physics_process(false) 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() queue_free()
return true return true

View File

@ -9,8 +9,6 @@ var _panel: PanelContainer
var _contents: VBoxContainer var _contents: VBoxContainer
var _bar: HBoxContainer var _bar: HBoxContainer
var _buttons: Dictionary = {} var _buttons: Dictionary = {}
var _was_paused := false
var _owns_pause := false
var _refresh_pending := false var _refresh_pending := false
func _ready() -> void: func _ready() -> void:
@ -78,19 +76,11 @@ func open_management() -> void:
if bool(player.get("_is_destroyed")): if bool(player.get("_is_destroyed")):
return return
if not _screen.visible: if not _screen.visible:
_was_paused = get_tree().paused
_owns_pause = true
get_tree().paused = true
_screen.show() _screen.show()
_build_management() _build_management()
func _close() -> void: func _close() -> void:
if not _captain.skill_offers.is_empty():
return
_screen.hide() _screen.hide()
if _owns_pause:
get_tree().paused = _was_paused
_owns_pause = false
func _build_management() -> void: func _build_management() -> void:
for child in _contents.get_children(): 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")) var title := _label("AWANS KAPITANA" if choosing else "UMIEJĘTNOŚCI KAPITANA", 25, Color("91dbea"))
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
heading.add_child(title) heading.add_child(title)
if not choosing:
var close := Button.new() var close := Button.new()
close.name = "Close" close.name = "Close"
close.text = "WRÓĆ DO LOTU" close.text = "WRÓĆ DO LOTU"
close.pressed.connect(_close) close.pressed.connect(_close)
heading.add_child(close) heading.add_child(close)
_contents.add_child(_label("POZIOM %d · GRA WSTRZYMANA" % _captain.level, 13, Color("91aabd"))) _contents.add_child(_label("POZIOM %d" % _captain.level, 13, Color("91aabd")))
if choosing: if choosing:
_contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16)) _contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16))
var choices := HBoxContainer.new() var choices := HBoxContainer.new()
@ -327,6 +316,4 @@ func _layout() -> void:
else: else:
_bar.position = Vector2(16, viewport_size.y - 190) _bar.position = Vector2(16, viewport_size.y - 190)
func _exit_tree() -> void:
if _owns_pause and is_inside_tree():
get_tree().paused = _was_paused

View File

@ -24,4 +24,10 @@ func _draw() -> void:
var warning_color := Color(1.0, 0.32, 0.26, pulse) 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) 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) 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)

View File

@ -35,7 +35,7 @@ func _draw() -> void:
if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50: if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50:
return 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)]) 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() var closed := outline.duplicate()
closed.append(outline[0]) closed.append(outline[0])
draw_polyline(closed, Color("376779"), 1.5, true) draw_polyline(closed, Color("376779"), 1.5, true)

View File

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

View File

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