Zmiany
BIN
artifacts/auxiliary_shield_preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
12
artifacts/auxiliary_shield_validation.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Tarcza zewnętrzna — weryfikacja
|
||||||
|
|
||||||
|
Godot 4.7.2, 2026-10-26.
|
||||||
|
|
||||||
|
- PASS: `tests/abilities/auxiliary_shield_test.tscn`: odblokowanie w laboratorium, aktywne rozszerzenie bez addonów, koszt 70% maksymalnej energii i blokada przy niewystarczającym zapasie, pojemność 50% maksymalnych tarcz w chwili aktywacji. Osobna pula nie regeneruje się i nie nakłada; obrażenia zużywają ją przed zwykłymi tarczami, a nadmiar przechodzi dalej na tarcze/kadłub. Prawidłowy cooldown, reset, dokowanie i wyłączenie przez Przekierowanie zasilania. Pociski i lasery zatrzymują się na większym obwodzie również przy pustych tarczach głównych. Zwykłe technologie tarcz nie zużywają odnowienia, gdy całe trafienie pochłonęła warstwa zewnętrzna.
|
||||||
|
- PASS: `tests/items/shield_collision_test.tscn`: dotychczasowe pociski, lasery, tarcze i przeszkody działają poprawnie.
|
||||||
|
- PASS: `tests/previews/auxiliary_shield_preview.tscn`: rzeczywista aktywacja rozszerzenia w scenie gry, podgląd obu warstw, ikona i odczyt dodatkowej pojemności, 1280×720.
|
||||||
|
- PASS: `git diff --check`.
|
||||||
|
|
||||||
|
Nowa warstwa ma promień 1,45× promień zwykłej tarczy. Efekt: fioletowy obwód i siatka sześciokątów, osobne błyski trafień oraz licznik punktów osłony.
|
||||||
|
|
||||||
|
Ikona: `assets/icons/extensions/auxiliary_shield.png`, wygenerowana wbudowanym image_gen z przezroczystym tłem. Prompt: `assets/icons/extensions/auxiliary_shield_prompt.md`.
|
||||||
BIN
artifacts/external_turret_preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
40
artifacts/external_turret_preview.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dly01imnvtint"
|
||||||
|
path="res://.godot/imported/external_turret_preview.png-71ead23f36f1b832eab1a61b36fe5af2.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/external_turret_preview.png"
|
||||||
|
dest_files=["res://.godot/imported/external_turret_preview.png-71ead23f36f1b832eab1a61b36fe5af2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
10
artifacts/external_turret_validation.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Działka zewnętrzne — weryfikacja
|
||||||
|
|
||||||
|
Godot 4.7.2, 2026-10-26.
|
||||||
|
|
||||||
|
- PASS: `tests/items/external_turret_test.tscn`. Odblokowanie w laboratorium; dokładnie połowa obrażeń działka maszynowego przy tym samym losowaniu na poziomach 1, 5, 30, 120 i 1000; 49 amunicji nie wystarcza; rozstawienie pobiera dokładnie 50 pomimo Oszczędnego magazynka III. Jedno kliknięcie rozstawia jedno działko, przytrzymanie nie rozstawia kolejnych. Działko zostaje w swoim miejscu po odlocie statku, zachowuje amunicję bez celu, wykrywa cel i oddaje dokładnie 50 rzeczywistych pocisków. Zweryfikowano obrażenia przeciwnika, brak dodatkowego pobierania amunicji i usunięcie po wyczerpaniu magazynka/dokowaniu. Zmiana lub zdjęcie broni nie zmienia już rozstawionego działka.
|
||||||
|
- PASS: `tests/ships/laboratory_loot_test.tscn`. Broń nie występuje w początkowych łupach i pojawia się po badaniu; brak duplikatów w puli.
|
||||||
|
- PASS: `tests/previews/external_turret_preview.tscn`. Podgląd grafiki i pocisków w działającej scenie gry, 1280×720.
|
||||||
|
- PASS: `git diff --check`.
|
||||||
|
|
||||||
|
Ikona i przezroczysty sprite powstały przez wbudowane image_gen. Pliki oraz prompty opisano w `assets/icons/external_turret_prompts.md`.
|
||||||
BIN
artifacts/new_captain_skills_combat.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
40
artifacts/new_captain_skills_combat.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cuyxd70erubuh"
|
||||||
|
path="res://.godot/imported/new_captain_skills_combat.png-258aa6a2d3c85d330ddcc3813c5322ab.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/new_captain_skills_combat.png"
|
||||||
|
dest_files=["res://.godot/imported/new_captain_skills_combat.png-258aa6a2d3c85d330ddcc3813c5322ab.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
artifacts/new_captain_skills_economy.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
40
artifacts/new_captain_skills_economy.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://te0rvaurv6rb"
|
||||||
|
path="res://.godot/imported/new_captain_skills_economy.png-4bbe4716bac2c848e6a45ba3c3e58f0d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/new_captain_skills_economy.png"
|
||||||
|
dest_files=["res://.godot/imported/new_captain_skills_economy.png-4bbe4716bac2c848e6a45ba3c3e58f0d.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
17
artifacts/new_captain_skills_validation.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Captain skills validation
|
||||||
|
|
||||||
|
Godot 4.7.2, 2026-10-26.
|
||||||
|
|
||||||
|
- PASS: `tests/captain/new_skills_test.tscn` — 14 skills; five new icons; training; all three ranks; laboratory timing; normal/radiation ammunition production and mineral costs; enemy bounty; missile cooldown; emergency repair resource checks, docking, cooldown, full hull and repair cap.
|
||||||
|
- PASS: `tests/ships/laboratory_loot_test.tscn` — starting plasma-only weapon pool, locked extensions, all research unlocks and no duplicate loot entries.
|
||||||
|
- PASS: `tests/previews/new_captain_skills_preview.tscn` — real game UI, all 14 owned skills, new choices, F1 assignment, emergency repair on the actual ship, and screenshots at 1280×720.
|
||||||
|
- PASS: `git diff --check`.
|
||||||
|
|
||||||
|
Additional older regression scenes do not pass:
|
||||||
|
|
||||||
|
- `tests/abilities/missile_system_test.gd:20` expects movement into weapon slot 2 without unlocking that slot. This change does not alter slot unlock rules.
|
||||||
|
- `tests/items/crafting_recipes_test.gd:54` expects immediate mineral credit after asteroid destruction; its null asteroid sprite also fails in `asteroid_destruction_effect.gd:8`. This change does not alter asteroid reward or debris behavior. The recipe transactions before that section succeed.
|
||||||
|
|
||||||
|
The headless editor/runtime also reports sandbox access warnings for external Godot/MCP settings directories. New skill tests and the rendered preview complete successfully.
|
||||||
|
|
||||||
|
Icons were generated with the built-in image_gen tool. Original prompts are saved in `assets/icons/captain/new_skills_prompts.md`.
|
||||||
BIN
artifacts/pulse_laser_flying_preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
40
artifacts/pulse_laser_flying_preview.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cm2lhdq7a3vul"
|
||||||
|
path="res://.godot/imported/pulse_laser_flying_preview.png-0c3fa1f2605505b790bdcb6cb95ee605.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/pulse_laser_flying_preview.png"
|
||||||
|
dest_files=["res://.godot/imported/pulse_laser_flying_preview.png-0c3fa1f2605505b790bdcb6cb95ee605.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
artifacts/pulse_laser_preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
40
artifacts/pulse_laser_preview.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://3b4vuo80tuyj"
|
||||||
|
path="res://.godot/imported/pulse_laser_preview.png-875bd161de519004da7a35acd303054d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/pulse_laser_preview.png"
|
||||||
|
dest_files=["res://.godot/imported/pulse_laser_preview.png-875bd161de519004da7a35acd303054d.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
13
artifacts/pulse_laser_validation.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Laser pulsacyjny — weryfikacja
|
||||||
|
|
||||||
|
Godot 4.7.2, 2026-10-26.
|
||||||
|
|
||||||
|
- PASS: `tests/items/pulse_laser_test.tscn`. Odblokowanie w laboratorium, 50% szansy na pełne ominięcie tarcz, granica losowania i zgodność z orbem przebicia. Przy identycznych losowaniach na poziomach 1, 5, 30, 120 i 1000 broń zadaje 70% obrażeń lasera wiązkowego i zużywa 130% jego energii. Pojedyncze czerwone impulsy lecą z prędkością 1500 px/s (bazowo strzał co 0,2 s). Sprawdzone przesunięcie o 60 px w 0,04 s, brak natychmiastowych obrażeń i naliczanie trafienia dopiero po dotarciu do tarcz lub kadłuba; współdziałanie z technologią podwójnego trafienia i brak strzału przy zbyt małej ilości energii.
|
||||||
|
- PASS: `tests/items/external_turret_test.tscn`. Brak regresji obrażeń działek zewnętrznych po ujednoliceniu mnożników obrażeń przedmiotów.
|
||||||
|
- PASS: `tests/previews/pulse_laser_preview.tscn`. Podgląd dwóch osobnych, krótkich czerwonych pocisków w scenie gry, 1280×720. Plik: `artifacts/pulse_laser_flying_preview.png`.
|
||||||
|
- PASS: `tests/items/shield_collision_test.tscn`. Zwykłe pociski i lasery nadal zatrzymują się na tarczach; osłony fizyczne i trafienia styczne działają poprawnie.
|
||||||
|
- PASS: `tests/items/laser_energy_regeneration_test.tscn` (600 klatek fizyki, fixed-fps 60). Zwykły laser nadal prawidłowo zużywa energię podczas 10 s strzelania.
|
||||||
|
- PASS: `tests/items/weapon_icon_transparency_test.tscn`. Wszystkie 11 ikon broni ma przezroczyste tło i widoczną grafikę. Pięć edytowanych plików i prompt opisano w `assets/icons/weapon_background_removal.md`.
|
||||||
|
- PASS: `git diff --check`.
|
||||||
|
|
||||||
|
Ikona powstała przez wbudowane image_gen. Plik `assets/icons/pulse_laser.png`, prompt `assets/icons/pulse_laser_prompt.md`; tło usunięto zgodnie z późniejszą prośbą. Lecące impulsy rysowane są przez istniejący system pocisków. Zwykłe i radiacyjne lasery zachowują dotychczasowe wiązki.
|
||||||
BIN
artifacts/salvage_drone_skills.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
40
artifacts/salvage_drone_skills.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dt73tyy7qplad"
|
||||||
|
path="res://.godot/imported/salvage_drone_skills.png-fda1f5a8c1e672dd96715b417d8c5d76.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/salvage_drone_skills.png"
|
||||||
|
dest_files=["res://.godot/imported/salvage_drone_skills.png-fda1f5a8c1e672dd96715b417d8c5d76.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
9
artifacts/salvage_drone_validation.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Zbieracz złomu i Dowódca dronów
|
||||||
|
|
||||||
|
Godot 4.7.2, 2026-10-26.
|
||||||
|
|
||||||
|
- PASS: `tests/captain/salvage_drone_skills_test.tscn`: 16 umiejętności, obie nowe są pasywne i dostępne do nauki, ikony są wczytywane. Sprawdzone prawdopodobieństwa 0/15/25/40%, 240 rzeczywistych wraków oraz brak ponownego naliczania części. Sprawdzone pociski trzech dronów dla wszystkich rang (0/10/20/30%), koszt amunicji i brak premii do broni statku.
|
||||||
|
- PASS: `tests/previews/salvage_drone_skills_preview.tscn`: prawdziwy panel kapitana i podgląd obu nowych umiejętności w 1280×720.
|
||||||
|
- PASS: `git diff --check`.
|
||||||
|
|
||||||
|
Grafiki wygenerowano przez wbudowane image_gen. Pliki: `assets/icons/captain/scrap_collector.png` i `assets/icons/captain/drone_commander.png`. Prompty: `assets/icons/captain/salvage_drone_prompts.md`.
|
||||||
BIN
artifacts/weapon_icons_transparent.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
40
artifacts/weapon_icons_transparent.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dx4jv5dk1xbrs"
|
||||||
|
path="res://.godot/imported/weapon_icons_transparent.png-8a1321b5ba9f77e927c721ea7866d572.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://artifacts/weapon_icons_transparent.png"
|
||||||
|
dest_files=["res://.godot/imported/weapon_icons_transparent.png-8a1321b5ba9f77e927c721ea7866d572.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/Ships/external_turret.png
Normal file
|
After Width: | Height: | Size: 1018 KiB |
40
assets/Ships/external_turret.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://s5oof01scm1m"
|
||||||
|
path="res://.godot/imported/external_turret.png-58ad502f88f5ae7f1f60d289aeaf4a6b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Ships/external_turret.png"
|
||||||
|
dest_files=["res://.godot/imported/external_turret.png-58ad502f88f5ae7f1f60d289aeaf4a6b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.2 MiB |
BIN
assets/icons/captain/bounty_hunter.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
40
assets/icons/captain/bounty_hunter.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d1vcvsbrhrmf4"
|
||||||
|
path="res://.godot/imported/bounty_hunter.png-8b18ddf2681343815eedd4d59beb82f6.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/bounty_hunter.png"
|
||||||
|
dest_files=["res://.godot/imported/bounty_hunter.png-8b18ddf2681343815eedd4d59beb82f6.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/drone_commander.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
40
assets/icons/captain/drone_commander.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cm0iwunhagi6v"
|
||||||
|
path="res://.godot/imported/drone_commander.png-5dc78d116784f5c76b3c0d1b41129369.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/drone_commander.png"
|
||||||
|
dest_files=["res://.godot/imported/drone_commander.png-5dc78d116784f5c76b3c0d1b41129369.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/emergency_procedure.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
40
assets/icons/captain/emergency_procedure.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ceqc0126fy1wf"
|
||||||
|
path="res://.godot/imported/emergency_procedure.png-12a516e7ce74a224dea88e0528e6aaf2.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/emergency_procedure.png"
|
||||||
|
dest_files=["res://.godot/imported/emergency_procedure.png-12a516e7ce74a224dea88e0528e6aaf2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/gunsmith.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
40
assets/icons/captain/gunsmith.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bmiwj4d4vsn2d"
|
||||||
|
path="res://.godot/imported/gunsmith.png-3fe35693f8c8ccdc7b74c1394e7c7767.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/gunsmith.png"
|
||||||
|
dest_files=["res://.godot/imported/gunsmith.png-3fe35693f8c8ccdc7b74c1394e7c7767.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/missile_specialist.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
40
assets/icons/captain/missile_specialist.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://do5k2oipk04kx"
|
||||||
|
path="res://.godot/imported/missile_specialist.png-475ab16333c302ad739380e687c16621.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/missile_specialist.png"
|
||||||
|
dest_files=["res://.godot/imported/missile_specialist.png-475ab16333c302ad739380e687c16621.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
17
assets/icons/captain/new_skills_prompts.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Captain skill icons
|
||||||
|
Generated with the built-in image_gen tool.
|
||||||
|
|
||||||
|
scientist.png
|
||||||
|
Use case: stylized-concept. Asset type: square captain skill inventory icon for a sci-fi space RPG. A futuristic laboratory research apparatus: silver microscope examining a glowing cyan crystal, small orbital atom rings, scientific discovery. Polished detailed 3D game item render, silver gunmetal and brass materials, strong readable silhouette, centered single compact composition filling 80% of the square, subtle colored glow. Dark navy nearly black vignette background. Match a premium sci-fi loot icon collection. No lettering, no words, no numbers, no border, no watermark. Deliver one square icon.
|
||||||
|
|
||||||
|
gunsmith.png
|
||||||
|
Use case: stylized-concept. Asset type: square captain ability icon for a science-fiction space exploration game. A compact futuristic industrial fabrication station with three copper cylindrical components, steel gear and precision caliper, warm amber glow. Polished detailed 3D game item render, silver gunmetal and brass, readable silhouette, centered compact composition filling 80% of square, subtle colored glow. Dark navy nearly black vignette background. No lettering, words, numbers, border or watermark. One square icon.
|
||||||
|
|
||||||
|
bounty_hunter.png
|
||||||
|
Use case: stylized-concept. Asset type: square captain ability icon for a science-fiction space exploration game. A golden reward coin inside a futuristic silver navigation target reticle, glowing amber center, a small star embossed on the coin. Polished detailed 3D game item render, silver gunmetal and brass, readable silhouette, centered compact composition filling 80% of square, subtle colored glow. Dark navy nearly black vignette background. No lettering, words, numbers, border or watermark. One square icon.
|
||||||
|
|
||||||
|
missile_specialist.png
|
||||||
|
Use case: stylized-concept. Asset type: square captain ability icon for a science-fiction space exploration game. Two miniature sleek silver aerospace rockets with red nose cones, cyan orbital guidance arcs, orange exhaust glow, space exploration theme. Polished detailed 3D game item render, silver gunmetal and brass, readable silhouette, centered compact composition filling 80% of square, subtle colored glow. Dark navy nearly black vignette background. No lettering, words, numbers, border or watermark. One square icon.
|
||||||
|
|
||||||
|
emergency_procedure.png
|
||||||
|
Use case: stylized-concept. Asset type: square captain ability icon for a science-fiction space exploration game. A futuristic maintenance device: steel mechanical clamp repairing a cracked spaceship armor panel with bright green energy sparks and luminous green plus symbol. Polished detailed 3D game item render, silver gunmetal and brass, readable silhouette, centered compact composition filling 80% of square, subtle colored glow. Dark navy nearly black vignette background. No lettering, words, numbers, border or watermark. One square icon.
|
||||||
11
assets/icons/captain/salvage_drone_prompts.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Zbieracz złomu i Dowódca dronów — ikony
|
||||||
|
|
||||||
|
Wygenerowano wbudowanym narzędziem image_gen. Pliki PNG w tym folderze.
|
||||||
|
|
||||||
|
## scrap_collector.png
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square captain skill icon for a science fiction space exploration game. A futuristic salvage manipulator claw holding a single valuable silver mechanical spare part above a small pile of worn spaceship metal plates, amber and turquoise glow. Polished detailed 3D game item render, silver gunmetal and brass, strong readable silhouette, centered composition filling 80 percent of the square. Dark navy nearly black vignette background. Match premium sci-fi inventory icons. No text, letters, numbers, borders or watermarks. One square icon.
|
||||||
|
|
||||||
|
## drone_commander.png
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square captain skill icon for a science fiction space exploration game. Three compact futuristic silver escort drones flying in a triangular formation around a glowing cyan command network emblem, blue engine glow, precise coordinated formation. Polished detailed 3D game item render, silver gunmetal and brass, strong readable silhouette, centered composition filling 80 percent of the square. Dark navy nearly black vignette background. Match premium sci-fi inventory icons. No text, letters, numbers, borders or watermarks. One square icon.
|
||||||
BIN
assets/icons/captain/scientist.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
40
assets/icons/captain/scientist.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c77wfevhr44c4"
|
||||||
|
path="res://.godot/imported/scientist.png-abf7bdb4ef5ddc4d4814cba7c7d9c41b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/scientist.png"
|
||||||
|
dest_files=["res://.godot/imported/scientist.png-abf7bdb4ef5ddc4d4814cba7c7d9c41b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/scrap_collector.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
40
assets/icons/captain/scrap_collector.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c6m75lcwlno1s"
|
||||||
|
path="res://.godot/imported/scrap_collector.png-817780b245e5e65824c57f32c6d36774.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/scrap_collector.png"
|
||||||
|
dest_files=["res://.godot/imported/scrap_collector.png-817780b245e5e65824c57f32c6d36774.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/extensions/auxiliary_shield.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
40
assets/icons/extensions/auxiliary_shield.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://l1nalaps8sx5"
|
||||||
|
path="res://.godot/imported/auxiliary_shield.png-37edfc6ae717c3f9315f43e2835224a3.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/extensions/auxiliary_shield.png"
|
||||||
|
dest_files=["res://.godot/imported/auxiliary_shield.png-37edfc6ae717c3f9315f43e2835224a3.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
5
assets/icons/extensions/auxiliary_shield_prompt.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Tarcza zewnętrzna — ikona
|
||||||
|
|
||||||
|
Wygenerowano wbudowanym image_gen z przezroczystym tłem. Plik: `assets/icons/extensions/auxiliary_shield.png`. Efekt większej osłony jest rysowany w grze przez `scripts/game/auxiliary_shield_visual.gd`.
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square inventory extension icon for a science fiction spaceship game, isolated on truly transparent alpha background. A compact silver gunmetal and brass shield generator with glowing violet and cyan capacitor coils, projecting two concentric holographic protective domes: a smaller cyan shield inside a visibly larger violet hexagonal energy barrier. Centered premium detailed 3D game item render, strong readable silhouette filling 80 percent of square, matching realistic silver/brass sci-fi equipment icons. Keep all parts within canvas. No floor, no background, no text, no numbers, no border. Soft translucent energy glow allowed around the generator.
|
||||||
BIN
assets/icons/external_turret.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
40
assets/icons/external_turret.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dg66tr30feho4"
|
||||||
|
path="res://.godot/imported/external_turret.png-e487322423837fe1dade2618e2a5c7a9.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/external_turret.png"
|
||||||
|
dest_files=["res://.godot/imported/external_turret.png-e487322423837fe1dade2618e2a5c7a9.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
11
assets/icons/external_turret_prompts.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Działka zewnętrzne — grafiki
|
||||||
|
|
||||||
|
Wygenerowano wbudowanym image_gen. Ikona: `assets/icons/external_turret.png`. Sprite: `assets/Ships/external_turret.png` (przezroczyste tło).
|
||||||
|
|
||||||
|
## external_turret_icon
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square inventory weapon icon for a science fiction space game. One small autonomous floating sentry turret, compact circular silver gunmetal and brass chassis, twin forward-facing short mechanical barrels, visible brass ammunition drum holding cartridges, three small cyan levitation thrusters. Polished detailed 3D game item render, centered three-quarter view, strong readable silhouette, fills 80 percent of square, dark navy nearly black vignette background. No words, letters, numbers, border or watermark.
|
||||||
|
|
||||||
|
## external_turret_sprite
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: top-down 2D game sprite, isolated on actual transparent background. One small autonomous science fiction floating sentry turret. Strict orthographic view directly from above, no perspective. Compact circular silver gunmetal armored chassis with brass accents, two short parallel mechanical barrels pointing toward TOP of image, small ammunition drum, symmetrical side housings, three compact cyan anti-gravity thruster lights around the base. Hard-surface polished detailed 3D render matching a realistic sci-fi spaceship sprite set. Complete object centered fully inside square with generous transparent padding; crisp readable silhouette for rendering at 48 pixels. No ground, no shadow outside object, no backdrop, no words, no letters, no numbers, no border.
|
||||||
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.6 MiB |
BIN
assets/icons/pulse_laser.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
40
assets/icons/pulse_laser.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cyhy0cu1rdkaq"
|
||||||
|
path="res://.godot/imported/pulse_laser.png-ee7e313e6cd939780e2ffdbbfdbf414f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/pulse_laser.png"
|
||||||
|
dest_files=["res://.godot/imported/pulse_laser.png-ee7e313e6cd939780e2ffdbbfdbf414f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
5
assets/icons/pulse_laser_prompt.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Laser pulsacyjny — ikona
|
||||||
|
|
||||||
|
Wygenerowano wbudowanym image_gen. Plik: `assets/icons/pulse_laser.png`.
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square inventory weapon icon for a science fiction space game. One compact pulse laser emitter, silver gunmetal and brass housing, three red glowing capacitor rings, forward optical aperture emitting a short vivid crimson red energy pulse. Polished detailed 3D game item render, centered three-quarter view, strong readable silhouette filling 80 percent of square, dark navy nearly black vignette background, premium sci-fi equipment icon matching silver and brass spaceship technology. No words, letters, numbers, border or watermark.
|
||||||
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 1.5 MiB |
15
assets/icons/weapon_background_removal.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Ikony broni — usunięcie tła
|
||||||
|
|
||||||
|
Edytowano wbudowanym image_gen z transparent_background=true. Zastąpione pliki w assets/icons/:
|
||||||
|
|
||||||
|
- incendiary_missile.png
|
||||||
|
- radiation_laser.png
|
||||||
|
- artillery_cannon_v2.png
|
||||||
|
- external_turret.png
|
||||||
|
- pulse_laser.png
|
||||||
|
|
||||||
|
Pozostałe sześć ikon broni zachowano — już miały przezroczystość.
|
||||||
|
|
||||||
|
Prompt dla każdej z pięciu edycji (oryginalna ikona jako edit target):
|
||||||
|
|
||||||
|
Use case: background-extraction. Edit target: the provided inventory weapon icon. Remove ONLY the dark navy/black background and its vignette. Output actual transparent alpha background, including spaces around and between mechanical parts. Preserve the exact same weapon, silhouette, position, crop, scale, metallic textures, colors, lighting, detail and luminous accents. Keep attached flames/energy/thruster light as soft translucent glow where present. Do not redesign, add, remove or reposition any part of the weapon; do not add a floor, shadow, outline or replacement backdrop. Preserve the square canvas composition.
|
||||||
20
resources/items/auxiliary_shield.tres
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
||||||
|
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="definition"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/abilities/auxiliary_shield_ability.gd" id="ability_script"]
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/icons/extensions/auxiliary_shield.png" id="icon"]
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="ability"]
|
||||||
|
script = ExtResource("ability_script")
|
||||||
|
ability_id = "auxiliary_shield"
|
||||||
|
cooldown = 30.0
|
||||||
|
display_name = "Tarcza zewnętrzna"
|
||||||
|
description = "Zużywa 70% maksymalnej energii i tworzy osobną tarczę o pojemności 50% aktualnych maksymalnych tarcz statku. Większa, fioletowa osłona pochłania obrażenia przed zwykłymi tarczami. Działa do wyczerpania, nie regeneruje się i nie nakłada się na kolejną kopię. Dokowanie i Przekierowanie zasilania ją wyłączają."
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("definition")
|
||||||
|
item_id = "auxiliary_shield"
|
||||||
|
display_name = "Tarcza zewnętrzna"
|
||||||
|
icon = ExtResource("icon")
|
||||||
|
category = 5
|
||||||
|
description = "Aktywne rozszerzenie statku. Tworzy dodatkową, większą warstwę ochronną. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
|
active_ability = SubResource("ability")
|
||||||
@ -10,7 +10,7 @@ icon = ExtResource("icon")
|
|||||||
item_id = "beam_laser"
|
item_id = "beam_laser"
|
||||||
display_name = "Laser wiązkowy"
|
display_name = "Laser wiązkowy"
|
||||||
category = 0
|
category = 0
|
||||||
description = "Oszczędny laser pulsacyjny: małe obrażenia i zasięg około jednego ekranu. Koszt impulsu rośnie z obrażeniami i addonami egzemplarza."
|
description = "Oszczędny laser wiązkowy: małe obrażenia i zasięg około jednego ekranu. Koszt impulsu rośnie z obrażeniami i addonami egzemplarza."
|
||||||
primary_stat = "damage"
|
primary_stat = "damage"
|
||||||
primary_value = 3.0
|
primary_value = 3.0
|
||||||
stat_suffix = " DMG"
|
stat_suffix = " DMG"
|
||||||
|
|||||||
27
resources/items/external_turret.tres
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"]
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/icons/external_turret.png" id="icon"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_definition")
|
||||||
|
icon = ExtResource("icon")
|
||||||
|
item_id = "external_turret"
|
||||||
|
display_name = "Działka zewnętrzne"
|
||||||
|
category = 0
|
||||||
|
description = "Kliknięcie rozstawia autonomiczne działko lewitujące w miejscu. Koszt: dokładnie 50 amunicji, która trafia do jego magazynka. Działko samodzielnie celuje i oddaje 50 strzałów; potem znika. Obrażenia każdego pocisku są o 50% niższe niż działka maszynowego z takimi samymi parametrami. Nie zużywa dalszej amunicji statku. Znika przy dokowaniu lub zniszczeniu statku."
|
||||||
|
primary_stat = "damage"
|
||||||
|
primary_value = 3.5
|
||||||
|
stat_suffix = " DMG"
|
||||||
|
weapon_kind = 1
|
||||||
|
weapon_effect = "external_turret"
|
||||||
|
fire_interval = 0.1
|
||||||
|
weapon_cooldown = 1.0
|
||||||
|
ammunition_cost = 50
|
||||||
|
projectile_speed = 1150.0
|
||||||
|
weapon_range = 550.0
|
||||||
|
energy_cost = 0.0
|
||||||
|
damage_lower_roll_min = 2
|
||||||
|
damage_lower_roll_max = 4
|
||||||
|
damage_spread_roll_min = 1
|
||||||
|
damage_spread_roll_max = 1
|
||||||
25
resources/items/pulse_laser.tres
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3]
|
||||||
|
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="definition"]
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/icons/pulse_laser.png" id="icon"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("definition")
|
||||||
|
icon = ExtResource("icon")
|
||||||
|
item_id = "pulse_laser"
|
||||||
|
display_name = "Laser pulsacyjny"
|
||||||
|
category = 0
|
||||||
|
description = "Wystrzeliwuje pojedyncze krótkie czerwone wiązki, które szybko lecą przez przestrzeń. Każdy impuls ma 50% szansy całkowicie ominąć tarcze i trafić w kadłub. Zadaje o 30% mniej obrażeń niż laser wiązkowy z takimi samymi parametrami i zużywa o 30% więcej energii."
|
||||||
|
primary_stat = "damage"
|
||||||
|
primary_value = 3.0
|
||||||
|
stat_suffix = " DMG"
|
||||||
|
weapon_kind = 2
|
||||||
|
weapon_effect = "pulse_laser"
|
||||||
|
fire_interval = 0.2
|
||||||
|
laser_pulse_duration = 0.05
|
||||||
|
projectile_speed = 1500.0
|
||||||
|
weapon_range = 600.0
|
||||||
|
energy_cost = 3.25
|
||||||
|
damage_lower_roll_min = 1
|
||||||
|
damage_lower_roll_max = 3
|
||||||
|
damage_spread_roll_min = 1
|
||||||
|
damage_spread_roll_max = 3
|
||||||
16
scripts/abilities/auxiliary_shield_ability.gd
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
extends ActiveAbility
|
||||||
|
|
||||||
|
func unavailable_reason(ship: CharacterBody2D) -> String:
|
||||||
|
if GameState.captain.is_skill_active("power_redirect"):
|
||||||
|
return "Tarcze wyłączone przez Przekierowanie zasilania"
|
||||||
|
if float(ship.get("max_shields")) <= 0.0:
|
||||||
|
return "Wymagana dodatnia maksymalna pojemność tarcz"
|
||||||
|
if float(ship.get("auxiliary_shields")) > 0.0:
|
||||||
|
return "Dodatkowa tarcza już działa"
|
||||||
|
if float(ship.get("max_energy")) <= 0.0 or float(ship.get("energy")) < float(ship.get("max_energy"))*0.7:
|
||||||
|
return "Wymagane 70% maksymalnej energii"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func activate(ship: CharacterBody2D) -> void:
|
||||||
|
if not unavailable_reason(ship).is_empty(): return
|
||||||
|
ship.call("create_auxiliary_shield")
|
||||||
1
scripts/abilities/auxiliary_shield_ability.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dl7dvyvf0ad1s
|
||||||
@ -37,6 +37,12 @@ func get_skill_rank(skill: String) -> int:
|
|||||||
func get_skill_bonus(skill: String) -> float:
|
func get_skill_bonus(skill: String) -> float:
|
||||||
return SKILLS.value(skill, get_skill_rank(skill))
|
return SKILLS.value(skill, get_skill_rank(skill))
|
||||||
|
|
||||||
|
func roll_wreck_spare_part(roll: float) -> bool:
|
||||||
|
if roll >= get_skill_bonus("scrap_collector"):
|
||||||
|
return false
|
||||||
|
parts += 1.0
|
||||||
|
return true
|
||||||
|
|
||||||
func get_energy_bank_fraction() -> float:
|
func get_energy_bank_fraction() -> float:
|
||||||
return maxf(0.10, get_skill_bonus("electrician"))
|
return maxf(0.10, get_skill_bonus("electrician"))
|
||||||
|
|
||||||
@ -173,16 +179,26 @@ func skill_unavailable_reason(skill: String, ship: CharacterBody2D) -> String:
|
|||||||
return "Odnowienie: %.1f s" % float(skill_cooldowns[skill])
|
return "Odnowienie: %.1f s" % float(skill_cooldowns[skill])
|
||||||
if skill == "power_redirect" and (float(ship.get("max_shields")) <= 0.0 or float(ship.get("shields")) < float(ship.get("max_shields")) * 0.25):
|
if skill == "power_redirect" and (float(ship.get("max_shields")) <= 0.0 or float(ship.get("shields")) < float(ship.get("max_shields")) * 0.25):
|
||||||
return "Wymagane min. 25% tarcz"
|
return "Wymagane min. 25% tarcz"
|
||||||
|
if skill == "emergency_procedure":
|
||||||
|
if parts < 1.0: return "Wymagana 1 część"
|
||||||
|
if float(ship.get("hull")) <= 0.0: return "Statek jest zniszczony"
|
||||||
|
if float(ship.get("hull")) >= float(ship.get("max_hull")): return "Kadłub nie wymaga naprawy"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
func activate_skill(skill: String, ship: CharacterBody2D) -> bool:
|
func activate_skill(skill: String, ship: CharacterBody2D) -> bool:
|
||||||
if not skill_unavailable_reason(skill, ship).is_empty():
|
if not skill_unavailable_reason(skill, ship).is_empty():
|
||||||
return false
|
return false
|
||||||
skill_cooldowns[skill] = SKILLS.COOLDOWN
|
skill_cooldowns[skill] = SKILLS.cooldown(skill)
|
||||||
skill_durations[skill] = SKILLS.duration(skill)
|
skill_durations[skill] = SKILLS.duration(skill)
|
||||||
if skill == "power_redirect":
|
if skill == "power_redirect":
|
||||||
ship.set("shields", 0.0)
|
ship.set("shields", 0.0)
|
||||||
ship.call("_update_shield_visual")
|
ship.call("_update_shield_visual")
|
||||||
|
if skill == "emergency_procedure":
|
||||||
|
parts -= 1.0
|
||||||
|
var maximum: float = ship.get("max_hull")
|
||||||
|
ship.set("hull", minf(maximum, float(ship.get("hull")) + maximum * get_skill_bonus(skill)))
|
||||||
|
if ship.has_method("_hull_technology_feedback"):
|
||||||
|
ship.call("_hull_technology_feedback", "PROCEDURA AWARYJNA")
|
||||||
emit_changed()
|
emit_changed()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@ -203,6 +219,6 @@ func reward_enemy(system_level: int, tier: int, xp_multiplier: float, credit_mul
|
|||||||
var index := clampi(tier, 0, 2)
|
var index := clampi(tier, 0, 2)
|
||||||
var scale_factor := preload("res://scripts/levels/level_balance.gd").item_stat_scale(clampi(system_level, 1, 1000))
|
var scale_factor := preload("res://scripts/levels/level_balance.gd").item_stat_scale(clampi(system_level, 1, 1000))
|
||||||
var xp := encounter_xp(BALANCE.KILL_XP[index] * scale_factor, system_level, xp_multiplier)
|
var xp := encounter_xp(BALANCE.KILL_XP[index] * scale_factor, system_level, xp_multiplier)
|
||||||
var money := roundi(BALANCE.KILL_CREDITS[index] * scale_factor * maxf(0.0, credit_multiplier))
|
var money := roundi(BALANCE.KILL_CREDITS[index] * scale_factor * maxf(0.0, credit_multiplier) * (1.0 + get_skill_bonus("bounty_hunter")))
|
||||||
add_rewards(xp, money if grant_credits else 0)
|
add_rewards(xp, money if grant_credits else 0)
|
||||||
return Vector2i(xp, money)
|
return Vector2i(xp, money)
|
||||||
|
|||||||
@ -4,8 +4,15 @@ const MAX_RANK := 3
|
|||||||
static var _icon_cache: Dictionary = {}
|
static var _icon_cache: Dictionary = {}
|
||||||
const COOLDOWN := 30.0
|
const COOLDOWN := 30.0
|
||||||
const DURATION := 5.0
|
const DURATION := 5.0
|
||||||
const IDS := ["business_shark", "reactor_overload", "tactical_maneuver", "efficient_magazine", "miner", "power_redirect", "lucky", "explorer", "electrician"]
|
const IDS := ["business_shark", "reactor_overload", "tactical_maneuver", "efficient_magazine", "miner", "power_redirect", "lucky", "explorer", "electrician", "scientist", "gunsmith", "bounty_hunter", "missile_specialist", "emergency_procedure", "scrap_collector", "drone_commander"]
|
||||||
const DATA := {
|
const DATA := {
|
||||||
|
"scrap_collector": {"name": "Zbieracz złomu", "active": false, "values": [0.15, 0.25, 0.40], "color": "d9b27c"},
|
||||||
|
"drone_commander": {"name": "Dowódca dronów", "active": false, "values": [0.10, 0.20, 0.30], "color": "70dfff"},
|
||||||
|
"scientist": {"name": "Naukowiec", "active": false, "values": [0.10, 0.20, 0.30], "color": "76d9ed"},
|
||||||
|
"gunsmith": {"name": "Rusznikarz", "active": false, "values": [0.10, 0.20, 0.30], "color": "e8b26e"},
|
||||||
|
"bounty_hunter": {"name": "Łowca nagród", "active": false, "values": [0.10, 0.20, 0.30], "color": "efcc70"},
|
||||||
|
"missile_specialist": {"name": "Specjalista rakietowy", "active": false, "values": [0.10, 0.15, 0.20], "color": "f29576"},
|
||||||
|
"emergency_procedure": {"name": "Procedura awaryjna", "active": true, "values": [0.05, 0.10, 0.15], "color": "85deb1"},
|
||||||
"electrician": {"name": "Elektryk", "active": false, "values": [0.15, 0.20, 0.25], "color": "70f5df"},
|
"electrician": {"name": "Elektryk", "active": false, "values": [0.15, 0.20, 0.25], "color": "70f5df"},
|
||||||
"lucky": {"name": "Farciarz", "active": false, "values": [0.05, 0.10, 0.25], "color": "e8c76f"},
|
"lucky": {"name": "Farciarz", "active": false, "values": [0.05, 0.10, 0.25], "color": "e8c76f"},
|
||||||
"explorer": {"name": "Eksplorator", "active": false, "values": [1, 3, 6], "color": "76d9cc"},
|
"explorer": {"name": "Eksplorator", "active": false, "values": [1, 3, 6], "color": "76d9cc"},
|
||||||
@ -23,11 +30,22 @@ static func value(skill: String, rank: int) -> float:
|
|||||||
return float(DATA[skill]["values"][clampi(rank, 1, MAX_RANK) - 1])
|
return float(DATA[skill]["values"][clampi(rank, 1, MAX_RANK) - 1])
|
||||||
|
|
||||||
static func duration(skill: String) -> float:
|
static func duration(skill: String) -> float:
|
||||||
|
if skill == "emergency_procedure": return 0.0
|
||||||
return 15.0 if skill == "reactor_overload" else (7.0 if skill == "power_redirect" else DURATION)
|
return 15.0 if skill == "reactor_overload" else (7.0 if skill == "power_redirect" else DURATION)
|
||||||
|
|
||||||
|
static func cooldown(skill: String) -> float:
|
||||||
|
return 60.0 if skill == "emergency_procedure" else COOLDOWN
|
||||||
|
|
||||||
static func icon(skill: String) -> Texture2D:
|
static func icon(skill: String) -> Texture2D:
|
||||||
if skill not in IDS: return null
|
if skill not in IDS: return null
|
||||||
if not _icon_cache.has(skill):
|
if not _icon_cache.has(skill):
|
||||||
|
if IDS.find(skill) >= 9:
|
||||||
|
var texture: Texture2D = load("res://assets/icons/captain/%s.png" % skill)
|
||||||
|
var artwork := texture.get_image()
|
||||||
|
artwork.resize(384,384,Image.INTERPOLATE_LANCZOS)
|
||||||
|
artwork.generate_mipmaps()
|
||||||
|
_icon_cache[skill] = ImageTexture.create_from_image(artwork)
|
||||||
|
return _icon_cache[skill]
|
||||||
var atlas: Texture2D = load("res://assets/icons/captain/unified_skills.png")
|
var atlas: Texture2D = load("res://assets/icons/captain/unified_skills.png")
|
||||||
var cell := atlas.get_size()/3.0
|
var cell := atlas.get_size()/3.0
|
||||||
var index := IDS.find(skill)
|
var index := IDS.find(skill)
|
||||||
@ -44,6 +62,20 @@ static func icon(skill: String) -> Texture2D:
|
|||||||
static func description(skill: String, rank: int) -> String:
|
static func description(skill: String, rank: int) -> String:
|
||||||
var amount := roundi(value(skill, rank) * 100.0)
|
var amount := roundi(value(skill, rank) * 100.0)
|
||||||
match skill:
|
match skill:
|
||||||
|
"scrap_collector":
|
||||||
|
return "%d%% szansy na 1 dodatkową część z przeszukanego wraku. Losowanie raz na wrak. Premie rang nie sumują się." % amount
|
||||||
|
"drone_commander":
|
||||||
|
return "Obrażenia dronów bojowych większe o %d%%. Premie rang nie sumują się." % amount
|
||||||
|
"scientist":
|
||||||
|
return "Nowe badania w laboratorium trwają o %d%% krócej. Premie rang nie sumują się." % amount
|
||||||
|
"gunsmith":
|
||||||
|
return "Produkcja zwykłej i radiacyjnej amunicji daje o %d%% więcej sztuk za ten sam koszt. Nie dotyczy rakiet. Premie rang nie sumują się." % amount
|
||||||
|
"bounty_hunter":
|
||||||
|
return "O %d%% więcej kredytów za pokonanych wrogów. Premia obejmuje kredyty do zebrania po walce. Premie rang nie sumują się." % amount
|
||||||
|
"missile_specialist":
|
||||||
|
return "Czas między wystrzeleniem rakiet krótszy o %d%%. Działa po premiach sprzętu; minimalny odstęp to 1 s. Premie rang nie sumują się." % amount
|
||||||
|
"emergency_procedure":
|
||||||
|
return "Natychmiast naprawia %d%% maksymalnego kadłuba za 1 część. Wymaga uszkodzonego statku i aktywnego lotu." % amount
|
||||||
"electrician":
|
"electrician":
|
||||||
return "Powerbank przywraca %d%% maksymalnej energii zamiast 10%%. Umiejętność pasywna; premie rang nie sumują się." % amount
|
return "Powerbank przywraca %d%% maksymalnej energii zamiast 10%%. Umiejętność pasywna; premie rang nie sumują się." % amount
|
||||||
"lucky":
|
"lucky":
|
||||||
|
|||||||
26
scripts/game/auxiliary_shield_visual.gd
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
extends "res://scripts/game/shield_visual.gd"
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if current_shields > 0.0:
|
||||||
|
var ratio := clampf(current_shields/max_shields,0.0,1.0)
|
||||||
|
var tint := Color("ba8cff")
|
||||||
|
var pulse := 0.85+sin(_time*2.0)*0.15
|
||||||
|
draw_circle(Vector2.ZERO,shield_radius,Color(tint,0.025*pulse))
|
||||||
|
draw_arc(Vector2.ZERO,shield_radius,0,TAU,112,Color(tint,0.55*pulse),1.8,true)
|
||||||
|
draw_arc(Vector2.ZERO,shield_radius+2.0,0,TAU,112,Color(tint,0.11),5.0,true)
|
||||||
|
for index in 12:
|
||||||
|
var start := TAU*index/12.0+_time*0.12
|
||||||
|
draw_arc(Vector2.ZERO,shield_radius-4.0,start,start+0.25,8,Color(Color("8ee9ff"),0.2+ratio*0.3),1.0,true)
|
||||||
|
for row in range(-3,4):
|
||||||
|
for column in range(-3,4):
|
||||||
|
var center := Vector2(column*18.0+9.0*(posmod(row,2)),row*15.6)
|
||||||
|
if center.length()+10.0 >= shield_radius: continue
|
||||||
|
var hexagon := PackedVector2Array()
|
||||||
|
for vertex in 7: hexagon.append(center+Vector2.from_angle(vertex*TAU/6.0+PI/6.0)*10.0)
|
||||||
|
draw_polyline(hexagon,Color(tint,0.045*pulse),0.7,true)
|
||||||
|
# Counter-rotate the readout so it stays legible as the ship turns.
|
||||||
|
draw_set_transform(Vector2.ZERO,-global_rotation)
|
||||||
|
draw_string(ThemeDB.fallback_font,Vector2(-65,-shield_radius-12),"OSŁONA %.0f / %.0f" % [current_shields,max_shields],HORIZONTAL_ALIGNMENT_CENTER,130,11,Color("d7bfff"))
|
||||||
|
draw_set_transform(Vector2.ZERO)
|
||||||
|
# Keep the existing directional impact ripples on the larger perimeter.
|
||||||
|
if not _hits.is_empty(): super._draw()
|
||||||
1
scripts/game/auxiliary_shield_visual.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://d2r7pt27irbcr
|
||||||
@ -90,7 +90,8 @@ func _clear_shot(target: Node2D) -> bool:
|
|||||||
func _fire(target: Node2D) -> void:
|
func _fire(target: Node2D) -> void:
|
||||||
var direction := (target.global_position - global_position).normalized()
|
var direction := (target.global_position - global_position).normalized()
|
||||||
var bullet := PROJECTILE.instantiate()
|
var bullet := PROJECTILE.instantiate()
|
||||||
bullet.call("configure", global_position + direction * 9.0, direction, ItemDefinition.WeaponKind.MACHINE_GUN, 1100.0, damage_per_shot, 650.0)
|
var damage := damage_per_shot * (1.0 + GameState.captain.get_skill_bonus("drone_commander"))
|
||||||
|
bullet.call("configure", global_position + direction * 9.0, direction, ItemDefinition.WeaponKind.MACHINE_GUN, 1100.0, damage, 650.0)
|
||||||
get_parent().add_child(bullet)
|
get_parent().add_child(bullet)
|
||||||
_flash = 0.06
|
_flash = 0.06
|
||||||
mother.get_node("ExtensionSystems").call("break_cloak_on_fire")
|
mother.get_node("ExtensionSystems").call("break_cloak_on_fire")
|
||||||
|
|||||||
85
scripts/game/deployed_turret.gd
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn")
|
||||||
|
const ARTWORK := preload("res://assets/Ships/external_turret.png")
|
||||||
|
const AUDIO := preload("res://scripts/game/weapon_audio.gd")
|
||||||
|
static var _sprite: Texture2D
|
||||||
|
var mother: CharacterBody2D
|
||||||
|
var weapon: ItemInstance
|
||||||
|
var ammunition := 50
|
||||||
|
var _fire_timer := 0.35
|
||||||
|
var _age := 0.0
|
||||||
|
var _flash := 0.0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if _sprite == null:
|
||||||
|
var image := ARTWORK.get_image()
|
||||||
|
image.resize(256,256,Image.INTERPOLATE_LANCZOS)
|
||||||
|
image.generate_mipmaps()
|
||||||
|
_sprite = ImageTexture.create_from_image(image)
|
||||||
|
add_to_group("player_external_turrets")
|
||||||
|
z_index = 5
|
||||||
|
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
if not is_instance_valid(mother) or bool(mother.get("_is_destroyed")) or bool(mother.get("is_docked")) or ammunition <= 0:
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
_age += delta
|
||||||
|
_flash = maxf(0.0,_flash-delta)
|
||||||
|
_fire_timer = maxf(0.0,_fire_timer-delta)
|
||||||
|
var target := _find_target()
|
||||||
|
if target != null:
|
||||||
|
var direction := target.global_position-global_position
|
||||||
|
rotation = rotate_toward(rotation,direction.angle()+PI*0.5,8.0*delta)
|
||||||
|
if _fire_timer <= 0.0 and absf(angle_difference(rotation,direction.angle()+PI*0.5)) < 0.12:
|
||||||
|
_fire(target)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _find_target() -> Node2D:
|
||||||
|
var closest: Node2D
|
||||||
|
var distance := weapon.definition.weapon_range
|
||||||
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0 or bool(enemy.get("virus_controlled")):
|
||||||
|
continue
|
||||||
|
var candidate := global_position.distance_to(enemy.global_position)
|
||||||
|
if candidate < distance and _clear_shot(enemy):
|
||||||
|
distance = candidate
|
||||||
|
closest = enemy
|
||||||
|
return closest
|
||||||
|
|
||||||
|
func _clear_shot(target: Node2D) -> bool:
|
||||||
|
var query := PhysicsRayQueryParameters2D.create(global_position,target.global_position,77,[mother.get_rid()])
|
||||||
|
var hit := get_world_2d().direct_space_state.intersect_ray(query)
|
||||||
|
return hit.is_empty() or hit.get("collider") == target
|
||||||
|
|
||||||
|
func _fire(target: Node2D) -> void:
|
||||||
|
if ammunition <= 0 or is_queued_for_deletion() or not is_instance_valid(target): return
|
||||||
|
var direction := (target.global_position-global_position).normalized()
|
||||||
|
var hit: Dictionary = mother.call("roll_weapon_hit",weapon)
|
||||||
|
var bullet := PROJECTILE.instantiate()
|
||||||
|
bullet.configure(global_position+direction*25.0,direction,ItemDefinition.WeaponKind.MACHINE_GUN,weapon.definition.projectile_speed,float(hit.damage),weapon.definition.weapon_range,null,bool(hit.critical))
|
||||||
|
bullet.shield_piercing = weapon.shield_piercing()
|
||||||
|
bullet.damage_type_override = weapon.get_damage_type()
|
||||||
|
bullet.source_ship = mother
|
||||||
|
get_parent().add_child(bullet)
|
||||||
|
ammunition -= 1
|
||||||
|
_fire_timer = weapon.get_turret_fire_interval()
|
||||||
|
_flash = 0.05
|
||||||
|
AUDIO.play(get_parent(),global_position,ItemDefinition.WeaponKind.MACHINE_GUN,-8.0)
|
||||||
|
mother.get_node("WeaponController").call("_break_cloak_on_fire")
|
||||||
|
if ammunition == 0: queue_free()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var bob := Vector2(0,sin(_age*2.5)*2.0)
|
||||||
|
var appear := smoothstep(0.0,0.3,_age)
|
||||||
|
draw_circle(Vector2.ZERO,19.0,Color(0.1,0.7,1.0,0.08))
|
||||||
|
draw_arc(Vector2.ZERO,30.0,-PI*0.5,-PI*0.5+TAU*float(ammunition)/50.0,48,Color("70dfff"),1.6,true)
|
||||||
|
draw_set_transform(bob,0.0,Vector2.ONE*lerpf(0.4,1.0,appear))
|
||||||
|
draw_texture_rect(_sprite,Rect2(-30,-30,60,60),false)
|
||||||
|
if _flash > 0.0:
|
||||||
|
for x in [-7.0,7.0]:
|
||||||
|
draw_line(Vector2(x,-24),Vector2(x,-38),Color("ffdb86"),3.0,true)
|
||||||
|
draw_circle(Vector2(x,-28),5.0,Color(1.0,0.65,0.2,0.45))
|
||||||
|
draw_set_transform(Vector2.ZERO,-rotation)
|
||||||
|
draw_string(ThemeDB.fallback_font,Vector2(-11,43),str(ammunition),HORIZONTAL_ALIGNMENT_CENTER,22,11,Color("b9e7f5"))
|
||||||
1
scripts/game/deployed_turret.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://3w0qsvkh8rob
|
||||||
@ -367,7 +367,7 @@ func _fire_plasma(direction: Vector2) -> void:
|
|||||||
WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, weapon_kind, -4.0)
|
WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, weapon_kind, -4.0)
|
||||||
|
|
||||||
func take_piercing_weapon_hit(amount: float, critical: bool, damage_type: int, piercing: float) -> void:
|
func take_piercing_weapon_hit(amount: float, critical: bool, damage_type: int, piercing: float) -> void:
|
||||||
_weapon_piercing = clampf(piercing,0.0,0.5)
|
_weapon_piercing = clampf(piercing,0.0,1.0)
|
||||||
take_weapon_hit(amount,critical,damage_type)
|
take_weapon_hit(amount,critical,damage_type)
|
||||||
_weapon_piercing = 0.0
|
_weapon_piercing = 0.0
|
||||||
|
|
||||||
@ -376,7 +376,7 @@ func take_weapon_hit(amount: float, critical: bool, damage_type: int = DamageTyp
|
|||||||
|
|
||||||
func take_research_weapon_hit(amount: float, critical: bool, damage_type: int, piercing: float, disabled_system: int, damage_scale := 1.0) -> void:
|
func take_research_weapon_hit(amount: float, critical: bool, damage_type: int, piercing: float, disabled_system: int, damage_scale := 1.0) -> void:
|
||||||
_research_damage_scale = damage_scale
|
_research_damage_scale = damage_scale
|
||||||
_weapon_piercing = clampf(piercing,0.0,0.5)
|
_weapon_piercing = clampf(piercing,0.0,1.0)
|
||||||
_take_player_hit(amount,critical,damage_type,true,disabled_system)
|
_take_player_hit(amount,critical,damage_type,true,disabled_system)
|
||||||
_weapon_piercing = 0.0
|
_weapon_piercing = 0.0
|
||||||
_research_damage_scale = 1.0
|
_research_damage_scale = 1.0
|
||||||
@ -443,7 +443,7 @@ func take_damage(amount: float, critical: bool = false, damage_type: int = Damag
|
|||||||
var piercing_damage := actual * _weapon_piercing if _research_player_hit else 0.0
|
var piercing_damage := actual * _weapon_piercing if _research_player_hit else 0.0
|
||||||
var shield_damage := minf(shields, (actual - piercing_damage) * shield_multiplier)
|
var shield_damage := minf(shields, (actual - piercing_damage) * shield_multiplier)
|
||||||
var double_hit := _research_direct_hit and research.learned("weapons_a_next_next_next")
|
var double_hit := _research_direct_hit and research.learned("weapons_a_next_next_next")
|
||||||
if double_hit: shield_damage = minf(shields,actual*shield_multiplier)
|
if double_hit and _weapon_piercing < 1.0: shield_damage = minf(shields,actual*shield_multiplier)
|
||||||
var remaining := actual if double_hit else maxf(0.0, actual - shield_damage / shield_multiplier)
|
var remaining := actual if double_hit else maxf(0.0, actual - shield_damage / shield_multiplier)
|
||||||
var shields_before := shields
|
var shields_before := shields
|
||||||
shields -= shield_damage
|
shields -= shield_damage
|
||||||
|
|||||||
@ -17,6 +17,7 @@ var _has_destination := false
|
|||||||
|
|
||||||
func reset_combat_state() -> void:
|
func reset_combat_state() -> void:
|
||||||
cooldowns.clear()
|
cooldowns.clear()
|
||||||
|
ship.clear_auxiliary_shield()
|
||||||
_clear_drones()
|
_clear_drones()
|
||||||
solar_remaining = 0.0
|
solar_remaining = 0.0
|
||||||
if cloak_remaining > 0.0:
|
if cloak_remaining > 0.0:
|
||||||
@ -167,7 +168,7 @@ func _start_cloak() -> void:
|
|||||||
if shadow != null:
|
if shadow != null:
|
||||||
_shadow_was_visible = shadow.visible
|
_shadow_was_visible = shadow.visible
|
||||||
shadow.hide()
|
shadow.hide()
|
||||||
for name in ["ShieldVisual", "EngineVisual", "SolarEnergyEffect", "EnergyIndicator"]:
|
for name in ["ShieldVisual", "AuxiliaryShieldVisual", "EngineVisual", "SolarEnergyEffect", "EnergyIndicator"]:
|
||||||
var visual := ship.get_node_or_null(name) as CanvasItem
|
var visual := ship.get_node_or_null(name) as CanvasItem
|
||||||
if visual != null:
|
if visual != null:
|
||||||
_visual_colors[name] = visual.modulate
|
_visual_colors[name] = visual.modulate
|
||||||
|
|||||||
@ -9,9 +9,13 @@ static func intercept(world_hit: Dictionary, start: Vector2, end: Vector2, mask:
|
|||||||
for target in tree.get_nodes_in_group("shielded_combatants"):
|
for target in tree.get_nodes_in_group("shielded_combatants"):
|
||||||
if not is_instance_valid(target) or target.is_queued_for_deletion(): continue
|
if not is_instance_valid(target) or target.is_queued_for_deletion(): continue
|
||||||
if (int(target.collision_layer)&mask)==0 or target.get_rid() in excluded: continue
|
if (int(target.collision_layer)&mask)==0 or target.get_rid() in excluded: continue
|
||||||
if float(target.shields)<=0.0 or float(target.hull)<=0.0: continue
|
if float(target.hull)<=0.0: continue
|
||||||
if target.has_method("is_mini_boosting") and (bool(target.is_docked) or bool(target._is_destroyed)): continue
|
if target.has_method("is_mini_boosting") and (bool(target.is_docked) or bool(target._is_destroyed)): continue
|
||||||
var shield := target.get_node_or_null("ShieldVisual") as Node2D
|
var shield: Node2D
|
||||||
|
if target.has_method("get_active_shield_visual"):
|
||||||
|
shield = target.get_active_shield_visual()
|
||||||
|
elif float(target.shields) > 0.0:
|
||||||
|
shield = target.get_node_or_null("ShieldVisual") as Node2D
|
||||||
if shield == null: continue
|
if shield == null: continue
|
||||||
var radius := float(shield.shield_radius)*shield.global_transform.x.length()
|
var radius := float(shield.shield_radius)*shield.global_transform.x.length()
|
||||||
var offset := start-shield.global_position
|
var offset := start-shield.global_position
|
||||||
|
|||||||
@ -34,6 +34,9 @@ var scanner_range := 1000.0
|
|||||||
@export var max_energy := 100.0
|
@export var max_energy := 100.0
|
||||||
var hull := 100.0
|
var hull := 100.0
|
||||||
var shields := 100.0
|
var shields := 100.0
|
||||||
|
var auxiliary_shields := 0.0
|
||||||
|
var auxiliary_max_shields := 0.0
|
||||||
|
var _auxiliary_shield_visual: Node2D
|
||||||
var energy := 100.0
|
var energy := 100.0
|
||||||
var energy_regeneration := 5.0
|
var energy_regeneration := 5.0
|
||||||
var shield_regeneration := 0.0
|
var shield_regeneration := 0.0
|
||||||
@ -125,6 +128,7 @@ func _ready() -> void:
|
|||||||
extension_systems.set_script(preload("res://scripts/game/extension_systems.gd"))
|
extension_systems.set_script(preload("res://scripts/game/extension_systems.gd"))
|
||||||
extension_systems.set("cooldowns", $ShipInventory.extension_cooldowns)
|
extension_systems.set("cooldowns", $ShipInventory.extension_cooldowns)
|
||||||
add_child(extension_systems)
|
add_child(extension_systems)
|
||||||
|
_ensure_auxiliary_shield_visual()
|
||||||
for index in ShipInventory.EXTENSION_SLOT_COUNT:
|
for index in ShipInventory.EXTENSION_SLOT_COUNT:
|
||||||
_ensure_input_action(StringName("ability_%d" % (index + 1)), (KEY_1 + index) as Key)
|
_ensure_input_action(StringName("ability_%d" % (index + 1)), (KEY_1 + index) as Key)
|
||||||
_ensure_input_action(&"use_energy_bank", KEY_R)
|
_ensure_input_action(&"use_energy_bank", KEY_R)
|
||||||
@ -201,9 +205,14 @@ func take_damage(amount: float, _critical: bool = false, damage_type: int = Dama
|
|||||||
get_parent().add_child(popup)
|
get_parent().add_child(popup)
|
||||||
return
|
return
|
||||||
var remaining_damage: float = hit["damage"]
|
var remaining_damage: float = hit["damage"]
|
||||||
|
var auxiliary_damage := minf(auxiliary_shields,remaining_damage)
|
||||||
|
auxiliary_shields -= auxiliary_damage
|
||||||
|
remaining_damage -= auxiliary_damage
|
||||||
|
if auxiliary_damage > 0.0 and is_instance_valid(_auxiliary_shield_visual):
|
||||||
|
_auxiliary_shield_visual.flash_hit(visual_contact.point,damage_type)
|
||||||
var research := preload("res://scripts/planets/alien_research.gd")
|
var research := preload("res://scripts/planets/alien_research.gd")
|
||||||
var shields_before := shields
|
var shields_before := shields
|
||||||
if shields > 0.0:
|
if shields > 0.0 and remaining_damage > 0.0:
|
||||||
if research.learned("shields_b_next_next") and _salvo_cooldown <= 0.0:
|
if research.learned("shields_b_next_next") and _salvo_cooldown <= 0.0:
|
||||||
remaining_damage *= 0.1
|
remaining_damage *= 0.1
|
||||||
_salvo_cooldown = 60.0
|
_salvo_cooldown = 60.0
|
||||||
@ -385,6 +394,38 @@ func _spawn_hit_effect(at_position: Vector2, effect_color: Color) -> void:
|
|||||||
func _update_shield_visual() -> void:
|
func _update_shield_visual() -> void:
|
||||||
if is_instance_valid(_shield_visual):
|
if is_instance_valid(_shield_visual):
|
||||||
_shield_visual.call("set_shield_values", shields, max_shields)
|
_shield_visual.call("set_shield_values", shields, max_shields)
|
||||||
|
if is_instance_valid(_auxiliary_shield_visual):
|
||||||
|
if GameState.captain.is_skill_active("power_redirect"): auxiliary_shields = 0.0
|
||||||
|
_auxiliary_shield_visual.shield_radius = $ShieldVisual.shield_radius*1.45
|
||||||
|
_auxiliary_shield_visual.scale = $ShieldVisual.scale
|
||||||
|
_auxiliary_shield_visual.set_shield_values(auxiliary_shields,auxiliary_max_shields)
|
||||||
|
|
||||||
|
func _ensure_auxiliary_shield_visual() -> void:
|
||||||
|
if is_instance_valid(_auxiliary_shield_visual): return
|
||||||
|
_auxiliary_shield_visual = Node2D.new()
|
||||||
|
_auxiliary_shield_visual.name = "AuxiliaryShieldVisual"
|
||||||
|
_auxiliary_shield_visual.set_script(preload("res://scripts/game/auxiliary_shield_visual.gd"))
|
||||||
|
_auxiliary_shield_visual.z_index = 3
|
||||||
|
add_child(_auxiliary_shield_visual)
|
||||||
|
_update_shield_visual()
|
||||||
|
|
||||||
|
func create_auxiliary_shield() -> void:
|
||||||
|
if _is_destroyed or is_docked or max_shields <= 0.0 or max_energy <= 0.0 or auxiliary_shields > 0.0 or energy < max_energy*0.7 or GameState.captain.is_skill_active("power_redirect"):
|
||||||
|
return
|
||||||
|
energy -= max_energy*0.7
|
||||||
|
auxiliary_max_shields = max_shields*0.5
|
||||||
|
auxiliary_shields = auxiliary_max_shields
|
||||||
|
_ensure_auxiliary_shield_visual()
|
||||||
|
_update_shield_visual()
|
||||||
|
_auxiliary_shield_visual.flash_hit()
|
||||||
|
|
||||||
|
func clear_auxiliary_shield() -> void:
|
||||||
|
auxiliary_shields = 0.0
|
||||||
|
_update_shield_visual()
|
||||||
|
|
||||||
|
func get_active_shield_visual() -> Node2D:
|
||||||
|
if auxiliary_shields > 0.0 and is_instance_valid(_auxiliary_shield_visual): return _auxiliary_shield_visual
|
||||||
|
return $ShieldVisual if shields > 0.0 else null
|
||||||
|
|
||||||
func _destroy_ship() -> void:
|
func _destroy_ship() -> void:
|
||||||
if _is_destroyed:
|
if _is_destroyed:
|
||||||
@ -463,6 +504,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
if GameState.captain.is_skill_active("power_redirect"):
|
if GameState.captain.is_skill_active("power_redirect"):
|
||||||
shields = 0.0
|
shields = 0.0
|
||||||
if _is_destroyed or is_docked:
|
if _is_destroyed or is_docked:
|
||||||
|
if auxiliary_shields > 0.0: clear_auxiliary_shield()
|
||||||
_mini_boost_active = false
|
_mini_boost_active = false
|
||||||
return
|
return
|
||||||
_refill_recovery_budgets(delta)
|
_refill_recovery_budgets(delta)
|
||||||
|
|||||||
@ -104,6 +104,11 @@ func _physics_process(delta: float) -> void:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
var definition := item.definition
|
var definition := item.definition
|
||||||
|
if definition.weapon_effect == "external_turret":
|
||||||
|
_laser_pulse_timers[slot_index] = 0.0
|
||||||
|
_laser_lines[slot_index].visible = false
|
||||||
|
if is_just_pressed: try_deploy_turret(slot_index)
|
||||||
|
continue
|
||||||
if definition.weapon_kind != ItemDefinition.WeaponKind.LASER:
|
if definition.weapon_kind != ItemDefinition.WeaponKind.LASER:
|
||||||
_laser_pulse_timers[slot_index] = 0.0
|
_laser_pulse_timers[slot_index] = 0.0
|
||||||
_laser_lines[slot_index].visible = false
|
_laser_lines[slot_index].visible = false
|
||||||
@ -121,6 +126,33 @@ func _physics_process(delta: float) -> void:
|
|||||||
for slot_index in ShipInventory.WEAPON_SLOT_COUNT:
|
for slot_index in ShipInventory.WEAPON_SLOT_COUNT:
|
||||||
_fire_just_pressed[slot_index] = false
|
_fire_just_pressed[slot_index] = false
|
||||||
|
|
||||||
|
func try_deploy_turret(slot_index: int) -> bool:
|
||||||
|
if slot_index < 0 or slot_index >= ShipInventory.WEAPON_SLOT_COUNT or _shot_timers[slot_index] > 0.0:
|
||||||
|
return false
|
||||||
|
if get_tree().paused or bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
|
||||||
|
return false
|
||||||
|
var item := inventory.get_item("weapon",slot_index)
|
||||||
|
if item == null or item.definition.weapon_effect != "external_turret" or inventory.ammunition < 50:
|
||||||
|
return false
|
||||||
|
var turret := Node2D.new()
|
||||||
|
turret.set_script(preload("res://scripts/game/deployed_turret.gd"))
|
||||||
|
turret.set("mother",ship)
|
||||||
|
turret.set("weapon",item.duplicate(true))
|
||||||
|
var direction := _aim_direction_from(ship.global_position)
|
||||||
|
turret.position = ship.global_position + direction * 70.0
|
||||||
|
turret.rotation = direction.angle() + PI * 0.5
|
||||||
|
inventory.ammunition -= 50
|
||||||
|
inventory.ammunition_changed.emit()
|
||||||
|
ship.get_parent().add_child(turret)
|
||||||
|
_shot_timers[slot_index] = item.get_fire_interval()
|
||||||
|
_break_cloak_on_fire()
|
||||||
|
var flash := Node2D.new()
|
||||||
|
flash.set_script(preload("res://scripts/game/deployment_flash.gd"))
|
||||||
|
flash.position = turret.position
|
||||||
|
flash.set("color",Color("70dfff"))
|
||||||
|
ship.get_parent().add_child(flash)
|
||||||
|
return true
|
||||||
|
|
||||||
func try_fire_missile(slot_index: int = 2) -> bool:
|
func try_fire_missile(slot_index: int = 2) -> bool:
|
||||||
if missile_cooldown_remaining > 0.0 or bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
|
if missile_cooldown_remaining > 0.0 or bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
|
||||||
return false
|
return false
|
||||||
@ -156,7 +188,8 @@ func _fire_projectile(item: ItemInstance) -> void:
|
|||||||
)
|
)
|
||||||
ship.get_parent().add_child(projectile)
|
ship.get_parent().add_child(projectile)
|
||||||
projectile.reset_physics_interpolation()
|
projectile.reset_physics_interpolation()
|
||||||
projectile.set("shield_piercing", item.shield_piercing())
|
projectile.set("shield_piercing", item.roll_shield_piercing())
|
||||||
|
if definition.weapon_effect == "pulse_laser": projectile.set("source_ship",ship)
|
||||||
projectile.set("damage_type_override", item.get_damage_type())
|
projectile.set("damage_type_override", item.get_damage_type())
|
||||||
projectile.set("missile_homing", definition.missile_homing)
|
projectile.set("missile_homing", definition.missile_homing)
|
||||||
if definition.weapon_effect == "artillery":
|
if definition.weapon_effect == "artillery":
|
||||||
@ -180,9 +213,17 @@ func _exit_tree() -> void:
|
|||||||
func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
||||||
var definition := item.definition
|
var definition := item.definition
|
||||||
var line := _laser_lines[slot_index]
|
var line := _laser_lines[slot_index]
|
||||||
|
if definition.weapon_effect == "pulse_laser":
|
||||||
|
line.visible = false
|
||||||
|
_laser_pulse_timers[slot_index] = 0.0
|
||||||
|
if _shot_timers[slot_index] <= 0.0 and _consume_energy(item.get_energy_cost()):
|
||||||
|
_fire_projectile(item)
|
||||||
|
_shot_timers[slot_index] = item.get_fire_interval()
|
||||||
|
return
|
||||||
line.set("radiation_beam",item.get_damage_type() == DamageTypes.Type.RADIATION)
|
line.set("radiation_beam",item.get_damage_type() == DamageTypes.Type.RADIATION)
|
||||||
if _shot_timers[slot_index] > 0.0 or float(ship.get("energy")) < _effective_energy_cost(item.get_energy_cost()):
|
if _shot_timers[slot_index] > 0.0 or float(ship.get("energy")) < _effective_energy_cost(item.get_energy_cost()):
|
||||||
return
|
return
|
||||||
|
var piercing := item.roll_shield_piercing()
|
||||||
var muzzle_position := _muzzle_position()
|
var muzzle_position := _muzzle_position()
|
||||||
var direction := _aim_direction_from(muzzle_position)
|
var direction := _aim_direction_from(muzzle_position)
|
||||||
var endpoint := muzzle_position + direction * definition.weapon_range
|
var endpoint := muzzle_position + direction * definition.weapon_range
|
||||||
@ -193,7 +234,8 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
|||||||
[ship.get_rid()]
|
[ship.get_rid()]
|
||||||
)
|
)
|
||||||
var result := ship.get_world_2d().direct_space_state.intersect_ray(query)
|
var result := ship.get_world_2d().direct_space_state.intersect_ray(query)
|
||||||
result = preload("res://scripts/game/shield_intersections.gd").intercept(result,muzzle_position,endpoint,RAY_COLLISION_MASK,[ship.get_rid()],get_tree())
|
if piercing < 1.0:
|
||||||
|
result = preload("res://scripts/game/shield_intersections.gd").intercept(result,muzzle_position,endpoint,RAY_COLLISION_MASK,[ship.get_rid()],get_tree())
|
||||||
if not result.is_empty():
|
if not result.is_empty():
|
||||||
endpoint = result["position"]
|
endpoint = result["position"]
|
||||||
line.clear_points()
|
line.clear_points()
|
||||||
@ -210,7 +252,7 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
|||||||
var target: Node = result.get("collider")
|
var target: Node = result.get("collider")
|
||||||
var hull_before := float(target.get("hull")) if target.has_method("take_research_weapon_hit") else -1.0
|
var hull_before := float(target.get("hull")) if target.has_method("take_research_weapon_hit") else -1.0
|
||||||
preload("res://scripts/game/combat_vfx.gd").mark_hit(target,endpoint,direction)
|
preload("res://scripts/game/combat_vfx.gd").mark_hit(target,endpoint,direction)
|
||||||
_apply_damage(target, float(hit["damage"]), bool(hit["critical"]), item.shield_piercing(), item.get_damage_type())
|
_apply_damage(target, float(hit["damage"]), bool(hit["critical"]), piercing, item.get_damage_type())
|
||||||
_apply_laser_radiation(target,item,float(hit.damage))
|
_apply_laser_radiation(target,item,float(hit.damage))
|
||||||
_spawn_laser_impact(endpoint,item.get_damage_type(),direction)
|
_spawn_laser_impact(endpoint,item.get_damage_type(),direction)
|
||||||
if hull_before >= 0 and float(target.get("hull")) < hull_before and preload("res://scripts/planets/alien_research.gd").learned("hull_a_next_next"):
|
if hull_before >= 0 and float(target.get("hull")) < hull_before and preload("res://scripts/planets/alien_research.gd").learned("hull_a_next_next"):
|
||||||
@ -222,12 +264,13 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
|||||||
if is_instance_valid(prior) and prior.get_rid() not in exclusions: exclusions.append(prior.get_rid())
|
if is_instance_valid(prior) and prior.get_rid() not in exclusions: exclusions.append(prior.get_rid())
|
||||||
var through := PhysicsRayQueryParameters2D.create(endpoint+direction*2.0,muzzle_position+direction*definition.weapon_range,RAY_COLLISION_MASK,exclusions)
|
var through := PhysicsRayQueryParameters2D.create(endpoint+direction*2.0,muzzle_position+direction*definition.weapon_range,RAY_COLLISION_MASK,exclusions)
|
||||||
var second := ship.get_world_2d().direct_space_state.intersect_ray(through)
|
var second := ship.get_world_2d().direct_space_state.intersect_ray(through)
|
||||||
second = preload("res://scripts/game/shield_intersections.gd").intercept(second,endpoint+direction*2.0,muzzle_position+direction*definition.weapon_range,RAY_COLLISION_MASK,exclusions,get_tree())
|
if piercing < 1.0:
|
||||||
|
second = preload("res://scripts/game/shield_intersections.gd").intercept(second,endpoint+direction*2.0,muzzle_position+direction*definition.weapon_range,RAY_COLLISION_MASK,exclusions,get_tree())
|
||||||
var second_end: Vector2 = second.get("position",muzzle_position+direction*definition.weapon_range)
|
var second_end: Vector2 = second.get("position",muzzle_position+direction*definition.weapon_range)
|
||||||
line.add_point(second_end)
|
line.add_point(second_end)
|
||||||
if not second.is_empty():
|
if not second.is_empty():
|
||||||
preload("res://scripts/game/combat_vfx.gd").mark_hit(second.get("collider"),second_end,direction)
|
preload("res://scripts/game/combat_vfx.gd").mark_hit(second.get("collider"),second_end,direction)
|
||||||
preload("res://scripts/game/alien_weapon_effects.gd").apply_hit(second.get("collider"),float(hit.damage)*0.5,bool(hit.critical),item.get_damage_type(),item.shield_piercing(),visited,false,-1,0.5)
|
preload("res://scripts/game/alien_weapon_effects.gd").apply_hit(second.get("collider"),float(hit.damage)*0.5,bool(hit.critical),item.get_damage_type(),piercing,visited,false,-1,0.5)
|
||||||
_apply_laser_radiation(second.get("collider"),item,float(hit.damage)*0.5)
|
_apply_laser_radiation(second.get("collider"),item,float(hit.damage)*0.5)
|
||||||
_spawn_laser_impact(second_end,item.get_damage_type(),direction)
|
_spawn_laser_impact(second_end,item.get_damage_type(),direction)
|
||||||
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
|
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
|
||||||
|
|||||||
@ -140,7 +140,8 @@ func _physics_process(delta: float) -> void:
|
|||||||
exclusions
|
exclusions
|
||||||
)
|
)
|
||||||
var result := get_world_2d().direct_space_state.intersect_ray(query)
|
var result := get_world_2d().direct_space_state.intersect_ray(query)
|
||||||
result = preload("res://scripts/game/shield_intersections.gd").intercept(result,previous_position,next_position,collision_mask,exclusions,get_tree())
|
if shield_piercing < 1.0:
|
||||||
|
result = preload("res://scripts/game/shield_intersections.gd").intercept(result,previous_position,next_position,collision_mask,exclusions,get_tree())
|
||||||
if not result.is_empty():
|
if not result.is_empty():
|
||||||
global_position = result["position"]
|
global_position = result["position"]
|
||||||
_hit_target(result.get("collider"))
|
_hit_target(result.get("collider"))
|
||||||
@ -199,6 +200,14 @@ func _find_enemy_in_acquisition_range() -> Node2D:
|
|||||||
return nearest_enemy
|
return nearest_enemy
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
|
if weapon_effect == "pulse_laser":
|
||||||
|
# A single compact bolt: it travels with this projectile, never spans the aim ray.
|
||||||
|
draw_line(Vector2(-27,0),Vector2(7,0),Color(1.0,0.02,0.06,0.12),13,true)
|
||||||
|
draw_line(Vector2(-24,0),Vector2(7,0),Color(1.0,0.04,0.08,0.35),7,true)
|
||||||
|
draw_line(Vector2(-22,0),Vector2(7,0),Color(1.0,0.08,0.12),3.5,true)
|
||||||
|
draw_line(Vector2(-17,0),Vector2(7,0),Color(1.0,0.7,0.7),1.1,true)
|
||||||
|
draw_circle(Vector2(7,0),2.3,Color("ffd4d7"))
|
||||||
|
return
|
||||||
if weapon_effect == "artillery":
|
if weapon_effect == "artillery":
|
||||||
draw_line(Vector2(-55,0),Vector2.ZERO,Color(1,0.4,0.08,0.2),10,true)
|
draw_line(Vector2(-55,0),Vector2.ZERO,Color(1,0.4,0.08,0.2),10,true)
|
||||||
draw_line(Vector2(-38,0),Vector2(10,0),Color(1,0.8,0.35),4,true)
|
draw_line(Vector2(-38,0),Vector2(10,0),Color(1,0.8,0.35),4,true)
|
||||||
@ -339,6 +348,7 @@ func _spawn_impact_effect(is_explosion: bool) -> void:
|
|||||||
is_explosion = true
|
is_explosion = true
|
||||||
effect.call("configure", is_explosion, effect_color)
|
effect.call("configure", is_explosion, effect_color)
|
||||||
effect.call("configure_contact",get_damage_type(),direction)
|
effect.call("configure_contact",get_damage_type(),direction)
|
||||||
|
if weapon_effect == "pulse_laser": effect.call("configure",false,Color("ff3448"))
|
||||||
|
|
||||||
func _explode_at_target(impact_target: Node) -> void:
|
func _explode_at_target(impact_target: Node) -> void:
|
||||||
if _harmless_intercepted: return
|
if _harmless_intercepted: return
|
||||||
|
|||||||
@ -17,3 +17,9 @@ const DATA := {
|
|||||||
static func icon(id: String) -> Texture2D:
|
static func icon(id: String) -> Texture2D:
|
||||||
var extension := "svg" if id == "energy_bank" else "png"
|
var extension := "svg" if id == "energy_bank" else "png"
|
||||||
return load("res://assets/icons/resources/%s.%s" % [id, extension]) as Texture2D
|
return load("res://assets/icons/resources/%s.%s" % [id, extension]) as Texture2D
|
||||||
|
|
||||||
|
static func output_amount(id: String, captain: Resource) -> int:
|
||||||
|
var amount := int(DATA[id]["amount"])
|
||||||
|
if id in ["ammunition", "irradiated_ammunition"]:
|
||||||
|
amount = roundi(amount * (1.0 + captain.get_skill_bonus("gunsmith")))
|
||||||
|
return amount
|
||||||
|
|||||||
@ -10,11 +10,14 @@ const DEFINITIONS := [
|
|||||||
preload("res://resources/items/mine_layer.tres"),
|
preload("res://resources/items/mine_layer.tres"),
|
||||||
preload("res://resources/items/flares.tres"),
|
preload("res://resources/items/flares.tres"),
|
||||||
preload("res://resources/items/solar_shield_charge.tres"),
|
preload("res://resources/items/solar_shield_charge.tres"),
|
||||||
|
preload("res://resources/items/auxiliary_shield.tres"),
|
||||||
|
|
||||||
preload("res://resources/items/gamma_cannon.tres"),
|
preload("res://resources/items/gamma_cannon.tres"),
|
||||||
preload("res://resources/items/plasma_cannon.tres"),
|
preload("res://resources/items/plasma_cannon.tres"),
|
||||||
preload("res://resources/items/beam_laser.tres"),
|
preload("res://resources/items/beam_laser.tres"),
|
||||||
|
preload("res://resources/items/pulse_laser.tres"),
|
||||||
preload("res://resources/items/machine_gun.tres"),
|
preload("res://resources/items/machine_gun.tres"),
|
||||||
|
preload("res://resources/items/external_turret.tres"),
|
||||||
preload("res://resources/items/missile_launcher.tres"),
|
preload("res://resources/items/missile_launcher.tres"),
|
||||||
preload("res://resources/items/unguided_missile.tres"),
|
preload("res://resources/items/unguided_missile.tres"),
|
||||||
preload("res://resources/items/reinforced_shield.tres"),
|
preload("res://resources/items/reinforced_shield.tres"),
|
||||||
@ -33,8 +36,8 @@ static func generate(level: int, enemy_tier: int = 0) -> ItemInstance:
|
|||||||
return generate_for_definition(available_definitions().pick_random(), level, enemy_tier)
|
return generate_for_definition(available_definitions().pick_random(), level, enemy_tier)
|
||||||
|
|
||||||
static func available_definitions() -> Array:
|
static func available_definitions() -> Array:
|
||||||
var pool := DEFINITIONS.duplicate()
|
|
||||||
var lab := preload("res://scripts/planets/laboratory.gd")
|
var lab := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
var pool := DEFINITIONS.filter(func(definition): return definition.item_id not in lab.IDS)
|
||||||
for id in lab.IDS:
|
for id in lab.IDS:
|
||||||
if GameState.captain.expedition.laboratory_unlocks.get(id,false): pool.append(lab.definition(id))
|
if GameState.captain.expedition.laboratory_unlocks.get(id,false): pool.append(lab.definition(id))
|
||||||
return pool
|
return pool
|
||||||
|
|||||||
@ -14,6 +14,17 @@ var _instance_ability: ActiveAbility
|
|||||||
func shield_piercing() -> float:
|
func shield_piercing() -> float:
|
||||||
return 0.5 if definition != null and definition.category == ItemDefinition.Category.WEAPON and "piercing" in special_orbs else 0.0
|
return 0.5 if definition != null and definition.category == ItemDefinition.Category.WEAPON and "piercing" in special_orbs else 0.0
|
||||||
|
|
||||||
|
func roll_shield_piercing(roll: float = -1.0) -> float:
|
||||||
|
if definition != null and definition.weapon_effect == "pulse_laser":
|
||||||
|
if (randf() if roll < 0.0 else roll) < 0.5: return 1.0
|
||||||
|
return shield_piercing()
|
||||||
|
|
||||||
|
func weapon_damage_scale() -> float:
|
||||||
|
if definition == null: return 1.0
|
||||||
|
if definition.weapon_effect == "external_turret": return 0.5
|
||||||
|
if definition.weapon_effect == "pulse_laser": return 0.7
|
||||||
|
return 1.0
|
||||||
|
|
||||||
func get_damage_type() -> int:
|
func get_damage_type() -> int:
|
||||||
if "electricity" in special_orbs: return DamageTypes.Type.ENERGY
|
if "electricity" in special_orbs: return DamageTypes.Type.ENERGY
|
||||||
if "radiation" in special_orbs: return DamageTypes.Type.RADIATION
|
if "radiation" in special_orbs: return DamageTypes.Type.RADIATION
|
||||||
@ -28,6 +39,9 @@ func get_fire_interval() -> float:
|
|||||||
return maxf(1.0, maxf(1.0, 5.0 - get_stat_total("missile_cooldown_reduction")) / attack_speed_multiplier())
|
return maxf(1.0, maxf(1.0, 5.0 - get_stat_total("missile_cooldown_reduction")) / attack_speed_multiplier())
|
||||||
return maxf(definition.fire_interval, definition.weapon_cooldown) / attack_speed_multiplier()
|
return maxf(definition.fire_interval, definition.weapon_cooldown) / attack_speed_multiplier()
|
||||||
|
|
||||||
|
func get_turret_fire_interval() -> float:
|
||||||
|
return definition.fire_interval / attack_speed_multiplier()
|
||||||
|
|
||||||
func is_passive_extension() -> bool:
|
func is_passive_extension() -> bool:
|
||||||
return definition != null and definition.category == ItemDefinition.Category.ACTIVE_EXTENSION and not definition.passive_effect.is_empty()
|
return definition != null and definition.category == ItemDefinition.Category.ACTIVE_EXTENSION and not definition.passive_effect.is_empty()
|
||||||
|
|
||||||
@ -115,10 +129,14 @@ func get_damage_range() -> Vector2:
|
|||||||
for modifier in additional_modifiers:
|
for modifier in additional_modifiers:
|
||||||
if modifier and modifier.stat_key == "damage":
|
if modifier and modifier.stat_key == "damage":
|
||||||
flat_bonus += modifier.value
|
flat_bonus += modifier.value
|
||||||
return Vector2(maxf(0.0, minf(lower, upper) + flat_bonus), maxf(0.0, maxf(lower, upper) + flat_bonus))
|
var result := Vector2(maxf(0.0, minf(lower, upper) + flat_bonus), maxf(0.0, maxf(lower, upper) + flat_bonus))
|
||||||
|
return result * weapon_damage_scale()
|
||||||
|
|
||||||
func roll_damage() -> float:
|
func roll_damage() -> float:
|
||||||
var damage_range := get_damage_range()
|
var damage_range := get_damage_range()
|
||||||
|
var damage_scale := weapon_damage_scale()
|
||||||
|
if damage_scale != 1.0:
|
||||||
|
return damage_range.x + randi_range(0, roundi((damage_range.y-damage_range.x)/damage_scale)) * damage_scale
|
||||||
# Inclusive integer rolls, preserving fractional flat bonuses on both bounds.
|
# Inclusive integer rolls, preserving fractional flat bonuses on both bounds.
|
||||||
var whole_span := floori(damage_range.y - damage_range.x)
|
var whole_span := floori(damage_range.y - damage_range.x)
|
||||||
return damage_range.x + randi_range(0, whole_span)
|
return damage_range.x + randi_range(0, whole_span)
|
||||||
@ -132,10 +150,14 @@ func get_energy_cost() -> float:
|
|||||||
var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5)
|
var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5)
|
||||||
var base_spread := roundf((definition.damage_spread_roll_min + definition.damage_spread_roll_max) * 0.5)
|
var base_spread := roundf((definition.damage_spread_roll_min + definition.damage_spread_roll_max) * 0.5)
|
||||||
var base_average := maxf(1.0, base_lower + base_spread * 0.5)
|
var base_average := maxf(1.0, base_lower + base_spread * 0.5)
|
||||||
|
base_average *= weapon_damage_scale()
|
||||||
var damage_range := get_damage_range()
|
var damage_range := get_damage_range()
|
||||||
var average := maxf(0.1, (damage_range.x + damage_range.y) * 0.5)
|
var average := maxf(0.1, (damage_range.x + damage_range.y) * 0.5)
|
||||||
var damage_factor := pow(average / base_average, definition.energy_damage_exponent)
|
var damage_factor := pow(average / base_average, definition.energy_damage_exponent)
|
||||||
var addon_factor := 1.0 + get_addon_count() * definition.energy_per_addon
|
var addon_factor := 1.0 + get_addon_count() * definition.energy_per_addon
|
||||||
|
if definition.weapon_effect == "pulse_laser":
|
||||||
|
# Apply the surcharge after the ordinary laser's rounding, preserving +30%.
|
||||||
|
return snappedf(maxf(0.0,definition.energy_cost/1.3*damage_factor*addon_factor),0.1)*1.3
|
||||||
return snappedf(maxf(0.0, definition.energy_cost * damage_factor * addon_factor), 0.1)
|
return snappedf(maxf(0.0, definition.energy_cost * damage_factor * addon_factor), 0.1)
|
||||||
|
|
||||||
func get_damage_range_text() -> String:
|
func get_damage_range_text() -> String:
|
||||||
@ -191,8 +213,12 @@ func get_tooltip_text() -> String:
|
|||||||
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))
|
||||||
if definition.category == ItemDefinition.Category.WEAPON:
|
if definition.category == ItemDefinition.Category.WEAPON:
|
||||||
lines.append("Typ obrażeń: %s" % DamageTypes.NAMES[get_damage_type()])
|
lines.append("Typ obrażeń: %s" % DamageTypes.NAMES[get_damage_type()])
|
||||||
|
if definition.weapon_effect == "pulse_laser": lines.append("Prędkość lecącej wiązki: %.0f px/s" % definition.projectile_speed)
|
||||||
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
|
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
|
||||||
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
if definition.weapon_effect == "external_turret":
|
||||||
|
lines.append("Zasięg działka: %.0f px • Magazynek: 50 pocisków • Koszt rozstawienia: 50 amunicji" % definition.weapon_range)
|
||||||
|
lines.append("Odstęp strzałów działka: %.2f s • Kolejne rozstawienie: %.2f s" % [get_turret_fire_interval(),get_fire_interval()])
|
||||||
|
elif definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
||||||
lines.append("Zasięg: %.0f px • Amunicja: %d / strzał" % [definition.weapon_range,definition.ammunition_cost])
|
lines.append("Zasięg: %.0f px • Amunicja: %d / strzał" % [definition.weapon_range,definition.ammunition_cost])
|
||||||
elif definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
elif definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
lines.append("Zasięg: %.0f px • Napromieniowana amunicja: 1 / strzał" % definition.weapon_range)
|
lines.append("Zasięg: %.0f px • Napromieniowana amunicja: 1 / strzał" % definition.weapon_range)
|
||||||
@ -200,7 +226,7 @@ func get_tooltip_text() -> String:
|
|||||||
lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii")
|
lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii")
|
||||||
else:
|
else:
|
||||||
lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()])
|
lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()])
|
||||||
lines.append("Odstęp strzałów: %.2f s" % get_fire_interval())
|
if definition.weapon_effect != "external_turret": lines.append("Odstęp strzałów: %.2f s" % get_fire_interval())
|
||||||
if not additional_modifiers.is_empty():
|
if not additional_modifiers.is_empty():
|
||||||
lines.append("\nADDONY (aktywne po zamontowaniu)")
|
lines.append("\nADDONY (aktywne po zamontowaniu)")
|
||||||
for modifier in additional_modifiers:
|
for modifier in additional_modifiers:
|
||||||
|
|||||||
@ -136,7 +136,7 @@ func craft(recipe_id: String) -> bool:
|
|||||||
var recipe: Dictionary = RECIPES.DATA[recipe_id]
|
var recipe: Dictionary = RECIPES.DATA[recipe_id]
|
||||||
for mineral in recipe["cost"]:
|
for mineral in recipe["cost"]:
|
||||||
GameState.captain.minerals[mineral] = int(GameState.captain.minerals.get(mineral, 0)) - int(recipe["cost"][mineral])
|
GameState.captain.minerals[mineral] = int(GameState.captain.minerals.get(mineral, 0)) - int(recipe["cost"][mineral])
|
||||||
var amount := int(recipe["amount"])
|
var amount := RECIPES.output_amount(recipe_id, GameState.captain)
|
||||||
match recipe_id:
|
match recipe_id:
|
||||||
"parts": GameState.captain.parts += amount
|
"parts": GameState.captain.parts += amount
|
||||||
"ammunition": ammunition += amount
|
"ammunition": ammunition += amount
|
||||||
@ -400,7 +400,7 @@ func get_missile_cooldown(item: ItemInstance) -> float:
|
|||||||
for equipped in weapon_slots.slice(0,unlocked_weapon_slots) + module_slots:
|
for equipped in weapon_slots.slice(0,unlocked_weapon_slots) + module_slots:
|
||||||
if equipped != null and equipped != item:
|
if equipped != null and equipped != item:
|
||||||
reduction += equipped.get_stat_total("missile_cooldown_reduction")
|
reduction += equipped.get_stat_total("missile_cooldown_reduction")
|
||||||
return maxf(1.0, maxf(1.0, 5.0 - reduction) / item.attack_speed_multiplier())
|
return maxf(1.0, maxf(1.0, 5.0 - reduction) / item.attack_speed_multiplier() * (1.0 - GameState.captain.get_skill_bonus("missile_specialist")))
|
||||||
|
|
||||||
func consume_missile() -> bool:
|
func consume_missile() -> bool:
|
||||||
if missile_ammunition <= 0:
|
if missile_ammunition <= 0:
|
||||||
@ -577,8 +577,9 @@ func _weapon_dps(item: ItemInstance, bonuses: Dictionary) -> float:
|
|||||||
var chance := clampf(GameState.captain.get_skill_bonus("sharp_eye") + float(bonuses.get("critical_chance_percent", 0.0)) / 100.0, 0.0, 0.60)
|
var chance := clampf(GameState.captain.get_skill_bonus("sharp_eye") + float(bonuses.get("critical_chance_percent", 0.0)) / 100.0, 0.0, 0.60)
|
||||||
var critical_factor := preload("res://scripts/captain/captain_balance.gd").BASE_CRITICAL_MULTIPLIER * (1.0 + maxf(0.0, float(bonuses.get("critical_damage_percent", 0.0)) / 100.0))
|
var critical_factor := preload("res://scripts/captain/captain_balance.gd").BASE_CRITICAL_MULTIPLIER * (1.0 + maxf(0.0, float(bonuses.get("critical_damage_percent", 0.0)) / 100.0))
|
||||||
var interval := item.get_fire_interval()
|
var interval := item.get_fire_interval()
|
||||||
|
if item.definition.weapon_effect == "external_turret": interval = item.get_turret_fire_interval()
|
||||||
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||||
interval = maxf(1.0, maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0))) / item.attack_speed_multiplier())
|
interval = maxf(1.0, maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0))) / item.attack_speed_multiplier() * (1.0 - GameState.captain.get_skill_bonus("missile_specialist")))
|
||||||
var value := item.get_stat_total("damage") * damage_factor * (1.0 + chance * (critical_factor - 1.0)) / maxf(0.001, interval)
|
var value := item.get_stat_total("damage") * damage_factor * (1.0 + chance * (critical_factor - 1.0)) / maxf(0.001, interval)
|
||||||
if item.definition.weapon_effect == "radiation_laser" and item.get_damage_type() == DamageTypes.Type.RADIATION:
|
if item.definition.weapon_effect == "radiation_laser" and item.get_damage_type() == DamageTypes.Type.RADIATION:
|
||||||
value *= 1.0+minf(preload("res://scripts/game/radiation_status.gd").DURATION,interval)
|
value *= 1.0+minf(preload("res://scripts/game/radiation_status.gd").DURATION,interval)
|
||||||
|
|||||||
@ -164,6 +164,9 @@ func claim() -> bool:
|
|||||||
var amount := _rng.randi_range(40, 60)
|
var amount := _rng.randi_range(40, 60)
|
||||||
inventory.ammunition += amount
|
inventory.ammunition += amount
|
||||||
summary = "+%d amunicji" % amount
|
summary = "+%d amunicji" % amount
|
||||||
|
if kind == "wreck" and GameState.captain.get_skill_bonus("scrap_collector") > 0.0:
|
||||||
|
if GameState.captain.roll_wreck_spare_part(_rng.randf()):
|
||||||
|
summary += " · +1 część (Zbieracz złomu)"
|
||||||
inventory.ammunition_changed.emit()
|
inventory.ammunition_changed.emit()
|
||||||
GameState.captain.emit_changed()
|
GameState.captain.emit_changed()
|
||||||
if kind != "ruin":
|
if kind != "ruin":
|
||||||
|
|||||||
@ -227,6 +227,7 @@ func _enter() -> void:
|
|||||||
_hud_was_visible = game.get_node("HUD/HUDRoot").visible
|
_hud_was_visible = game.get_node("HUD/HUDRoot").visible
|
||||||
game.get_node("HUD/HUDRoot").hide()
|
game.get_node("HUD/HUDRoot").hide()
|
||||||
player.is_docked = true
|
player.is_docked = true
|
||||||
|
player.clear_auxiliary_shield()
|
||||||
player.velocity = Vector2.ZERO
|
player.velocity = Vector2.ZERO
|
||||||
get_tree().paused = true
|
get_tree().paused = true
|
||||||
_prompt.hide()
|
_prompt.hide()
|
||||||
|
|||||||
@ -518,7 +518,7 @@ func _build_supply_production() -> void:
|
|||||||
var card := MENU.card(grid)
|
var card := MENU.card(grid)
|
||||||
MENU.style(card.get_parent(),6)
|
MENU.style(card.get_parent(),6)
|
||||||
card.add_theme_constant_override("separation",4)
|
card.add_theme_constant_override("separation",4)
|
||||||
card.get_parent().tooltip_text = recipe.name+" · partia: %d szt." % recipe.amount
|
card.get_parent().tooltip_text = recipe.name+" · partia: %d szt." % RECIPES.output_amount(id,captain)
|
||||||
var row := MENU.row(card)
|
var row := MENU.row(card)
|
||||||
MENU.icon(row,RECIPES.icon(id),28)
|
MENU.icon(row,RECIPES.icon(id),28)
|
||||||
var stock := MENU.label(row,str(inventory.get_crafted_stock(id) if is_instance_valid(inventory) else 0),13,MENU.GOLD)
|
var stock := MENU.label(row,str(inventory.get_crafted_stock(id) if is_instance_valid(inventory) else 0),13,MENU.GOLD)
|
||||||
@ -532,7 +532,7 @@ func _build_supply_production() -> void:
|
|||||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
var cost: Dictionary = recipe.cost.duplicate()
|
var cost: Dictionary = recipe.cost.duplicate()
|
||||||
for key in cost: cost[key] = int(cost[key])*batches
|
for key in cost: cost[key] = int(cost[key])*batches
|
||||||
button.tooltip_text = "%s: %d partii = %d szt.\nKoszt: %s" % [recipe.name,batches,int(recipe.amount)*batches,UI.material_text(cost)]
|
button.tooltip_text = "%s: %d partii = %d szt.\nKoszt: %s" % [recipe.name,batches,RECIPES.output_amount(id,captain)*batches,UI.material_text(cost)]
|
||||||
button.disabled = not _can_craft_supply(id,batches)
|
button.disabled = not _can_craft_supply(id,batches)
|
||||||
|
|
||||||
func _can_craft_supply(id: String, batches: int) -> bool:
|
func _can_craft_supply(id: String, batches: int) -> bool:
|
||||||
|
|||||||
@ -1,7 +1,33 @@
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
const DURATION := 60.0
|
const DURATION := 60.0
|
||||||
const IDS := ["incendiary_missile", "radiation_laser", "artillery_cannon"]
|
const IDS := [
|
||||||
|
"gamma_cannon", "beam_laser", "machine_gun", "missile_launcher", "unguided_missile",
|
||||||
|
"incendiary_missile", "radiation_laser", "artillery_cannon", "external_turret", "pulse_laser",
|
||||||
|
"recycler", "virus", "titanium_armor", "combat_drones", "teleport", "cloak",
|
||||||
|
"mine_layer", "flares", "solar_shield_charge", "auxiliary_shield",
|
||||||
|
"afterburner", "shield_charge", "repair_drones",
|
||||||
|
]
|
||||||
const COSTS := {
|
const COSTS := {
|
||||||
|
"auxiliary_shield":{"uranium":45,"aluminium":40,"titanium":30,"silicon":15},
|
||||||
|
"pulse_laser":{"uranium":40,"aluminium":35,"titanium":25,"copper":15},
|
||||||
|
"external_turret":{"iron":50,"titanium":35,"aluminium":25,"sulfur":20},
|
||||||
|
"gamma_cannon":{"uranium":45,"titanium":30,"aluminium":20},
|
||||||
|
"beam_laser":{"uranium":30,"aluminium":30,"iron":15},
|
||||||
|
"machine_gun":{"iron":30,"titanium":20,"sulfur":15},
|
||||||
|
"missile_launcher":{"iron":40,"sulfur":30,"uranium":15},
|
||||||
|
"unguided_missile":{"iron":30,"sulfur":25,"aluminium":15},
|
||||||
|
"recycler":{"iron":25,"aluminium":25,"titanium":15},
|
||||||
|
"virus":{"uranium":25,"aluminium":30,"titanium":15},
|
||||||
|
"titanium_armor":{"titanium":45,"iron":30},
|
||||||
|
"combat_drones":{"iron":35,"titanium":25,"uranium":20},
|
||||||
|
"teleport":{"uranium":45,"aluminium":30,"titanium":20},
|
||||||
|
"cloak":{"uranium":35,"aluminium":30,"titanium":20},
|
||||||
|
"mine_layer":{"iron":30,"sulfur":25,"uranium":15},
|
||||||
|
"flares":{"sulfur":25,"aluminium":20,"iron":15},
|
||||||
|
"solar_shield_charge":{"uranium":25,"aluminium":35,"titanium":15},
|
||||||
|
"afterburner":{"iron":25,"sulfur":30,"aluminium":20},
|
||||||
|
"shield_charge":{"uranium":30,"aluminium":25,"titanium":20},
|
||||||
|
"repair_drones":{"iron":30,"titanium":25,"aluminium":20},
|
||||||
"incendiary_missile":{"iron":45,"sulfur":60,"uranium":30,"copper":12,"mercury":6},
|
"incendiary_missile":{"iron":45,"sulfur":60,"uranium":30,"copper":12,"mercury":6},
|
||||||
"radiation_laser":{"uranium":60,"aluminium":45,"silicon":18,"copper":12},
|
"radiation_laser":{"uranium":60,"aluminium":45,"silicon":18,"copper":12},
|
||||||
"artillery_cannon":{"iron":75,"titanium":45,"nickel":15,"tungsten":10},
|
"artillery_cannon":{"iron":75,"titanium":45,"nickel":15,"tungsten":10},
|
||||||
@ -29,11 +55,15 @@ static func start(captain: Resource, id: String) -> bool:
|
|||||||
if not can_start(captain,id): return false
|
if not can_start(captain,id): return false
|
||||||
for key in COSTS[id]: captain.minerals[key] = int(captain.minerals.get(key,0))-int(COSTS[id][key])
|
for key in COSTS[id]: captain.minerals[key] = int(captain.minerals.get(key,0))-int(COSTS[id][key])
|
||||||
captain.expedition.laboratory_project = id
|
captain.expedition.laboratory_project = id
|
||||||
captain.expedition.laboratory_remaining = DURATION
|
captain.expedition.laboratory_duration = research_duration(captain)
|
||||||
|
captain.expedition.laboratory_remaining = captain.expedition.laboratory_duration
|
||||||
captain.emit_changed()
|
captain.emit_changed()
|
||||||
captain.expedition.emit_changed()
|
captain.expedition.emit_changed()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
static func research_duration(captain: Resource) -> float:
|
||||||
|
return DURATION * (1.0 - captain.get_skill_bonus("scientist"))
|
||||||
|
|
||||||
static func advance(progress: Resource, delta: float) -> bool:
|
static func advance(progress: Resource, delta: float) -> bool:
|
||||||
if progress.laboratory_project.is_empty() or delta <= 0.0: return false
|
if progress.laboratory_project.is_empty() or delta <= 0.0: return false
|
||||||
progress.laboratory_remaining = maxf(0.0,progress.laboratory_remaining-delta)
|
progress.laboratory_remaining = maxf(0.0,progress.laboratory_remaining-delta)
|
||||||
|
|||||||
@ -12,13 +12,13 @@ func _ready() -> void:
|
|||||||
_build()
|
_build()
|
||||||
|
|
||||||
func _state() -> String:
|
func _state() -> String:
|
||||||
return str(captain.expedition.laboratory_project)+str(captain.expedition.laboratory_unlocks)
|
return str(captain.expedition.laboratory_project)+str(captain.expedition.laboratory_unlocks)+str(captain.get_skill_rank("scientist"))
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if _signature != _state(): _build()
|
if _signature != _state(): _build()
|
||||||
if is_instance_valid(_timer):
|
if is_instance_valid(_timer):
|
||||||
_timer.text = "Pozostało: %d s gry" % ceili(captain.expedition.laboratory_remaining)
|
_timer.text = "Pozostało: %d s gry" % ceili(captain.expedition.laboratory_remaining)
|
||||||
_bar.value = LAB.DURATION-captain.expedition.laboratory_remaining
|
_bar.value = captain.expedition.laboratory_duration-captain.expedition.laboratory_remaining
|
||||||
for id in _buttons:
|
for id in _buttons:
|
||||||
if is_instance_valid(_buttons[id]): _buttons[id].disabled = not LAB.can_start(captain,id)
|
if is_instance_valid(_buttons[id]): _buttons[id].disabled = not LAB.can_start(captain,id)
|
||||||
|
|
||||||
@ -30,8 +30,8 @@ func _build() -> void:
|
|||||||
_buttons.clear()
|
_buttons.clear()
|
||||||
_signature = _state()
|
_signature = _state()
|
||||||
var progress: Resource = captain.expedition
|
var progress: Resource = captain.expedition
|
||||||
MENU.label(self,"PROJEKTY NOWEGO UZBROJENIA",15,MENU.GOLD)
|
MENU.label(self,"PROJEKTY BRONI I ROZSZERZEŃ",15,MENU.GOLD)
|
||||||
MENU.label(self,"Wybierz projekt. Badanie trwa 60 sekund gry. Wynalazek odblokowuje nową broń w łupach wrogów.",12,MENU.MUTED)
|
MENU.label(self,"Wybierz projekt. Nowe badanie trwa %d sekund gry. Odkryta broń lub rozszerzenie zacznie pojawiać się w łupach. Od początku dostępne jest działko plazmowe." % roundi(LAB.research_duration(captain)),12,MENU.MUTED)
|
||||||
if not progress.laboratory_project.is_empty():
|
if not progress.laboratory_project.is_empty():
|
||||||
var item := LAB.definition(progress.laboratory_project)
|
var item := LAB.definition(progress.laboratory_project)
|
||||||
var row := MENU.row(self)
|
var row := MENU.row(self)
|
||||||
@ -39,7 +39,7 @@ func _build() -> void:
|
|||||||
MENU.label(row,item.display_name,16,MENU.GOLD).size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
MENU.label(row,item.display_name,16,MENU.GOLD).size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
_timer = MENU.label(self,"",14)
|
_timer = MENU.label(self,"",14)
|
||||||
_bar = ProgressBar.new()
|
_bar = ProgressBar.new()
|
||||||
_bar.max_value = LAB.DURATION
|
_bar.max_value = progress.laboratory_duration
|
||||||
_bar.custom_minimum_size.y = 18
|
_bar.custom_minimum_size.y = 18
|
||||||
add_child(_bar)
|
add_child(_bar)
|
||||||
else:
|
else:
|
||||||
@ -55,7 +55,7 @@ func _build() -> void:
|
|||||||
var costs: Array[String] = []
|
var costs: Array[String] = []
|
||||||
for key in LAB.COSTS[id]: costs.append("%s: %d / %d" % [names[key],int(captain.minerals.get(key,0)),int(LAB.COSTS[id][key])])
|
for key in LAB.COSTS[id]: costs.append("%s: %d / %d" % [names[key],int(captain.minerals.get(key,0)),int(LAB.COSTS[id][key])])
|
||||||
MENU.label(card,"KOSZT · posiadasz / wymagane\n"+" · ".join(costs),12,MENU.MUTED)
|
MENU.label(card,"KOSZT · posiadasz / wymagane\n"+" · ".join(costs),12,MENU.MUTED)
|
||||||
var button := MENU.button(card,"ROZPOCZNIJ BADANIE · 60 s",_start.bind(id),true)
|
var button := MENU.button(card,"ROZPOCZNIJ BADANIE · %d s" % roundi(LAB.research_duration(captain)),_start.bind(id),true)
|
||||||
button.name = "Research_"+id
|
button.name = "Research_"+id
|
||||||
button.disabled = not LAB.can_start(captain,id)
|
button.disabled = not LAB.can_start(captain,id)
|
||||||
_buttons[id] = button
|
_buttons[id] = button
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
|
|||||||
@export var laboratory_offers: Array[String] = []
|
@export var laboratory_offers: Array[String] = []
|
||||||
@export var laboratory_project: String = ""
|
@export var laboratory_project: String = ""
|
||||||
@export var laboratory_remaining: float = 0.0
|
@export var laboratory_remaining: float = 0.0
|
||||||
|
@export var laboratory_duration: float = 60.0
|
||||||
@export var freighter_rooms: Dictionary = {"expansion_3":"module_storage"}
|
@export var freighter_rooms: Dictionary = {"expansion_3":"module_storage"}
|
||||||
@export var mining_stations: Dictionary = {}
|
@export var mining_stations: Dictionary = {}
|
||||||
@export var distant_maps: Dictionary = {}
|
@export var distant_maps: Dictionary = {}
|
||||||
|
|||||||
@ -207,7 +207,8 @@ func _choice_card(skill: String) -> PanelContainer:
|
|||||||
description.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
description.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
body.add_child(description)
|
body.add_child(description)
|
||||||
if data["active"]:
|
if data["active"]:
|
||||||
body.add_child(_label("DZIAŁANIE %d s · ODNOWIENIE 30 s" % int(CATALOG.duration(skill)), 11, Color("91aabd")))
|
var timing := "NATYCHMIAST" if CATALOG.duration(skill) <= 0.0 else "DZIAŁANIE %d s" % int(CATALOG.duration(skill))
|
||||||
|
body.add_child(_label("%s · ODNOWIENIE %d s" % [timing, int(CATALOG.cooldown(skill))], 11, Color("91aabd")))
|
||||||
else:
|
else:
|
||||||
body.add_child(_label("STAŁY EFEKT", 11, Color("91aabd")))
|
body.add_child(_label("STAŁY EFEKT", 11, Color("91aabd")))
|
||||||
var select := Button.new()
|
var select := Button.new()
|
||||||
@ -294,8 +295,8 @@ func _process(_delta: float) -> void:
|
|||||||
var cooldown := float(_captain.skill_cooldowns.get(skill, 0.0))
|
var cooldown := float(_captain.skill_cooldowns.get(skill, 0.0))
|
||||||
var reason := _captain.skill_unavailable_reason(skill, player)
|
var reason := _captain.skill_unavailable_reason(skill, player)
|
||||||
button.disabled = get_tree().paused or not reason.is_empty()
|
button.disabled = get_tree().paused or not reason.is_empty()
|
||||||
button.call("set_visual_state", cooldown / CATALOG.COOLDOWN, duration > 0.0, button.disabled)
|
button.call("set_visual_state", cooldown / CATALOG.cooldown(skill), duration > 0.0, button.disabled)
|
||||||
button.tooltip_text = "%s · poziom %d\n%s\n[%s] · Odnowienie 30 s%s" % [CATALOG.DATA[skill]["name"], _captain.get_skill_rank(skill), CATALOG.description(skill, _captain.get_skill_rank(skill)), entry["binding"], "\n" + reason if not reason.is_empty() else ""]
|
button.tooltip_text = "%s · poziom %d\n%s\n[%s] · Odnowienie %d s%s" % [CATALOG.DATA[skill]["name"], _captain.get_skill_rank(skill), CATALOG.description(skill, _captain.get_skill_rank(skill)), entry["binding"], int(CATALOG.cooldown(skill)), "\n" + reason if not reason.is_empty() else ""]
|
||||||
status.text = "%.1f" % duration if duration > 0.0 else (str(ceili(cooldown)) if cooldown > 0.0 else "")
|
status.text = "%.1f" % duration if duration > 0.0 else (str(ceili(cooldown)) if cooldown > 0.0 else "")
|
||||||
status.modulate = Color("a9ef83") if duration > 0.0 else Color("dae8ef")
|
status.modulate = Color("a9ef83") if duration > 0.0 else Color("dae8ef")
|
||||||
_bar.visible = not bool(player.get("_is_destroyed")) and not _screen.visible
|
_bar.visible = not bool(player.get("_is_destroyed")) and not _screen.visible
|
||||||
|
|||||||
@ -6,6 +6,7 @@ var inventory: ShipInventory
|
|||||||
var _stock: Label
|
var _stock: Label
|
||||||
var _availability: Label
|
var _availability: Label
|
||||||
var _picture: TextureRect
|
var _picture: TextureRect
|
||||||
|
var _output: Label
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
custom_minimum_size = Vector2(120, 166)
|
custom_minimum_size = Vector2(120, 166)
|
||||||
@ -42,10 +43,10 @@ func _ready() -> void:
|
|||||||
_picture.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_picture.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
artwork.add_child(_picture)
|
artwork.add_child(_picture)
|
||||||
_picture.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
_picture.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
var output := _label("+%d" % RECIPES.DATA[recipe_id]["amount"], 12, Color("efc681"))
|
_output = _label("", 12, Color("efc681"))
|
||||||
output.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
_output.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
artwork.add_child(output)
|
artwork.add_child(_output)
|
||||||
output.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
_output.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
var title := _label(RECIPES.DATA[recipe_id]["name"], 11, Color("e5edf4"))
|
var title := _label(RECIPES.DATA[recipe_id]["name"], 11, Color("e5edf4"))
|
||||||
title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
title.custom_minimum_size.y = 36
|
title.custom_minimum_size.y = 36
|
||||||
@ -70,6 +71,7 @@ func refresh() -> void:
|
|||||||
if _stock == null:
|
if _stock == null:
|
||||||
return
|
return
|
||||||
var available := inventory.can_craft(recipe_id)
|
var available := inventory.can_craft(recipe_id)
|
||||||
|
_output.text = "+%d" % RECIPES.output_amount(recipe_id, GameState.captain)
|
||||||
_stock.text = "Zapas: %d" % inventory.get_crafted_stock(recipe_id)
|
_stock.text = "Zapas: %d" % inventory.get_crafted_stock(recipe_id)
|
||||||
_availability.text = "WYTWÓRZ" if available else "BRAK SUROWCÓW"
|
_availability.text = "WYTWÓRZ" if available else "BRAK SUROWCÓW"
|
||||||
_availability.add_theme_color_override("font_color", Color("85deb1") if available else Color("d08c8e"))
|
_availability.add_theme_color_override("font_color", Color("85deb1") if available else Color("d08c8e"))
|
||||||
@ -90,7 +92,7 @@ func _make_custom_tooltip(_text: String) -> Object:
|
|||||||
var contents := VBoxContainer.new()
|
var contents := VBoxContainer.new()
|
||||||
contents.add_theme_constant_override("separation", 8)
|
contents.add_theme_constant_override("separation", 8)
|
||||||
panel.add_child(contents)
|
panel.add_child(contents)
|
||||||
contents.add_child(_label("%s ×%d" % [RECIPES.DATA[recipe_id]["name"], RECIPES.DATA[recipe_id]["amount"]], 13, Color("efc681")))
|
contents.add_child(_label("%s ×%d" % [RECIPES.DATA[recipe_id]["name"], RECIPES.output_amount(recipe_id, GameState.captain)], 13, Color("efc681")))
|
||||||
contents.add_child(HSeparator.new())
|
contents.add_child(HSeparator.new())
|
||||||
for mineral in RECIPES.DATA[recipe_id]["cost"]:
|
for mineral in RECIPES.DATA[recipe_id]["cost"]:
|
||||||
var needed := int(RECIPES.DATA[recipe_id]["cost"][mineral])
|
var needed := int(RECIPES.DATA[recipe_id]["cost"][mineral])
|
||||||
|
|||||||
@ -66,7 +66,7 @@ func _heading(caption: String, color: Color, font_size: int = 11) -> Label:
|
|||||||
|
|
||||||
func _craft(id: String) -> void:
|
func _craft(id: String) -> void:
|
||||||
if _inventory.craft(id):
|
if _inventory.craft(id):
|
||||||
_status.text = "WYTWORZONO: %s ×%d" % [RECIPES.DATA[id]["name"], RECIPES.DATA[id]["amount"]]
|
_status.text = "WYTWORZONO: %s ×%d" % [RECIPES.DATA[id]["name"], RECIPES.output_amount(id, GameState.captain)]
|
||||||
_status.add_theme_color_override("font_color", Color("85deb1"))
|
_status.add_theme_color_override("font_color", Color("85deb1"))
|
||||||
else:
|
else:
|
||||||
_status.text = "BRAKUJE SUROWCÓW — NAJEDŹ NA RECEPTURĘ, ABY SPRAWDZIĆ KOSZT"
|
_status.text = "BRAKUJE SUROWCÓW — NAJEDŹ NA RECEPTURĘ, ABY SPRAWDZIĆ KOSZT"
|
||||||
|
|||||||
@ -215,9 +215,13 @@ func _weapons() -> void:
|
|||||||
UI.label(card, "Brak zamontowanej broni", 13, UI.MUTED)
|
UI.label(card, "Brak zamontowanej broni", 13, UI.MUTED)
|
||||||
continue
|
continue
|
||||||
_item_header(card,item)
|
_item_header(card,item)
|
||||||
_metric(card, "weapon_dps_%d" % index, "DPS", func(): return "%.1f" % inventory.get_weapon_dps(index), GOLD)
|
var external := item.definition.weapon_effect == "external_turret"
|
||||||
|
_metric(card, "weapon_dps_%d" % index, "DPS jednego działka" if external else "DPS", func(): return "%.1f" % inventory.get_weapon_dps(index), GOLD)
|
||||||
_metric(card, "weapon_damage_%d" % index, "Obrażenia trafienia", func(): return "%.1f – %.1f" % [item.get_damage_range().x*player.get_weapon_damage_multiplier(), item.get_damage_range().y*player.get_weapon_damage_multiplier()], GOLD, "Zakres przed krytykiem i obroną przeciwnika.")
|
_metric(card, "weapon_damage_%d" % index, "Obrażenia trafienia", func(): return "%.1f – %.1f" % [item.get_damage_range().x*player.get_weapon_damage_multiplier(), item.get_damage_range().y*player.get_weapon_damage_multiplier()], GOLD, "Zakres przed krytykiem i obroną przeciwnika.")
|
||||||
_metric(card, "weapon_interval_%d" % index, "Odstęp między strzałami", func(): return "%.2f s" % (inventory.get_missile_cooldown(item) if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE else item.get_fire_interval()))
|
_metric(card, "weapon_interval_%d" % index, "Odstęp strzałów działka" if external else "Odstęp między strzałami", func(): return "%.2f s" % (item.get_turret_fire_interval() if external else (inventory.get_missile_cooldown(item) if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE else item.get_fire_interval())))
|
||||||
|
if external:
|
||||||
|
_metric(card,"turret_deploy_%d" % index,"Odstęp rozstawiania",func(): return "%.2f s" % item.get_fire_interval())
|
||||||
|
UI.label(card,"50 amunicji → 1 działko z 50 pociskami",12,UI.MUTED)
|
||||||
_metric(card, "weapon_energy_%d" % index, "Energia na strzał", func(): return "%.1f" % player.get_weapon_energy_cost(item.get_energy_cost()), ENERGY)
|
_metric(card, "weapon_energy_%d" % index, "Energia na strzał", func(): return "%.1f" % player.get_weapon_energy_cost(item.get_energy_cost()), ENERGY)
|
||||||
_metric(card, "weapon_range_%d" % index, "Zasięg", func(): return "%.0f px" % item.definition.weapon_range)
|
_metric(card, "weapon_range_%d" % index, "Zasięg", func(): return "%.0f px" % item.definition.weapon_range)
|
||||||
UI.label(card,"Typ: " + DamageTypes.NAMES[item.get_damage_type()],12,UI.MUTED)
|
UI.label(card,"Typ: " + DamageTypes.NAMES[item.get_damage_type()],12,UI.MUTED)
|
||||||
|
|||||||
107
tests/abilities/auxiliary_shield_test.gd
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const DEFINITION := preload("res://resources/items/auxiliary_shield.tres")
|
||||||
|
const GEN := preload("res://scripts/items/item_generator.gd")
|
||||||
|
const LAB := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
assert(DEFINITION not in GEN.available_definitions())
|
||||||
|
var captain := GameState.captain
|
||||||
|
var progress: Resource = captain.expedition
|
||||||
|
progress.freighter_rooms.expansion_1 = "laboratory"
|
||||||
|
progress.laboratory_offers.assign(["auxiliary_shield"])
|
||||||
|
for key in LAB.COSTS.auxiliary_shield: captain.minerals[key] = 100
|
||||||
|
assert(LAB.start(captain,"auxiliary_shield"))
|
||||||
|
LAB.advance(progress,60)
|
||||||
|
assert(DEFINITION in GEN.available_definitions())
|
||||||
|
var item := GEN.generate_for_definition(DEFINITION,30)
|
||||||
|
assert(item.additional_modifiers.is_empty() and item.definition.icon != null)
|
||||||
|
item.extension_cooldown = 30
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate()
|
||||||
|
ship.get_node("Camera2D").enabled = false
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var systems := ship.get_node("ExtensionSystems")
|
||||||
|
systems.set_physics_process(false)
|
||||||
|
ship.get_node("ShipInventory").extension_slots[0] = item
|
||||||
|
ship.max_energy = 200.0
|
||||||
|
ship.max_shields = 120.0
|
||||||
|
ship.shields = 120.0
|
||||||
|
ship.hull = 100.0
|
||||||
|
ship.absorption = Vector3.ZERO
|
||||||
|
ship.energy = 139.99
|
||||||
|
assert(not ship.activate_ability(0))
|
||||||
|
assert(ship.auxiliary_shields == 0.0 and not systems.cooldowns.has("auxiliary_shield"))
|
||||||
|
ship.energy = 140.0
|
||||||
|
assert(ship.activate_ability(0))
|
||||||
|
assert(ship.energy == 0.0 and ship.auxiliary_shields == 60.0 and ship.auxiliary_max_shields == 60.0)
|
||||||
|
assert(ship.shields == 120.0 and ship.hull == 100.0)
|
||||||
|
var outer: Node2D = ship.get_node("AuxiliaryShieldVisual")
|
||||||
|
assert(is_equal_approx(outer.shield_radius,ship.get_node("ShieldVisual").shield_radius*1.45))
|
||||||
|
assert(ship.get_active_shield_visual() == outer)
|
||||||
|
ship.take_damage(25.0)
|
||||||
|
assert(ship.auxiliary_shields == 35.0 and ship.shields == 120.0 and ship.hull == 100.0)
|
||||||
|
ship.take_damage(50.0)
|
||||||
|
assert(ship.auxiliary_shields == 0.0 and ship.shields == 105.0 and ship.hull == 100.0)
|
||||||
|
ship.take_damage(200.0)
|
||||||
|
assert(ship.shields == 0.0 and ship.hull == 5.0)
|
||||||
|
ship.energy = 200.0
|
||||||
|
assert(not ship.activate_ability(0),"Normal extension cooldown applies")
|
||||||
|
systems._physics_process(30.0)
|
||||||
|
assert(ship.activate_ability(0),"Works with empty ordinary shields")
|
||||||
|
assert(ship.energy == 60.0 and ship.auxiliary_shields == 60.0)
|
||||||
|
systems._physics_process(30.0)
|
||||||
|
ship.energy = 200.0
|
||||||
|
assert(not ship.activate_ability(0) and ship.energy == 200.0,"Cannot stack or refill an active outer layer")
|
||||||
|
ship.max_shields = 200.0
|
||||||
|
ship._update_shield_visual()
|
||||||
|
assert(ship.auxiliary_shields == 60.0 and ship.auxiliary_max_shields == 60.0,"Capacity is captured at activation")
|
||||||
|
ship.clear_auxiliary_shield()
|
||||||
|
assert(ship.activate_ability(0) and ship.auxiliary_shields == 100.0)
|
||||||
|
await get_tree().physics_frame
|
||||||
|
await get_tree().physics_frame
|
||||||
|
var bolt := preload("res://scenes/game/weapon_projectile.tscn").instantiate()
|
||||||
|
bolt.enemy_shot = true
|
||||||
|
bolt.collision_mask = 2
|
||||||
|
bolt.configure(Vector2(-150,0),Vector2.RIGHT,ItemDefinition.WeaponKind.PLASMA,1200.0,20.0,1000.0)
|
||||||
|
add_child(bolt)
|
||||||
|
bolt.set_physics_process(false)
|
||||||
|
bolt._physics_process(0.25)
|
||||||
|
assert(bolt.is_queued_for_deletion())
|
||||||
|
assert(is_equal_approx(bolt.position.length(),outer.shield_radius),"Fast bullets stop on the larger perimeter")
|
||||||
|
assert(ship.auxiliary_shields == 80.0 and ship.shields == 0.0 and ship.hull == 5.0)
|
||||||
|
var hit: Dictionary = preload("res://scripts/game/shield_intersections.gd").intercept({},Vector2(-200,0),Vector2(200,0),2,[],get_tree())
|
||||||
|
assert(hit.collider == ship and is_equal_approx(hit.position.length(),outer.shield_radius),"Lasers hit the outer shield even with normal shields empty")
|
||||||
|
systems.reset_combat_state()
|
||||||
|
assert(ship.auxiliary_shields == 0.0)
|
||||||
|
ship.energy = 200.0
|
||||||
|
assert(ship.activate_ability(0))
|
||||||
|
captain.skills.power_redirect = 1
|
||||||
|
captain.equip_skill(0,"power_redirect")
|
||||||
|
ship.shields = ship.max_shields
|
||||||
|
assert(captain.activate_skill("power_redirect",ship))
|
||||||
|
assert(ship.auxiliary_shields == 0.0)
|
||||||
|
systems.cooldowns.clear()
|
||||||
|
ship.energy = 200.0
|
||||||
|
assert(not ship.activate_ability(0))
|
||||||
|
captain.reset_combat_state()
|
||||||
|
ship.is_docked = true
|
||||||
|
assert(not ship.activate_ability(0))
|
||||||
|
ship.is_docked = false
|
||||||
|
assert(ship.activate_ability(0))
|
||||||
|
ship.is_docked = true
|
||||||
|
ship._physics_process(0.1)
|
||||||
|
assert(ship.auxiliary_shields == 0.0)
|
||||||
|
# Normal-shield emergency technologies must not trigger for absorbed hits.
|
||||||
|
ship.is_docked = false
|
||||||
|
ship.energy = 200.0
|
||||||
|
ship.shields = ship.max_shields
|
||||||
|
systems.cooldowns.clear()
|
||||||
|
assert(ship.activate_ability(0))
|
||||||
|
progress.alien_skills.shields_b_next_next = true
|
||||||
|
ship.take_damage(5.0)
|
||||||
|
assert(ship._salvo_cooldown == 0.0 and ship.auxiliary_shields == 95.0)
|
||||||
|
print("AUXILIARY_SHIELD_TEST: PASS (lab unlock, 70% max energy cost, 50% max shield snapshot, no stacking, outer-first damage and overflow, larger bullet/laser collision with empty main shields, cooldown, reset, power redirect and docking)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/abilities/auxiliary_shield_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://boarpjs60rq7x
|
||||||
4
tests/abilities/auxiliary_shield_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/abilities/auxiliary_shield_test.gd" id="1"]
|
||||||
|
[node name="AuxiliaryShieldTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
95
tests/captain/new_skills_test.gd
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const SKILLS := preload("res://scripts/captain/captain_skills.gd")
|
||||||
|
const LAB := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
const RECIPES := preload("res://scripts/items/crafting_recipes.gd")
|
||||||
|
const NEW_SKILLS := ["scientist", "gunsmith", "bounty_hunter", "missile_specialist", "emergency_procedure"]
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
var captain := GameState.captain
|
||||||
|
var ship := RepairShip.new()
|
||||||
|
# A real inventory without starting the flight simulation.
|
||||||
|
var inventory := ShipInventory.new()
|
||||||
|
inventory.weapon_slots.resize(3)
|
||||||
|
inventory.module_slots.resize(5)
|
||||||
|
var missile := ItemInstance.new()
|
||||||
|
missile.definition = preload("res://resources/items/missile_launcher.tres")
|
||||||
|
var baseline_money := captain.reward_enemy(1,0,1.0,1.0,false).y
|
||||||
|
assert(SKILLS.IDS.size() == 16)
|
||||||
|
for id in NEW_SKILLS:
|
||||||
|
assert(SKILLS.icon(id) != null)
|
||||||
|
for rank in range(1,4):
|
||||||
|
assert(not SKILLS.description(id,rank).is_empty())
|
||||||
|
captain.learning_points = 1
|
||||||
|
captain.skill_offers.assign([id])
|
||||||
|
assert(captain.train_skill(id))
|
||||||
|
assert(captain.get_skill_rank(id) == 1)
|
||||||
|
captain.skills[id] = 0
|
||||||
|
for rank in range(1,4):
|
||||||
|
for id in NEW_SKILLS: captain.skills[id] = rank
|
||||||
|
var progress: Resource = captain.expedition
|
||||||
|
progress.freighter_rooms.expansion_1 = "laboratory"
|
||||||
|
progress.laboratory_project = ""
|
||||||
|
progress.laboratory_offers.assign(["machine_gun"])
|
||||||
|
progress.laboratory_unlocks.clear()
|
||||||
|
for key in LAB.COSTS.machine_gun: captain.minerals[key] = 100
|
||||||
|
assert(LAB.start(captain,"machine_gun"))
|
||||||
|
assert(is_equal_approx(progress.laboratory_remaining,[54.0,48.0,42.0][rank-1]))
|
||||||
|
assert(progress.laboratory_duration == progress.laboratory_remaining)
|
||||||
|
LAB.advance(progress,progress.laboratory_remaining-1.0)
|
||||||
|
assert(not progress.laboratory_unlocks.has("machine_gun"))
|
||||||
|
LAB.advance(progress,1.0)
|
||||||
|
assert(progress.laboratory_unlocks.has("machine_gun"))
|
||||||
|
for id in ["ammunition","irradiated_ammunition"]:
|
||||||
|
for key in RECIPES.DATA[id].cost: captain.minerals[key] = 100
|
||||||
|
var before := inventory.get_crafted_stock(id)
|
||||||
|
var iron_before: int = captain.minerals.iron
|
||||||
|
assert(inventory.craft(id))
|
||||||
|
assert(inventory.get_crafted_stock(id)-before == RECIPES.output_amount(id,captain))
|
||||||
|
assert(captain.minerals.iron == iron_before-int(RECIPES.DATA[id].cost.get("iron",0)))
|
||||||
|
assert(RECIPES.output_amount("ammunition",captain) == [11,12,13][rank-1])
|
||||||
|
assert(RECIPES.output_amount("irradiated_ammunition",captain) == [55,60,65][rank-1])
|
||||||
|
assert(RECIPES.output_amount("missile",captain) == 1)
|
||||||
|
assert(RECIPES.output_amount("parts",captain) == 1)
|
||||||
|
var credits_before := captain.credits
|
||||||
|
var reward := captain.reward_enemy(1,0,1.0,1.0,false)
|
||||||
|
assert(reward.y == roundi(baseline_money*(1.0+SKILLS.value("bounty_hunter",rank))))
|
||||||
|
assert(captain.credits == credits_before,"Enemy credits are collected from pickups")
|
||||||
|
assert(is_equal_approx(inventory.get_missile_cooldown(missile),[4.5,4.25,4.0][rank-1]))
|
||||||
|
assert(captain.equip_skill(0,"emergency_procedure"))
|
||||||
|
captain.reset_combat_state()
|
||||||
|
ship.max_hull = 200.0
|
||||||
|
ship.hull = 100.0
|
||||||
|
captain.parts = 0.5
|
||||||
|
assert(not captain.activate_skill("emergency_procedure",ship))
|
||||||
|
assert(not captain.skill_cooldowns.has("emergency_procedure"))
|
||||||
|
captain.parts = 2.0
|
||||||
|
ship.is_docked = true
|
||||||
|
assert(not captain.activate_skill("emergency_procedure",ship))
|
||||||
|
ship.is_docked = false
|
||||||
|
# Feedback requires a parent; use a minimal stub for this logic test.
|
||||||
|
var stub := RepairShip.new()
|
||||||
|
assert(captain.activate_skill("emergency_procedure",stub))
|
||||||
|
assert(is_equal_approx(stub.hull,100.0+[10.0,20.0,30.0][rank-1]))
|
||||||
|
assert(captain.parts == 1.0 and captain.skill_cooldowns.emergency_procedure == 60.0)
|
||||||
|
assert(not captain.activate_skill("emergency_procedure",stub))
|
||||||
|
captain.tick_skills(60.0)
|
||||||
|
stub.hull = 199.0
|
||||||
|
assert(captain.activate_skill("emergency_procedure",stub))
|
||||||
|
assert(stub.hull == 200.0 and captain.parts == 0.0)
|
||||||
|
captain.reset_combat_state()
|
||||||
|
captain.parts = 1.0
|
||||||
|
assert(not captain.activate_skill("emergency_procedure",stub))
|
||||||
|
assert(captain.parts == 1.0 and not captain.skill_cooldowns.has("emergency_procedure"))
|
||||||
|
stub.free()
|
||||||
|
ship.free()
|
||||||
|
inventory.free()
|
||||||
|
print("NEW_CAPTAIN_SKILLS_TEST: PASS (16 skills, icons, training, all ranks, research duration, ammo output/cost, bounty, missile cooldown, repair conditions/cost/cooldown/cap)")
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
class RepairShip extends CharacterBody2D:
|
||||||
|
var hull := 100.0
|
||||||
|
var max_hull := 200.0
|
||||||
|
var _is_destroyed := false
|
||||||
|
var is_docked := false
|
||||||
1
tests/captain/new_skills_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b0l117x36bluj
|
||||||
4
tests/captain/new_skills_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/captain/new_skills_test.gd" id="1"]
|
||||||
|
[node name="NewSkillsTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
81
tests/captain/salvage_drone_skills_test.gd
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const SKILLS := preload("res://scripts/captain/captain_skills.gd")
|
||||||
|
const POI := preload("res://scripts/levels/exploration_poi.gd")
|
||||||
|
const PROJECTILE := preload("res://scripts/game/weapon_projectile.gd")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
var captain := GameState.captain
|
||||||
|
assert(SKILLS.IDS.size() == 16)
|
||||||
|
for id in ["scrap_collector","drone_commander"]:
|
||||||
|
assert(SKILLS.icon(id) != null)
|
||||||
|
captain.learning_points = 1
|
||||||
|
captain.skill_offers.assign([id])
|
||||||
|
assert(captain.train_skill(id))
|
||||||
|
assert(not captain.equip_skill(0,id),"Both new skills are passive")
|
||||||
|
for rank in range(4):
|
||||||
|
captain.skills.scrap_collector = rank
|
||||||
|
captain.parts = 0.0
|
||||||
|
for roll in 1000:
|
||||||
|
captain.roll_wreck_spare_part((roll+0.5)/1000.0)
|
||||||
|
assert(captain.parts == [0,150,250,400][rank],"Exact probability at each rank")
|
||||||
|
var threshold: float = [0.0,0.15,0.25,0.40][rank]
|
||||||
|
var before := captain.parts
|
||||||
|
assert(not captain.roll_wreck_spare_part(threshold))
|
||||||
|
assert(captain.parts == before)
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate()
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var inventory: ShipInventory = ship.get_node("ShipInventory")
|
||||||
|
var systems: Node = ship.get_node("ExtensionSystems")
|
||||||
|
systems.set_physics_process(false)
|
||||||
|
for rank in range(4):
|
||||||
|
captain.skills.scrap_collector = rank
|
||||||
|
var successes := 0
|
||||||
|
for value in range(1,61):
|
||||||
|
var wreck := StaticBody2D.new()
|
||||||
|
wreck.set_script(POI)
|
||||||
|
wreck.kind = "wreck"
|
||||||
|
wreck.player = ship
|
||||||
|
wreck.reward_seed = value
|
||||||
|
wreck.poi_id = "salvage:%d:%d" % [rank,value]
|
||||||
|
add_child(wreck)
|
||||||
|
wreck.set_physics_process(false)
|
||||||
|
var before := captain.parts
|
||||||
|
assert(wreck.claim())
|
||||||
|
var gained := captain.parts-before
|
||||||
|
assert(gained == 0.0 or gained == 1.0)
|
||||||
|
assert(not wreck.claim(),"Cannot roll or grant parts twice")
|
||||||
|
assert(captain.parts == before+gained)
|
||||||
|
successes += int(gained)
|
||||||
|
assert(successes == 0 if rank == 0 else successes > 0 and successes < 60)
|
||||||
|
await get_tree().process_frame
|
||||||
|
var target := Node2D.new()
|
||||||
|
target.position = Vector2(300,0)
|
||||||
|
add_child(target)
|
||||||
|
var baseline_dps := inventory.get_total_dps()
|
||||||
|
for rank in range(4):
|
||||||
|
captain.skills.drone_commander = rank
|
||||||
|
inventory.ammunition = 100
|
||||||
|
systems.activate(5)
|
||||||
|
assert(inventory.ammunition == 50)
|
||||||
|
var drones := get_tree().get_nodes_in_group("player_combat_drones")
|
||||||
|
assert(drones.size() == 3)
|
||||||
|
for drone in drones:
|
||||||
|
drone.set_physics_process(false)
|
||||||
|
drone._fire(target)
|
||||||
|
var count := 0
|
||||||
|
for child in get_children():
|
||||||
|
if child.get_script() == PROJECTILE:
|
||||||
|
assert(is_equal_approx(child.damage,baseline_dps*0.5/3.0*0.25*[1.0,1.1,1.2,1.3][rank]))
|
||||||
|
count += 1
|
||||||
|
child.free()
|
||||||
|
assert(count == 1)
|
||||||
|
assert(inventory.ammunition == 50,"No extra cost per drone shot")
|
||||||
|
assert(is_equal_approx(inventory.get_total_dps(),baseline_dps),"Ship weapons do not gain drone damage")
|
||||||
|
systems.reset_combat_state()
|
||||||
|
await get_tree().process_frame
|
||||||
|
print("SALVAGE_DRONE_SKILLS_TEST: PASS (16 skills, passive training, 0/15/25/40% probabilities, 240 wrecks, no double claims, three drones, actual projectile damage at all ranks, unchanged ammo cost and ship DPS)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/captain/salvage_drone_skills_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dyiv2uihif830
|
||||||
4
tests/captain/salvage_drone_skills_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/captain/salvage_drone_skills_test.gd" id="1"]
|
||||||
|
[node name="SalvageDroneSkillsTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
119
tests/items/external_turret_test.gd
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const DEFINITION := preload("res://resources/items/external_turret.tres")
|
||||||
|
const MACHINE_GUN := preload("res://resources/items/machine_gun.tres")
|
||||||
|
const GENERATOR := preload("res://scripts/items/item_generator.gd")
|
||||||
|
const LAB := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
const PROJECTILE := preload("res://scripts/game/weapon_projectile.gd")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
assert(DEFINITION not in GENERATOR.available_definitions())
|
||||||
|
var progress: Resource = GameState.captain.expedition
|
||||||
|
progress.freighter_rooms.expansion_1 = "laboratory"
|
||||||
|
progress.laboratory_offers.assign(["external_turret"])
|
||||||
|
for key in LAB.COSTS.external_turret: GameState.captain.minerals[key] = 100
|
||||||
|
assert(LAB.start(GameState.captain,"external_turret"))
|
||||||
|
LAB.advance(progress,60.0)
|
||||||
|
assert(DEFINITION in GENERATOR.available_definitions())
|
||||||
|
for level in [1,5,30,120,1000]:
|
||||||
|
var turret_item := ItemInstance.new()
|
||||||
|
turret_item.definition = DEFINITION
|
||||||
|
var machine_item := ItemInstance.new()
|
||||||
|
machine_item.definition = MACHINE_GUN
|
||||||
|
seed(327+level)
|
||||||
|
GENERATOR.roll_base_values(turret_item,level)
|
||||||
|
seed(327+level)
|
||||||
|
GENERATOR.roll_base_values(machine_item,level)
|
||||||
|
assert(turret_item.get_damage_range().is_equal_approx(machine_item.get_damage_range()*0.5))
|
||||||
|
for value in 20:
|
||||||
|
seed(value)
|
||||||
|
var turret_damage := turret_item.roll_damage()
|
||||||
|
seed(value)
|
||||||
|
assert(is_equal_approx(turret_damage,machine_item.roll_damage()*0.5))
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate()
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
ship.get_node("ExtensionSystems").set_physics_process(false)
|
||||||
|
var controller: Node = ship.get_node("WeaponController")
|
||||||
|
var inventory: ShipInventory = ship.get_node("ShipInventory")
|
||||||
|
var item := ItemInstance.new()
|
||||||
|
item.definition = DEFINITION
|
||||||
|
item.damage_min = 8.0
|
||||||
|
item.damage_max = 12.0
|
||||||
|
inventory.weapon_slots[0] = item
|
||||||
|
GameState.captain.skills.efficient_magazine = 3
|
||||||
|
GameState.captain.skills.drone_commander = 3
|
||||||
|
inventory.ammunition = 49
|
||||||
|
assert(not controller.try_deploy_turret(0))
|
||||||
|
assert(inventory.ammunition == 49 and get_tree().get_nodes_in_group("player_external_turrets").is_empty())
|
||||||
|
inventory.ammunition = 100
|
||||||
|
assert(controller.try_deploy_turret(0))
|
||||||
|
assert(inventory.ammunition == 50)
|
||||||
|
assert(not controller.try_deploy_turret(0),"Deployment cooldown prevents repeated clicks")
|
||||||
|
var turret: Node2D = get_tree().get_nodes_in_group("player_external_turrets")[0]
|
||||||
|
turret.set_physics_process(false)
|
||||||
|
assert(turret.ammunition == 50)
|
||||||
|
var anchor := turret.global_position
|
||||||
|
ship.position += Vector2(1000,1000)
|
||||||
|
turret._physics_process(0.1)
|
||||||
|
assert(turret.global_position == anchor and turret.ammunition == 50,"Stays in space and holds ammo without targets")
|
||||||
|
ship.position = Vector2.ZERO
|
||||||
|
var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate()
|
||||||
|
enemy.position = anchor+Vector2(250,0)
|
||||||
|
add_child(enemy)
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
enemy.hull = 10000.0
|
||||||
|
enemy.max_hull = 10000.0
|
||||||
|
enemy.shields = 0.0
|
||||||
|
enemy.absorption = Vector3.ZERO
|
||||||
|
enemy._player = ship
|
||||||
|
await get_tree().physics_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
assert(turret._find_target() == enemy)
|
||||||
|
var base_ammo := inventory.ammunition
|
||||||
|
# The turret retains its own item even if the weapon is modified or unequipped.
|
||||||
|
item.damage_min = 100.0
|
||||||
|
item.damage_max = 100.0
|
||||||
|
inventory.weapon_slots[0] = null
|
||||||
|
var shots := 0
|
||||||
|
for index in 50:
|
||||||
|
turret._fire(enemy)
|
||||||
|
for child in get_children():
|
||||||
|
if child.get_script() == PROJECTILE:
|
||||||
|
shots += 1
|
||||||
|
assert(child.damage >= 4.0 and child.damage <= 6.0)
|
||||||
|
assert(child.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN)
|
||||||
|
assert(child.source_ship == ship)
|
||||||
|
if index == 0:
|
||||||
|
var hull_before: float = enemy.hull
|
||||||
|
child._hit_target(enemy)
|
||||||
|
assert(enemy.hull < hull_before,"Turret bullets really damage enemies")
|
||||||
|
child.free()
|
||||||
|
assert(shots == 50 and turret.ammunition == 0)
|
||||||
|
assert(turret.is_queued_for_deletion())
|
||||||
|
assert(inventory.ammunition == base_ammo,"Only prepaid ammunition is used")
|
||||||
|
turret._fire(enemy)
|
||||||
|
assert(turret.ammunition == 0,"Never shoots a 51st bullet")
|
||||||
|
await get_tree().process_frame
|
||||||
|
inventory.weapon_slots[0] = item
|
||||||
|
controller._shot_timers[0] = 0.0
|
||||||
|
ship.is_docked = true
|
||||||
|
assert(not controller.try_deploy_turret(0) and inventory.ammunition == base_ammo)
|
||||||
|
ship.is_docked = false
|
||||||
|
controller._fire_buttons_down[0] = true
|
||||||
|
controller._fire_just_pressed[0] = true
|
||||||
|
controller._physics_process(0.01)
|
||||||
|
assert(inventory.ammunition == 0)
|
||||||
|
var second: Node2D = get_tree().get_nodes_in_group("player_external_turrets")[0]
|
||||||
|
second.set_physics_process(false)
|
||||||
|
inventory.ammunition = 50
|
||||||
|
controller._physics_process(2.0)
|
||||||
|
assert(inventory.ammunition == 50 and get_tree().get_nodes_in_group("player_external_turrets").size() == 1,"Holding the mouse does not deploy more turrets")
|
||||||
|
ship.is_docked = true
|
||||||
|
second._physics_process(0.1)
|
||||||
|
assert(second.is_queued_for_deletion(),"Docking clears deployed turrets")
|
||||||
|
await get_tree().create_timer(0.5).timeout
|
||||||
|
print("EXTERNAL_TURRET_TEST: PASS (laboratory unlock, exact half machine-gun damage at five levels, 50 prepaid ammo despite ammo skill, placement/cooldown/docking, fixed position, target acquisition, 50 real bullets and damage, snapshot, exhaustion cleanup)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/items/external_turret_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dc154wsfpq5rp
|
||||||
4
tests/items/external_turret_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/items/external_turret_test.gd" id="1"]
|
||||||
|
[node name="ExternalTurretTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
110
tests/items/pulse_laser_test.gd
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const PULSE := preload("res://resources/items/pulse_laser.tres")
|
||||||
|
const BEAM := preload("res://resources/items/beam_laser.tres")
|
||||||
|
const GEN := preload("res://scripts/items/item_generator.gd")
|
||||||
|
const LAB := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
|
||||||
|
class StraightLaser extends "res://scripts/game/weapon_controller.gd":
|
||||||
|
func _aim_direction_from(_origin: Vector2) -> Vector2: return Vector2.RIGHT
|
||||||
|
|
||||||
|
class ForcedPulse extends ItemInstance:
|
||||||
|
var bypass := false
|
||||||
|
func roll_shield_piercing(_roll: float = -1.0) -> float:
|
||||||
|
return 1.0 if bypass else shield_piercing()
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
assert(PULSE not in GEN.available_definitions())
|
||||||
|
var progress: Resource = GameState.captain.expedition
|
||||||
|
progress.freighter_rooms.expansion_1 = "laboratory"
|
||||||
|
progress.laboratory_offers.assign(["pulse_laser"])
|
||||||
|
for key in LAB.COSTS.pulse_laser: GameState.captain.minerals[key] = 100
|
||||||
|
assert(LAB.start(GameState.captain,"pulse_laser"))
|
||||||
|
LAB.advance(progress,60)
|
||||||
|
assert(PULSE in GEN.available_definitions())
|
||||||
|
var pulse := ItemInstance.new()
|
||||||
|
pulse.definition = PULSE
|
||||||
|
var beam := ItemInstance.new()
|
||||||
|
beam.definition = BEAM
|
||||||
|
var successes := 0
|
||||||
|
for value in 1000: successes += int(pulse.roll_shield_piercing((value+0.5)/1000.0) == 1.0)
|
||||||
|
assert(successes == 500)
|
||||||
|
assert(pulse.roll_shield_piercing(0.5) == 0.0)
|
||||||
|
assert(beam.roll_shield_piercing(0.0) == 0.0)
|
||||||
|
pulse.special_orbs.assign(["piercing"])
|
||||||
|
assert(pulse.roll_shield_piercing(0.49) == 1.0 and pulse.roll_shield_piercing(0.5) == 0.5)
|
||||||
|
pulse.special_orbs.clear()
|
||||||
|
for level in [1,5,30,120,1000]:
|
||||||
|
seed(457+level)
|
||||||
|
GEN.roll_base_values(pulse,level)
|
||||||
|
seed(457+level)
|
||||||
|
GEN.roll_base_values(beam,level)
|
||||||
|
assert(pulse.get_damage_range().is_equal_approx(beam.get_damage_range()*0.7))
|
||||||
|
assert(is_equal_approx(pulse.get_energy_cost(),beam.get_energy_cost()*1.3))
|
||||||
|
for value in 20:
|
||||||
|
seed(value)
|
||||||
|
var damage := pulse.roll_damage()
|
||||||
|
seed(value)
|
||||||
|
assert(is_equal_approx(damage,beam.roll_damage()*0.7))
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate()
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
ship.get_node("ExtensionSystems").set_physics_process(false)
|
||||||
|
var controller := StraightLaser.new()
|
||||||
|
ship.add_child(controller)
|
||||||
|
controller.set_physics_process(false)
|
||||||
|
var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate()
|
||||||
|
enemy.position = Vector2(240,-28)
|
||||||
|
add_child(enemy)
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
enemy._player = ship
|
||||||
|
enemy.max_hull = 10000.0
|
||||||
|
enemy.max_shields = 1000.0
|
||||||
|
enemy.absorption = Vector3.ZERO
|
||||||
|
var item := ForcedPulse.new()
|
||||||
|
item.definition = PULSE
|
||||||
|
item.damage_min = 10.0
|
||||||
|
item.damage_max = 10.0
|
||||||
|
await get_tree().physics_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
for bypass in [false,true]:
|
||||||
|
enemy.hull = 10000.0
|
||||||
|
enemy.shields = 1000.0
|
||||||
|
item.bypass = bypass
|
||||||
|
controller._shot_timers[0] = 0.0
|
||||||
|
ship.energy = 1000.0
|
||||||
|
controller._fire_laser(0,item)
|
||||||
|
assert(is_equal_approx(ship.energy,1000.0-item.get_energy_cost()))
|
||||||
|
assert(enemy.hull == 10000.0 and enemy.shields == 1000.0,"A pulse does not hit instantly")
|
||||||
|
var bolts := get_children().filter(func(child): return child.get_script() == preload("res://scripts/game/weapon_projectile.gd"))
|
||||||
|
assert(bolts.size() == 1)
|
||||||
|
var bolt: Area2D = bolts[0]
|
||||||
|
bolt.set_physics_process(false)
|
||||||
|
assert(bolt.weapon_effect == "pulse_laser" and bolt.projectile_speed == 1500.0)
|
||||||
|
assert(bolt.shield_piercing == (1.0 if bypass else 0.0))
|
||||||
|
var origin := bolt.global_position
|
||||||
|
bolt._physics_process(0.04)
|
||||||
|
assert(is_equal_approx(bolt.global_position.distance_to(origin),60.0))
|
||||||
|
assert(enemy.hull == 10000.0 and enemy.shields == 1000.0,"Damage waits for arrival")
|
||||||
|
bolt._physics_process(0.2)
|
||||||
|
assert(is_equal_approx(enemy.hull,9993.0 if bypass else 10000.0))
|
||||||
|
assert(is_equal_approx(enemy.shields,1000.0 if bypass else 993.0))
|
||||||
|
assert(not controller._laser_lines[0].visible and controller._laser_pulse_timers[0] == 0.0)
|
||||||
|
bolt.free()
|
||||||
|
# Full bypass also stays full with the alien double-hit technology.
|
||||||
|
progress.alien_skills.weapons_a_next_next_next = true
|
||||||
|
enemy.hull = 10000.0
|
||||||
|
enemy.shields = 1000.0
|
||||||
|
controller._apply_damage(enemy,7.0,false,1.0,DamageTypes.Type.ENERGY)
|
||||||
|
assert(enemy.shields == 1000.0 and enemy.hull == 9993.0)
|
||||||
|
progress.alien_skills.clear()
|
||||||
|
controller._shot_timers[0] = 0.0
|
||||||
|
ship.energy = item.get_energy_cost()-0.01
|
||||||
|
var before: float = enemy.hull
|
||||||
|
controller._laser_lines[0].visible = false
|
||||||
|
controller._fire_laser(0,item)
|
||||||
|
assert(enemy.hull == before and not controller._laser_lines[0].visible)
|
||||||
|
print("PULSE_LASER_TEST: PASS (laboratory unlock, 50% full bypass and orb compatibility, -30% damage/+30% energy at five levels, moving 1500px/s red projectiles, no instant damage, shields/hull routing on impact, technology compatibility, insufficient energy)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/items/pulse_laser_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dtugyxs5su4lg
|
||||||
4
tests/items/pulse_laser_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/items/pulse_laser_test.gd" id="1"]
|
||||||
|
[node name="PulseLaserTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
27
tests/items/weapon_icon_transparency_test.gd
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var generator := preload("res://scripts/items/item_generator.gd")
|
||||||
|
var lab := preload("res://scripts/planets/laboratory.gd")
|
||||||
|
var definitions: Array = generator.DEFINITIONS.duplicate()
|
||||||
|
for id in lab.IDS:
|
||||||
|
var definition := lab.definition(id)
|
||||||
|
if definition not in definitions: definitions.append(definition)
|
||||||
|
var count := 0
|
||||||
|
for definition in definitions:
|
||||||
|
if definition.category != ItemDefinition.Category.WEAPON: continue
|
||||||
|
var image: Image = definition.icon.get_image()
|
||||||
|
assert(image != null and not image.is_empty())
|
||||||
|
var transparent := 0
|
||||||
|
var visible := 0
|
||||||
|
for y in range(0,image.get_height(),8):
|
||||||
|
for x in range(0,image.get_width(),8):
|
||||||
|
var color := image.get_pixel(x,y)
|
||||||
|
if color.a < 0.01: transparent += 1
|
||||||
|
if color.a > 0.5: visible += 1
|
||||||
|
assert(transparent > 100,"Weapon icon background must be transparent: "+definition.item_id)
|
||||||
|
assert(visible > 100,"Weapon artwork must remain visible: "+definition.item_id)
|
||||||
|
count += 1
|
||||||
|
assert(count == 11)
|
||||||
|
print("WEAPON_ICON_TRANSPARENCY_TEST: PASS (11 weapon icons with transparent backgrounds and visible artwork)")
|
||||||
|
get_tree().quit()
|
||||||