Zmiany
|
Before Width: | Height: | Size: 797 KiB After Width: | Height: | Size: 910 KiB |
|
Before Width: | Height: | Size: 785 KiB After Width: | Height: | Size: 888 KiB |
BIN
artifacts/alien_weapon_icons_preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
40
artifacts/alien_weapon_icons_preview.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cviddy840ck45"
|
||||
path="res://.godot/imported/alien_weapon_icons_preview.png-00f065cd4dbfd932766d0e5aac769bc5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://artifacts/alien_weapon_icons_preview.png"
|
||||
dest_files=["res://.godot/imported/alien_weapon_icons_preview.png-00f065cd4dbfd932766d0e5aac769bc5.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
|
||||
354
artifacts/ship_level_120_audit.json
Normal file
@ -0,0 +1,354 @@
|
||||
{
|
||||
"atomic_engine_cost_s": 27.4375902704401,
|
||||
"booster_cost_s": 0.0,
|
||||
"enemies": [
|
||||
{
|
||||
"dps": 96.234,
|
||||
"hull": 806.25,
|
||||
"shields": 322.5,
|
||||
"speed": 65.689,
|
||||
"tier": 0
|
||||
},
|
||||
{
|
||||
"dps": 125.1042,
|
||||
"hull": 1612.5,
|
||||
"shields": 645.0,
|
||||
"speed": 65.689,
|
||||
"tier": 1
|
||||
},
|
||||
{
|
||||
"dps": 615.8976,
|
||||
"hull": 25800.0,
|
||||
"shields": 6192.0,
|
||||
"speed": 131.378,
|
||||
"tier": 2
|
||||
}
|
||||
],
|
||||
"energy_max": {
|
||||
"afterburner_speed": 463.95,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 3098.5,
|
||||
"regen": 46.0519998474121,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 33.3333333333333,
|
||||
"shield_regen": 42.357074640564,
|
||||
"shields": 1411.9024880188,
|
||||
"single_gun_cost_s": 83.5,
|
||||
"speed": 154.65,
|
||||
"stop_from_tab_s": 10.0,
|
||||
"tab_speed": 309.3,
|
||||
"two_guns_and_tab_cost_s": 238.2624,
|
||||
"two_guns_cost_s": 167.0
|
||||
},
|
||||
"energy_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 18.0124620915739,
|
||||
"energy_start": 323.92,
|
||||
"failed_shots": 199,
|
||||
"shots": 101
|
||||
},
|
||||
"lasers": {
|
||||
"median_two_lasers_cost_s": 84.5815827446423,
|
||||
"samples": 200,
|
||||
"two_lasers_sustainable_fraction": 0.0
|
||||
},
|
||||
"movement_runtime": {
|
||||
"coast_to_stop_seconds": 9.98333333333341,
|
||||
"space_drag": 48.0,
|
||||
"tab_speed_after_20s": 480.0,
|
||||
"thrust_acceleration": 90.0
|
||||
},
|
||||
"polarization": {
|
||||
"energy_per_s_at_5_hits": 91.5558898519632,
|
||||
"generator_capacity": 249.432638822183,
|
||||
"per_hit": 18.3111779703926
|
||||
},
|
||||
"polarization_actual_5_tiny_hits": {
|
||||
"energy_gained": 5.0,
|
||||
"shield_damage": 5.0
|
||||
},
|
||||
"random_boss": {
|
||||
"afterburner_speed": {
|
||||
"max": 600.0,
|
||||
"median": 507.018091035123,
|
||||
"min": 353.137627549784,
|
||||
"p90": 600.0
|
||||
},
|
||||
"max_energy": {
|
||||
"max": 323.589558083697,
|
||||
"median": 285.500100144797,
|
||||
"min": 249.290733871268,
|
||||
"p90": 315.970795614247
|
||||
},
|
||||
"raw_dps": {
|
||||
"max": 6347.0,
|
||||
"median": 2680.688722916,
|
||||
"min": 1038.77122326506,
|
||||
"p90": 3938.72175291923
|
||||
},
|
||||
"regen": {
|
||||
"max": 35.8429503926344,
|
||||
"median": 26.876527508128,
|
||||
"min": 20.1948989159178,
|
||||
"p90": 30.9219924728325
|
||||
},
|
||||
"scanner_range": {
|
||||
"max": 4356.22367972439,
|
||||
"median": 3811.52696089991,
|
||||
"min": 3240.64599322552,
|
||||
"p90": 4233.98804543628
|
||||
},
|
||||
"shield_leech_percent": {
|
||||
"max": 10.0,
|
||||
"median": 10.0,
|
||||
"min": 0.0,
|
||||
"p90": 10.0
|
||||
},
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": {
|
||||
"max": 44.6631993163473,
|
||||
"median": 25.043783971798,
|
||||
"min": 0.0,
|
||||
"p90": 32.3302940672334
|
||||
},
|
||||
"shield_refill_s": {
|
||||
"max": 751391.899957019,
|
||||
"median": 38.4844442719922,
|
||||
"min": 33.3333333333333,
|
||||
"p90": 127.604214286799
|
||||
},
|
||||
"shield_regen": {
|
||||
"max": 22.902648193883,
|
||||
"median": 12.3089268813893,
|
||||
"min": 0.0,
|
||||
"p90": 16.8659462695747
|
||||
},
|
||||
"shields": {
|
||||
"max": 893.263986326946,
|
||||
"median": 517.16306950776,
|
||||
"min": 324.0066162357,
|
||||
"p90": 659.869313505764
|
||||
},
|
||||
"single_gun_cost_s": {
|
||||
"max": 79.7287914190814,
|
||||
"median": 60.0,
|
||||
"min": 52.4,
|
||||
"p90": 69.9263464576862
|
||||
},
|
||||
"single_gun_sustainable_fraction": 0.0,
|
||||
"speed": {
|
||||
"max": 240.0,
|
||||
"median": 169.006030345041,
|
||||
"min": 117.712542516595,
|
||||
"p90": 205.609633461282
|
||||
},
|
||||
"stop_from_tab_s": {
|
||||
"max": 10.0,
|
||||
"median": 10.0,
|
||||
"min": 10.0,
|
||||
"p90": 10.0
|
||||
},
|
||||
"tab_speed": {
|
||||
"max": 480.0,
|
||||
"median": 338.012060690082,
|
||||
"min": 235.42508503319,
|
||||
"p90": 411.219266922564
|
||||
},
|
||||
"two_guns_and_tab_cost_s": {
|
||||
"max": 219.373632560223,
|
||||
"median": 184.260575282892,
|
||||
"min": 162.597386184591,
|
||||
"p90": 199.328372912332
|
||||
},
|
||||
"two_guns_cost_s": {
|
||||
"max": 151.948700340403,
|
||||
"median": 120.8,
|
||||
"min": 104.8,
|
||||
"p90": 135.126194351132
|
||||
},
|
||||
"two_guns_sustainable_fraction": 0.0
|
||||
},
|
||||
"random_normal": {
|
||||
"afterburner_speed": {
|
||||
"max": 600.0,
|
||||
"median": 452.447683133625,
|
||||
"min": 352.000006972615,
|
||||
"p90": 551.133275529222
|
||||
},
|
||||
"max_energy": {
|
||||
"max": 323.907385644101,
|
||||
"median": 286.494693367482,
|
||||
"min": 249.292095061386,
|
||||
"p90": 316.928152668998
|
||||
},
|
||||
"raw_dps": {
|
||||
"max": 4101.02153201906,
|
||||
"median": 1564.75616821901,
|
||||
"min": 896.4,
|
||||
"p90": 2304.76932912164
|
||||
},
|
||||
"regen": {
|
||||
"max": 32.1884921461455,
|
||||
"median": 25.2923000261937,
|
||||
"min": 19.9307386902277,
|
||||
"p90": 28.4830161031905
|
||||
},
|
||||
"scanner_range": {
|
||||
"max": 4355.11419896663,
|
||||
"median": 3793.7352641508,
|
||||
"min": 3239.4014536892,
|
||||
"p90": 4226.36066470803
|
||||
},
|
||||
"shield_leech_percent": {
|
||||
"max": 10.0,
|
||||
"median": 5.0,
|
||||
"min": 0.0,
|
||||
"p90": 10.0
|
||||
},
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": {
|
||||
"max": 36.3476545005851,
|
||||
"median": 18.536993061517,
|
||||
"min": 0.0,
|
||||
"p90": 25.7666683023867
|
||||
},
|
||||
"shield_refill_s": {
|
||||
"max": 726953.090011701,
|
||||
"median": 72.1297170840986,
|
||||
"min": 33.3333333333333,
|
||||
"p90": 491588.742516387
|
||||
},
|
||||
"shield_regen": {
|
||||
"max": 19.7616361313606,
|
||||
"median": 6.1617020184605,
|
||||
"min": 0.0,
|
||||
"p90": 12.8778917074988
|
||||
},
|
||||
"shields": {
|
||||
"max": 726.953090011701,
|
||||
"median": 433.00099690197,
|
||||
"min": 324.415418261372,
|
||||
"p90": 561.527853994357
|
||||
},
|
||||
"single_gun_cost_s": {
|
||||
"max": 78.9490941963897,
|
||||
"median": 54.4,
|
||||
"min": 46.0,
|
||||
"p90": 62.4083553618865
|
||||
},
|
||||
"single_gun_sustainable_fraction": 0.0,
|
||||
"speed": {
|
||||
"max": 237.892011493927,
|
||||
"median": 150.815894377875,
|
||||
"min": 117.333335657538,
|
||||
"p90": 183.711091843074
|
||||
},
|
||||
"stop_from_tab_s": {
|
||||
"max": 10.0,
|
||||
"median": 10.0,
|
||||
"min": 10.0,
|
||||
"p90": 10.0
|
||||
},
|
||||
"tab_speed": {
|
||||
"max": 475.784022987853,
|
||||
"median": 301.63178875575,
|
||||
"min": 234.666671315076,
|
||||
"p90": 367.422183686148
|
||||
},
|
||||
"two_guns_and_tab_cost_s": {
|
||||
"max": 214.370179879324,
|
||||
"median": 172.033770480785,
|
||||
"min": 148.42563852579,
|
||||
"p90": 185.679488102582
|
||||
},
|
||||
"two_guns_cost_s": {
|
||||
"max": 144.923398364779,
|
||||
"median": 108.0,
|
||||
"min": 93.2,
|
||||
"p90": 121.727379755121
|
||||
},
|
||||
"two_guns_sustainable_fraction": 0.0
|
||||
},
|
||||
"representative": {
|
||||
"afterburner_speed": 521.607400551023,
|
||||
"max_energy": 249.432638822183,
|
||||
"raw_dps": 1844.24679253242,
|
||||
"regen": 26.5001715211498,
|
||||
"scanner_range": 3444.02684605805,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 366223.559407853,
|
||||
"shield_regen": 0.0,
|
||||
"shields": 366.223559407853,
|
||||
"single_gun_cost_s": 50.4,
|
||||
"speed": 173.869133517008,
|
||||
"stop_from_tab_s": 10.0,
|
||||
"tab_speed": 347.738267034016,
|
||||
"two_guns_and_tab_cost_s": 160.07518054088,
|
||||
"two_guns_cost_s": 105.2
|
||||
},
|
||||
"representative_energy_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 10.1961149313249,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 159,
|
||||
"shots": 81
|
||||
},
|
||||
"representative_single_gun_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 10.7961149313243,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 38,
|
||||
"shots": 82
|
||||
},
|
||||
"shield_leech_actual_from_1000_damage": 70.5951244009399,
|
||||
"shield_max": {
|
||||
"afterburner_speed": 463.95,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 3470.32,
|
||||
"regen": 27.392,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 10.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 70.5951244009399,
|
||||
"shield_refill_s": 33.3333333333333,
|
||||
"shield_regen": 42.357074640564,
|
||||
"shields": 1411.9024880188,
|
||||
"single_gun_cost_s": 66.8,
|
||||
"speed": 154.65,
|
||||
"stop_from_tab_s": 10.0,
|
||||
"tab_speed": 309.3,
|
||||
"two_guns_and_tab_cost_s": 204.8624,
|
||||
"two_guns_cost_s": 133.6
|
||||
},
|
||||
"shield_runtime": {
|
||||
"empty_to_full_seconds": 33.3499999999994,
|
||||
"leech_on_1000_actual_damage": 100.0,
|
||||
"regen_works_during_combat": true
|
||||
},
|
||||
"solar_regen": 46.4547826269245,
|
||||
"solar_two_guns_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 11.9018712528032,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 112,
|
||||
"shots": 128
|
||||
},
|
||||
"speed_max": {
|
||||
"afterburner_speed": 600.0,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 6816.7,
|
||||
"regen": 27.392,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 435880.0,
|
||||
"shield_regen": 0.0,
|
||||
"shields": 435.88,
|
||||
"single_gun_cost_s": 83.5,
|
||||
"speed": 240.0,
|
||||
"stop_from_tab_s": 10.0,
|
||||
"tab_speed": 480.0,
|
||||
"two_guns_and_tab_cost_s": 238.2624,
|
||||
"two_guns_cost_s": 167.0
|
||||
}
|
||||
}
|
||||
58
artifacts/ship_level_120_audit.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Audyt statku na poziomie 120 — przed poprawkami
|
||||
|
||||
Poniższe wyniki opisują stan przed poprawkami. Dane pierwotne: `ship_level_120_audit_before.json`. Wyniki po poprawkach: `ship_level_120_balance_changes.md` i `ship_level_120_audit.json`.
|
||||
|
||||
## Metoda i ograniczenia
|
||||
|
||||
- Szerszeń, najszybsza bazowa jednostka: 108 px/s, bez umiejętności kapitana.
|
||||
- Pełne wyposażenie poziomu 120: dwa działka plazmowe, wyrzutnia rakiet, pięć modułów. Wszystkie sloty odblokowane.
|
||||
- 500 losowych zestawów ze zwykłego dropu, 500 z dropu bossów, 200 zestawów z laserami.
|
||||
- Trzy legalne zestawy z maksymalnymi rollami nastawione na prędkość, energię lub tarcze. To granice osiągalnych parametrów, nie opis typowego łupu.
|
||||
- Testy zasobów: symulacja 30 sekund na krokach 1/60 s, z rzeczywistą funkcją kosztu strzału. Nie jest to pełny test celności, unikania pocisków ani całej kampanii. Model ponawia nieudane strzały w następnym terminie strzału.
|
||||
- Ruch sprawdzony wspólną funkcją sterowania. Odzysk tarcz oraz polaryzację sprawdzono rzeczywistymi funkcjami trafienia i leczenia.
|
||||
- Pomiar DPS oznacza teoretyczny ogień wszystkich slotów, bez absorpcji celu, ograniczeń energii i wyczerpania amunicji.
|
||||
- `shield_refill_s` w surowych danych przy braku regeneracji ma bardzo dużą wartość zastępczą; oznacza brak pasywnego ładowania.
|
||||
|
||||
## Wyniki
|
||||
|
||||
| Wskaźnik | Mediana zwykłego łupu | Mediana łupu bossów |
|
||||
|---|---:|---:|
|
||||
| Prędkość podstawowa | 151 px/s | 169 px/s |
|
||||
| Prędkość TAB | 302 px/s | 338 px/s |
|
||||
| Regeneracja energii | 62,7/s | 64,2/s |
|
||||
| Pojemność generatora | 286 | 286 |
|
||||
| Koszt jednego działka | 54,4/s | 60,0/s |
|
||||
| Koszt dwóch działek | 108,0/s | 120,8/s |
|
||||
| Pojemność tarcz | 433 | 517 |
|
||||
| Pasywna regeneracja tarcz | 6,2/s | 14,1/s |
|
||||
| Odzysk tarcz z obrażeń | 5% | 10% |
|
||||
|
||||
### Energia
|
||||
|
||||
82,8% zwykłych losowych zestawów oraz 64% zestawów bossowych podtrzymuje jedno działko plazmowe bez spadku zapasu energii. Żaden nie podtrzymuje dwóch plazm jednocześnie. Dwa lasery podtrzymuje 4% spośród 200 zestawów.
|
||||
|
||||
Konkretny zestaw: regeneracja 61,7/s, koszt jednego działka 50,4/s, dwóch 105,2/s, pojemność 249,4. Po 30 sekundach z jedną bronią nadal pełny generator. Z dwoma działkami 25,4 energii i 79 pominiętych strzałów. Blisko gwiazdy regeneracja wzrosła do 124,0/s i podtrzymała oba działka z pełnym generatorem.
|
||||
|
||||
Zwykły TAB kosztuje tylko 20/s, atomowy 10/s, orb dopalacza 0/s. Każdy z 1000 badanych reaktorów przewyższa koszt zwykłego TAB. Można zatem dopalać bez końca, jeżeli inne działania nie zużywają nadwyżki. Przyczyna strukturalna: regeneracja rośnie liniowo z poziomem, koszt energii broni tylko z pierwiastkiem obrażeń, a koszt TAB jest stały.
|
||||
|
||||
### Prędkość
|
||||
|
||||
Wyspecjalizowany zestaw osiąga 385,5 px/s, TAB 771,0 px/s, aktywny dopalacz teoretycznie 1156,5 px/s. Test sterowania osiągnął 771,0 px/s. Sam dryf po puszczeniu sterowania trwa około 48 s; aktywne hamowanie jest znacznie szybsze. Szerokość sektora wynosi 5200 px. Zwykły przeciwnik poziomu 120 porusza się około 65,7 px/s, boss 131,4 px/s.
|
||||
|
||||
### Tarcze
|
||||
|
||||
Pasywne ładowanie samo w sobie nie jest natychmiastowe. Zestaw tarczowy: 1411,9 pojemności, 74,6/s regeneracji, od zera do pełna 18,9 s. Regeneracja działa także w walce.
|
||||
|
||||
Odzysk tarcz sumuje się bez globalnego limitu: osiem addonów daje 40% faktycznie zadanych obrażeń. Wywołanie leczenia dla 1000 obrażeń odnowiło 400 tarcz. Przy mocnym DPS ten efekt wielokrotnie przewyższa pasywną regenerację i może zdominować przeżywalność. Szacowanie leczenia na podstawie samego teoretycznego DPS jest górną ilustracją; rzeczywisty wynik ograniczają zasoby, trafienia i dostępne cele.
|
||||
|
||||
### Orb polaryzacyjny
|
||||
|
||||
Pięć trafień po 1 punkcie zabrało łącznie 5 tarcz i oddało 91,6 energii. Każdy drobny kontakt aktywuje pełne 5% maksymalnych tarcz. Brak limitu częstotliwości lub powiązania z wielkością trafienia tworzy bardzo korzystną zamianę drobnych obrażeń na energię.
|
||||
|
||||
### Mnożniki ofensywne
|
||||
|
||||
Przy maksymalnych rollach sumowanie bonusów obrażeń, szansy krytycznej i obrażeń krytycznych daje około 85 tys. teoretycznego DPS dla jednego z legalnych zestawów. Sam bonus +7% bazowych parametrów nie ogranicza iloczynu tych mnożników. Zestaw nie utrzymuje pełnego ognia bez dodatkowego źródła energii.
|
||||
|
||||
## Wniosek
|
||||
|
||||
Brak błędów runtime w audycie. Są jednak anomalie balansu: nieograniczony TAB z reaktorem 120, częsty nieograniczony ogień jednej broni, nadmierne sumowanie odzysku tarcz, zamiana drobnych trafień na dużo energii przez polaryzację oraz skrajne zestawy prędkości i krytyków. Nie zmieniano parametrów rozgrywki podczas audytu.
|
||||
354
artifacts/ship_level_120_audit_before.json
Normal file
@ -0,0 +1,354 @@
|
||||
{
|
||||
"atomic_engine_cost_s": 10.0,
|
||||
"booster_cost_s": 0.0,
|
||||
"enemies": [
|
||||
{
|
||||
"dps": 38.4936,
|
||||
"hull": 806.25,
|
||||
"shields": 322.5,
|
||||
"speed": 65.689,
|
||||
"tier": 0
|
||||
},
|
||||
{
|
||||
"dps": 50.04168,
|
||||
"hull": 1612.5,
|
||||
"shields": 645.0,
|
||||
"speed": 65.689,
|
||||
"tier": 1
|
||||
},
|
||||
{
|
||||
"dps": 235.885714285714,
|
||||
"hull": 25800.0,
|
||||
"shields": 6192.0,
|
||||
"speed": 131.378,
|
||||
"tier": 2
|
||||
}
|
||||
],
|
||||
"energy_max": {
|
||||
"afterburner_speed": 463.95,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 5018.33057163238,
|
||||
"regen": 90.8359998474121,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 18.9161642492539,
|
||||
"shield_regen": 74.6399993896484,
|
||||
"shields": 1411.9024880188,
|
||||
"single_gun_cost_s": 83.5,
|
||||
"speed": 154.65,
|
||||
"stop_from_tab_s": 19.33125,
|
||||
"tab_speed": 309.3,
|
||||
"two_guns_and_tab_cost_s": 187.0,
|
||||
"two_guns_cost_s": 167.0
|
||||
},
|
||||
"energy_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 24.7860620915736,
|
||||
"energy_start": 323.92,
|
||||
"failed_shots": 119,
|
||||
"shots": 181
|
||||
},
|
||||
"lasers": {
|
||||
"median_two_lasers_cost_s": 84.5815827446423,
|
||||
"samples": 200,
|
||||
"two_lasers_sustainable_fraction": 0.04
|
||||
},
|
||||
"movement_runtime": {
|
||||
"coast_to_stop_seconds": 48.1333333333319,
|
||||
"space_drag": 16.0,
|
||||
"tab_speed_after_20s": 771.023010253906,
|
||||
"thrust_acceleration": 90.0
|
||||
},
|
||||
"polarization": {
|
||||
"energy_per_s_at_5_hits": 91.5558898519632,
|
||||
"generator_capacity": 249.432638822183,
|
||||
"per_hit": 18.3111779703926
|
||||
},
|
||||
"polarization_actual_5_tiny_hits": {
|
||||
"energy_gained": 91.5558898519632,
|
||||
"shield_damage": 5.0
|
||||
},
|
||||
"random_boss": {
|
||||
"afterburner_speed": {
|
||||
"max": 818.649817801595,
|
||||
"median": 507.018091035123,
|
||||
"min": 353.137627549784,
|
||||
"p90": 618.814400415383
|
||||
},
|
||||
"max_energy": {
|
||||
"max": 323.589558083697,
|
||||
"median": 285.500100144797,
|
||||
"min": 249.290733871268,
|
||||
"p90": 315.970795614247
|
||||
},
|
||||
"raw_dps": {
|
||||
"max": 12607.4883806826,
|
||||
"median": 3042.71482066662,
|
||||
"min": 1038.77122326506,
|
||||
"p90": 5568.48584820499
|
||||
},
|
||||
"regen": {
|
||||
"max": 79.6612432948376,
|
||||
"median": 64.233671778534,
|
||||
"min": 50.5846967477534,
|
||||
"p90": 73.5136057273531
|
||||
},
|
||||
"scanner_range": {
|
||||
"max": 4356.22367972439,
|
||||
"median": 3811.52696089991,
|
||||
"min": 3240.64599322552,
|
||||
"p90": 4233.98804543628
|
||||
},
|
||||
"shield_leech_percent": {
|
||||
"max": 30.0,
|
||||
"median": 10.0,
|
||||
"min": 0.0,
|
||||
"p90": 15.0
|
||||
},
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": {
|
||||
"max": 756.449302840955,
|
||||
"median": 109.828278207903,
|
||||
"min": 0.0,
|
||||
"p90": 261.073693922348
|
||||
},
|
||||
"shield_refill_s": {
|
||||
"max": 751391.899957019,
|
||||
"median": 38.4844442719922,
|
||||
"min": 10.1880518766895,
|
||||
"p90": 127.604214286799
|
||||
},
|
||||
"shield_regen": {
|
||||
"max": 47.0856902545033,
|
||||
"median": 14.0510470081203,
|
||||
"min": 0.0,
|
||||
"p90": 27.9299953889516
|
||||
},
|
||||
"shields": {
|
||||
"max": 893.263986326946,
|
||||
"median": 517.16306950776,
|
||||
"min": 324.0066162357,
|
||||
"p90": 659.869313505764
|
||||
},
|
||||
"single_gun_cost_s": {
|
||||
"max": 79.7287914190814,
|
||||
"median": 60.0,
|
||||
"min": 52.4,
|
||||
"p90": 69.9263464576862
|
||||
},
|
||||
"single_gun_sustainable_fraction": 0.64,
|
||||
"speed": {
|
||||
"max": 272.883272600532,
|
||||
"median": 169.006030345041,
|
||||
"min": 117.712542516595,
|
||||
"p90": 206.271466805128
|
||||
},
|
||||
"stop_from_tab_s": {
|
||||
"max": 34.1104090750664,
|
||||
"median": 21.1257537931301,
|
||||
"min": 14.7140678145743,
|
||||
"p90": 25.7839333506409
|
||||
},
|
||||
"tab_speed": {
|
||||
"max": 545.766545201063,
|
||||
"median": 338.012060690082,
|
||||
"min": 235.42508503319,
|
||||
"p90": 412.542933610255
|
||||
},
|
||||
"two_guns_and_tab_cost_s": {
|
||||
"max": 171.948700340403,
|
||||
"median": 140.8,
|
||||
"min": 124.8,
|
||||
"p90": 155.126194351132
|
||||
},
|
||||
"two_guns_cost_s": {
|
||||
"max": 151.948700340403,
|
||||
"median": 120.8,
|
||||
"min": 104.8,
|
||||
"p90": 135.126194351132
|
||||
},
|
||||
"two_guns_sustainable_fraction": 0.0
|
||||
},
|
||||
"random_normal": {
|
||||
"afterburner_speed": {
|
||||
"max": 713.67603448178,
|
||||
"median": 452.447683133625,
|
||||
"min": 352.000006972615,
|
||||
"p90": 551.133275529222
|
||||
},
|
||||
"max_energy": {
|
||||
"max": 323.907385644101,
|
||||
"median": 286.494693367482,
|
||||
"min": 249.292095061386,
|
||||
"p90": 316.928152668998
|
||||
},
|
||||
"raw_dps": {
|
||||
"max": 8919.67306740631,
|
||||
"median": 1588.30306956395,
|
||||
"min": 896.4,
|
||||
"p90": 2393.21450762625
|
||||
},
|
||||
"regen": {
|
||||
"max": 75.793565046211,
|
||||
"median": 62.6697740547914,
|
||||
"min": 49.7922160706832,
|
||||
"p90": 71.1795886787146
|
||||
},
|
||||
"scanner_range": {
|
||||
"max": 4355.11419896663,
|
||||
"median": 3793.7352641508,
|
||||
"min": 3239.4014536892,
|
||||
"p90": 4226.36066470803
|
||||
},
|
||||
"shield_leech_percent": {
|
||||
"max": 20.0,
|
||||
"median": 5.0,
|
||||
"min": 0.0,
|
||||
"p90": 10.0
|
||||
},
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": {
|
||||
"max": 178.393461348126,
|
||||
"median": 26.5760132536783,
|
||||
"min": 0.0,
|
||||
"p90": 77.6438581247026
|
||||
},
|
||||
"shield_refill_s": {
|
||||
"max": 726953.090011701,
|
||||
"median": 72.1297170840986,
|
||||
"min": 16.2584431117418,
|
||||
"p90": 491588.742516387
|
||||
},
|
||||
"shield_regen": {
|
||||
"max": 25.7070644403072,
|
||||
"median": 6.1617020184605,
|
||||
"min": 0.0,
|
||||
"p90": 14.5198328869572
|
||||
},
|
||||
"shields": {
|
||||
"max": 726.953090011701,
|
||||
"median": 433.00099690197,
|
||||
"min": 324.415418261372,
|
||||
"p90": 561.527853994357
|
||||
},
|
||||
"single_gun_cost_s": {
|
||||
"max": 78.9490941963897,
|
||||
"median": 54.4,
|
||||
"min": 46.0,
|
||||
"p90": 62.4083553618865
|
||||
},
|
||||
"single_gun_sustainable_fraction": 0.828,
|
||||
"speed": {
|
||||
"max": 237.892011493927,
|
||||
"median": 150.815894377875,
|
||||
"min": 117.333335657538,
|
||||
"p90": 183.711091843074
|
||||
},
|
||||
"stop_from_tab_s": {
|
||||
"max": 29.7365014367408,
|
||||
"median": 18.8519867972344,
|
||||
"min": 14.6666669571923,
|
||||
"p90": 22.9638864803843
|
||||
},
|
||||
"tab_speed": {
|
||||
"max": 475.784022987853,
|
||||
"median": 301.63178875575,
|
||||
"min": 234.666671315076,
|
||||
"p90": 367.422183686148
|
||||
},
|
||||
"two_guns_and_tab_cost_s": {
|
||||
"max": 164.923398364779,
|
||||
"median": 128.0,
|
||||
"min": 113.2,
|
||||
"p90": 141.727379755121
|
||||
},
|
||||
"two_guns_cost_s": {
|
||||
"max": 144.923398364779,
|
||||
"median": 108.0,
|
||||
"min": 93.2,
|
||||
"p90": 121.727379755121
|
||||
},
|
||||
"two_guns_sustainable_fraction": 0.0
|
||||
},
|
||||
"representative": {
|
||||
"afterburner_speed": 521.607400551023,
|
||||
"max_energy": 249.432638822183,
|
||||
"raw_dps": 1844.24679253242,
|
||||
"regen": 61.6884683811878,
|
||||
"scanner_range": 3444.02684605805,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 366223.559407853,
|
||||
"shield_regen": 0.0,
|
||||
"shields": 366.223559407853,
|
||||
"single_gun_cost_s": 50.4,
|
||||
"speed": 173.869133517008,
|
||||
"stop_from_tab_s": 21.733641689626,
|
||||
"tab_speed": 347.738267034016,
|
||||
"two_guns_and_tab_cost_s": 125.2,
|
||||
"two_guns_cost_s": 105.2
|
||||
},
|
||||
"representative_energy_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 25.358549118128,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 79,
|
||||
"shots": 161
|
||||
},
|
||||
"representative_single_gun_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 249.432638822183,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 0,
|
||||
"shots": 120
|
||||
},
|
||||
"shield_leech_actual_from_1000_damage": 400.0,
|
||||
"shield_max": {
|
||||
"afterburner_speed": 463.95,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 8029.32891461182,
|
||||
"regen": 72.176,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 40.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 1284.69262633789,
|
||||
"shield_refill_s": 18.9161642492539,
|
||||
"shield_regen": 74.6399993896484,
|
||||
"shields": 1411.9024880188,
|
||||
"single_gun_cost_s": 66.8,
|
||||
"speed": 154.65,
|
||||
"stop_from_tab_s": 19.33125,
|
||||
"tab_speed": 309.3,
|
||||
"two_guns_and_tab_cost_s": 153.6,
|
||||
"two_guns_cost_s": 133.6
|
||||
},
|
||||
"shield_runtime": {
|
||||
"empty_to_full_seconds": 18.9166666666669,
|
||||
"leech_on_1000_actual_damage": 400.0,
|
||||
"regen_works_during_combat": true
|
||||
},
|
||||
"solar_regen": 124.046628086734,
|
||||
"solar_two_guns_runtime": {
|
||||
"duration": 30.0,
|
||||
"energy_end": 249.432638822183,
|
||||
"energy_start": 249.432638822183,
|
||||
"failed_shots": 0,
|
||||
"shots": 240
|
||||
},
|
||||
"speed_max": {
|
||||
"afterburner_speed": 1156.53455433655,
|
||||
"max_energy": 323.92,
|
||||
"raw_dps": 84950.3023669497,
|
||||
"regen": 72.176,
|
||||
"scanner_range": 4358.8,
|
||||
"shield_leech_percent": 0.0,
|
||||
"shield_leech_s_at_50pct_accuracy_20pct_absorption": 0.0,
|
||||
"shield_refill_s": 435880.0,
|
||||
"shield_regen": 0.0,
|
||||
"shields": 435.88,
|
||||
"single_gun_cost_s": 83.5,
|
||||
"speed": 385.511518112183,
|
||||
"stop_from_tab_s": 48.1889397640228,
|
||||
"tab_speed": 771.023036224365,
|
||||
"two_guns_and_tab_cost_s": 187.0,
|
||||
"two_guns_cost_s": 167.0
|
||||
}
|
||||
}
|
||||
51
artifacts/ship_level_120_balance_changes.md
Normal file
@ -0,0 +1,51 @@
|
||||
# Balans statku — poprawki po audycie poziomu 120
|
||||
|
||||
Zmiany nie zmieniają wzrostu bazowych parametrów przedmiotów +7% ani wzrostu HP, tarcz i ataku przeciwników +10% / +20% po poziomie 120. Ograniczają łączne efekty wyposażenia. Istniejące przedmioty również podlegają limitom po przeliczeniu statystyk.
|
||||
|
||||
## Energia
|
||||
|
||||
- Nowe reaktory mają bazowy bonus regeneracji 4 zamiast 12 przed dotychczasowym mnożnikiem 0,5. Pozostaje skalowanie +7% na poziom.
|
||||
- Łączna regeneracja bazowa i ze sprzętu: do 15% pojemności generatora/s, z minimum limitu 5/s. Dodatkowe addony regeneracji pozostają użyteczne.
|
||||
- Pełne ładowanie słoneczne: dodatkowo do 8% generatora/s zamiast 25%. Maleje z odległością od gwiazdy.
|
||||
- TAB: większa z wartości 20 energii/s i 22% pojemności generatora/s. Podstawowa regeneracja nie podtrzyma go sama.
|
||||
- Orb silników atomowych nadal zmniejsza koszt TAB o połowę. Orb dopalacza zachowuje darmowy TAB.
|
||||
- Czasowe umiejętności kapitana nadal mogą podnieść regenerację ponad limit sprzętu. To ograniczona czasowo korzyść.
|
||||
|
||||
## Ruch
|
||||
|
||||
- Łączna premia prędkości ze sprzętu: do +60%.
|
||||
- Zwykły lot: do 240 px/s; TAB: do 480 px/s; aktywny dopalacz: do 600 px/s.
|
||||
- Opór ruchu rośnie wraz z prędkością maksymalną: `max(16, max_speed * 0.2)`. Dryf z pełnego TAB zanika w około 10 s, zamiast wydłużać się do kilkudziesięciu sekund. Aktywne hamowanie jest szybsze.
|
||||
|
||||
## Tarcze i polaryzacja
|
||||
|
||||
- Pasywna regeneracja: do 3% maksymalnych tarcz/s.
|
||||
- Odzysk z obrażeń: do 10% faktycznie zadanych obrażeń ze wszystkich addonów łącznie.
|
||||
- Odzysk tarcz ma odnawianą rezerwę wielkości 5% maksymalnych tarcz. Rezerwa odnawia się w tempie 5% maksymalnych tarcz/s. Seria trafień i obrażenia obszarowe nie obchodzą tego limitu; dostępna na początku pełna rezerwa pozwala na jeden większy skok leczenia.
|
||||
- Polaryzacja: energia z trafienia nie przekracza mniejszej wartości z 5% maksymalnych tarcz i faktycznej straty tarcz. Ma osobną rezerwę 5% pojemności generatora, odnawianą w tym samym tempie na sekundę.
|
||||
- Uniki, trafienia w sam kadłub, dokowanie i zniszczenie nie uruchamiają polaryzacji. Leczenie nie działa po zniszczeniu ani podczas przekierowania zasilania.
|
||||
- Pełny generator lub pełne tarcze nie zużywają rezerw. Przekładanie modułów nie odnawia rezerw.
|
||||
|
||||
## Obrażenia i krytyki
|
||||
|
||||
- Premia obrażeń ze sprzętu: do +100%.
|
||||
- Szansa krytyczna ze sprzętu: do 40%; łącznie z kapitanem do 60%.
|
||||
- Premia mnożnika krytycznego ze sprzętu: do +100%, czyli maksymalnie ×4 przy bazie ×2.
|
||||
- Statystyki, porównywanie wyposażenia i obliczenia DPS uwzględniają te same limity. Podpowiedzi w statystykach wyjaśniają ograniczenia.
|
||||
|
||||
## Ponowny audyt
|
||||
|
||||
Ta sama próbka: Szerszeń, poziom 120, 500 zwykłych zestawów, 500 zestawów z bossów, 200 zestawów laserowych; brak umiejętności kapitana. Dodatkowo zestawy z maksymalnymi rollami. Ogień badano na krokach 1/60 s przez 30 s, a ruch wspólną funkcją sterowania.
|
||||
|
||||
| Pomiar | Przed | Po |
|
||||
|---|---:|---:|
|
||||
| Mediana regeneracji energii, zwykłe zestawy | 62,7/s | 25,3/s |
|
||||
| Zwykłe zestawy podtrzymujące jedną plazmę | 82,8% | 0% |
|
||||
| Maksymalna prędkość badanego zestawu na TAB | 771 px/s | 480 px/s |
|
||||
| Dryf po puszczeniu TAB w skrajnym zestawie | 48 s | 10 s |
|
||||
| Pięć trafień po 1 pkt: odzysk energii | 91,6 | 5,0 |
|
||||
| Leczenie z 1000 obrażeń, zestaw tarczowy | 400 | 70,6 |
|
||||
| Ładowanie pustych tarcz, zestaw tarczowy | 18,9 s | 33,3 s |
|
||||
| Teoretyczny DPS skrajnego zestawu ofensywnego | 84 950 | 6 817 |
|
||||
|
||||
Wyniki dotyczą mierzonej próbki, nie wszystkich możliwych buildów. Specjalizacja energetyczna, umiejętności, orby i ładowanie słoneczne nadal mogą świadomie poprawiać bilans energii. Teoretyczny DPS nie uwzględnia ograniczeń energii, amunicji, celności i absorpcji celu; nie jest to symulacja całej kampanii.
|
||||
36
assets/freighter/research/weapon_technology_prompts.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Ikony technologii broni
|
||||
|
||||
Wygenerowane wbudowanym narzędziem ImageGen. Osiem osobnych grafik, bez tekstu i ramek; ramki dodaje interfejs.
|
||||
|
||||
## weapons_a.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Weak point: a single dark metallic alien starship armor plate with one glowing orange exposed vulnerable core, precision crosshair aimed at that core. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Offensive technology, amber and orange accent palette.
|
||||
|
||||
## weapons_a_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Precision shot disabling a system: a bright orange precision bolt hitting an alien engine mechanism; a compact engine turbine with orange electric sparks and one severed circuit. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Offensive technology, amber and orange accent palette.
|
||||
|
||||
## weapons_a_next_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Ricochet area weapon: an orange projectile striking a central alien hull and a tight circular orange shockwave reaching two nearby small alien silhouettes. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Offensive technology, amber and orange accent palette.
|
||||
|
||||
## weapons_a_next_next_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Perfect shot: an orange lance passing through a cyan shield bubble and striking the armored alien hull inside, both shield and hull visibly flash at once. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Offensive technology, amber and orange accent palette.
|
||||
|
||||
## weapons_b.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Predictive maneuver: a small emerald green futuristic fighter banking away from one incoming red projectile; two short green motion arcs communicate evasion. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Defensive technology, emerald green accent palette.
|
||||
|
||||
## weapons_b_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Alien missile defense: a green point defense turret intercepting a red incoming missile, the missile breaks apart into harmless orange fragments outside a small green shield arc. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Defensive technology, emerald green accent palette.
|
||||
|
||||
## weapons_b_next_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Alien targeting disruption: a green electronic targeting reticle with a red alien gun firing three diverging red trajectories that miss a centered green ship symbol. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Defensive technology, emerald green accent palette.
|
||||
|
||||
## weapons_b_next_next_next.png
|
||||
|
||||
Use case: stylized-concept. Asset type: single square research technology icon for a gritty top-down science-fiction space game. Alien friendly fire jamming: two angular dark alien fighters and a red projectile from one striking the other, surrounded by a small emerald green electronic interference halo. Hand-painted polished game inventory icon, readable strong centered silhouette at 26 pixels, restrained metallic detail, dark navy-black background, subtle glow, high contrast. One compact composition occupies 85% of square. No text, no letters, no numbers, no outer frame, no border, no interface or multi-icon grid. Defensive technology, emerald green accent palette.
|
||||
|
||||
BIN
assets/freighter/research/weapons_a.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
40
assets/freighter/research/weapons_a.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bax7w2mtykgor"
|
||||
path="res://.godot/imported/weapons_a.png-9c43553085a1a9139f5c7d1f7d0bef38.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_a.png"
|
||||
dest_files=["res://.godot/imported/weapons_a.png-9c43553085a1a9139f5c7d1f7d0bef38.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/freighter/research/weapons_a_next.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
40
assets/freighter/research/weapons_a_next.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c85khktupflwa"
|
||||
path="res://.godot/imported/weapons_a_next.png-1c0b66600bfe7f38773b0fc786a50c2f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_a_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_a_next.png-1c0b66600bfe7f38773b0fc786a50c2f.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/freighter/research/weapons_a_next_next.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
40
assets/freighter/research/weapons_a_next_next.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b718pubb2lt04"
|
||||
path="res://.godot/imported/weapons_a_next_next.png-bc17497d133cebc24c72e43d138e4de5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_a_next_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_a_next_next.png-bc17497d133cebc24c72e43d138e4de5.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/freighter/research/weapons_a_next_next_next.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://efkejqdlvi51"
|
||||
path="res://.godot/imported/weapons_a_next_next_next.png-131ca07347d68b9ce7a46fc260e67260.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_a_next_next_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_a_next_next_next.png-131ca07347d68b9ce7a46fc260e67260.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/freighter/research/weapons_b.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
40
assets/freighter/research/weapons_b.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://nhyqavg1336a"
|
||||
path="res://.godot/imported/weapons_b.png-4e29879260d1fae488aa0493ee6d4995.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_b.png"
|
||||
dest_files=["res://.godot/imported/weapons_b.png-4e29879260d1fae488aa0493ee6d4995.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/freighter/research/weapons_b_next.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
40
assets/freighter/research/weapons_b_next.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://co72crk8cu3bc"
|
||||
path="res://.godot/imported/weapons_b_next.png-6d1626c8af414faf3da334bfa7036e27.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_b_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_b_next.png-6d1626c8af414faf3da334bfa7036e27.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/freighter/research/weapons_b_next_next.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
40
assets/freighter/research/weapons_b_next_next.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjllc0ta5hxp2"
|
||||
path="res://.godot/imported/weapons_b_next_next.png-9450fc181f456eca2b704c7549250e7e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_b_next_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_b_next_next.png-9450fc181f456eca2b704c7549250e7e.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/freighter/research/weapons_b_next_next_next.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://clw685uw2quu2"
|
||||
path="res://.godot/imported/weapons_b_next_next_next.png-fc7b7e382143f62d5fd23260d45a93db.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/freighter/research/weapons_b_next_next_next.png"
|
||||
dest_files=["res://.godot/imported/weapons_b_next_next_next.png-fc7b7e382143f62d5fd23260d45a93db.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
|
||||
1
assets/icons/orbs/atomic_engine.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#ff9573" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#ff9573" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#ff9573" stroke-opacity=".25"/><g fill="none" stroke="#ff9573" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="44" cy="44" rx="29" ry="11"/><ellipse cx="44" cy="44" rx="29" ry="11" transform="rotate(60 44 44)"/><ellipse cx="44" cy="44" rx="29" ry="11" transform="rotate(120 44 44)"/><circle cx="44" cy="44" r="4"/></g></svg>
|
||||
|
After Width: | Height: | Size: 710 B |
43
assets/icons/orbs/atomic_engine.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://tibgmtl72krv"
|
||||
path="res://.godot/imported/atomic_engine.svg-ea8c1ddea3c501df275722077f68d270.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/atomic_engine.svg"
|
||||
dest_files=["res://.godot/imported/atomic_engine.svg-ea8c1ddea3c501df275722077f68d270.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
assets/icons/orbs/electricity.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#6ceaff" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#6ceaff" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#6ceaff" stroke-opacity=".25"/><g fill="none" stroke="#6ceaff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M49 15 29 47h15l-5 26 25-37H48z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 537 B |
43
assets/icons/orbs/electricity.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://pkyo5pilyr4c"
|
||||
path="res://.godot/imported/electricity.svg-d427d134c8b8d820e86b5b3762ee5b15.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/electricity.svg"
|
||||
dest_files=["res://.godot/imported/electricity.svg-d427d134c8b8d820e86b5b3762ee5b15.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
assets/icons/orbs/kinetic.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#ffc17c" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#ffc17c" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#ffc17c" stroke-opacity=".25"/><g fill="none" stroke="#ffc17c" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m27 58 29-29 10 10-29 29zm20-32 8-8 15 15-8 8M19 49l11-11m-10 23 7-7m13 19 11-11"/></g></svg>
|
||||
|
After Width: | Height: | Size: 586 B |
43
assets/icons/orbs/kinetic.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bkges2n325bqb"
|
||||
path="res://.godot/imported/kinetic.svg-409d1b4b5c4a384e9b5ab9b80af7b4f7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/kinetic.svg"
|
||||
dest_files=["res://.godot/imported/kinetic.svg-409d1b4b5c4a384e9b5ab9b80af7b4f7.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
assets/icons/orbs/polarization.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#86baff" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#86baff" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#86baff" stroke-opacity=".25"/><g fill="none" stroke="#86baff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M44 17 65 26v18c0 12-9 22-21 28-12-6-21-16-21-28V26zM30 39h11m-5-5v10m14 7h11"/><path d="m43 32-5 16h10l-4 13"/></g></svg>
|
||||
|
After Width: | Height: | Size: 615 B |
43
assets/icons/orbs/polarization.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dph88visw4jc"
|
||||
path="res://.godot/imported/polarization.svg-f5403fd4cf6ca590b6c7ce5aaf6e6329.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/polarization.svg"
|
||||
dest_files=["res://.godot/imported/polarization.svg-f5403fd4cf6ca590b6c7ce5aaf6e6329.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
assets/icons/orbs/radiation.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#b6f765" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#b6f765" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#b6f765" stroke-opacity=".25"/><g fill="none" stroke="#b6f765" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="44" cy="44" r="5"/><path d="m39 34-8-14a27 27 0 0 1 26 0l-8 14zm17 7 16 0a27 27 0 0 1-13 23l-8-14zm-19 9-8 14a27 27 0 0 1-13-23h16z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 640 B |
43
assets/icons/orbs/radiation.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dtmcg8nyvbxqx"
|
||||
path="res://.godot/imported/radiation.svg-5d7a413a86edffcb25ca8f6d2859d05e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/radiation.svg"
|
||||
dest_files=["res://.godot/imported/radiation.svg-5d7a413a86edffcb25ca8f6d2859d05e.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
assets/icons/orbs/replication.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88"><defs><radialGradient id="g"><stop stop-color="#bd9cff" stop-opacity=".28"/><stop offset="1" stop-color="#101b29"/></radialGradient></defs><circle cx="44" cy="44" r="41" fill="url(#g)" stroke="#bd9cff" stroke-width="2"/><circle cx="44" cy="44" r="36" fill="none" stroke="#bd9cff" stroke-opacity=".25"/><g fill="none" stroke="#bd9cff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="22" y="19" width="31" height="31" rx="5"/><rect x="36" y="33" width="31" height="31" rx="5"/><path d="M51 41v15m-7-7h14"/></g></svg>
|
||||
|
After Width: | Height: | Size: 625 B |
43
assets/icons/orbs/replication.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bkr5f7o0h5s03"
|
||||
path="res://.godot/imported/replication.svg-f343de83ad94ff3d19a8ce7e870152e2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/icons/orbs/replication.svg"
|
||||
dest_files=["res://.godot/imported/replication.svg-f343de83ad94ff3d19a8ce7e870152e2.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
BIN
assets/planets/deuterium_deposit.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
40
assets/planets/deuterium_deposit.png.import
Normal file
@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vouqi0b4nliq"
|
||||
path="res://.godot/imported/deuterium_deposit.png-535b593bd24823844108c2999bc0fbac.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/planets/deuterium_deposit.png"
|
||||
dest_files=["res://.godot/imported/deuterium_deposit.png-535b593bd24823844108c2999bc0fbac.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/planets/deuterium_deposit.prompt.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Deuterium deposit
|
||||
|
||||
Generated with built-in image_gen, transparent PNG.
|
||||
|
||||
undefined
|
||||
@ -1,4 +1,4 @@
|
||||
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=19 format=3]
|
||||
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=20 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_database.gd" id="1_database"]
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="2_type"]
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
[ext_resource type="Resource" path="res://resources/items/affixes/luck_percent.tres" id="luck"]
|
||||
|
||||
[ext_resource type="Resource" path="res://resources/items/affixes/attack_speed_percent.tres" id="attack_speed"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_database")
|
||||
affixes = Array[ExtResource("2_type")]([ExtResource("3_affix"), ExtResource("4_affix"), ExtResource("5_affix"), ExtResource("6_affix"), ExtResource("7_affix"), ExtResource("8_affix"), ExtResource("9_affix"), ExtResource("10_affix"), ExtResource("11_affix"), ExtResource("12_affix"), ExtResource("13_affix"), ExtResource("14_affix"), ExtResource("kinetic"), ExtResource("energy"), ExtResource("radiation"), ExtResource("luck")])
|
||||
affixes = Array[ExtResource("2_type")]([ExtResource("3_affix"), ExtResource("4_affix"), ExtResource("5_affix"), ExtResource("6_affix"), ExtResource("7_affix"), ExtResource("8_affix"), ExtResource("9_affix"), ExtResource("10_affix"), ExtResource("11_affix"), ExtResource("12_affix"), ExtResource("13_affix"), ExtResource("14_affix"), ExtResource("kinetic"), ExtResource("energy"), ExtResource("radiation"), ExtResource("luck"), ExtResource("attack_speed")])
|
||||
|
||||
13
resources/items/affixes/attack_speed_percent.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="ItemAffixDefinition" load_steps=2 format=3]
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="1"]
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
affix_id = "attack_speed_percent"
|
||||
display_name = "Szybkostrzelność"
|
||||
description = "Zwiększa tempo strzałów tej broni. Koszt pojedynczego strzału pozostaje bez zmian. Limit bonusu: 25%."
|
||||
stat_key = "attack_speed_percent"
|
||||
category_mask = 1
|
||||
minimum_value_level_1 = 2.0
|
||||
maximum_value_level_1 = 3.0
|
||||
minimum_value_cap = 15.0
|
||||
maximum_value_cap = 25.0
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 2
|
||||
maximum_value_level_1 = 5
|
||||
minimum_value_level_1000 = 40
|
||||
maximum_value_level_1000 = 75
|
||||
minimum_value_cap = 40
|
||||
maximum_value_cap = 75
|
||||
|
||||
|
||||
@ -11,5 +11,5 @@ unit = "%"
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 2.0
|
||||
minimum_value_level_1000 = 10.0
|
||||
maximum_value_level_1000 = 20.0
|
||||
minimum_value_cap = 10.0
|
||||
maximum_value_cap = 20.0
|
||||
|
||||
@ -11,5 +11,5 @@ unit = "%"
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 5.0
|
||||
maximum_value_level_1 = 10.0
|
||||
minimum_value_level_1000 = 50.0
|
||||
maximum_value_level_1000 = 100.0
|
||||
minimum_value_cap = 50.0
|
||||
maximum_value_cap = 100.0
|
||||
|
||||
@ -9,5 +9,5 @@ description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 10.0
|
||||
maximum_value_level_1000 = 15.0
|
||||
minimum_value_cap = 10.0
|
||||
maximum_value_cap = 15.0
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 0.2
|
||||
maximum_value_level_1 = 0.5
|
||||
minimum_value_level_1000 = 100
|
||||
maximum_value_level_1000 = 180
|
||||
minimum_value_cap = 100
|
||||
maximum_value_cap = 180
|
||||
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 2
|
||||
maximum_value_level_1 = 5
|
||||
minimum_value_level_1000 = 40
|
||||
maximum_value_level_1000 = 75
|
||||
minimum_value_cap = 40
|
||||
maximum_value_cap = 75
|
||||
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1
|
||||
maximum_value_level_1 = 3
|
||||
minimum_value_level_1000 = 25
|
||||
maximum_value_level_1000 = 50
|
||||
minimum_value_cap = 25
|
||||
maximum_value_cap = 50
|
||||
|
||||
|
||||
@ -9,5 +9,5 @@ description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 10.0
|
||||
maximum_value_level_1000 = 15.0
|
||||
minimum_value_cap = 10.0
|
||||
maximum_value_cap = 15.0
|
||||
|
||||
@ -11,5 +11,5 @@ selection_weight = 0.7
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 0.5
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 3.0
|
||||
maximum_value_level_1000 = 6.0
|
||||
minimum_value_cap = 3.0
|
||||
maximum_value_cap = 6.0
|
||||
|
||||
@ -9,5 +9,5 @@ unit = "s"
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 0.1
|
||||
maximum_value_level_1 = 0.15
|
||||
minimum_value_level_1000 = 1.0
|
||||
maximum_value_level_1000 = 2.0
|
||||
minimum_value_cap = 1.0
|
||||
maximum_value_cap = 2.0
|
||||
|
||||
@ -9,5 +9,5 @@ description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 10.0
|
||||
maximum_value_level_1000 = 15.0
|
||||
minimum_value_cap = 10.0
|
||||
maximum_value_cap = 15.0
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
script = ExtResource("1_affix")
|
||||
affix_id = "shield_on_damage"
|
||||
display_name = "Odzysk tarcz z obrażeń"
|
||||
description = "Globalnie odnawia tarcze o procent faktycznie zadanych obrażeń każdą bronią."
|
||||
description = "Globalnie odnawia tarcze z faktycznie zadanych obrażeń. Łącznie do 10% obrażeń; tempo odzysku do 5% maksymalnych tarcz/s, z rezerwą na jedno takie odnowienie."
|
||||
stat_key = "shield_on_damage_percent"
|
||||
unit = "%"
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 5.0
|
||||
maximum_value_level_1000 = 5.0
|
||||
minimum_value_cap = 5.0
|
||||
maximum_value_cap = 5.0
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1
|
||||
maximum_value_level_1 = 3
|
||||
minimum_value_level_1000 = 25
|
||||
maximum_value_level_1000 = 50
|
||||
minimum_value_cap = 25
|
||||
maximum_value_cap = 50
|
||||
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 0.5
|
||||
maximum_value_level_1 = 1
|
||||
minimum_value_level_1000 = 200
|
||||
maximum_value_level_1000 = 350
|
||||
minimum_value_cap = 200
|
||||
maximum_value_cap = 350
|
||||
|
||||
|
||||
@ -12,6 +12,6 @@ selection_weight = 1.0
|
||||
category_mask = 95
|
||||
minimum_value_level_1 = 1
|
||||
maximum_value_level_1 = 2
|
||||
minimum_value_level_1000 = 15
|
||||
maximum_value_level_1000 = 25
|
||||
minimum_value_cap = 15
|
||||
maximum_value_cap = 25
|
||||
|
||||
|
||||
@ -14,4 +14,4 @@ description = "Zwiększa maksymalną pojemność energii."
|
||||
primary_stat = "max_energy"
|
||||
primary_value = 20.0
|
||||
stat_suffix = " energii"
|
||||
energy_regeneration_bonus = 12.0
|
||||
energy_regeneration_bonus = 4.0
|
||||
|
||||
@ -201,7 +201,7 @@ func encounter_xp(base_amount: float, system_level: int, xp_multiplier: float =
|
||||
|
||||
func reward_enemy(system_level: int, tier: int, xp_multiplier: float, credit_multiplier: float, grant_credits: bool = true) -> Vector2i:
|
||||
var index := clampi(tier, 0, 2)
|
||||
var scale_factor := 1.0 + 0.05 * (clampi(system_level, 1, 1000) - 1)
|
||||
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 money := roundi(BALANCE.KILL_CREDITS[index] * scale_factor * maxf(0.0, credit_multiplier))
|
||||
add_rewards(xp, money if grant_credits else 0)
|
||||
|
||||
26
scripts/game/alien_weapon_effects.gd
Normal file
@ -0,0 +1,26 @@
|
||||
extends RefCounted
|
||||
const DATA := preload("res://scripts/planets/alien_research.gd")
|
||||
|
||||
static func roll_disabled_system(roll := -1.0, system := -1) -> int:
|
||||
if not DATA.learned("weapons_a_next") or (randf() if roll < 0 else roll) >= 0.01: return -1
|
||||
return randi_range(0,2) if system < 0 else clampi(system,0,2)
|
||||
|
||||
static func apply_hit(target: Variant, damage: float, critical: bool, kind: int, piercing: float, visited: Dictionary, splash := true, disabled_system := -2) -> void:
|
||||
if not target is Node or target.is_queued_for_deletion(): return
|
||||
var id: int = target.get_instance_id()
|
||||
if visited.has(id): return
|
||||
visited[id] = true
|
||||
if target.has_method("take_research_weapon_hit"):
|
||||
target.take_research_weapon_hit(damage,critical,kind,piercing,disabled_system)
|
||||
elif target.has_method("take_piercing_weapon_hit"):
|
||||
target.take_piercing_weapon_hit(damage,critical,kind,piercing)
|
||||
elif target.has_method("take_weapon_hit"):
|
||||
target.take_weapon_hit(damage,critical,kind)
|
||||
elif target.has_method("take_damage"):
|
||||
target.take_damage(damage)
|
||||
if not splash or not DATA.learned("weapons_a_next_next") or not target is Node2D or not target.is_in_group("enemies"): return
|
||||
var radius := float(target.get_visual_size())*0.5+10.0
|
||||
for other in target.get_tree().get_nodes_in_group("enemies"):
|
||||
if other == target or other.is_queued_for_deletion() or float(other.get("hull")) <= 0: continue
|
||||
if target.global_position.distance_to(other.global_position) <= radius+float(other.get_visual_size())*0.5:
|
||||
apply_hit(other,damage,critical,kind,piercing,visited,false,-1)
|
||||
1
scripts/game/alien_weapon_effects.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bphkxpc45ig5e
|
||||
@ -1,13 +1,16 @@
|
||||
extends RefCounted
|
||||
const MAP_CHANCE := 0.15
|
||||
const BANK_CHANCE := 0.15
|
||||
static func roll(level: int, boss: bool, seed_value: int, map_roll: float, _orb_roll: float) -> Array[Dictionary]:
|
||||
const REPLICATION_CHANCE := 0.05
|
||||
static func roll(level: int, boss: bool, seed_value: int, map_roll: float, orb_roll: float) -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
if boss or map_roll < MAP_CHANCE:
|
||||
var profile := {"id":"distant:%d" % seed_value,"seed":seed_value,"name":"Daleka wyprawa %d" % level}
|
||||
result.append({"map":preload("res://scripts/planets/distant_expeditions.gd").offer(profile,level)})
|
||||
if boss:
|
||||
result.append({"orb":preload("res://scripts/items/special_orbs.gd").IDS[posmod(seed_value,preload("res://scripts/items/special_orbs.gd").IDS.size())]})
|
||||
var pool := preload("res://scripts/items/special_orbs.gd").COMMON_IDS
|
||||
result.append({"orb":pool[posmod(seed_value,pool.size())]})
|
||||
if orb_roll < REPLICATION_CHANCE: result.append({"orb":"replication"})
|
||||
return result
|
||||
static func crystal_amount(level: int, tier: int, roll_value: int) -> int:
|
||||
return clampi(roll_value,1,3) * maxi(1,ceili(maxi(1,level)/10.0)) * [1,2,5][clampi(tier,0,2)]
|
||||
|
||||
@ -2,7 +2,6 @@ extends Node
|
||||
const GENERATOR := preload("res://scripts/levels/distant_sector_generator.gd")
|
||||
var game: Node2D
|
||||
var _status: Label
|
||||
var _return: Button
|
||||
var _refresh := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
@ -19,21 +18,24 @@ func _ready() -> void:
|
||||
if int(data.tier) == 2: game._pending_boss = data.duplicate()
|
||||
else: game._spawn_enemy(data,data.position)
|
||||
_build_structures()
|
||||
var expeditions := Node.new()
|
||||
expeditions.set_script(preload("res://scripts/planets/expedition_controller.gd"))
|
||||
expeditions.game = game
|
||||
expeditions.player = game.player
|
||||
game.add_child(expeditions)
|
||||
var panel := PanelContainer.new()
|
||||
panel.name = "DistantReturnPanel"
|
||||
panel.name = "DistantStatusPanel"
|
||||
game.get_node("HUD/HUDRoot").add_child(panel)
|
||||
panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP)
|
||||
panel.offset_left = -195
|
||||
panel.offset_right = 195
|
||||
panel.offset_top = 136
|
||||
panel.offset_bottom = 206
|
||||
panel.offset_top = 120
|
||||
panel.offset_bottom = 190
|
||||
var ui := preload("res://scripts/planets/freighter_menu.gd")
|
||||
ui.style(panel,8,0.5)
|
||||
var column := VBoxContainer.new()
|
||||
panel.add_child(column)
|
||||
_status = ui.label(column,"",12,Color("e9b4fc"))
|
||||
_return = ui.button(column,"POWRÓT DO FRACHTOWCA",_return_home,true)
|
||||
_return.name = "ReturnFromDistant"
|
||||
_update_status()
|
||||
|
||||
func _build_structures() -> void:
|
||||
@ -75,13 +77,15 @@ func return_blocked() -> bool:
|
||||
|
||||
func _update_status() -> void:
|
||||
_status.text = "WYPRAWA %d · %s" % [GameState.system_level,"SEKTOR OCZYSZCZONY" if game.system_cleared else ("BOSS" if game._boss_spawned else "%d wrogów" % game.get_remaining_enemies())]
|
||||
_return.disabled = bool(game.player.get("_is_destroyed")) or return_blocked()
|
||||
_return.tooltip_text = "Oddal się od przeciwników i zakończ walkę." if _return.disabled else "Wróć do układu startowego. Powrót nie zużywa deuteru. Zbierz łup przed odlotem."
|
||||
|
||||
func _return_home() -> void:
|
||||
if game._transitioning or bool(game.player.get("_is_destroyed")) or return_blocked(): return
|
||||
func _return_home() -> bool:
|
||||
var controller := game.get_node_or_null("PlanetExpeditions")
|
||||
if controller == null or not is_instance_valid(controller.screen) or not game.player.is_docked: return false
|
||||
if not preload("res://scripts/planets/orbital_contact_range.gd").at_freighter(game.player, controller.freighter): return false
|
||||
if game._transitioning or bool(game.player.get("_is_destroyed")) or return_blocked(): return false
|
||||
game._transitioning = true
|
||||
_load_home.call_deferred()
|
||||
return true
|
||||
|
||||
func _load_home() -> void:
|
||||
var active: Dictionary = GameState.active_distant_map.duplicate(true)
|
||||
|
||||
@ -44,9 +44,12 @@ var _combat_target: Node2D
|
||||
var virus_controlled := false
|
||||
var _virus_infection_attempted := false
|
||||
var _research_player_hit := false
|
||||
var _research_direct_hit := false
|
||||
var _weapon_piercing := 0.0
|
||||
var combat_level := 1
|
||||
var _reward_claimed := false
|
||||
var _disabled_systems := [0.0,0.0,0.0]
|
||||
var _offline_shields := 0.0
|
||||
|
||||
func roll_virus_infection() -> bool:
|
||||
if loot_tier == 2 or _virus_infection_attempted:
|
||||
@ -169,11 +172,12 @@ func regenerate_defenses(delta: float) -> void:
|
||||
if loot_tier == 2 or hull <= 0.0 or is_queued_for_deletion():
|
||||
return
|
||||
hull = minf(max_hull, hull + max_hull * 0.01 * delta)
|
||||
shields = minf(max_shields, shields + max_shields * 0.01 * delta)
|
||||
if not system_disabled(2): shields = minf(max_shields, shields + max_shields * 0.01 * delta)
|
||||
if is_instance_valid(_shield_visual):
|
||||
_shield_visual.set_shield_values(shields, max_shields)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
tick_disabled_systems(delta)
|
||||
regenerate_defenses(delta)
|
||||
_slow_remaining = maxf(0.0, _slow_remaining - delta)
|
||||
_weapon_timer = maxf(0.0, _weapon_timer - delta)
|
||||
@ -202,7 +206,8 @@ func _physics_process(delta: float) -> void:
|
||||
var movement := combat_movement(to_player)
|
||||
var desired_rotation := (movement if fleeing else direction).angle() + PI * 0.5
|
||||
rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta)
|
||||
velocity = velocity.move_toward(movement * approach_speed * (0.5 if fleeing else 1.0) * get_slow_multiplier(), approach_acceleration * get_slow_multiplier() * delta)
|
||||
var desired_velocity := Vector2.ZERO if system_disabled(0) else movement * approach_speed * (0.5 if fleeing else 1.0) * get_slow_multiplier()
|
||||
velocity = velocity.move_toward(desired_velocity, approach_acceleration * delta)
|
||||
if not fleeing and to_player.length() <= weapon_range and _weapon_timer <= 0.0:
|
||||
_fire_plasma(direction)
|
||||
_weapon_timer = get_fire_interval()
|
||||
@ -301,7 +306,10 @@ func _check_player_collision() -> void:
|
||||
_player.call("register_enemy_collision", global_position)
|
||||
|
||||
func _fire_plasma(direction: Vector2) -> void:
|
||||
if system_disabled(1): return
|
||||
direction = research_aim_direction(direction)
|
||||
var shot_mask := 37 if virus_controlled else 115
|
||||
if not virus_controlled and preload("res://scripts/planets/alien_research.gd").learned("weapons_b_next_next_next"): shot_mask |= 4
|
||||
var muzzle := global_position + direction * (get_visual_size() * 0.5 + 8.0)
|
||||
if weapon_kind == ItemDefinition.WeaponKind.LASER:
|
||||
var endpoint := muzzle + _laser_aim_direction(direction) * weapon_range
|
||||
@ -323,6 +331,7 @@ func _fire_plasma(direction: Vector2) -> void:
|
||||
else:
|
||||
var projectile := preload("res://scenes/game/weapon_projectile.tscn").instantiate()
|
||||
projectile.enemy_shot = true
|
||||
projectile.source_ship = self
|
||||
projectile.collision_mask = shot_mask
|
||||
projectile.missile_homing = false
|
||||
var speed := 430.0 if weapon_kind == ItemDefinition.WeaponKind.GAMMA else (950.0 if weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN else 600.0)
|
||||
@ -336,15 +345,65 @@ func take_piercing_weapon_hit(amount: float, critical: bool, damage_type: int, p
|
||||
_weapon_piercing = 0.0
|
||||
|
||||
func take_weapon_hit(amount: float, critical: bool, damage_type: int = DamageTypes.Type.KINETIC) -> void:
|
||||
if is_queued_for_deletion() or hull <= 0.0:
|
||||
_take_player_hit(amount,critical,damage_type,true,-2)
|
||||
|
||||
func take_research_weapon_hit(amount: float, critical: bool, damage_type: int, piercing: float, disabled_system: int) -> void:
|
||||
_weapon_piercing = clampf(piercing,0.0,0.5)
|
||||
_take_player_hit(amount,critical,damage_type,true,disabled_system)
|
||||
_weapon_piercing = 0.0
|
||||
|
||||
func take_research_dot(amount: float, damage_type: int, piercing: float) -> void:
|
||||
_weapon_piercing = clampf(piercing,0.0,0.5)
|
||||
_take_player_hit(amount,false,damage_type,false,-1)
|
||||
_weapon_piercing = 0.0
|
||||
|
||||
func _take_player_hit(amount: float, critical: bool, damage_type: int, direct: bool, disabled_system: int) -> void:
|
||||
if is_queued_for_deletion() or hull <= 0.0 or amount <= 0.0:
|
||||
return
|
||||
var before := hull + shields
|
||||
_research_player_hit = true
|
||||
var research := preload("res://scripts/planets/alien_research.gd")
|
||||
if direct and amount > 0 and research.learned("weapons_a"): amount += 5.0
|
||||
_research_direct_hit = direct
|
||||
take_damage(amount, critical, damage_type)
|
||||
_research_direct_hit = false
|
||||
_research_player_hit = false
|
||||
if direct and hull > 0 and not is_queued_for_deletion():
|
||||
var chosen := preload("res://scripts/game/alien_weapon_effects.gd").roll_disabled_system() if disabled_system == -2 else disabled_system
|
||||
if chosen >= 0: disable_system(chosen)
|
||||
if is_instance_valid(_player):
|
||||
_player.call("restore_shields_from_damage", maxf(0.0, before - hull - shields))
|
||||
|
||||
func system_disabled(system: int) -> bool:
|
||||
return system >= 0 and system < 3 and _disabled_systems[system] > 0.0
|
||||
|
||||
func disable_system(system: int) -> void:
|
||||
if system < 0 or system >= 3 or hull <= 0 or is_queued_for_deletion(): return
|
||||
if system == 2 and not system_disabled(2):
|
||||
_offline_shields = shields
|
||||
shields = 0.0
|
||||
_disabled_systems[system] = 3.0
|
||||
if is_instance_valid(_shield_visual): _shield_visual.set_shield_values(shields,max_shields)
|
||||
var popup := DAMAGE_POPUP_SCENE.instantiate() as Label
|
||||
popup.text = "%s OFF · 3 s" % ["SILNIK","BROŃ","TARCZE"][system]
|
||||
popup.set("text_color",Color("efa653"))
|
||||
popup.position = position+Vector2(-30,-50)
|
||||
get_parent().add_child(popup)
|
||||
|
||||
func tick_disabled_systems(delta: float) -> void:
|
||||
var shields_offline := system_disabled(2)
|
||||
for system in 3: _disabled_systems[system] = maxf(0.0,_disabled_systems[system]-delta)
|
||||
if shields_offline and not system_disabled(2) and hull > 0:
|
||||
shields = minf(max_shields,_offline_shields)
|
||||
_offline_shields = 0.0
|
||||
if is_instance_valid(_shield_visual): _shield_visual.set_shield_values(shields,max_shields)
|
||||
|
||||
func research_aim_direction(direction: Vector2, roll := -1.0, side := 0.0) -> Vector2:
|
||||
if virus_controlled or not preload("res://scripts/planets/alien_research.gd").learned("weapons_b_next_next"): return direction
|
||||
if (randf() if roll < 0 else roll) >= 0.25: return direction
|
||||
var angle := randf_range(0.20,0.45)
|
||||
return direction.rotated(angle*(signf(side) if side != 0.0 else (-1.0 if randf()<0.5 else 1.0)))
|
||||
|
||||
func take_damage(amount: float, critical: bool = false, damage_type: int = DamageTypes.Type.KINETIC, bypass_absorption: bool = false) -> void:
|
||||
if is_queued_for_deletion() or hull <= 0.0:
|
||||
return
|
||||
@ -355,7 +414,9 @@ func take_damage(amount: float, critical: bool = false, damage_type: int = Damag
|
||||
var hull_multiplier := 1.0 + (research.bonus(progress, "hull") 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 remaining := maxf(0.0, actual - shield_damage / shield_multiplier)
|
||||
var double_hit := _research_direct_hit and research.learned("weapons_a_next_next_next")
|
||||
if double_hit: shield_damage = minf(shields,actual*shield_multiplier)
|
||||
var remaining := actual if double_hit else maxf(0.0, actual - shield_damage / shield_multiplier)
|
||||
shields -= shield_damage
|
||||
var hull_damage := minf(hull, remaining * hull_multiplier)
|
||||
hull = maxf(0.0, hull - hull_damage)
|
||||
|
||||
@ -45,8 +45,8 @@ func _create_navigation() -> void:
|
||||
navigation.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP)
|
||||
navigation.offset_left = -32
|
||||
navigation.offset_right = 32
|
||||
navigation.offset_top = 44
|
||||
navigation.offset_bottom = 124
|
||||
navigation.offset_top = 28
|
||||
navigation.offset_bottom = 108
|
||||
navigation.add_theme_constant_override("separation", 10)
|
||||
_captain_skills_ui = CanvasLayer.new()
|
||||
_captain_skills_ui.set_script(preload("res://scripts/ui/captain_skills_ui.gd"))
|
||||
@ -127,7 +127,7 @@ func _ready() -> void:
|
||||
func _create_sidebar() -> void:
|
||||
_captain_panel = $HUDRoot/CaptainPanel
|
||||
_captain_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||
_captain_panel.position = Vector2(12, 44)
|
||||
_captain_panel.position = Vector2(12, 28)
|
||||
_captain_panel.size = _captain_panel.get_combined_minimum_size()
|
||||
_captain_panel.resized.connect(_layout_debug)
|
||||
_create_system_badge()
|
||||
@ -221,8 +221,8 @@ func _create_sidebar() -> void:
|
||||
_sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
||||
_sidebar_toggle.offset_left = -233
|
||||
_sidebar_toggle.offset_right = -12
|
||||
_sidebar_toggle.offset_top = 44
|
||||
_sidebar_toggle.offset_bottom = 115.5
|
||||
_sidebar_toggle.offset_top = 28
|
||||
_sidebar_toggle.offset_bottom = 99.5
|
||||
_sidebar_toggle.pressed.connect(_toggle_sidebar)
|
||||
_cockpit = Control.new()
|
||||
_cockpit.name = "Cockpit"
|
||||
@ -298,7 +298,7 @@ func _layout_sidebar() -> void:
|
||||
var scroll := _sidebar.get_node("Scroll") as ScrollContainer
|
||||
scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
var content_height: float = scroll.get_node("Contents").get_combined_minimum_size().y + 16.0
|
||||
var available_height := maxf(100.0, $HUDRoot.size.y - 180.0 - _ship_passive_reserved_height)
|
||||
var available_height := maxf(100.0, $HUDRoot.size.y - 164.0 - _ship_passive_reserved_height)
|
||||
var panel_scale := minf(EQUIPMENT_UI_SCALE, available_height / content_height)
|
||||
_sidebar.scale = Vector2.ONE * panel_scale
|
||||
_sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), content_height)
|
||||
@ -315,7 +315,7 @@ func _layout_sidebar() -> void:
|
||||
_captain_skills_ui.call_deferred("_layout")
|
||||
_layout_debug()
|
||||
if not _sidebar.has_meta("floating_window_dragged"):
|
||||
_sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * _sidebar.scale.x - 8, 168)
|
||||
_sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * _sidebar.scale.x - 8, 152)
|
||||
else:
|
||||
_window_manager.call("clamp_window", _sidebar)
|
||||
_sidebar.visible = _sidebar_open
|
||||
@ -633,8 +633,8 @@ func _update_system_badge() -> void:
|
||||
if not is_instance_valid(_system_badge): return
|
||||
_system_badge.position = Vector2(_captain_panel.position.x + _captain_panel.size.x + 10, _captain_panel.position.y)
|
||||
_system_title.text = ("Wyprawa %d" if not GameState.active_distant_map.is_empty() else "System %d") % GameState.system_level
|
||||
_system_mode.text = "HARD" if GameState.system_level > 60 else "NORMALNY"
|
||||
_system_mode.add_theme_color_override("font_color", Color("e88972") if GameState.system_level > 60 else Color("88a7ae"))
|
||||
_system_mode.text = "HARD" if preload("res://scripts/levels/level_balance.gd").is_hard(GameState.system_level) else "NORMALNY"
|
||||
_system_mode.add_theme_color_override("font_color", Color("e88972") if preload("res://scripts/levels/level_balance.gd").is_hard(GameState.system_level) else Color("88a7ae"))
|
||||
|
||||
func _create_ship_statistics() -> void:
|
||||
_ship_statistics = preload("res://scripts/ui/ship_statistics.gd").new()
|
||||
|
||||
@ -262,10 +262,12 @@ func _load_distant(map_id: String) -> void:
|
||||
_transitioning = false
|
||||
return
|
||||
var cost: int = preload("res://scripts/planets/distant_expeditions.gd").fuel_cost(GameState.active_distant_map)
|
||||
var map_snapshot: Dictionary = GameState.active_distant_map.duplicate(true)
|
||||
get_tree().paused = false
|
||||
var error := get_tree().reload_current_scene()
|
||||
if error != OK:
|
||||
GameState.restore_distant_home()
|
||||
GameState.captain.expedition.distant_maps[map_id] = map_snapshot
|
||||
GameState.captain.expedition.deuter += cost
|
||||
GameState.captain.expedition.emit_changed()
|
||||
GameState.restart_inventory.clear()
|
||||
|
||||
@ -26,6 +26,7 @@ func depart_distant(map_id: String) -> bool:
|
||||
if captain.expedition.deuter < cost: return false
|
||||
distant_home = {"level":system_level,"seed":system_seed}
|
||||
active_distant_map = map.duplicate(true)
|
||||
captain.expedition.distant_maps.erase(map_id)
|
||||
captain.expedition.deuter -= cost
|
||||
system_level = int(map.level)
|
||||
system_seed = int(map.seed)
|
||||
|
||||
@ -40,7 +40,9 @@ func _physics_process(delta: float) -> void:
|
||||
if _tick_elapsed >= 0.5 or (remaining <= 0.0 and _tick_elapsed > 0.0):
|
||||
step = _tick_elapsed
|
||||
_tick_elapsed = 0.0
|
||||
if credited_to_player and target.has_method("take_piercing_weapon_hit"):
|
||||
if credited_to_player and target.has_method("take_research_dot"):
|
||||
target.take_research_dot(tick_damage*step,DamageTypes.Type.RADIATION,shield_piercing)
|
||||
elif credited_to_player and target.has_method("take_piercing_weapon_hit"):
|
||||
target.take_piercing_weapon_hit(tick_damage * step, false, DamageTypes.Type.RADIATION, shield_piercing)
|
||||
elif credited_to_player and target.has_method("take_weapon_hit"):
|
||||
target.take_weapon_hit(tick_damage * step, false, DamageTypes.Type.RADIATION)
|
||||
|
||||
@ -8,6 +8,7 @@ const CAPTAIN_BALANCE := preload("res://scripts/captain/captain_balance.gd")
|
||||
const SHIP_CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
||||
const COMBAT_FLASH := preload("res://scripts/game/combat_flash.gd")
|
||||
const MINI_BOOST_ENERGY_PER_SECOND := 20.0
|
||||
const EQUIPMENT_BALANCE := preload("res://scripts/items/equipment_balance.gd")
|
||||
const DESTRUCTION_EFFECT := preload("res://scripts/game/player_destruction_effect.gd")
|
||||
|
||||
@export var max_speed := 76.0
|
||||
@ -46,7 +47,7 @@ var critical_chance_bonus := 0.0
|
||||
var critical_damage_bonus := 0.0
|
||||
@export var solar_charge_radius := 550.0
|
||||
@export var solar_charge_full_radius := 300.0
|
||||
@export var solar_charge_fraction_per_second := 0.25
|
||||
@export var solar_charge_fraction_per_second := 0.08
|
||||
var charging_star: Node2D
|
||||
|
||||
var angular_velocity := 0.0
|
||||
@ -62,6 +63,8 @@ var _boost_velocity_acceleration := 0.0
|
||||
var _mini_boost_active := false
|
||||
var _mini_boost_exhausted := false
|
||||
var _mini_boost_coast_speed := 0.0
|
||||
var _shield_leech_budget := -1.0
|
||||
var _polarization_budget := -1.0
|
||||
@onready var _shield_visual: Node2D = $ShieldVisual
|
||||
|
||||
func _enter_tree() -> void:
|
||||
@ -74,12 +77,14 @@ func _enter_tree() -> void:
|
||||
base_max_speed = float(chassis["speed"])
|
||||
|
||||
func apply_equipment_bonuses(bonuses: Dictionary) -> void:
|
||||
bonuses = EQUIPMENT_BALANCE.bonuses(bonuses)
|
||||
absorption = DamageTypes.capped(Vector3(float(bonuses.get("kinetic_absorption", 0.0)), float(bonuses.get("energy_absorption", 0.0)), float(bonuses.get("radiation_absorption", 0.0))))
|
||||
max_hull = (base_max_hull + float(bonuses.get("max_hull", 0.0))) * (1.0 + float(bonuses.get("hull_percent", 0.0)) / 100.0)
|
||||
max_shields = (base_max_shields + float(bonuses.get("max_shields", 0.0))) * (1.0 + float(bonuses.get("shield_percent", 0.0)) / 100.0)
|
||||
scanner_range = base_scanner_range + float(bonuses.get("scanner_range", 0.0))
|
||||
max_energy = base_max_energy + float(bonuses.get("max_energy", 0.0))
|
||||
max_speed = (base_max_speed + float(bonuses.get("max_speed", 0.0))) * (1.0 + float(bonuses.get("speed_percent", 0.0)) / 100.0)
|
||||
max_speed = EQUIPMENT_BALANCE.speed(base_max_speed,float(bonuses.get("max_speed",0.0)),float(bonuses.get("speed_percent",0.0)))
|
||||
space_drag = maxf(16.0,max_speed*0.2)
|
||||
damage_multiplier = 1.0 + float(bonuses.get("damage_percent", 0.0)) / 100.0
|
||||
experience_multiplier = 1.0 + float(bonuses.get("experience_percent", 0.0)) / 100.0
|
||||
credits_multiplier = 1.0 + float(bonuses.get("credits_percent", 0.0)) / 100.0
|
||||
@ -89,6 +94,12 @@ func apply_equipment_bonuses(bonuses: Dictionary) -> void:
|
||||
critical_damage_bonus = float(bonuses.get("critical_damage_percent", 0.0)) / 100.0
|
||||
energy_regeneration = base_energy_regeneration + float(bonuses.get("energy_regeneration", 0.0))
|
||||
shield_regeneration = base_shield_regeneration + float(bonuses.get("shield_regeneration", 0.0))
|
||||
energy_regeneration = EQUIPMENT_BALANCE.energy_regen(energy_regeneration,max_energy)
|
||||
shield_regeneration = EQUIPMENT_BALANCE.shield_regen(shield_regeneration,max_shields)
|
||||
var leech_capacity := max_shields*EQUIPMENT_BALANCE.SHIELD_LEECH_FRACTION
|
||||
var polarization_capacity := max_energy*EQUIPMENT_BALANCE.POLARIZATION_FRACTION
|
||||
_shield_leech_budget = leech_capacity if _shield_leech_budget < 0 else minf(_shield_leech_budget,leech_capacity)
|
||||
_polarization_budget = polarization_capacity if _polarization_budget < 0 else minf(_polarization_budget,polarization_capacity)
|
||||
reverse_max_speed = max_speed * 0.5
|
||||
max_strafe_speed = max_speed * 0.5
|
||||
hull = minf(hull, max_hull)
|
||||
@ -169,6 +180,13 @@ func take_damage(amount: float, _critical: bool = false, damage_type: int = Dama
|
||||
var remaining_damage: float = hit["damage"]
|
||||
var shield_damage := minf(shields, remaining_damage)
|
||||
shields -= shield_damage
|
||||
if shield_damage > 0.0:
|
||||
var shield: ItemInstance = $ShipInventory.get_item("module",0)
|
||||
if shield != null and "polarization" in shield.special_orbs:
|
||||
var recovered := minf(max_shields*0.05,minf(shield_damage,maxf(0.0,_polarization_budget)))
|
||||
recovered = minf(recovered,maxf(0.0,max_energy-energy))
|
||||
energy += recovered
|
||||
_polarization_budget -= recovered
|
||||
remaining_damage -= shield_damage
|
||||
hull = maxf(0.0, hull - remaining_damage)
|
||||
if shield_damage > 0.0 and shields > 0.0:
|
||||
@ -180,7 +198,10 @@ func take_damage(amount: float, _critical: bool = false, damage_type: int = Dama
|
||||
func restore_shields_from_damage(actual_damage: float) -> void:
|
||||
if _is_destroyed or actual_damage <= 0.0 or shield_on_damage_percent <= 0.0 or GameState.captain.is_skill_active("power_redirect"):
|
||||
return
|
||||
shields = minf(max_shields, shields + actual_damage * shield_on_damage_percent / 100.0)
|
||||
var recovered := minf(actual_damage*minf(10.0,shield_on_damage_percent)/100.0,maxf(0.0,_shield_leech_budget))
|
||||
recovered = minf(recovered,maxf(0.0,max_shields-shields))
|
||||
shields += recovered
|
||||
_shield_leech_budget -= recovered
|
||||
_update_shield_visual()
|
||||
|
||||
func get_passive_extension_value(effect: String) -> float:
|
||||
@ -229,7 +250,8 @@ func _sync_virus_allies() -> void:
|
||||
ally.call("set_virus_controlled", false)
|
||||
|
||||
func get_evasion_chance() -> float:
|
||||
return GameState.captain.get_skill_bonus("tactical_maneuver") if GameState.captain.is_skill_active("tactical_maneuver") else 0.0
|
||||
var base := GameState.captain.get_skill_bonus("tactical_maneuver") if GameState.captain.is_skill_active("tactical_maneuver") else 0.0
|
||||
return 1.0-(1.0-base)*0.95 if preload("res://scripts/planets/alien_research.gd").learned("weapons_b") else base
|
||||
|
||||
func get_energy_regeneration() -> float:
|
||||
return (energy_regeneration + get_solar_regeneration()) * GameState.captain.energy_regeneration_multiplier()
|
||||
@ -245,7 +267,7 @@ func get_weapon_damage_multiplier() -> float:
|
||||
return damage_multiplier * (1.0 + GameState.captain.get_skill_bonus("gunner"))
|
||||
|
||||
func get_critical_chance() -> float:
|
||||
return clampf(critical_chance_bonus + GameState.captain.get_skill_bonus("sharp_eye"), 0.0, 1.0)
|
||||
return clampf(critical_chance_bonus + GameState.captain.get_skill_bonus("sharp_eye"), 0.0, 0.60)
|
||||
|
||||
func get_critical_multiplier() -> float:
|
||||
return CAPTAIN_BALANCE.BASE_CRITICAL_MULTIPLIER * (1.0 + maxf(0.0, critical_damage_bonus))
|
||||
@ -330,6 +352,7 @@ func _physics_process(delta: float) -> void:
|
||||
if _is_destroyed or is_docked:
|
||||
_mini_boost_active = false
|
||||
return
|
||||
_refill_recovery_budgets(delta)
|
||||
_contact_damage_timer = maxf(0.0, _contact_damage_timer - delta)
|
||||
energy = minf(max_energy, energy + get_energy_regeneration() * delta)
|
||||
var was_mini_boosting := _mini_boost_active
|
||||
@ -380,7 +403,7 @@ func is_mini_boosting() -> bool:
|
||||
return _mini_boost_active and not _is_destroyed and not is_docked
|
||||
|
||||
func _get_allowed_speed() -> float:
|
||||
return maxf(max_speed + _boost_speed, max_speed * 2.0 if _mini_boost_active else max_speed + _mini_boost_coast_speed)
|
||||
return minf(EQUIPMENT_BALANCE.MAX_BOOST_SPEED,maxf(max_speed + _boost_speed, max_speed * 2.0 if _mini_boost_active else max_speed + _mini_boost_coast_speed))
|
||||
|
||||
func has_free_mini_boost() -> bool:
|
||||
var engine: ItemInstance = $ShipInventory.get_item("module",2)
|
||||
@ -397,13 +420,25 @@ func _update_mini_boost(delta: float) -> void:
|
||||
return
|
||||
if _mini_boost_exhausted or Input.is_action_pressed("ship_brake"):
|
||||
return
|
||||
var cost := MINI_BOOST_ENERGY_PER_SECOND * delta
|
||||
var cost := get_mini_boost_energy_cost() * delta
|
||||
if energy < cost or energy <= 0.0:
|
||||
_mini_boost_exhausted = true
|
||||
return
|
||||
energy = maxf(0.0, energy - cost)
|
||||
_mini_boost_active = true
|
||||
|
||||
func get_mini_boost_energy_cost() -> float:
|
||||
if has_free_mini_boost(): return 0.0
|
||||
var engine: ItemInstance = $ShipInventory.get_item("module",2)
|
||||
var base_cost := maxf(MINI_BOOST_ENERGY_PER_SECOND,max_energy*0.22)
|
||||
return base_cost * (0.5 if engine != null and "atomic_engine" in engine.special_orbs else 1.0)
|
||||
|
||||
func _refill_recovery_budgets(delta: float) -> void:
|
||||
var leech := max_shields*EQUIPMENT_BALANCE.SHIELD_LEECH_FRACTION
|
||||
var polarization := max_energy*EQUIPMENT_BALANCE.POLARIZATION_FRACTION
|
||||
_shield_leech_budget = minf(leech,maxf(0.0,_shield_leech_budget)+leech*delta)
|
||||
_polarization_budget = minf(polarization,maxf(0.0,_polarization_budget)+polarization*delta)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.is_action("ship_mini_boost"):
|
||||
# Tab controls the engine rather than moving focus between HUD buttons.
|
||||
@ -414,7 +449,7 @@ func get_solar_regeneration() -> float:
|
||||
return 0.0
|
||||
var distance := global_position.distance_to(charging_star.global_position)
|
||||
var strength := 1.0 - smoothstep(solar_charge_full_radius, solar_charge_radius, distance)
|
||||
return max_energy * solar_charge_fraction_per_second * strength
|
||||
return max_energy * minf(solar_charge_fraction_per_second,0.08) * strength
|
||||
|
||||
func start_boost(speed_bonus: float, acceleration_time: float) -> void:
|
||||
_boost_target = max_speed * maxf(0.0, speed_bonus)
|
||||
|
||||
@ -116,7 +116,7 @@ func _physics_process(delta: float) -> void:
|
||||
_:
|
||||
if _shot_timers[slot_index] <= 0.0 and _consume_shot_resource(item):
|
||||
_fire_projectile(item)
|
||||
_shot_timers[slot_index] = definition.fire_interval
|
||||
_shot_timers[slot_index] = item.get_fire_interval()
|
||||
for slot_index in ShipInventory.WEAPON_SLOT_COUNT:
|
||||
_fire_just_pressed[slot_index] = false
|
||||
|
||||
@ -155,6 +155,7 @@ func _fire_projectile(item: ItemInstance) -> void:
|
||||
ship.get_parent().add_child(projectile)
|
||||
projectile.reset_physics_interpolation()
|
||||
projectile.set("shield_piercing", item.shield_piercing())
|
||||
projectile.set("damage_type_override", item.get_damage_type())
|
||||
projectile.set("missile_homing", definition.missile_homing)
|
||||
WEAPON_AUDIO_SCRIPT.play(ship.get_parent(), muzzle_position, definition.weapon_kind)
|
||||
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
||||
@ -199,10 +200,10 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
||||
_break_cloak_on_fire()
|
||||
if not result.is_empty():
|
||||
var hit: Dictionary = ship.call("roll_weapon_hit", item)
|
||||
_apply_damage(result.get("collider"), float(hit["damage"]), bool(hit["critical"]), item.shield_piercing())
|
||||
_apply_damage(result.get("collider"), float(hit["damage"]), bool(hit["critical"]), item.shield_piercing(), item.get_damage_type())
|
||||
_spawn_laser_impact(endpoint)
|
||||
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
|
||||
_shot_timers[slot_index] = definition.fire_interval
|
||||
_shot_timers[slot_index] = item.get_fire_interval()
|
||||
|
||||
func _break_cloak_on_fire() -> void:
|
||||
ship.landing_combat_lock = 6.0
|
||||
@ -232,13 +233,8 @@ func _consume_energy(amount: float) -> bool:
|
||||
ship.set("energy", current_energy - amount)
|
||||
return true
|
||||
|
||||
func _apply_damage(target: Variant, damage: float, critical: bool = false, piercing: float = 0.0) -> void:
|
||||
if target is Node and target.has_method("take_piercing_weapon_hit"):
|
||||
target.call("take_piercing_weapon_hit", damage, critical, DamageTypes.Type.ENERGY, piercing)
|
||||
elif target is Node and target.has_method("take_weapon_hit"):
|
||||
target.call("take_weapon_hit", damage, critical, DamageTypes.Type.ENERGY)
|
||||
elif target is Node and target.has_method("take_damage"):
|
||||
target.call("take_damage", damage)
|
||||
func _apply_damage(target: Variant, damage: float, critical: bool = false, piercing: float = 0.0, damage_type: int = DamageTypes.Type.ENERGY) -> void:
|
||||
preload("res://scripts/game/alien_weapon_effects.gd").apply_hit(target,damage,critical,damage_type,piercing,{})
|
||||
|
||||
func _spawn_laser_impact(at_position: Vector2) -> void:
|
||||
var effect := IMPACT_EFFECT_SCENE.instantiate() as Node2D
|
||||
|
||||
@ -29,8 +29,16 @@ var _already_hit: Dictionary = {}
|
||||
var _impact_handled := false
|
||||
var _engine_time := 0.0
|
||||
var shield_piercing := 0.0
|
||||
var damage_type_override := -1
|
||||
var _smoke_emission_timer := 0.0
|
||||
var _smoke_puffs: Array[Dictionary] = []
|
||||
var source_ship: Node2D
|
||||
var research_disabled_system := -2
|
||||
var _interception_checked := false
|
||||
var _harmless_intercepted := false
|
||||
|
||||
func get_damage_type() -> int:
|
||||
return damage_type_override if damage_type_override >= 0 else DamageTypes.for_weapon(weapon_kind)
|
||||
|
||||
func _ready() -> void:
|
||||
body_entered.connect(_on_body_entered)
|
||||
@ -60,6 +68,12 @@ func configure(
|
||||
homing_target = target
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if enemy_shot and (not is_instance_valid(source_ship) or not bool(source_ship.get("virus_controlled"))):
|
||||
if preload("res://scripts/planets/alien_research.gd").learned("weapons_b_next_next_next"): collision_mask |= 4
|
||||
if weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||
var player := get_parent().get_node_or_null("PlayerShip") as Node2D
|
||||
if is_instance_valid(player) and global_position.distance_to(player.global_position) <= 240.0:
|
||||
if try_intercept(player): return
|
||||
var travel_velocity := direction * projectile_speed
|
||||
if weapon_kind == ItemDefinition.WeaponKind.MISSILE and missile_homing:
|
||||
_flight_time += delta
|
||||
@ -109,11 +123,13 @@ func _physics_process(delta: float) -> void:
|
||||
var step := displacement.length()
|
||||
var previous_position := global_position
|
||||
var next_position := previous_position + displacement
|
||||
var exclusions: Array[RID] = [get_rid()]
|
||||
if is_instance_valid(source_ship) and source_ship is CollisionObject2D: exclusions.append(source_ship.get_rid())
|
||||
var query := PhysicsRayQueryParameters2D.create(
|
||||
previous_position,
|
||||
next_position,
|
||||
collision_mask,
|
||||
[get_rid()]
|
||||
exclusions
|
||||
)
|
||||
var result := get_world_2d().direct_space_state.intersect_ray(query)
|
||||
if not result.is_empty():
|
||||
@ -249,6 +265,9 @@ func _on_area_entered(area: Area2D) -> void:
|
||||
_hit_target(area)
|
||||
|
||||
func _hit_target(target: Node) -> void:
|
||||
if target == source_ship: return
|
||||
if enemy_shot and weapon_kind == ItemDefinition.WeaponKind.MISSILE and target is Node2D and target.name == "PlayerShip":
|
||||
if try_intercept(target): return
|
||||
if _impact_handled:
|
||||
return
|
||||
_impact_handled = true
|
||||
@ -274,37 +293,61 @@ func _spawn_impact_effect(is_explosion: bool) -> void:
|
||||
effect.call("configure", is_explosion, effect_color)
|
||||
|
||||
func _explode_at_target(impact_target: Node) -> void:
|
||||
if _harmless_intercepted: return
|
||||
_apply_damage_once(impact_target)
|
||||
var explosion_shape := CircleShape2D.new()
|
||||
explosion_shape.radius = 120.0
|
||||
if not enemy_shot and preload("res://scripts/planets/alien_research.gd").learned("weapons_a_next_next"): explosion_shape.radius += 10.0
|
||||
var query := PhysicsShapeQueryParameters2D.new()
|
||||
query.shape = explosion_shape
|
||||
query.transform = Transform2D(0.0, global_position)
|
||||
# Player blasts can damage infected ships; converted fire still targets hostiles.
|
||||
var converted_shot := enemy_shot and (collision_mask & 4) != 0
|
||||
query.collision_mask = 4 if converted_shot else (66 if enemy_shot else 68)
|
||||
if enemy_shot and preload("res://scripts/planets/alien_research.gd").learned("weapons_b_next_next_next"): query.collision_mask |= 4
|
||||
for result in get_world_2d().direct_space_state.intersect_shape(query, 32):
|
||||
_apply_damage_once(result.get("collider"))
|
||||
|
||||
func _apply_damage_once(target: Variant) -> void:
|
||||
if _harmless_intercepted: return
|
||||
if not (target is Node):
|
||||
return
|
||||
if target == source_ship: return
|
||||
if enemy_shot and target.is_in_group("asteroids"):
|
||||
return
|
||||
var instance_id: int = target.get_instance_id()
|
||||
if _already_hit.has(instance_id):
|
||||
return
|
||||
if not enemy_shot:
|
||||
if research_disabled_system == -2: research_disabled_system = preload("res://scripts/game/alien_weapon_effects.gd").roll_disabled_system()
|
||||
preload("res://scripts/game/alien_weapon_effects.gd").apply_hit(target,damage,is_critical,get_damage_type(),shield_piercing,_already_hit,weapon_kind != ItemDefinition.WeaponKind.MISSILE,research_disabled_system)
|
||||
if weapon_kind == ItemDefinition.WeaponKind.GAMMA and get_damage_type() == DamageTypes.Type.RADIATION and not target.is_queued_for_deletion():
|
||||
preload("res://scripts/game/radiation_status.gd").apply(target,damage*0.25,true,shield_piercing)
|
||||
return
|
||||
_already_hit[instance_id] = true
|
||||
if enemy_shot and target is CharacterBody2D and target.has_method("take_damage"):
|
||||
target.call("take_damage", damage, is_critical, DamageTypes.for_weapon(weapon_kind))
|
||||
target.call("take_damage", damage, is_critical, get_damage_type())
|
||||
elif target.has_method("take_piercing_weapon_hit"):
|
||||
target.call("take_piercing_weapon_hit", damage, is_critical, DamageTypes.for_weapon(weapon_kind), shield_piercing)
|
||||
target.call("take_piercing_weapon_hit", damage, is_critical, get_damage_type(), shield_piercing)
|
||||
elif target.has_method("take_weapon_hit"):
|
||||
target.call("take_weapon_hit", damage, is_critical, DamageTypes.for_weapon(weapon_kind))
|
||||
target.call("take_weapon_hit", damage, is_critical, get_damage_type())
|
||||
elif target.has_method("take_damage"):
|
||||
if target is CharacterBody2D:
|
||||
target.call("take_damage", damage, is_critical, DamageTypes.for_weapon(weapon_kind))
|
||||
target.call("take_damage", damage, is_critical, get_damage_type())
|
||||
else:
|
||||
target.call("take_damage", damage)
|
||||
if weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||
if weapon_kind == ItemDefinition.WeaponKind.GAMMA and get_damage_type() == DamageTypes.Type.RADIATION:
|
||||
preload("res://scripts/game/radiation_status.gd").apply(target, damage * 0.25, not enemy_shot, shield_piercing if not enemy_shot else 0.0)
|
||||
|
||||
func try_intercept(player: Node2D, roll := -1.0) -> bool:
|
||||
if _interception_checked or _impact_handled or not enemy_shot or weapon_kind != ItemDefinition.WeaponKind.MISSILE: return false
|
||||
if not preload("res://scripts/planets/alien_research.gd").learned("weapons_b_next"): return false
|
||||
if bool(player.get("_is_destroyed")) or bool(player.get("is_docked")): return false
|
||||
if global_position.distance_to(player.global_position) > 240.0: return false
|
||||
_interception_checked = true
|
||||
if (randf() if roll < 0 else roll) >= 0.25: return false
|
||||
_impact_handled = true
|
||||
_harmless_intercepted = true
|
||||
_spawn_impact_effect(true)
|
||||
queue_free()
|
||||
return true
|
||||
|
||||
@ -5,10 +5,10 @@ const NAMES := {"iron": "Żelazo", "titanium": "Tytan", "uranium": "Uran", "alum
|
||||
const COLORS := {"iron": Color("c5b5a0"), "titanium": Color("9ecbe9"), "uranium": Color("a9ef83"), "aluminium": Color("dce8ef"), "sulfur": Color("f4d466")}
|
||||
const IDS := ["parts", "ammunition", "missile", "energy_bank", "irradiated_ammunition"]
|
||||
# Base asteroid yield increased from mean 2 (1-3) to mean 6 (5-7).
|
||||
# Triple mineral costs to preserve base crafts per asteroid; miner remains a bonus.
|
||||
# Most recipes retain triple mineral costs; ordinary ammunition uses its own balance.
|
||||
const DATA := {
|
||||
"parts": {"name": "Części", "amount": 1, "cost": {"titanium": 3, "iron": 3, "aluminium": 3}},
|
||||
"ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 3, "sulfur": 3}},
|
||||
"ammunition": {"name": "Amunicja", "amount": 10, "cost": {"iron": 1, "sulfur": 1}},
|
||||
"missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 3, "sulfur": 3, "uranium": 3}},
|
||||
"energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 3, "aluminium": 3}},
|
||||
"irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 3, "uranium": 3}},
|
||||
|
||||
23
scripts/items/equipment_balance.gd
Normal file
@ -0,0 +1,23 @@
|
||||
extends RefCounted
|
||||
const MAX_SPEED := 240.0
|
||||
const MAX_BOOST_SPEED := 600.0
|
||||
const ENERGY_REGEN_FRACTION := 0.15
|
||||
const SHIELD_REGEN_FRACTION := 0.03
|
||||
const SHIELD_LEECH_FRACTION := 0.05
|
||||
const POLARIZATION_FRACTION := 0.05
|
||||
const BONUS_CAPS := {"speed_percent":60.0,"damage_percent":100.0,"critical_chance_percent":40.0,"critical_damage_percent":100.0,"shield_on_damage_percent":10.0}
|
||||
|
||||
static func bonuses(raw: Dictionary) -> Dictionary:
|
||||
var result := raw.duplicate()
|
||||
for key in BONUS_CAPS:
|
||||
result[key] = clampf(float(result.get(key,0.0)),0.0,BONUS_CAPS[key])
|
||||
return result
|
||||
|
||||
static func speed(base: float, flat: float, percent: float) -> float:
|
||||
return minf(MAX_SPEED,(base+flat)*(1.0+clampf(percent,0.0,60.0)/100.0))
|
||||
|
||||
static func energy_regen(value: float, maximum: float) -> float:
|
||||
return minf(value,maxf(5.0,maximum*ENERGY_REGEN_FRACTION))
|
||||
|
||||
static func shield_regen(value: float, maximum: float) -> float:
|
||||
return minf(value,maximum*SHIELD_REGEN_FRACTION)
|
||||
1
scripts/items/equipment_balance.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://d0hrl5rjlb3ga
|
||||
@ -14,7 +14,7 @@ func _ready() -> void:
|
||||
_label.size.x = 240
|
||||
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_label.add_theme_font_size_override("font_size",12)
|
||||
_label.text = preload("res://scripts/items/special_orbs.gd").NAMES[str(payload.orb)] if payload.has("orb") else "Mapa wyprawy · poz. %d" % int(payload.map.level)
|
||||
_label.text = preload("res://scripts/items/special_orbs.gd").NAMES[str(payload.orb)] if payload.has("orb") else "Mapa odległych systemów · poz. %d" % int(payload.map.level)
|
||||
add_child(_label)
|
||||
func _process(delta: float) -> void:
|
||||
_time += delta
|
||||
|
||||
@ -20,8 +20,9 @@ class_name ItemAffixDefinition
|
||||
@export_range(1, 1000) var maximum_level: int = 1000
|
||||
@export var minimum_value_level_1: float = 1.0
|
||||
@export var maximum_value_level_1: float = 3.0
|
||||
@export var minimum_value_level_1000: float = 25.0
|
||||
@export var maximum_value_level_1000: float = 50.0
|
||||
# Caps apply to percentages and cooldown reductions; flat stats keep growing.
|
||||
@export var minimum_value_cap: float = 25.0
|
||||
@export var maximum_value_cap: float = 50.0
|
||||
|
||||
@export_group("Zasady łączenia")
|
||||
# At most one affix from a nonempty group can appear on the same item.
|
||||
@ -52,7 +53,10 @@ func roll_value(level: int) -> float:
|
||||
return randf_range(bounds.x, bounds.y)
|
||||
|
||||
func get_value_range(level: int) -> Vector2:
|
||||
var progression := float(clampi(level, 1, 1000) - 1) / 999.0
|
||||
var lower := lerpf(minimum_value_level_1, minimum_value_level_1000, progression)
|
||||
var upper := lerpf(maximum_value_level_1, maximum_value_level_1000, progression)
|
||||
var scale_factor := preload("res://scripts/levels/level_balance.gd").item_stat_scale(clampi(level, 1, 1000))
|
||||
var lower := minimum_value_level_1 * scale_factor
|
||||
var upper := maximum_value_level_1 * scale_factor
|
||||
if unit == "%" or stat_key == "missile_cooldown_reduction":
|
||||
lower = minf(lower, minimum_value_cap)
|
||||
upper = minf(upper, maximum_value_cap)
|
||||
return Vector2(minf(lower, upper), maxf(lower, upper))
|
||||
|
||||
@ -59,7 +59,7 @@ static func generate_for_definition(definition: ItemDefinition, level: int, enem
|
||||
static func roll_base_values(item: ItemInstance, level: int) -> void:
|
||||
var definition := item.definition
|
||||
item.item_level = clampi(level, 1, 1000)
|
||||
var scale_factor := 1.0 + 0.05 * (item.item_level - 1)
|
||||
var scale_factor := preload("res://scripts/levels/level_balance.gd").item_stat_scale(item.item_level)
|
||||
item.primary_value_override = definition.primary_value * scale_factor * randf_range(0.8, 1.2)
|
||||
if definition.primary_stat == "max_speed":
|
||||
item.primary_value_override = randf_range(1.0, 5.0) * scale_factor
|
||||
|
||||
@ -12,6 +12,20 @@ var _instance_ability: ActiveAbility
|
||||
func shield_piercing() -> float:
|
||||
return 0.5 if definition != null and definition.category == ItemDefinition.Category.WEAPON and "piercing" in special_orbs else 0.0
|
||||
|
||||
func get_damage_type() -> int:
|
||||
if "electricity" in special_orbs: return DamageTypes.Type.ENERGY
|
||||
if "radiation" in special_orbs: return DamageTypes.Type.RADIATION
|
||||
if "kinetic" in special_orbs: return DamageTypes.Type.KINETIC
|
||||
return definition.get_damage_type() if definition != null else DamageTypes.Type.KINETIC
|
||||
|
||||
func attack_speed_multiplier() -> float:
|
||||
return 1.0 + clampf(get_stat_total("attack_speed_percent"), 0.0, 25.0) / 100.0
|
||||
|
||||
func get_fire_interval() -> float:
|
||||
if definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||
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()
|
||||
|
||||
func is_passive_extension() -> bool:
|
||||
return definition != null and definition.category == ItemDefinition.Category.ACTIVE_EXTENSION and not definition.passive_effect.is_empty()
|
||||
|
||||
@ -169,7 +183,7 @@ func get_tooltip_text() -> String:
|
||||
if definition.energy_regeneration_bonus > 0.0:
|
||||
lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))
|
||||
if definition.category == ItemDefinition.Category.WEAPON:
|
||||
lines.append("Typ obrażeń: %s" % DamageTypes.NAMES[definition.get_damage_type()])
|
||||
lines.append("Typ obrażeń: %s" % DamageTypes.NAMES[get_damage_type()])
|
||||
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
|
||||
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
||||
lines.append("Zasięg: %.0f px • Amunicja: 1 / strzał" % definition.weapon_range)
|
||||
@ -179,7 +193,7 @@ func get_tooltip_text() -> String:
|
||||
lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii")
|
||||
else:
|
||||
lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()])
|
||||
lines.append("Odstęp strzałów: %.2f s" % maxf(definition.fire_interval, definition.weapon_cooldown))
|
||||
lines.append("Odstęp strzałów: %.2f s" % get_fire_interval())
|
||||
if not additional_modifiers.is_empty():
|
||||
lines.append("\nADDONY (aktywne po zamontowaniu)")
|
||||
for modifier in additional_modifiers:
|
||||
@ -189,7 +203,9 @@ func get_tooltip_text() -> String:
|
||||
for orb in special_orbs:
|
||||
if orb == "piercing": lines.append("\nORB PRZEBICIA · 50% obrażeń omija tarcze. Tylko ta broń.")
|
||||
elif orb == "booster": lines.append("\nORB DOPALACZA · TAB bez kosztu energii. Działa po zamontowaniu silnika.")
|
||||
elif orb in ["electricity", "radiation", "kinetic", "atomic_engine", "polarization"]:
|
||||
lines.append("\n%s · %s" % [preload("res://scripts/items/special_orbs.gd").NAMES[orb], preload("res://scripts/items/special_orbs.gd").DESCRIPTIONS[orb]])
|
||||
return "\n".join(lines)
|
||||
|
||||
static func stat_name(key: String) -> String:
|
||||
return str({"luck_percent": "Szczęście", "kinetic_absorption": "Absorpcja kinetyczna", "energy_absorption": "Absorpcja energetyczna", "radiation_absorption": "Absorpcja radiacyjna", "missile_cooldown_reduction": "Skrócenie cooldownu rakiet", "scanner_range": "Zasięg skanera", "shield_on_damage_percent": "Odzysk tarcz z obrażeń", "damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key))
|
||||
return str({"attack_speed_percent":"Szybkostrzelność", "luck_percent": "Szczęście", "kinetic_absorption": "Absorpcja kinetyczna", "energy_absorption": "Absorpcja energetyczna", "radiation_absorption": "Absorpcja radiacyjna", "missile_cooldown_reduction": "Skrócenie cooldownu rakiet", "scanner_range": "Zasięg skanera", "shield_on_damage_percent": "Odzysk tarcz z obrażeń", "damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key))
|
||||
|
||||
@ -15,7 +15,7 @@ const MISSILE_DEFINITIONS := [preload("res://resources/items/missile_launcher.tr
|
||||
const MODULE_SLOT_COUNT := 5
|
||||
const EXTENSION_SLOT_COUNT := 5
|
||||
const CARGO_SLOT_COST := {"iron": 100, "aluminium": 50}
|
||||
const WEAPON_UNLOCK_COSTS := [100, 500]
|
||||
const WEAPON_UNLOCK_COSTS := [30, 200]
|
||||
const EXTENSION_UNLOCK_BASE_COST := 40
|
||||
var unlocked_weapon_slots := 1
|
||||
var unlocked_extension_slots := 1
|
||||
@ -338,7 +338,7 @@ func get_missile_cooldown(item: ItemInstance) -> float:
|
||||
for equipped in weapon_slots.slice(0,unlocked_weapon_slots) + module_slots:
|
||||
if equipped != null and equipped != item:
|
||||
reduction += equipped.get_stat_total("missile_cooldown_reduction")
|
||||
return maxf(1.0, 5.0 - reduction)
|
||||
return maxf(1.0, maxf(1.0, 5.0 - reduction) / item.attack_speed_multiplier())
|
||||
|
||||
func consume_missile() -> bool:
|
||||
if missile_ammunition <= 0:
|
||||
@ -452,19 +452,24 @@ func _comparison_bonuses(incoming: ItemInstance = null, outgoing: ItemInstance =
|
||||
_add_comparison_bonuses(bonuses, item)
|
||||
if incoming != null:
|
||||
_add_comparison_bonuses(bonuses, incoming)
|
||||
return bonuses
|
||||
return preload("res://scripts/items/equipment_balance.gd").bonuses(bonuses)
|
||||
|
||||
func _add_comparison_bonuses(bonuses: Dictionary, item: ItemInstance) -> void:
|
||||
for key in ["kinetic_absorption", "energy_absorption", "radiation_absorption", "max_hull", "max_shields", "max_energy", "max_speed", "scanner_range", "hull_percent", "shield_percent", "speed_percent", "damage_percent", "critical_damage_percent", "critical_chance_percent", "shield_on_damage_percent", "energy_regeneration", "shield_regeneration", "missile_cooldown_reduction", "experience_percent", "credits_percent", "luck_percent"]:
|
||||
bonuses[key] = float(bonuses.get(key, 0.0)) + item.get_stat_total(key)
|
||||
|
||||
func _comparison_stats(bonuses: Dictionary) -> Dictionary:
|
||||
bonuses = preload("res://scripts/items/equipment_balance.gd").bonuses(bonuses)
|
||||
var stats := {}
|
||||
for data in [["max_hull", "base_max_hull", "hull_percent", "Pancerz", "pkt"], ["max_shields", "base_max_shields", "shield_percent", "Tarcze", "pkt"], ["max_speed", "base_max_speed", "speed_percent", "Prędkość", "px/s"]]:
|
||||
var value := (float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0))) * (1.0 + float(bonuses.get(data[2], 0.0)) / 100.0)
|
||||
stats[data[0]] = {"value": value, "name": data[3], "unit": data[4]}
|
||||
stats.max_speed.value = minf(240.0,float(stats.max_speed.value))
|
||||
for data in [["max_energy", "base_max_energy", "Energia", "pkt"], ["scanner_range", "base_scanner_range", "Zasięg skanera", "px"], ["energy_regeneration", "base_energy_regeneration", "Regeneracja energii", "pkt/s"], ["shield_regeneration", "base_shield_regeneration", "Regeneracja tarcz", "pkt/s"]]:
|
||||
stats[data[0]] = {"value": float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0)), "name": data[2], "unit": data[3]}
|
||||
var balance := preload("res://scripts/items/equipment_balance.gd")
|
||||
stats.energy_regeneration.value = balance.energy_regen(float(stats.energy_regeneration.value),float(stats.max_energy.value))
|
||||
stats.shield_regeneration.value = balance.shield_regen(float(stats.shield_regeneration.value),float(stats.max_shields.value))
|
||||
# These stats are bonuses in their own right, not rewards or hit scenarios.
|
||||
for data in [["kinetic_absorption", "Absorpcja kinetyczna"], ["energy_absorption", "Absorpcja energetyczna"], ["radiation_absorption", "Absorpcja radiacyjna"], ["damage_percent", "Obrażenia broni"], ["critical_damage_percent", "Obrażenia krytyczne"], ["critical_chance_percent", "Szansa na krytyk"], ["shield_on_damage_percent", "Odzysk tarcz z obrażeń"], ["experience_percent", "Doświadczenie"], ["credits_percent", "Kredyty"]]:
|
||||
stats[data[0]] = {"value": float(bonuses.get(data[0], 0.0)), "name": data[1], "unit": "%"}
|
||||
@ -498,12 +503,13 @@ func _weapon_flat_damage_addons(item: ItemInstance) -> float:
|
||||
func _weapon_dps(item: ItemInstance, bonuses: Dictionary) -> float:
|
||||
if item == null or item.definition == null:
|
||||
return 0.0
|
||||
bonuses = preload("res://scripts/items/equipment_balance.gd").bonuses(bonuses)
|
||||
var damage_factor := (1.0 + float(bonuses.get("damage_percent", 0.0)) / 100.0) * (1.0 + GameState.captain.get_skill_bonus("gunner"))
|
||||
var chance := clampf(GameState.captain.get_skill_bonus("sharp_eye") + float(bonuses.get("critical_chance_percent", 0.0)) / 100.0, 0.0, 1.0)
|
||||
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 interval := item.definition.fire_interval
|
||||
var interval := item.get_fire_interval()
|
||||
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||
interval = maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0)))
|
||||
interval = maxf(1.0, maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0))) / item.attack_speed_multiplier())
|
||||
return item.get_stat_total("damage") * damage_factor * (1.0 + chance * (critical_factor - 1.0)) / maxf(0.001, interval)
|
||||
|
||||
func consume_irradiated_ammunition() -> bool:
|
||||
@ -539,8 +545,8 @@ func get_weapon_dps(slot_index: int) -> float:
|
||||
if not is_slot_unlocked("weapon", slot_index): return 0.0
|
||||
var item := get_item("weapon", slot_index)
|
||||
var value := _weapon_dps(item, _comparison_bonuses())
|
||||
if item != null and item.definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||
value *= 1.0 + 0.25 * minf(preload("res://scripts/game/radiation_status.gd").DURATION, item.definition.fire_interval)
|
||||
if item != null and item.definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA and item.get_damage_type() == DamageTypes.Type.RADIATION:
|
||||
value *= 1.0 + 0.25 * minf(preload("res://scripts/game/radiation_status.gd").DURATION, item.get_fire_interval())
|
||||
return value
|
||||
|
||||
func get_total_dps() -> float:
|
||||
|
||||
@ -1,23 +1,38 @@
|
||||
extends RefCounted
|
||||
# Every orb is consumed once. Further advancement requires another orb.
|
||||
const IDS := ["piercing", "booster", "advancement"]
|
||||
const NAMES := {"piercing":"Orb przebicia", "booster":"Orb dopalacza", "advancement":"Orb awansu"}
|
||||
const DESCRIPTIONS := {"piercing":"50% obrażeń broni omija tarcze i trafia bezpośrednio w kadłub. Tylko broń.", "booster":"Dopalacz pod TAB nie zużywa energii i działa również przy pustym generatorze. Tylko silnik.", "advancement":"Awansuje przedmiot o 1 poziom i ponownie losuje jego parametry oraz wartości obecnych addonów. Zachowuje ich typy, liczbę i efekty orbów. Wyniki losowania mogą być niższe. Jedna sztuka daje jeden awans. Kolejny awans wymaga nowego orba (limit poziomu 1000). Nie działa na materiały zużywalne."}
|
||||
const IDS := ["piercing", "booster", "advancement", "replication", "electricity", "radiation", "kinetic", "atomic_engine", "polarization"]
|
||||
const COMMON_IDS := ["piercing", "booster", "advancement", "electricity", "radiation", "kinetic", "atomic_engine", "polarization"]
|
||||
const DAMAGE_ORBS := ["electricity", "radiation", "kinetic"]
|
||||
const NAMES := {"piercing":"Orb przebicia", "booster":"Orb dopalacza", "advancement":"Orb awansu", "replication":"Orb replikacji", "electricity":"Orb elektryczności", "radiation":"Orb radiacji", "kinetic":"Orb kinetyczny", "atomic_engine":"Orb silników atomowych", "polarization":"Orb polaryzacyjny"}
|
||||
const DESCRIPTIONS := {"replication":"Tworzy dokładną, niezależną kopię wyposażenia w magazynie frachtowca. Oryginał zostaje. Kopiuje poziom, addony i efekty orbów. Drop: dodatkowe 5% z bossa dalekiej wyprawy.", "electricity":"Trwale zmienia typ obrażeń broni na energetyczny (jak laser). Nie zwiększa obrażeń ani kosztu strzału. Zastępuje poprzedni orb typu obrażeń.", "radiation":"Trwale zmienia typ obrażeń broni na radiacyjny. Nie zwiększa obrażeń ani kosztu strzału. Zastępuje poprzedni orb typu obrażeń.", "kinetic":"Trwale zmienia typ obrażeń broni na kinetyczny. Nie zwiększa obrażeń ani kosztu strzału. Zastępuje poprzedni orb typu obrażeń.", "atomic_engine":"Tylko silnik. Trwale zmniejsza zużycie energii dopalacza TAB o 50%. Nie sumuje się. Orb darmowego dopalacza ma pierwszeństwo.", "polarization":"Tylko tarcze. Trafienie w tarcze odnawia do 5% maksymalnych tarcz jako energię, nie więcej niż faktycznie stracone tarcze. Tempo odzysku: do 5% pojemności generatora/s, z rezerwą na jedno takie odnowienie. Uniki i trafienia w sam kadłub nie aktywują efektu.", "piercing":"50% obrażeń broni omija tarcze i trafia bezpośrednio w kadłub. Tylko broń.", "booster":"Dopalacz pod TAB nie zużywa energii i działa również przy pustym generatorze. Tylko silnik.", "advancement":"Awansuje przedmiot o 1 poziom i ponownie losuje jego parametry oraz wartości obecnych addonów. Zachowuje ich typy, liczbę i efekty orbów. Wyniki losowania mogą być niższe. Jedna sztuka daje jeden awans. Kolejny awans wymaga nowego orba (główna progresja do 120; 1000 to limit techniczny). Nie działa na materiały zużywalne."}
|
||||
static func icon(id: String) -> Texture2D:
|
||||
return load("res://assets/icons/orbs/%s.png" % id) as Texture2D
|
||||
return load("res://assets/icons/orbs/%s.%s" % [id, "png" if id in ["piercing", "booster", "advancement"] else "svg"]) as Texture2D
|
||||
static func accepts(item: ItemInstance, id: String) -> bool:
|
||||
if id not in IDS or item == null or item.definition == null: return false
|
||||
if id == "advancement": return preload("res://scripts/items/item_generator.gd").can_advance(item)
|
||||
if id == "replication": return item.definition.category != ItemDefinition.Category.CONSUMABLE
|
||||
if id in item.special_orbs: return false
|
||||
if id in DAMAGE_ORBS:
|
||||
return item.definition.category == ItemDefinition.Category.WEAPON
|
||||
if id == "polarization": return item.definition.category == ItemDefinition.Category.SHIELD
|
||||
return item.definition.category == (ItemDefinition.Category.WEAPON if id == "piercing" else ItemDefinition.Category.ENGINE)
|
||||
static func apply(progress: Resource, inventory: Node, item: ItemInstance, id: String) -> bool:
|
||||
if not accepts(item,id) or int(progress.orbs.get(id,0)) <= 0: return false
|
||||
if inventory == null or not bool(inventory.get_parent().get("is_docked")): return false
|
||||
var owned: Array = inventory.cargo + inventory.weapon_slots + inventory.module_slots + progress.freighter_storage
|
||||
var owned: Array = inventory.cargo.slice(0,inventory.unlocked_cargo_slots) + inventory.weapon_slots.slice(0,inventory.unlocked_weapon_slots) + inventory.module_slots + inventory.extension_slots.slice(0,inventory.unlocked_extension_slots) + progress.freighter_storage
|
||||
if not owned.has(item): return false
|
||||
if id == "advancement":
|
||||
if not preload("res://scripts/items/item_generator.gd").advance(item): return false
|
||||
elif id == "replication":
|
||||
var copy := item.duplicate() as ItemInstance
|
||||
copy.special_orbs = item.special_orbs.duplicate()
|
||||
copy.additional_modifiers = []
|
||||
for modifier in item.additional_modifiers:
|
||||
copy.additional_modifiers.append(modifier.duplicate() as ItemStatModifier if modifier != null else null)
|
||||
progress.freighter_storage.append(copy)
|
||||
else:
|
||||
if id in DAMAGE_ORBS:
|
||||
for previous in DAMAGE_ORBS: item.special_orbs.erase(previous)
|
||||
item.special_orbs.append(id)
|
||||
progress.orbs[id] = int(progress.orbs[id])-1
|
||||
item.emit_changed()
|
||||
|
||||
@ -92,7 +92,7 @@ static func wreck_reward_from_roll(roll: int) -> String:
|
||||
return "module"
|
||||
|
||||
func reward_scale() -> float:
|
||||
return 1.0 + 0.05 * (clampi(GameState.system_level, 1, 1000) - 1)
|
||||
return preload("res://scripts/levels/level_balance.gd").item_stat_scale(clampi(GameState.system_level, 1, 1000))
|
||||
|
||||
func _update_label(near: bool) -> void:
|
||||
if kind == "ruin":
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
extends RefCounted
|
||||
|
||||
const NORMAL_MAX_LEVEL := 60
|
||||
const NORMAL_MAX_LEVEL := 120
|
||||
const ABSORPTION_MID_LEVEL := 60
|
||||
const MAX_LEVEL := 120
|
||||
const HARD_STAT_MULTIPLIER := 2.0
|
||||
const ENEMY_GROWTH_PER_LEVEL := 0.10
|
||||
const ENDGAME_ENEMY_GROWTH_PER_LEVEL := 0.20
|
||||
const ITEM_GROWTH_PER_LEVEL := 0.07
|
||||
const BOSS_HULL_MULTIPLIER := 5.0
|
||||
const BOSS_SHIELD_MULTIPLIER := 3.0
|
||||
const BOSS_DAMAGE_MULTIPLIER := 0.5
|
||||
@ -33,16 +36,22 @@ static func enemy_tiers(level: int) -> Array[int]:
|
||||
return tiers
|
||||
|
||||
static func power_scale(level: int) -> float:
|
||||
return 1.0 + 0.05 * (maxi(level, 1) - 1)
|
||||
return item_stat_scale(level)
|
||||
|
||||
static func item_stat_scale(level: int) -> float:
|
||||
# Additive growth from level-one values, never compound interest.
|
||||
return 1.0 + ITEM_GROWTH_PER_LEVEL * (maxi(level, 1) - 1)
|
||||
|
||||
static func is_hard(level: int) -> bool:
|
||||
return level > NORMAL_MAX_LEVEL
|
||||
|
||||
static func enemy_stat_scale(level: int) -> float:
|
||||
return power_scale(level) * (HARD_STAT_MULTIPLIER if is_hard(level) else 1.0)
|
||||
var normal_steps := mini(maxi(level, 1), MAX_LEVEL) - 1
|
||||
var endgame_steps := maxi(0, level - MAX_LEVEL)
|
||||
return 1.0 + ENEMY_GROWTH_PER_LEVEL * normal_steps + ENDGAME_ENEMY_GROWTH_PER_LEVEL * endgame_steps
|
||||
|
||||
static func enemy_absorption_total(level: int) -> float:
|
||||
var clamped_level := clampi(level, 1, MAX_LEVEL)
|
||||
if clamped_level <= NORMAL_MAX_LEVEL:
|
||||
return lerpf(2.0, 30.0, pow(float(clamped_level - 1) / float(NORMAL_MAX_LEVEL - 1), 1.25))
|
||||
return lerpf(30.0, 60.0, float(clamped_level - NORMAL_MAX_LEVEL) / float(MAX_LEVEL - NORMAL_MAX_LEVEL))
|
||||
if clamped_level <= ABSORPTION_MID_LEVEL:
|
||||
return lerpf(2.0, 30.0, pow(float(clamped_level - 1) / float(ABSORPTION_MID_LEVEL - 1), 1.25))
|
||||
return lerpf(30.0, 60.0, float(clamped_level - ABSORPTION_MID_LEVEL) / float(MAX_LEVEL - ABSORPTION_MID_LEVEL))
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
extends RefCounted
|
||||
const ACTIVE_IDS := ["general", "weapons", "hull", "shields"]
|
||||
const ACTIVE_IDS := ["general", "weapons", "hull", "shields", "weapons_a", "weapons_a_next", "weapons_a_next_next", "weapons_a_next_next_next", "weapons_b", "weapons_b_next", "weapons_b_next_next", "weapons_b_next_next_next"]
|
||||
const BRANCHES := ["weapons", "hull", "shields"]
|
||||
const TITLES := {"general":"Ogólna wiedza", "weapons":"Wiedza o broni", "hull":"Wiedza o kadłubie", "shields":"Wiedza o tarczach"}
|
||||
const TITLES := {"general":"Ogólna wiedza", "weapons":"Wiedza o broni", "hull":"Wiedza o kadłubie", "shields":"Wiedza o tarczach", "weapons_a":"Słaby punkt", "weapons_a_next":"Celny strzał", "weapons_a_next_next":"Rykoszet", "weapons_a_next_next_next":"Strzał w 10", "weapons_b":"Manewr wyprzedzający", "weapons_b_next":"System obrony przed obcymi", "weapons_b_next_next":"Dezorientacja celowania", "weapons_b_next_next_next":"Zakłócenia systemów obcych"}
|
||||
|
||||
static func learned(id: String) -> bool:
|
||||
return id in ACTIVE_IDS and bool(GameState.captain.expedition.alien_skills.get(id,false))
|
||||
|
||||
static func path_color(id: String) -> Color:
|
||||
return Color("efa653") if "_a" in id else Color("79cba8")
|
||||
|
||||
static func parent(id: String) -> String:
|
||||
if id == "general": return ""
|
||||
return id.split("_")[0] if "_" in id else "general"
|
||||
return id.substr(0,id.rfind("_")) if "_" in id else "general"
|
||||
|
||||
static func cost(id: String) -> int:
|
||||
var depth := 2 if "_" in id else (0 if id == "general" else 1)
|
||||
return 10 + depth * (depth + 3)
|
||||
var depth := 0 if id == "general" else id.split("_").size()
|
||||
return 10 + depth * 5
|
||||
|
||||
static func title(id: String) -> String:
|
||||
return str(TITLES.get(id, "Pusty slot"))
|
||||
@ -20,6 +26,14 @@ static func description(id: String) -> String:
|
||||
"weapons": return "Broń obcych zadaje o 10% mniej obrażeń."
|
||||
"hull": return "Twoja broń zadaje o 10% więcej obrażeń kadłubom obcych."
|
||||
"shields": return "Twoja broń zadaje o 10% więcej obrażeń tarczom obcych."
|
||||
"weapons_a": return "Każde bezpośrednie trafienie twojej broni zadaje obcym dodatkowe 5 obrażeń przed absorpcją."
|
||||
"weapons_a_next": return "Każdy strzał ma 1% szansy wyłączyć na 3 sekundy losowy system obcego: silnik, broń lub tarcze. Obrażenia okresowe nie losują ponownie."
|
||||
"weapons_a_next_next": return "Trafienie dowolną bronią razi też obcych do 10 px od obrysu trafionego statku. Promień eksplozji twoich rakiet rośnie o 10 px. Bez łańcuchowych rykoszetów."
|
||||
"weapons_a_next_next_next": return "Każde bezpośrednie trafienie zadaje pełne obrażenia jednocześnie tarczom i kadłubowi obcego. Nadal działa absorpcja."
|
||||
"weapons_b": return "Dodatkowe 5% szansy uniknięcia obrażeń od wrogich obcych. Łączy się niezależnie z pozostałymi unikami."
|
||||
"weapons_b_next": return "Każda nadlatująca wroga rakieta ma 25% szansy zostać przechwycona w zasięgu 240 px od statku. Przechwycona rakieta wybucha bez zadawania obrażeń. Jeden rzut na rakietę."
|
||||
"weapons_b_next_next": return "Wrogie pociski i lasery mają 25% szansy na dodatkowy szeroki rozrzut celowania."
|
||||
"weapons_b_next_next_next": return "Pociski, rakiety i lasery obcych mogą trafiać także innych wrogich obcych. Trafienie własnego statku strzelca jest wykluczone."
|
||||
return "Miejsce na przyszłe badanie."
|
||||
|
||||
static func learned_count(progress: Resource) -> int:
|
||||
|
||||
@ -2,12 +2,14 @@ extends Control
|
||||
const DATA := preload("res://scripts/planets/alien_research.gd")
|
||||
const MENU := preload("res://scripts/planets/freighter_menu.gd")
|
||||
const COLORS := [Color("dfb775"), Color("e68c77"), Color("79cba8"), Color("7bc9e6")]
|
||||
const TILE_SIZE := Vector2(36,40)
|
||||
const PLACEHOLDER_LEVELS := 4
|
||||
var progress: Resource
|
||||
var refreshed: Callable
|
||||
var points: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(290, 294)
|
||||
custom_minimum_size = Vector2(290, 300)
|
||||
resized.connect(_build)
|
||||
_build()
|
||||
|
||||
@ -17,17 +19,22 @@ func _build() -> void:
|
||||
child.queue_free()
|
||||
points.clear()
|
||||
var width := maxf(290, size.x)
|
||||
_add_skill("general", Vector2(width * 0.5, 38), COLORS[0])
|
||||
_add_skill("general", Vector2(width * 0.5, 24), COLORS[0])
|
||||
for i in range(3):
|
||||
var branch: String = DATA.BRANCHES[i]
|
||||
var x := width * (float(i) + 0.5) / 3.0
|
||||
_add_skill(branch, Vector2(x, 137), COLORS[i + 1])
|
||||
_add_placeholder(branch + "_a", Vector2(x - 31, 252))
|
||||
_add_placeholder(branch + "_b", Vector2(x + 31, 252))
|
||||
_add_skill(branch, Vector2(x, 80), COLORS[i + 1])
|
||||
for path in ["a","b"]:
|
||||
var id: String = branch+"_"+path
|
||||
for level in PLACEHOLDER_LEVELS:
|
||||
var center := Vector2(x+(-22 if path=="a" else 22),142+level*46)
|
||||
if id in DATA.ACTIVE_IDS: _add_skill(id,center,DATA.path_color(id))
|
||||
else: _add_placeholder(id,center)
|
||||
id += "_next"
|
||||
var caption := MENU.label(self, ["BROŃ", "KADŁUB", "TARCZE"][i], 12, MENU.MUTED)
|
||||
caption.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
caption.position = Vector2(x - 55, 177)
|
||||
caption.size = Vector2(110, 18)
|
||||
caption.position = Vector2(x - 45, 104)
|
||||
caption.size = Vector2(90, 16)
|
||||
caption.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
queue_redraw()
|
||||
|
||||
@ -39,27 +46,28 @@ func _add_skill(id: String, center: Vector2, color: Color) -> void:
|
||||
var button := MENU.button(self, "", _purchase.bind(id), learned)
|
||||
button.set_script(preload("res://scripts/planets/research_skill_button.gd"))
|
||||
button.name = id
|
||||
button.position = center - Vector2(28, 34)
|
||||
button.size = Vector2(56, 68)
|
||||
button.position = center - TILE_SIZE*0.5
|
||||
button.size = TILE_SIZE
|
||||
button.disabled = learned or not unlocked or progress.analysis_samples < DATA.cost(id)
|
||||
if "_" in id: _style_path(button,DATA.path_color(id))
|
||||
var status := "Zbadano" if learned else "Koszt: %d próbek" % DATA.cost(id)
|
||||
if not unlocked: status += "\nWymaga: " + DATA.title(prerequisite)
|
||||
elif not learned and progress.analysis_samples < DATA.cost(id): status += "\nZa mało próbek"
|
||||
button.tooltip_text = DATA.title(id) + "\n" + DATA.description(id) + "\n" + status
|
||||
var icon := TextureRect.new()
|
||||
icon.texture = load("res://assets/freighter/research/" + id.split("_")[0] + ".png")
|
||||
icon.texture = load("res://assets/freighter/research/" + id + ".png")
|
||||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
icon.position = Vector2(4, 2)
|
||||
icon.size = Vector2(48, 48)
|
||||
icon.position = Vector2(5, 1)
|
||||
icon.size = Vector2(26, 26)
|
||||
if not unlocked: icon.modulate = Color(0.5, 0.55, 0.55, 0.65)
|
||||
button.add_child(icon)
|
||||
var price := MENU.label(button, "✓" if learned else str(DATA.cost(id)), 12, color if unlocked else MENU.MUTED)
|
||||
price.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
price.position = Vector2(0, 48)
|
||||
price.size = Vector2(56, 16)
|
||||
price.position = Vector2(0, 27)
|
||||
price.size = Vector2(36, 12)
|
||||
price.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
|
||||
func _add_placeholder(id: String, center: Vector2) -> void:
|
||||
@ -67,18 +75,26 @@ func _add_placeholder(id: String, center: Vector2) -> void:
|
||||
var slot := MENU.button(self, "", func(): pass)
|
||||
slot.name = id
|
||||
slot.disabled = true
|
||||
_style_path(slot,DATA.path_color(id))
|
||||
slot.focus_mode = Control.FOCUS_NONE
|
||||
slot.mouse_default_cursor_shape = Control.CURSOR_ARROW
|
||||
slot.set_script(preload("res://scripts/planets/research_skill_button.gd"))
|
||||
slot.position = center - Vector2(28, 34)
|
||||
slot.size = Vector2(56, 68)
|
||||
slot.position = center - TILE_SIZE*0.5
|
||||
slot.size = TILE_SIZE
|
||||
slot.tooltip_text = "Pusty slot\nMiejsce na przyszłe badanie."
|
||||
var mark := MENU.label(slot, "·", 24, MENU.MUTED)
|
||||
mark.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
mark.position = Vector2(0, 16)
|
||||
mark.size = Vector2(56, 28)
|
||||
mark.position = Vector2(0, 8)
|
||||
mark.size = Vector2(36, 28)
|
||||
mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
|
||||
func _style_path(button: Button, color: Color) -> void:
|
||||
for state in ["normal","hover","pressed","disabled","focus"]:
|
||||
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
|
||||
style.border_color = color
|
||||
style.set_border_width_all(1)
|
||||
button.add_theme_stylebox_override(state,style)
|
||||
|
||||
func _purchase(id: String) -> void:
|
||||
if DATA.purchase(progress, id): refreshed.call()
|
||||
|
||||
@ -86,11 +102,10 @@ func _draw() -> void:
|
||||
for id in points:
|
||||
var prerequisite := DATA.parent(id)
|
||||
if prerequisite.is_empty(): continue
|
||||
var start: Vector2 = points[prerequisite] + Vector2(0, 62 if "_" in id else 34)
|
||||
var end: Vector2 = points[id] - Vector2(0, 34)
|
||||
var start: Vector2 = points[prerequisite] + Vector2(0,TILE_SIZE.y*0.5)
|
||||
var end: Vector2 = points[id] - Vector2(0,TILE_SIZE.y*0.5)
|
||||
var active: bool = id in DATA.ACTIVE_IDS and progress.alien_skills.get(prerequisite, false)
|
||||
var color := Color("79b9a4") if active else Color("3c5158")
|
||||
var color := DATA.path_color(id) if "_" in id else (Color("79b9a4") if active else Color("3c5158"))
|
||||
var middle := (start.y + end.y) * 0.5
|
||||
if "_" in id: middle = end.y - 12
|
||||
draw_polyline(PackedVector2Array([start, Vector2(start.x, middle), Vector2(end.x, middle), end]), color, 1.5, true)
|
||||
|
||||
|
||||
@ -16,6 +16,13 @@ static func offer(profile: Dictionary, source_level: int) -> Dictionary:
|
||||
var level := maxi(1, source_level) + roll(profile, 0x4d415053).randi_range(1, 5)
|
||||
return {"id":str(profile.id)+":distant_map", "level":level, "seed":roll(profile, 0x44495354).randi(), "source_level":maxi(1,source_level), "source_planet":str(profile.get("name","Nieznana planeta")), "name":"Odległy sektor %03d" % level}
|
||||
|
||||
static func visit_offer(progress: Resource, profile: Dictionary, source_level: int) -> Dictionary:
|
||||
var visit := maxi(1, int(progress.visits.get(str(profile.id), 1)))
|
||||
var visit_profile := profile.duplicate(true)
|
||||
visit_profile.id = str(profile.id) + ":visit:%d" % visit
|
||||
visit_profile.seed = int(profile.get("seed", 0)) ^ (visit * 7919)
|
||||
return offer(visit_profile, source_level)
|
||||
|
||||
static func map_cost(map: Dictionary) -> int:
|
||||
return maxi(1, int(map.level)) * CREDITS_PER_LEVEL
|
||||
|
||||
@ -24,9 +31,10 @@ static func fuel_cost(map: Dictionary) -> int:
|
||||
|
||||
static func buy(progress: Resource, captain: Resource, profile: Dictionary, source_level: int) -> bool:
|
||||
if not has_hermit(profile): return false
|
||||
var map := offer(profile, source_level)
|
||||
if progress.distant_maps.has(str(map.id)) or captain.credits < map_cost(map): return false
|
||||
var map := visit_offer(progress, profile, source_level)
|
||||
if progress.hermit_purchases.has(str(map.id)) or captain.credits < map_cost(map): return false
|
||||
captain.credits -= map_cost(map)
|
||||
progress.hermit_purchases[str(map.id)] = true
|
||||
progress.distant_maps[str(map.id)] = map.duplicate(true)
|
||||
captain.emit_changed()
|
||||
progress.emit_changed()
|
||||
|
||||
@ -12,14 +12,18 @@ static func build(screen: Control) -> void:
|
||||
art.custom_minimum_size.y = 125
|
||||
art.clip_contents = true
|
||||
section.add_child(art)
|
||||
MENU.label(section, "DALEKIE WYPRAWY / ARCHIWUM MAP", 16, Color("e9b4fc"))
|
||||
MENU.label(section, "Sektory poza znanymi szlakami. Podwójna liczba wrogów, jeden boss. Kryształy czasu z każdego wroga. Zwykli wrogowie: mapy i powerbanki po 15%. Boss zawsze zostawia dokładnie 1 mapę i 1 losowy orb.", 12)
|
||||
MENU.label(section, "DALEKIE WYPRAWY / SEKTORY DO ZBADANIA", 16, Color("e9b4fc"))
|
||||
MENU.label(section, "Główna progresja: poziomy 1–120. HP, tarcze i atak wrogów rosną o 10% wartości bazowej na poziom, po 120 o 20%. Bazowe parametry łupu rosną o 7% na poziom przedmiotu. Bonusy procentowe mają limity. Poziom 1000 to limit techniczny wyposażenia.", 12, MENU.MUTED)
|
||||
if not GameState.active_distant_map.is_empty():
|
||||
MENU.label(section, "Trwa wyprawa do sektora %d. Użyj przycisku POWRÓT Z WYPRAWY na pokładzie, aby wrócić do układu startowego przed kolejnym lotem." % GameState.system_level, 12, MENU.GOLD)
|
||||
return
|
||||
MENU.label(section, "Sektory poza znanymi szlakami. Podwójna liczba wrogów, jeden boss. Kryształy czasu z każdego wroga. Zwykli wrogowie: mapy i powerbanki po 15%. Boss zawsze zostawia 1 mapę i 1 losowy orb, a dodatkowo ma 5% szansy na Orb replikacji.", 12)
|
||||
var fuel := MENU.label(section, "", 13, Color("95dce8"))
|
||||
fuel.name = "ExpeditionFuel"
|
||||
screen._economy_labels["expedition_fuel"] = fuel
|
||||
MENU.label(section, "Lot: poziom × 1000 deuteru. Powrót bezpłatny. Mapy pozostają w archiwum i pozwalają wracać do sektora.", 12, MENU.GOLD)
|
||||
MENU.label(section, "Lot: poziom × 1000 deuteru. Powrót bezpłatny. Mapa zużywa się przy wylocie. Każdy sektor można odwiedzić tylko raz.", 12, MENU.GOLD)
|
||||
if progress.distant_maps.is_empty():
|
||||
MENU.label(section, "Archiwum jest puste. Szukaj pustelnika na planecie lub map w łupach z wypraw.", 12, MENU.MUTED)
|
||||
MENU.label(section, "Brak sektorów do zbadania. Szukaj pustelnika na planecie lub map w łupach z wypraw.", 12, MENU.MUTED)
|
||||
var maps: Array = progress.distant_maps.values()
|
||||
maps.sort_custom(func(a,b): return int(a.level)<int(b.level))
|
||||
for map in maps:
|
||||
@ -34,7 +38,7 @@ static func build(screen: Control) -> void:
|
||||
var map_id := str(map.id)
|
||||
var fly := MENU.button(row,"LEĆ",func(): screen._travel_to_distant(map_id),true)
|
||||
fly.name = "FlyDistant_"+map_id.md5_text().substr(0,8)
|
||||
fly.tooltip_text = "Rozpocznij wyprawę. Pobiera %d deuteru." % cost
|
||||
fly.tooltip_text = "Jednorazowa wyprawa. Zużywa mapę i pobiera %d deuteru." % cost
|
||||
var refresh_status := func():
|
||||
var missing := maxf(0.0,cost-progress.deuter)
|
||||
status.text = "Paliwo gotowe" if missing <= 0 else "Brakuje %.1f deuteru" % missing
|
||||
|
||||
@ -14,7 +14,6 @@ var _text: Label
|
||||
var _contact_label: Label
|
||||
var _prompt_target: Node2D
|
||||
var _freighter_half_size := Vector2.ZERO
|
||||
var _scan_button: Button
|
||||
var _land_button: Button
|
||||
var _near_planet: Node2D
|
||||
var _message := ""
|
||||
@ -23,6 +22,8 @@ var _previous_pause := false
|
||||
var _previous_docked := false
|
||||
var _hud_was_visible := true
|
||||
var _prompt_refresh_time := 0.0
|
||||
var _last_combat_blocked := false
|
||||
var _needs_prompt_refresh := true
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
@ -54,9 +55,8 @@ func _ready() -> void:
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation",7)
|
||||
column.add_child(row)
|
||||
_scan_button = UI.button(row,"SKANUJ [F]",scan_nearby)
|
||||
_land_button = UI.button(row,"LĄDUJ [L]",land_nearby)
|
||||
for button in [_scan_button,_land_button]:
|
||||
for button in [_land_button]:
|
||||
CONTACT.compact_button(button,UI.CYAN)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_prompt.hide()
|
||||
@ -66,7 +66,7 @@ func combat_blocked() -> bool:
|
||||
if float(player.get("landing_combat_lock")) > 0:
|
||||
return true
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if enemy is Node2D and player.global_position.distance_to(enemy.global_position) < 650:
|
||||
if enemy is Node2D and not enemy.is_queued_for_deletion() and float(enemy.get("hull")) > 0.0 and player.global_position.distance_to(enemy.global_position) < 650:
|
||||
return true
|
||||
return false
|
||||
|
||||
@ -74,7 +74,13 @@ func _process(delta: float) -> void:
|
||||
_message_time = maxf(0, _message_time - delta)
|
||||
if is_instance_valid(screen) or get_tree().paused or bool(player.get("_is_destroyed")):
|
||||
_prompt.hide()
|
||||
_needs_prompt_refresh = true
|
||||
return
|
||||
var blocked := combat_blocked()
|
||||
if blocked != _last_combat_blocked or _needs_prompt_refresh:
|
||||
_prompt_refresh_time = 0.1
|
||||
_last_combat_blocked = blocked
|
||||
_needs_prompt_refresh = false
|
||||
if _prompt.visible: _layout_prompt()
|
||||
_prompt_refresh_time += delta
|
||||
# Proximity hints do not need to rebuild at the rendering frame rate.
|
||||
@ -108,6 +114,10 @@ func _refresh_prompt() -> void:
|
||||
if blocked:
|
||||
_prompt.hide()
|
||||
return
|
||||
if is_instance_valid(_near_planet):
|
||||
var profile: Dictionary = _near_planet.planet_profile
|
||||
if not GameState.captain.expedition.scans.has(profile.id):
|
||||
GameState.captain.expedition.scan(profile)
|
||||
var text := ""
|
||||
if _message_time > 0:
|
||||
text = _message
|
||||
@ -119,32 +129,38 @@ func _refresh_prompt() -> void:
|
||||
_prompt.tooltip_text = "Zadokuj, aby naprawić lądownik, rozbudować statek i przenieść wyposażenie."
|
||||
else:
|
||||
var profile: Dictionary = _near_planet.planet_profile
|
||||
_contact_label.text = "● TELEMETRIA / "+("SKAN GOTOWY" if GameState.captain.expedition.scans.has(profile.id) else "NIEZBADANA PLANETA")
|
||||
if GameState.captain.expedition.scans.has(profile.id):
|
||||
_contact_label.text = "● TELEMETRIA / DANE PLANETY"
|
||||
text = _compact_scan(profile)
|
||||
_prompt.tooltip_text = PROFILES.scan_description(profile,GameState.captain)
|
||||
else:
|
||||
text = "%s / EARTHLIKE\nZeskanuj warunki przed lądowaniem." % profile.name
|
||||
_prompt.tooltip_text = "Skan ujawni warunki, zagrożenie i szanse powodzenia POI."
|
||||
if blocked and _message_time <= 0:
|
||||
text += "\nDokowanie i lądowanie zablokowane podczas walki."
|
||||
var changed := _text.text != text or _scan_button.visible != (is_instance_valid(_near_planet) and not nearby_freighter) or _land_button.visible != has_destination
|
||||
# A hidden card must be laid out again even if the target and text stayed the same.
|
||||
var changed := not _prompt.visible or _text.text != text or _land_button.visible != has_destination
|
||||
if _text.text != text:
|
||||
_text.text = text
|
||||
_scan_button.visible = is_instance_valid(_near_planet) and not nearby_freighter
|
||||
_land_button.visible = has_destination
|
||||
_land_button.text = "DOKUJ [L]" if nearby_freighter else "LĄDUJ [L]"
|
||||
_land_button.disabled = blocked
|
||||
_prompt.show()
|
||||
if changed:
|
||||
_layout_prompt()
|
||||
# A standalone PanelContainer otherwise keeps its previous large height.
|
||||
_layout_prompt.call_deferred()
|
||||
|
||||
func _layout_prompt() -> void:
|
||||
if not is_instance_valid(_prompt):
|
||||
return
|
||||
_prompt.size = Vector2(300, _prompt.get_combined_minimum_size().y)
|
||||
# Do not invalidate the wrapped report's container layout every frame.
|
||||
var desired_size := Vector2(300, _prompt.get_combined_minimum_size().y)
|
||||
if not _prompt.size.is_equal_approx(desired_size):
|
||||
_prompt.size = desired_size
|
||||
var viewport_size := get_viewport().get_visible_rect().size
|
||||
# Return reports belong to the screen, not to a moving planet or ship.
|
||||
# Object avoidance can switch sides abruptly as the camera moves.
|
||||
if _message_time > 0:
|
||||
_prompt.position = Vector2(
|
||||
maxf(10, (viewport_size.x - _prompt.size.x) * 0.5),
|
||||
maxf(preload("res://scripts/ui/resource_bar.gd").HEIGHT + 12, viewport_size.y - _prompt.size.y - 160)
|
||||
)
|
||||
return
|
||||
var target: Node2D = _prompt_target if is_instance_valid(_prompt_target) else player
|
||||
var canvas := target.get_global_transform_with_canvas()
|
||||
var radius := _freighter_half_size.x if target == freighter else float(target.get("radius")) if target != player else 30.0
|
||||
@ -163,28 +179,17 @@ func _compact_scan(profile: Dictionary) -> String:
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if is_instance_valid(screen) or get_tree().paused or not event.pressed or event.echo:
|
||||
return
|
||||
if event.keycode == KEY_F:
|
||||
scan_nearby()
|
||||
elif event.keycode == KEY_L:
|
||||
if event.keycode == KEY_L:
|
||||
land_nearby()
|
||||
else:
|
||||
return
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func scan_nearby() -> void:
|
||||
_refresh_prompt()
|
||||
if not is_instance_valid(_near_planet):
|
||||
return
|
||||
GameState.captain.expedition.scan(_near_planet.planet_profile)
|
||||
_message_time = 0
|
||||
_refresh_prompt()
|
||||
|
||||
func land_nearby() -> void:
|
||||
_refresh_prompt()
|
||||
if is_instance_valid(screen) or bool(player.get("_is_destroyed")):
|
||||
return
|
||||
if combat_blocked():
|
||||
_notice("Odsuń się od przeciwników i zakończ walkę.")
|
||||
return
|
||||
if CONTACT_RANGE.at_freighter(player,freighter):
|
||||
open_freighter()
|
||||
@ -192,9 +197,6 @@ func land_nearby() -> void:
|
||||
if not is_instance_valid(_near_planet):
|
||||
return
|
||||
var progress: Resource = GameState.captain.expedition
|
||||
if not progress.scans.has(_near_planet.planet_profile.id):
|
||||
scan_nearby()
|
||||
return
|
||||
if not progress.begin_visit(_near_planet.planet_profile):
|
||||
_notice("Lądownik wymaga naprawy do 100% we frachtowcu.")
|
||||
return
|
||||
@ -243,6 +245,7 @@ func finish_surface(success: bool) -> void:
|
||||
_message += "\nBrak miejsca: %d modułów czeka we frachtowcu." % progress.delivered_modules.size()
|
||||
|
||||
func _leave() -> void:
|
||||
_needs_prompt_refresh = true
|
||||
if is_instance_valid(screen):
|
||||
_layer.remove_child(screen)
|
||||
screen.queue_free()
|
||||
@ -276,7 +279,15 @@ func request_distant_travel(map_id: String) -> bool:
|
||||
return true
|
||||
|
||||
func request_system_travel(level: int) -> bool:
|
||||
if not GameState.active_distant_map.is_empty(): return false
|
||||
if not is_instance_valid(screen) or GameState.captain.expedition.active or level == GameState.system_level or not GameState.can_visit_system(level): return false
|
||||
if not game.travel_to_system(level): return false
|
||||
_leave()
|
||||
return true
|
||||
|
||||
func request_distant_return() -> bool:
|
||||
if not is_instance_valid(screen) or GameState.active_distant_map.is_empty(): return false
|
||||
var sector := game.get_node_or_null("DistantSector")
|
||||
if sector == null or not sector._return_home(): return false
|
||||
_leave()
|
||||
return true
|
||||
|
||||
@ -14,13 +14,14 @@ static func build(screen: Control) -> void:
|
||||
destination.add_theme_font_size_override("font_size", 11)
|
||||
destination.add_theme_font_override("font", ThemeDB.fallback_font)
|
||||
for level in range(1, GameState.highest_system + 1):
|
||||
destination.add_item("System %d%s" % [level, " · HARD" if level > 60 else ""], level)
|
||||
destination.select(GameState.system_level - 1)
|
||||
destination.add_item("System %d%s" % [level, " · HARD" if preload("res://scripts/levels/level_balance.gd").is_hard(level) else ""], level)
|
||||
if GameState.active_distant_map.is_empty(): destination.select(GameState.system_level - 1)
|
||||
destination.disabled = not GameState.active_distant_map.is_empty()
|
||||
row.add_child(destination)
|
||||
var travel := MENU.button(row, "LEĆ DO UKŁADU", func(): screen._travel_to_system(destination.get_selected_id()), true)
|
||||
travel.name = "TravelToSystem"
|
||||
travel.disabled = true
|
||||
destination.item_selected.connect(func(_index): travel.disabled = destination.get_selected_id() == GameState.system_level)
|
||||
destination.item_selected.connect(func(_index): travel.disabled = not GameState.active_distant_map.is_empty() or destination.get_selected_id() == GameState.system_level)
|
||||
MENU.label(navigation, "Nowe układy odblokujesz, przechodząc dalej przez tunel po pokonaniu przeciwników.", 11, MENU.MUTED)
|
||||
preload("res://scripts/planets/distant_expeditions_panel.gd").build(screen)
|
||||
var summary := MENU.card(screen._content)
|
||||
|
||||
@ -67,6 +67,11 @@ func _build_header() -> void:
|
||||
var storage_button := MENU.button(header,"MAGAZYN",func(): _select_section("storage"))
|
||||
storage_button.custom_minimum_size.x = 100
|
||||
storage_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
if not GameState.active_distant_map.is_empty():
|
||||
var return_button := MENU.button(header,"POWRÓT Z WYPRAWY",_return_from_distant,true)
|
||||
return_button.name = "ReturnFromDistant"
|
||||
return_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
return_button.tooltip_text = "Zakończ wyprawę i wróć do układu startowego. Powrót jest bezpłatny."
|
||||
var leave := MENU.button(header,"ODLOT [L]",func(): closed.emit())
|
||||
leave.custom_minimum_size.x = 110
|
||||
leave.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
@ -581,6 +586,10 @@ func _travel_to_distant(map_id: String) -> void:
|
||||
var controller := get_parent().get_parent()
|
||||
if controller.has_method("request_distant_travel"): controller.request_distant_travel(map_id)
|
||||
|
||||
func _return_from_distant() -> void:
|
||||
var controller := get_parent().get_parent()
|
||||
if controller.has_method("request_distant_return"): controller.request_distant_return()
|
||||
|
||||
func _assign_station(station_id: String, option: OptionButton) -> void:
|
||||
preload("res://scripts/planets/mining_network.gd").assign(captain.expedition, station_id, str(option.get_selected_metadata()))
|
||||
_refresh()
|
||||
@ -615,7 +624,7 @@ func _build_alien_research() -> void:
|
||||
var row := MENU.row(_content)
|
||||
var balance := MENU.label(row, "PRÓBKI / %d" % progress.analysis_samples, 15, MENU.GOLD)
|
||||
balance.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
MENU.label(row, "%d / 4 ZBADANO" % preload("res://scripts/planets/alien_research.gd").learned_count(progress), 12, MENU.MUTED).custom_minimum_size.x = 110
|
||||
MENU.label(row, "%d / %d ZBADANO" % [preload("res://scripts/planets/alien_research.gd").learned_count(progress),preload("res://scripts/planets/alien_research.gd").ACTIVE_IDS.size()], 12, MENU.MUTED).custom_minimum_size.x = 110
|
||||
var tree := preload("res://scripts/planets/alien_research_tree.gd").new()
|
||||
tree.progress = progress
|
||||
tree.refreshed = _research_completed
|
||||
|
||||
@ -4,7 +4,7 @@ const ORBS := preload("res://scripts/items/special_orbs.gd")
|
||||
static func build(screen: Control) -> void:
|
||||
var progress: Resource = screen.captain.expedition
|
||||
MENU.label(screen._content,"ORBY / SPECJALNE ULEPSZENIA",15,Color("d9b5ff"))
|
||||
MENU.label(screen._content,"Każdy orb jest jednorazowy i znika po użyciu. Kolejny awans wymaga kolejnej sztuki. Efekty przebicia i dopalacza nie sumują się.",12,MENU.MUTED)
|
||||
MENU.label(screen._content,"Każdy orb jest jednorazowy i znika po użyciu. Kolejny awans wymaga kolejnej sztuki. Efekty tego samego orba nie sumują się. Orby typu obrażeń zastępują się. Replika trafia do magazynu.",12,MENU.MUTED)
|
||||
for id in ORBS.IDS:
|
||||
var card := MENU.card(screen._content)
|
||||
var header := MENU.row(card)
|
||||
@ -20,14 +20,14 @@ static func build(screen: Control) -> void:
|
||||
choices.add_theme_font_size_override("font_size",12)
|
||||
card.add_child(choices)
|
||||
if is_instance_valid(screen.inventory):
|
||||
for kind in ["weapon","module","cargo"]:
|
||||
for kind in ["weapon","module","extension","cargo"]:
|
||||
var items: Array = screen.inventory._get_slots(kind)
|
||||
for index in items.size():
|
||||
if not screen.inventory.is_slot_unlocked(kind,index): continue
|
||||
_add_item(choices,items[index],id,{"weapon":"Broń","module":"Moduł","cargo":"Ładownia"}[kind])
|
||||
_add_item(choices,items[index],id,{"weapon":"Broń","module":"Moduł","cargo":"Ładownia","extension":"Rozszerzenie"}[kind])
|
||||
for item in progress.freighter_storage: _add_item(choices,item,id,"Magazyn fregaty")
|
||||
if choices.item_count == 0: choices.add_item("Brak odpowiedniego przedmiotu")
|
||||
var apply := MENU.button(card,"AWANSUJ +1 POZIOM" if id == "advancement" else "NAŁÓŻ ORB",func():
|
||||
var apply := MENU.button(card,"AWANSUJ +1 POZIOM" if id == "advancement" else ("REPLIKUJ" if id == "replication" else "NAŁÓŻ ORB"),func():
|
||||
var target: ItemInstance = choices.get_selected_metadata()
|
||||
if ORBS.apply(progress,screen.inventory,target,id):
|
||||
screen._audio.service_completed(-0.48)
|
||||
|
||||
@ -11,6 +11,7 @@ const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
|
||||
@export var freighter_rooms: Dictionary = {"expansion_3":"module_storage"}
|
||||
@export var mining_stations: Dictionary = {}
|
||||
@export var distant_maps: Dictionary = {}
|
||||
@export var hermit_purchases: Dictionary = {}
|
||||
@export var orbs: Dictionary = {}
|
||||
@export var time_crystals: int = 0
|
||||
@export var crew: Dictionary = {}
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
extends Sprite2D
|
||||
## Static world-space ore: no animated shimmer, screen-space noise or UV snapping.
|
||||
const ORE := preload("res://assets/planets/mineral_deposit.png")
|
||||
const DEUTERIUM := preload("res://assets/planets/deuterium_deposit.png")
|
||||
const WIDTH := 128.0
|
||||
|
||||
func configure(map: Dictionary, poi: Dictionary) -> void:
|
||||
name = "MineralDeposit%d" % int(poi.id)
|
||||
texture = ORE
|
||||
texture = DEUTERIUM if poi.kind == "deep_deposit" else ORE
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||
position = poi.position
|
||||
z_index = 10
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = int(map.seed) ^ (int(poi.id)*7919) ^ 0x4f524553
|
||||
rotation = rng.randf_range(-0.35,0.35)
|
||||
scale = Vector2.ONE*WIDTH*rng.randf_range(0.95,1.08)/maxf(ORE.get_width(),ORE.get_height())
|
||||
scale = Vector2.ONE*WIDTH*rng.randf_range(0.95,1.08)/maxf(texture.get_width(),texture.get_height())
|
||||
var blend := ShaderMaterial.new()
|
||||
blend.shader = preload("res://resources/effects/mineral_deposit.gdshader")
|
||||
var tint: Color = (map.landscape.low as Color).lerp(map.landscape.high,0.4)*0.8
|
||||
|
||||
@ -419,7 +419,10 @@ func _show_colony_poi(message: String) -> void:
|
||||
var network := preload("res://scripts/planets/mining_network.gd")
|
||||
var is_deposit: bool = _poi.kind == "deep_deposit"
|
||||
var art := TextureRect.new()
|
||||
art.texture = load("res://assets/planets/props/deuterium_outpost.png" if is_deposit else "res://assets/planets/props/engineer_camp.png")
|
||||
var art_path := "res://assets/planets/props/engineer_camp.png"
|
||||
if is_deposit:
|
||||
art_path = "res://assets/planets/props/deuterium_outpost.png" if progress.mining_stations.has(str(progress.active_profile.id)) else "res://assets/planets/deuterium_deposit.png"
|
||||
art.texture = load(art_path)
|
||||
art.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
art.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
art.custom_minimum_size.y = 170
|
||||
@ -481,7 +484,7 @@ func _maximize_addon() -> void:
|
||||
func _show_hermit(message: String) -> void:
|
||||
var expeditions := preload("res://scripts/planets/distant_expeditions.gd")
|
||||
var profile: Dictionary = progress.active_profile
|
||||
var map: Dictionary = expeditions.offer(profile, int(profile.get("system_level", GameState.system_level)))
|
||||
var map: Dictionary = expeditions.visit_offer(progress, profile, int(profile.get("system_level", GameState.system_level)))
|
||||
var art := TextureRect.new()
|
||||
art.texture = load("res://assets/planets/props/hermit_cartographer.png")
|
||||
art.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
@ -493,8 +496,8 @@ func _show_hermit(message: String) -> void:
|
||||
if not message.is_empty(): POI_CARD.text(_choices, message, 13, POI_CARD.GOLD)
|
||||
POI_CARD.text(_choices, "%s · poziom wyprawy %d" % [map.name, map.level], 15, POI_CARD.GOLD)
|
||||
POI_CARD.text(_choices, "Mapa: %d kredytów\nPaliwo na lot: %d deuteru\nPosiadasz: %d kredytów" % [expeditions.map_cost(map),expeditions.fuel_cost(map),captain.credits], 13)
|
||||
if progress.distant_maps.has(str(map.id)):
|
||||
POI_CARD.text(_choices, "Mapa jest już w archiwum na frachtowcu.", 13)
|
||||
if progress.hermit_purchases.has(str(map.id)):
|
||||
POI_CARD.text(_choices, "Mapa z tej wizyty została kupiona. Wróć po kolejnym lądowaniu po nową ofertę.", 13)
|
||||
else:
|
||||
var buy := POI_CARD.button(_choices, "KUP MAPĘ · %d KREDYTÓW" % expeditions.map_cost(map), _buy_distant_map, true)
|
||||
buy.name = "BuyDistantMap"
|
||||
@ -506,7 +509,7 @@ func _buy_distant_map() -> void:
|
||||
var profile: Dictionary = progress.active_profile
|
||||
if preload("res://scripts/planets/distant_expeditions.gd").buy(progress,captain,profile,int(profile.get("system_level",GameState.system_level))):
|
||||
world.refresh_pois(lander.position)
|
||||
_show_poi("Mapa zapisana w archiwum dalekich wypraw na frachtowcu.")
|
||||
_show_poi("Sektor dodany do listy wypraw na frachtowcu. Mapa pozwala na jeden lot.")
|
||||
else:
|
||||
_show_poi("Mapa została już kupiona lub brakuje kredytów.")
|
||||
|
||||
|
||||
@ -159,7 +159,9 @@ func refresh_pois(lander_position: Vector2) -> void:
|
||||
label.text = "Inżynier chronalny" + ("\n[F] Maksymalizuj addon" if lander_position.distance_to(poi.position) <= 150 else "")
|
||||
continue
|
||||
if poi.kind == "hermit":
|
||||
var owned: bool = GameState.captain.expedition.distant_maps.has(str(data.profile.id)+":distant_map")
|
||||
var progress: Resource = GameState.captain.expedition
|
||||
var map := preload("res://scripts/planets/distant_expeditions.gd").visit_offer(progress, data.profile, int(data.profile.get("system_level", GameState.system_level)))
|
||||
var owned: bool = progress.hermit_purchases.has(str(map.id))
|
||||
label.text = "Pustelnik / Kartograf" + (" · mapa kupiona" if owned else ("\n[F] Kup mapę" if lander_position.distance_to(poi.position) <= 150 else ""))
|
||||
continue
|
||||
if poi.kind == "crew":
|
||||
|
||||
@ -243,7 +243,7 @@ func _make_custom_tooltip(for_text: String) -> Object:
|
||||
label.push_font_size(13)
|
||||
color = Color("ffd166")
|
||||
if line.begins_with("Typ obrażeń:"):
|
||||
color = [Color("efb568"), Color("91dbea"), Color("a9ef83")][item.definition.get_damage_type()] if item != null else Color("e6f2fc")
|
||||
color = [Color("efb568"), Color("91dbea"), Color("a9ef83")][item.get_damage_type()] if item != null else Color("e6f2fc")
|
||||
if line.begins_with("Każdy strzał"):
|
||||
color = Color("8298ad")
|
||||
label.push_color(color)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
extends Control
|
||||
const HEIGHT := 32.0
|
||||
const HEIGHT := 16.0
|
||||
const ENTRIES := [
|
||||
["credits","Kredyty","svg",Color("efd48e")],
|
||||
["parts","Części","png",Color("dfded2")],
|
||||
@ -107,7 +107,7 @@ func _layout() -> void:
|
||||
if not is_instance_valid(_row): return
|
||||
var minimum := _row.get_combined_minimum_size()
|
||||
var available := maxf(1,size.x-24)
|
||||
var factor := minf(1.0,available/maxf(1,minimum.x))
|
||||
var factor := minf(0.5,available/maxf(1,minimum.x))
|
||||
_row.scale = Vector2.ONE*factor
|
||||
_row.size = Vector2(available/factor,minimum.y)
|
||||
_row.position = Vector2(12,(HEIGHT-minimum.y*factor)*0.5)
|
||||
|
||||
@ -148,7 +148,7 @@ func _metric(card: VBoxContainer, key: String, caption: String, read_value: Call
|
||||
func _refresh_values() -> void:
|
||||
if not is_instance_valid(player): return
|
||||
var ship := ShipCatalog.for_path(GameState.selected_ship_path)
|
||||
_subtitle.text = "%s / System %d / %s / DPS %.1f" % [ship.name, GameState.system_level, "HARD" if GameState.system_level > 60 else "NORMALNY", inventory.get_total_dps()]
|
||||
_subtitle.text = "%s / System %d / %s / DPS %.1f" % [ship.name, GameState.system_level, "HARD" if preload("res://scripts/levels/level_balance.gd").is_hard(GameState.system_level) else "NORMALNY", inventory.get_total_dps()]
|
||||
for entry in _bindings.values():
|
||||
entry.label.text = str(entry.read.call())
|
||||
|
||||
@ -161,19 +161,19 @@ func _parameters() -> void:
|
||||
_metric(card, DamageTypes.KEYS[index], "Absorpcja: " + DamageTypes.NAMES[index].to_lower(), func(): return "%.1f%%" % player.absorption[index], DEFENSE)
|
||||
card = _section("02 / TARCZE I ENERGIA", ENERGY)
|
||||
_metric(card, "shields", "Tarcze", func(): return "%.0f / %.0f" % [player.shields,player.max_shields], CYAN)
|
||||
_metric(card, "shield_regeneration", "Regeneracja tarcz", func(): return "%.1f / s" % player.shield_regeneration, CYAN)
|
||||
_metric(card, "shield_on_damage", "Odzysk tarcz z obrażeń", func(): return "%.1f%%" % player.shield_on_damage_percent, CYAN)
|
||||
_metric(card, "shield_regeneration", "Regeneracja tarcz", func(): return "%.1f / s" % player.shield_regeneration, CYAN, "Do 3% maksymalnych tarcz na sekundę.")
|
||||
_metric(card, "shield_on_damage", "Odzysk tarcz z obrażeń", func(): return "%.1f%%" % player.shield_on_damage_percent, CYAN, "Łącznie do 10% faktycznie zadanych obrażeń. Tempo: do 5% maksymalnych tarcz/s, z rezerwą na jedno takie odnowienie.")
|
||||
_metric(card, "energy", "Energia", func(): return "%.0f / %.0f" % [player.energy,player.max_energy], ENERGY)
|
||||
_metric(card, "energy_regeneration", "Regeneracja energii", func(): return "%.1f / s" % player.get_energy_regeneration(), ENERGY, "Aktualna regeneracja, także ładowanie przy gwieździe i premie kapitana.")
|
||||
_metric(card, "energy_regeneration", "Regeneracja energii", func(): return "%.1f / s" % player.get_energy_regeneration(), ENERGY, "Sprzęt: do 15% pojemności generatora/s. Ładowanie przy gwieździe i czasowe umiejętności dodają osobne premie.")
|
||||
_metric(card, "solar", "Ładowanie słoneczne", func(): return "%.1f / s" % player.get_solar_regeneration(), ENERGY)
|
||||
card = _section("03 / SIŁA OGNIA", GOLD)
|
||||
_metric(card, "dps", "Łączny DPS", func(): return "%.1f" % inventory.get_total_dps(), GOLD, "Teoretyczny DPS: uwzględnia krytyki i promieniowanie; przed obroną celu, przy dostępnej energii i amunicji.")
|
||||
_metric(card, "damage", "Premia do obrażeń", func(): return "%+.1f%%" % ((player.get_weapon_damage_multiplier()-1)*100), GOLD)
|
||||
_metric(card, "critical", "Szansa na krytyk", func(): return "%.1f%%" % (player.get_critical_chance()*100), GOLD)
|
||||
_metric(card, "critical_damage", "Mnożnik krytyka", func(): return "×%.2f" % player.get_critical_multiplier(), GOLD)
|
||||
_metric(card, "damage", "Premia do obrażeń", func(): return "%+.1f%%" % ((player.get_weapon_damage_multiplier()-1)*100), GOLD, "Premia ze sprzętu: do +100%. Umiejętności kapitana działają osobno.")
|
||||
_metric(card, "critical", "Szansa na krytyk", func(): return "%.1f%%" % (player.get_critical_chance()*100), GOLD, "Sprzęt: do 40%. Łącznie z umiejętnościami: do 60%.")
|
||||
_metric(card, "critical_damage", "Mnożnik krytyka", func(): return "×%.2f" % player.get_critical_multiplier(), GOLD, "Sprzęt: do +100% mnożnika bazowego, maksymalnie ×4.")
|
||||
card = _section("04 / NAPĘD I NAWIGACJA", CYAN)
|
||||
_metric(card, "speed", "Prędkość aktualna", func(): return "%.0f px/s" % player.velocity.length())
|
||||
_metric(card, "max_speed", "Prędkość maksymalna", func(): return "%.0f px/s" % player.max_speed)
|
||||
_metric(card, "max_speed", "Prędkość maksymalna", func(): return "%.0f px/s" % player.max_speed, CYAN, "Zwykły lot: do 240 px/s. TAB: do 480 px/s. Aktywny dopalacz: do 600 px/s.")
|
||||
_metric(card, "acceleration", "Przyspieszenie", func(): return "%.0f px/s²" % player.thrust_acceleration)
|
||||
_metric(card, "rotation", "Prędkość obrotu", func(): return "%.0f °/s" % rad_to_deg(player.max_turn_speed))
|
||||
_metric(card, "scanner", "Zasięg skanera", func(): return "%.0f px" % player.scanner_range)
|
||||
@ -204,10 +204,10 @@ func _weapons() -> void:
|
||||
_item_header(card,item)
|
||||
_metric(card, "weapon_dps_%d" % index, "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_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.definition.fire_interval))
|
||||
_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_energy_%d" % index, "Energia na strzał", func(): return "%.1f" % item.get_energy_cost(), ENERGY)
|
||||
_metric(card, "weapon_range_%d" % index, "Zasięg", func(): return "%.0f px" % item.definition.weapon_range)
|
||||
UI.label(card,"Typ: " + DamageTypes.NAMES[item.definition.get_damage_type()],12,UI.MUTED)
|
||||
UI.label(card,"Typ: " + DamageTypes.NAMES[item.get_damage_type()],12,UI.MUTED)
|
||||
var info := _section("ODCZYT PARAMETRÓW", CYAN)
|
||||
UI.label(info,"DPS uwzględnia premie wyposażenia, kapitana, krytyki i promieniowanie. To wartość teoretyczna, przed obroną celu; rzeczywisty wynik zależy od trafień, energii i amunicji.",13,UI.MUTED)
|
||||
UI.label(info,"Najedź na nazwę przedmiotu, aby zobaczyć wszystkie addony i parametry.",13,UI.MUTED)
|
||||
|
||||
@ -19,7 +19,7 @@ func run_checks() -> Dictionary:
|
||||
if DATA.fuel_cost(map) != int(map.level)*1000: failures.append("Wrong fuel")
|
||||
var captain = preload("res://scripts/captain/captain_progress.gd").new()
|
||||
var progress: Resource = captain.expedition
|
||||
var map := DATA.offer(profile,120)
|
||||
var map := DATA.visit_offer(progress,profile,120)
|
||||
var price := DATA.map_cost(map)
|
||||
captain.credits = price-1
|
||||
if DATA.buy(progress,captain,profile,120): failures.append("Bought without credits")
|
||||
@ -32,6 +32,13 @@ func run_checks() -> Dictionary:
|
||||
if DATA.buy(progress,captain,profile,120): failures.append("Duplicate purchase")
|
||||
if captain.credits != price: failures.append("Duplicate charged")
|
||||
if progress.deuter != 500000: failures.append("Purchase consumed fuel")
|
||||
progress.distant_maps.clear()
|
||||
if DATA.buy(progress,captain,profile,120): failures.append("Consumed map repurchased in same visit")
|
||||
progress.visits[str(profile.id)] = 2
|
||||
var next_map := DATA.visit_offer(progress,profile,120)
|
||||
if next_map.id == map.id or next_map.seed == map.seed: failures.append("Revisit reused sector")
|
||||
captain.credits = DATA.map_cost(next_map)
|
||||
if not DATA.buy(progress,captain,profile,120): failures.append("Revisit purchase failed")
|
||||
var clone: Resource = progress.duplicate(true)
|
||||
if clone.distant_maps != progress.distant_maps: failures.append("Archive not preserved")
|
||||
return {"failures":failures,"offers_checked":500,"beyond_120_supported":true}
|
||||
|
||||
@ -19,6 +19,10 @@ func freeze_game(game: Node) -> void:
|
||||
game.get_node("PlayerShip").set_physics_process(false)
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false)
|
||||
func _run() -> void:
|
||||
var map_checks := preload("res://tests/distant_expeditions_checks.gd").new()
|
||||
var map_report: Dictionary = map_checks.run_checks()
|
||||
for failure in map_report.failures: check(false, str(failure))
|
||||
map_checks.free()
|
||||
for level in [1,60,120,125,500]:
|
||||
var ordinary := preload("res://scripts/levels/level_balance.gd").enemy_tiers(level)
|
||||
var tiers := GENERATOR.enemy_tiers(level)
|
||||
@ -75,9 +79,12 @@ func _run() -> void:
|
||||
game = get_tree().current_scene
|
||||
freeze_game(game)
|
||||
check(GameState.captain.expedition.deuter==0,"Exactly one fuel payment")
|
||||
check(not GameState.captain.expedition.distant_maps.has("integration"),"Map consumed on departure")
|
||||
check(GameState.system_level==125 and GameState.highest_system==7,"Isolated progression")
|
||||
check(game.get_node_or_null("CelestialBodies")==null,"No celestial bodies")
|
||||
check(game.get_node_or_null("PlanetExpeditions")==null,"No planet expeditions")
|
||||
check(game.has_node("PlanetExpeditions") and game.has_node("Freighter"),"Freighter available in expedition")
|
||||
check(game.get_node("HUD/HUDRoot").find_child("ReturnFromDistant",true,false)==null,"No return button in flight HUD")
|
||||
check(not game.get_node("DistantSector")._return_home(),"Return requires docking")
|
||||
check(game.get_node("AncientStructures").get_child_count()==14,"Decorative structures")
|
||||
check(game.get_remaining_enemies()==46 and not game._boss_spawned,"Doubled encounter before boss")
|
||||
player = game.get_node("PlayerShip")
|
||||
@ -136,12 +143,18 @@ func _run() -> void:
|
||||
if pickup.get_script() == preload("res://scripts/items/expedition_pickup.gd"):
|
||||
if pickup.payload.has("map"): maps += 1
|
||||
if pickup.payload.has("orb"): orbs += 1
|
||||
check(maps==1 and orbs==1,"Actual boss death drops exactly one map and orb")
|
||||
check(maps==1 and orbs>=1 and orbs<=2,"Actual boss death drops map, guaranteed orb and optional replication")
|
||||
check(get_tree().get_nodes_in_group("time_crystal_pickups").size()==1,"Boss also drops time crystals")
|
||||
game.get_node("DistantSector")._physics_process(0.3)
|
||||
check(game.system_cleared,"Sector cleared")
|
||||
player.landing_combat_lock = 0
|
||||
game.get_node("DistantSector")._return_home()
|
||||
var controller := game.get_node("PlanetExpeditions")
|
||||
player.is_docked = false
|
||||
player.position = controller.freighter.position
|
||||
controller.land_nearby()
|
||||
check(is_instance_valid(controller.screen) and player.is_docked,"Normal docking during expedition")
|
||||
check(controller.screen.find_child("ReturnFromDistant",true,false)!=null,"Return button inside freighter")
|
||||
controller.screen._return_from_distant()
|
||||
await frames(8)
|
||||
game = get_tree().current_scene
|
||||
freeze_game(game)
|
||||
@ -149,4 +162,6 @@ func _run() -> void:
|
||||
check(GameState.captain.expedition.deuter==0,"Return free")
|
||||
check(game.has_node("PlanetExpeditions") and game.has_node("CelestialBodies"),"Normal scene restored")
|
||||
check(is_instance_valid(game.get_node("PlanetExpeditions").screen) and game.get_node("PlayerShip").is_docked,"Return docks at freighter")
|
||||
var combat_checks := preload("res://tests/items/combat_orbs_checks.gd").new()
|
||||
for failure in combat_checks.run(game): check(false,str(failure))
|
||||
print("DISTANT_INTEGRATION_RESULT: "+JSON.stringify({"failures":failures,"passed":failures.is_empty()}))
|
||||
|
||||
@ -4,7 +4,7 @@ const GENERATOR := preload("res://scripts/items/item_generator.gd")
|
||||
const DATABASE := preload("res://resources/items/affix_database.tres")
|
||||
|
||||
func _ready() -> void:
|
||||
assert(DATABASE.affixes.size() == 16)
|
||||
assert(DATABASE.affixes.size() == 17)
|
||||
for level in [1, 50, 1000]:
|
||||
for tier in [0, 1, 2]:
|
||||
for sample in 50:
|
||||
|
||||