Compare commits
2 Commits
72839139eb
...
9aa2dfebd6
| Author | SHA1 | Date | |
|---|---|---|---|
| 9aa2dfebd6 | |||
| 458db974c0 |
BIN
assets/Ships/combat_drone.png
Normal file
BIN
assets/Ships/combat_drone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
40
assets/Ships/combat_drone.png.import
Normal file
40
assets/Ships/combat_drone.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d4hxo2htne3yy"
|
||||||
|
path="res://.godot/imported/combat_drone.png-1ee839fafe5ff3881843088267b833ba.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Ships/combat_drone.png"
|
||||||
|
dest_files=["res://.godot/imported/combat_drone.png-1ee839fafe5ff3881843088267b833ba.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=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/captain/electrician.png
Normal file
BIN
assets/icons/captain/electrician.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
40
assets/icons/captain/electrician.png.import
Normal file
40
assets/icons/captain/electrician.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://baxcqpl7y3dfd"
|
||||||
|
path="res://.godot/imported/electrician.png-cd4ace91776ba47a19c9950998a779a9.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/captain/electrician.png"
|
||||||
|
dest_files=["res://.godot/imported/electrician.png-cd4ace91776ba47a19c9950998a779a9.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
assets/icons/extensions/combat_drones_art.png
Normal file
BIN
assets/icons/extensions/combat_drones_art.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
40
assets/icons/extensions/combat_drones_art.png.import
Normal file
40
assets/icons/extensions/combat_drones_art.png.import
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://huejfa7inar0"
|
||||||
|
path="res://.godot/imported/combat_drones_art.png-ce5df9b0b0abb663da961f963a875f37.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/extensions/combat_drones_art.png"
|
||||||
|
dest_files=["res://.godot/imported/combat_drones_art.png-ce5df9b0b0abb663da961f963a875f37.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
17
assets/icons/new_skills_art.md
Normal file
17
assets/icons/new_skills_art.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Elektryk i drony bojowe
|
||||||
|
|
||||||
|
Wygenerowano narzędziem wbudowanym `image_gen`, z przezroczystym tłem.
|
||||||
|
|
||||||
|
## Elektryk — captain/electrician.png
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: square transparent sci-fi spaceship game skill icon, high quality painted game UI art. Subject: Elektryk, futuristic engineer's armored gloved hand holding a compact metallic powerbank with a brilliant cyan lightning core, copper wire contacts and tiny electric arcs. Single strong readable centered silhouette, navy steel copper and electric turquoise palette, crisp bevels, cinematic rim lighting, detailed yet readable at 48px, generous transparent margins. No text, letters, frame, background or watermark. Save the PNG asset for the project.
|
||||||
|
|
||||||
|
## Drony bojowe — extensions/combat_drones_art.png
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: transparent square sci-fi game equipment icon. Subject three compact autonomous combat drones in triangular formation, metallic gunmetal hulls with angular swept wings, twin small kinetic gun barrels and bright cyan engine cores. Top down three quarter view, polished hand painted realistic spaceship inventory art, crisp strong silhouettes readable at 48px, restrained gold accents and cyan rim lighting, isolated centered with transparent margins. No lettering, frame, background or watermark. Save PNG for game project.
|
||||||
|
|
||||||
|
## Dron w rozgrywce — ../Ships/combat_drone.png
|
||||||
|
|
||||||
|
Wygenerowano wbudowanym `image_gen`, z przezroczystym tłem. Kadłub jest teksturą; silniki i błyski działek animuje combat_drone.gd.
|
||||||
|
|
||||||
|
Use case: stylized-concept. Asset type: production gameplay sprite for a top-down 2D space shooter, transparent PNG. ONE small autonomous combat drone spaceship, perfectly orthographic TOP DOWN view, nose pointing straight UP, bilateral symmetry. Compact gunmetal titanium armored fuselage, swept angular short wings, two small forward-facing mechanical autocannons ending near the nose, two cyan engine exhaust ports at rear, tiny turquoise sensors, restrained brass details. Detailed painted realistic sci-fi spacecraft, crisp edges and clear silhouette readable at 44 pixels tall. Centered entire ship with 12 percent transparent padding. Lighting from upper left, dark panel grooves and bright metallic edge highlights. No pilot, perspective, isometric angle, cast shadow, engine flame, muzzle flash, text, UI, frame, scenery, stars, watermark or other spacecraft. Engines and muzzle flashes are animated separately in the game.
|
||||||
BIN
assets/sounds/music/menu_music2.mp3
Normal file
BIN
assets/sounds/music/menu_music2.mp3
Normal file
Binary file not shown.
19
assets/sounds/music/menu_music2.mp3.import
Normal file
19
assets/sounds/music/menu_music2.mp3.import
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="mp3"
|
||||||
|
type="AudioStreamMP3"
|
||||||
|
uid="uid://nscqyrjfn7u4"
|
||||||
|
path="res://.godot/imported/menu_music2.mp3-7efdba0b08e1c22b65cc3d25ef9b8f90.mp3str"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sounds/music/menu_music2.mp3"
|
||||||
|
dest_files=["res://.godot/imported/menu_music2.mp3-7efdba0b08e1c22b65cc3d25ef9b8f90.mp3str"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Script" uid="uid://bpaiuqwsxswii" path="res://scripts/abilities/afterburner_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://bpaiuqwsxswii" path="res://scripts/abilities/afterburner_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://kukr8bhgqa7p" path="res://assets/icons/afterburner.png" id="icon"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/afterburner.png" id="icon"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
@ -12,9 +11,9 @@ description = "Szybko rozpędza do 300% zwykłej prędkości (+200%), następnie
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "afterburner"
|
item_id = "afterburner"
|
||||||
display_name = "Dopalacz"
|
display_name = "Dopalacz"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie silnika."
|
description = "Aktywne rozszerzenie silnika."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://drdg44cvejr2n"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/extensions/cloak_art.png" id="icon"]
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://ddqhcasuuecgc" path="res://assets/icons/extensions/cloak_art.png" id="icon"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
kind = 1
|
||||||
ability_id = "cloak"
|
ability_id = "cloak"
|
||||||
cooldown = 60.0
|
cooldown = 60.0
|
||||||
display_name = "Kamuflaż"
|
display_name = "Kamuflaż"
|
||||||
description = "Przez 10 s statek staje się niemal niewidoczny. Przeciwnicy nie wykrywają go ani nie śledzą. Nie zapewnia odporności na już wystrzelone pociski. Odnowienie: 60 s."
|
description = "Przez 10 s statek staje się niemal niewidoczny. Przeciwnicy nie wykrywają go ani nie śledzą. Oddanie strzału przerywa kamuflaż. Nie zapewnia odporności na już wystrzelone pociski. Odnowienie: 60 s."
|
||||||
kind = 1
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "cloak"
|
item_id = "cloak"
|
||||||
display_name = "Kamuflaż"
|
display_name = "Kamuflaż"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
22
resources/items/combat_drones.tres
Normal file
22
resources/items/combat_drones.tres
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cvauqvsl4x5gk"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://huejfa7inar0" path="res://assets/icons/extensions/combat_drones_art.png" id="icon"]
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Ability"]
|
||||||
|
script = ExtResource("2")
|
||||||
|
kind = 5
|
||||||
|
ability_id = "combat_drones"
|
||||||
|
cooldown = 30.0
|
||||||
|
display_name = "Drony bojowe"
|
||||||
|
description = "Wypuszcza 3 drony na 10 s. Strzelają działkami mechanicznymi; łącznie zadają 50% DPS statku w momencie startu. Koszt: 50 szt. zwykłej amunicji. Odnowienie: 30 s."
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
item_id = "combat_drones"
|
||||||
|
display_name = "Drony bojowe"
|
||||||
|
icon = ExtResource("icon")
|
||||||
|
category = 5
|
||||||
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
|
active_ability = SubResource("Ability")
|
||||||
@ -1,19 +1,22 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://demqfp0to5pnx"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/extensions/flares_art.png" id="icon"]
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://ccdxl4xyjwyg3" path="res://assets/icons/extensions/flares_art.png" id="icon"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
kind = 3
|
||||||
ability_id = "flares"
|
ability_id = "flares"
|
||||||
cooldown = 30.0
|
cooldown = 30.0
|
||||||
display_name = "Flary"
|
display_name = "Flary"
|
||||||
description = "Wypuszcza trzy flary na 10 s. Wrogowie w zasięgu obierają je za cel zamiast gracza. Odnowienie: 30 s."
|
description = "Wypuszcza trzy flary na 10 s. Wrogowie w zasięgu obierają je za cel zamiast gracza. Odnowienie: 30 s."
|
||||||
kind = 3
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "flares"
|
item_id = "flares"
|
||||||
display_name = "Flary"
|
display_name = "Flary"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://cnlcstlicfthn"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/extensions/mine_art.png" id="icon"]
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dwv0fw3dyupj3" path="res://assets/icons/extensions/mine_art.png" id="icon"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
kind = 2
|
||||||
ability_id = "mine_layer"
|
ability_id = "mine_layer"
|
||||||
cooldown = 5.0
|
cooldown = 5.0
|
||||||
display_name = "Miny"
|
display_name = "Miny"
|
||||||
description = "Zostawia minę na 60 s. Uzbraja się po 0,7 s. Trafiony wróg otrzymuje obrażenia równe 5% maksymalnego kadłuba (bez redukcji absorpcji) i zwalnia o 50% na 5 s. Zrzut co 5 s."
|
description = "Zostawia minę na 60 s. Uzbraja się po 0,7 s. Trafiony wróg otrzymuje obrażenia równe 5% maksymalnego kadłuba (bez redukcji absorpcji) i zwalnia o 50% na 5 s. Zrzut co 5 s."
|
||||||
kind = 2
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "mine_layer"
|
item_id = "mine_layer"
|
||||||
display_name = "Miny"
|
display_name = "Miny"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Script" uid="uid://bgofku10gduus" path="res://scripts/abilities/repair_drones_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://bgofku10gduus" path="res://scripts/abilities/repair_drones_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://b1v55dh3vgl78" path="res://assets/icons/repair_drones.png" id="icon"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/repair_drones.png" id="icon"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
@ -12,9 +11,9 @@ description = "Naprawiają tyle kadłuba, ile pozwalają części i brakujące p
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "repair_drones"
|
item_id = "repair_drones"
|
||||||
display_name = "Drony naprawcze"
|
display_name = "Drony naprawcze"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie naprawcze."
|
description = "Aktywne rozszerzenie naprawcze."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Script" uid="uid://bwka1dwxq64sl" path="res://scripts/abilities/shield_charge_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://bwka1dwxq64sl" path="res://scripts/abilities/shield_charge_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bckqak06f6qxc" path="res://assets/icons/shield_charge.png" id="icon"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/shield_charge.png" id="icon"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
@ -12,9 +11,9 @@ description = "Przywraca 100 punktów tarcz, do ich maksimum. Wymaga i zużywa 1
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "shield_charge"
|
item_id = "shield_charge"
|
||||||
display_name = "Ładowanie tarcz"
|
display_name = "Ładowanie tarcz"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie tarcz."
|
description = "Aktywne rozszerzenie tarcz."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://x5cduw4c4tes"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/extensions/solar_shield_charge_art.png" id="icon"]
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://jemfbex10v14" path="res://assets/icons/extensions/solar_shield_charge_art.png" id="icon"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
kind = 4
|
||||||
ability_id = "solar_shield_charge"
|
ability_id = "solar_shield_charge"
|
||||||
cooldown = 30.0
|
cooldown = 30.0
|
||||||
display_name = "Doładowanie tarcz"
|
display_name = "Doładowanie tarcz"
|
||||||
description = "W zasięgu gwiazdy przywraca 20% maksymalnych tarcz na sekundę przez maksymalnie 5 s. Opuszczenie zasięgu kończy efekt. Odnowienie: 30 s."
|
description = "W zasięgu gwiazdy przywraca 20% maksymalnych tarcz na sekundę przez maksymalnie 5 s. Opuszczenie zasięgu kończy efekt. Odnowienie: 30 s."
|
||||||
kind = 4
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "solar_shield_charge"
|
item_id = "solar_shield_charge"
|
||||||
display_name = "Doładowanie tarcz"
|
display_name = "Doładowanie tarcz"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=5 format=3]
|
[gd_resource type="Resource" script_class="ItemDefinition" format=3 uid="uid://dt406oym4bd6n"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
[ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/icons/extensions/teleport_art.png" id="icon"]
|
[ext_resource type="Script" uid="uid://mjofjrs3v7by" path="res://scripts/abilities/tactical_extension_ability.gd" id="2"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://c8emovr704ky4" path="res://assets/icons/extensions/teleport_art.png" id="icon"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Ability"]
|
[sub_resource type="Resource" id="Ability"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
ability_id = "teleport"
|
ability_id = "teleport"
|
||||||
cooldown = 60.0
|
cooldown = 60.0
|
||||||
display_name = "Teleport"
|
display_name = "Teleport"
|
||||||
description = "Teleportuje w losowe, bezpieczne miejsce systemu. Zużywa całą aktualną energię (wymaga dodatniej energii). Odnowienie: 60 s."
|
description = "Teleportuje w losowe, bezpieczne miejsce systemu. Zużywa całą aktualną energię (wymaga dodatniej energii). Odnowienie: 60 s."
|
||||||
kind = 0
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
icon = ExtResource("icon")
|
|
||||||
item_id = "teleport"
|
item_id = "teleport"
|
||||||
display_name = "Teleport"
|
display_name = "Teleport"
|
||||||
|
icon = ExtResource("icon")
|
||||||
category = 5
|
category = 5
|
||||||
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
description = "Aktywne rozszerzenie statku. Zamontuj w slocie rozszerzeń i użyj przypisanego klawisza 1–5."
|
||||||
active_ability = SubResource("Ability")
|
active_ability = SubResource("Ability")
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
[gd_resource type="Theme" load_steps=3 format=3]
|
[gd_resource type="Theme" format=3 uid="uid://c0tot0b54dn2e"]
|
||||||
|
|
||||||
[ext_resource type="FontFile" path="res://assets/fonts/font2.ttf" id="1_font"]
|
[ext_resource type="FontFile" uid="uid://drp85tpfncq5m" path="res://assets/fonts/font2.ttf" id="1_font"]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="TooltipShell"]
|
[sub_resource type="StyleBoxFlat" id="TooltipShell"]
|
||||||
bg_color = Color(0.035, 0.055, 0.085, 1)
|
|
||||||
content_margin_left = 2.0
|
content_margin_left = 2.0
|
||||||
content_margin_top = 2.0
|
content_margin_top = 2.0
|
||||||
content_margin_right = 2.0
|
content_margin_right = 2.0
|
||||||
content_margin_bottom = 2.0
|
content_margin_bottom = 2.0
|
||||||
shadow_size = 0
|
bg_color = Color(0.035, 0.055, 0.085, 1)
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
default_font = ExtResource("1_font")
|
default_font = ExtResource("1_font")
|
||||||
TooltipPanel/styles/panel = SubResource("TooltipShell")
|
|
||||||
TooltipLabel/font_sizes/font_size = 11
|
TooltipLabel/font_sizes/font_size = 11
|
||||||
|
TooltipPanel/styles/panel = SubResource("TooltipShell")
|
||||||
|
|||||||
@ -93,10 +93,7 @@ ENERGIA: 100 / 100"
|
|||||||
[node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")]
|
[node name="ShipEquipmentPanel" parent="HUD/HUDRoot" unique_id=1637844841 instance=ExtResource("5_equipment")]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
offset_left = -270.0
|
offset_left = -270.0
|
||||||
offset_top = 244.0
|
|
||||||
offset_right = -10.0
|
|
||||||
offset_bottom = 422.0
|
offset_bottom = 422.0
|
||||||
grow_vertical = 1
|
|
||||||
|
|
||||||
[node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")]
|
[node name="ShipInventoryPanel" parent="HUD/HUDRoot" unique_id=1116676462 instance=ExtResource("6_inventory")]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://csjojorlptcs1" path="res://scripts/ui/main_menu.gd" id="1_script"]
|
[ext_resource type="Script" uid="uid://csjojorlptcs1" path="res://scripts/ui/main_menu.gd" id="1_script"]
|
||||||
[ext_resource type="Texture2D" uid="uid://bfc6l14s8iwhp" path="res://assets/background/background_menu.jpg" id="2_background"]
|
[ext_resource type="Texture2D" uid="uid://bfc6l14s8iwhp" path="res://assets/background/background_menu.jpg" id="2_background"]
|
||||||
[ext_resource type="Script" path="res://scripts/game/scene_music_player.gd" id="3_scene_music"]
|
[ext_resource type="Script" uid="uid://dvokq8qdccw37" path="res://scripts/game/scene_music_player.gd" id="3_scene_music"]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBox_primary"]
|
[sub_resource type="StyleBoxFlat" id="StyleBox_primary"]
|
||||||
content_margin_left = 22.0
|
content_margin_left = 22.0
|
||||||
@ -67,7 +67,7 @@ script = ExtResource("1_script")
|
|||||||
|
|
||||||
[node name="MenuMusic" type="AudioStreamPlayer" parent="." unique_id=784369201]
|
[node name="MenuMusic" type="AudioStreamPlayer" parent="." unique_id=784369201]
|
||||||
script = ExtResource("3_scene_music")
|
script = ExtResource("3_scene_music")
|
||||||
track_path = "res://assets/sounds/music/menu_music.mp3"
|
track_path = "res://assets/sounds/music/menu_music2.mp3"
|
||||||
playback_volume_db = -13.0
|
playback_volume_db = -13.0
|
||||||
|
|
||||||
[node name="Background" type="TextureRect" parent="." unique_id=1339534440]
|
[node name="Background" type="TextureRect" parent="." unique_id=1339534440]
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
extends ActiveAbility
|
extends ActiveAbility
|
||||||
|
|
||||||
@export_enum("Teleport", "Cloak", "Mine", "Flares", "Solar shields") var kind := 0
|
@export_enum("Teleport", "Cloak", "Mine", "Flares", "Solar shields", "Combat drones") var kind := 0
|
||||||
|
|
||||||
func unavailable_reason(ship: CharacterBody2D) -> String:
|
func unavailable_reason(ship: CharacterBody2D) -> String:
|
||||||
match kind:
|
match kind:
|
||||||
|
5:
|
||||||
|
if float(ship.get_node("ExtensionSystems").get("drones_remaining")) > 0.0:
|
||||||
|
return "Drony już walczą"
|
||||||
|
if int(ship.get_node("ShipInventory").get("ammunition")) < 50:
|
||||||
|
return "Wymagane 50 szt. zwykłej amunicji"
|
||||||
|
if float(ship.get_node("ShipInventory").call("get_total_dps")) <= 0.0:
|
||||||
|
return "Wymagana zamontowana broń"
|
||||||
0:
|
0:
|
||||||
if float(ship.get("energy")) <= 0.0:
|
if float(ship.get("energy")) <= 0.0:
|
||||||
return "Brak energii"
|
return "Brak energii"
|
||||||
|
|||||||
@ -32,6 +32,9 @@ func get_skill_rank(skill: String) -> int:
|
|||||||
func get_skill_bonus(skill: String) -> float:
|
func get_skill_bonus(skill: String) -> float:
|
||||||
return SKILLS.value(skill, get_skill_rank(skill))
|
return SKILLS.value(skill, get_skill_rank(skill))
|
||||||
|
|
||||||
|
func get_energy_bank_fraction() -> float:
|
||||||
|
return maxf(0.10, get_skill_bonus("electrician"))
|
||||||
|
|
||||||
func normalize_skill_ranks() -> void:
|
func normalize_skill_ranks() -> void:
|
||||||
for skill in skills.keys():
|
for skill in skills.keys():
|
||||||
if not SKILLS.DATA.has(skill):
|
if not SKILLS.DATA.has(skill):
|
||||||
|
|||||||
@ -3,12 +3,13 @@ extends RefCounted
|
|||||||
const MAX_RANK := 3
|
const MAX_RANK := 3
|
||||||
const COOLDOWN := 30.0
|
const COOLDOWN := 30.0
|
||||||
const DURATION := 5.0
|
const DURATION := 5.0
|
||||||
const IDS := ["business_shark", "reactor_overload", "tactical_maneuver", "efficient_magazine", "miner", "power_redirect", "lucky", "explorer"]
|
const IDS := ["business_shark", "reactor_overload", "tactical_maneuver", "efficient_magazine", "miner", "power_redirect", "lucky", "explorer", "electrician"]
|
||||||
const DATA := {
|
const DATA := {
|
||||||
|
"electrician": {"name": "Elektryk", "active": false, "values": [0.15, 0.20, 0.25], "color": "70f5df"},
|
||||||
"lucky": {"name": "Farciarz", "active": false, "values": [0.05, 0.10, 0.25], "color": "e8c76f"},
|
"lucky": {"name": "Farciarz", "active": false, "values": [0.05, 0.10, 0.25], "color": "e8c76f"},
|
||||||
"explorer": {"name": "Eksplorator", "active": false, "values": [1, 3, 6], "color": "76d9cc"},
|
"explorer": {"name": "Eksplorator", "active": false, "values": [1, 3, 6], "color": "76d9cc"},
|
||||||
"business_shark": {"name": "Rekin biznesu", "active": false, "values": [0.10, 0.30, 0.60], "color": "efb568"},
|
"business_shark": {"name": "Rekin biznesu", "active": false, "values": [0.10, 0.30, 0.60], "color": "efb568"},
|
||||||
"reactor_overload": {"name": "Przeciążenie reaktora", "active": true, "values": [0.10, 0.20, 0.70], "color": "63d9f5"},
|
"reactor_overload": {"name": "Przeciążenie reaktora", "active": true, "values": [0.20, 0.50, 1.00], "color": "63d9f5"},
|
||||||
"tactical_maneuver": {"name": "Manewr taktyczny", "active": true, "values": [0.30, 0.60, 1.00], "color": "b9a0ef"},
|
"tactical_maneuver": {"name": "Manewr taktyczny", "active": true, "values": [0.30, 0.60, 1.00], "color": "b9a0ef"},
|
||||||
"efficient_magazine": {"name": "Oszczędny magazynek", "active": false, "values": [0.10, 0.20, 0.50], "color": "a9d69a"},
|
"efficient_magazine": {"name": "Oszczędny magazynek", "active": false, "values": [0.10, 0.20, 0.50], "color": "a9d69a"},
|
||||||
"miner": {"name": "Górnik", "active": false, "values": [3, 6, 10], "color": "e5ac76"},
|
"miner": {"name": "Górnik", "active": false, "values": [3, 6, 10], "color": "e5ac76"},
|
||||||
@ -27,6 +28,8 @@ static func icon(skill: String) -> Texture2D:
|
|||||||
static func description(skill: String, rank: int) -> String:
|
static func description(skill: String, rank: int) -> String:
|
||||||
var amount := roundi(value(skill, rank) * 100.0)
|
var amount := roundi(value(skill, rank) * 100.0)
|
||||||
match skill:
|
match skill:
|
||||||
|
"electrician":
|
||||||
|
return "Powerbank przywraca %d%% maksymalnej energii zamiast 10%%. Umiejętność pasywna; premie rang nie sumują się." % amount
|
||||||
"lucky":
|
"lucky":
|
||||||
return "Szczęście: %d%% szans na przedmiot o 1 poziom wyższy niż bazowy loot planszy. Dotyczy lootu z wrogów i znalezisk. Bazowo: 0%%. Sumuje się ze sprzętem do limitu 60%%. Premie rang nie sumują się." % amount
|
return "Szczęście: %d%% szans na przedmiot o 1 poziom wyższy niż bazowy loot planszy. Dotyczy lootu z wrogów i znalezisk. Bazowo: 0%%. Sumuje się ze sprzętem do limitu 60%%. Premie rang nie sumują się." % amount
|
||||||
"explorer":
|
"explorer":
|
||||||
|
|||||||
@ -1,116 +1,88 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
const SETTLE_TIME := 8.0
|
const PICKUP := preload("res://scripts/items/mineral_pickup.gd")
|
||||||
var _texture: Texture2D
|
|
||||||
var _fragments: Array[Dictionary] = []
|
|
||||||
var _dust: Array[Dictionary] = []
|
|
||||||
var _elapsed := 0.0
|
var _elapsed := 0.0
|
||||||
var _rng := RandomNumberGenerator.new()
|
var _dust: Array[Dictionary] = []
|
||||||
var _dust_color := Color("b4a99a")
|
|
||||||
|
|
||||||
func configure(sprite: Sprite2D) -> void:
|
func configure(sprite: Sprite2D, count: int = 6, mineral: String = "iron") -> void:
|
||||||
_texture = sprite.texture
|
|
||||||
transform = sprite.global_transform
|
transform = sprite.global_transform
|
||||||
var sprite_scale := global_scale
|
var source_scale := scale
|
||||||
scale = Vector2.ONE
|
scale = Vector2.ONE
|
||||||
_rng.randomize()
|
var dimensions := sprite.texture.get_size()
|
||||||
var dimensions := _texture.get_size()
|
var offset := randf()
|
||||||
var grid: Array[Vector2] = []
|
var cracks: Array[PackedVector2Array] = []
|
||||||
for y in 5:
|
for index in count:
|
||||||
for x in 5:
|
var endpoint := _edge_point(offset + float(index) * 4.0 / count) * dimensions
|
||||||
var vertex := Vector2(x, y) * dimensions / 4.0
|
cracks.append(_fracture_edge(endpoint, count))
|
||||||
if x > 0 and x < 4:
|
for index in count:
|
||||||
vertex.x += _rng.randf_range(-0.3, 0.3) * dimensions.x / 4.0
|
# Neighbours share the same jagged fracture, so the pieces fit together.
|
||||||
if y > 0 and y < 4:
|
var start := offset + float(index) * 4.0 / count
|
||||||
vertex.y += _rng.randf_range(-0.3, 0.3) * dimensions.y / 4.0
|
var finish := offset + float(index + 1) * 4.0 / count
|
||||||
grid.append(vertex)
|
var vertices := cracks[index].duplicate()
|
||||||
# Shared jagged cracks keep neighbouring pieces fitted together.
|
for corner in range(int(floor(start)) + 1, int(ceil(finish))):
|
||||||
var horizontal: Array[PackedVector2Array] = []
|
vertices.append(_edge_point(float(corner)) * dimensions)
|
||||||
var vertical: Array[PackedVector2Array] = []
|
var next_crack := cracks[(index + 1) % count]
|
||||||
for y in 5:
|
for vertex_index in range(next_crack.size() - 1, 0, -1):
|
||||||
for x in 4:
|
vertices.append(next_crack[vertex_index])
|
||||||
horizontal.append(_fracture_edge(grid[y * 5 + x], grid[y * 5 + x + 1], y > 0 and y < 4))
|
var center := Vector2.ZERO
|
||||||
for y in 4:
|
for vertex in vertices:
|
||||||
for x in 5:
|
center += vertex
|
||||||
vertical.append(_fracture_edge(grid[y * 5 + x], grid[(y + 1) * 5 + x], x > 0 and x < 4))
|
center /= vertices.size()
|
||||||
for y in 4:
|
var fragment_points := PackedVector2Array()
|
||||||
for x in 4:
|
var fragment_uv := PackedVector2Array()
|
||||||
var outline := PackedVector2Array()
|
for vertex in vertices:
|
||||||
outline.append_array(_edge_vertices(horizontal[y * 4 + x], false))
|
fragment_points.append((vertex - center) * source_scale)
|
||||||
outline.append_array(_edge_vertices(vertical[y * 5 + x + 1], false))
|
fragment_uv.append(vertex / dimensions + Vector2(0.5, 0.5))
|
||||||
outline.append_array(_edge_vertices(horizontal[(y + 1) * 4 + x], true))
|
var fragment := Node2D.new()
|
||||||
outline.append_array(_edge_vertices(vertical[y * 5 + x], true))
|
fragment.set_script(PICKUP)
|
||||||
# Rare jagged cracks can cross at a corner. Repair the silhouette once,
|
fragment.set("mineral", mineral)
|
||||||
# rather than submitting an invalid polygon every rendered frame.
|
fragment.set("texture", sprite.texture)
|
||||||
if Geometry2D.triangulate_polygon(outline).is_empty():
|
fragment.set("points", fragment_points)
|
||||||
outline = Geometry2D.convex_hull(outline)
|
fragment.set("uv", fragment_uv)
|
||||||
if outline.size() > 1 and outline[0].is_equal_approx(outline[outline.size() - 1]):
|
fragment.position = center * source_scale
|
||||||
outline.remove_at(outline.size() - 1)
|
fragment.set("drift", center.normalized() * randf_range(65.0, 115.0))
|
||||||
if outline.size() < 3 or Geometry2D.triangulate_polygon(outline).is_empty():
|
fragment.set("spin", randf_range(-2.5, 2.5))
|
||||||
continue
|
add_child(fragment)
|
||||||
var center := Vector2.ZERO
|
|
||||||
for vertex in outline:
|
|
||||||
center += vertex
|
|
||||||
center /= outline.size()
|
|
||||||
var origin := (center - dimensions * 0.5) * sprite_scale
|
|
||||||
var points := PackedVector2Array()
|
|
||||||
var uv := PackedVector2Array()
|
|
||||||
for vertex in outline:
|
|
||||||
points.append((vertex - center) * sprite_scale)
|
|
||||||
uv.append(vertex / dimensions)
|
|
||||||
var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT
|
|
||||||
_fragments.append({"points": points, "uv": uv, "position": origin,
|
|
||||||
"velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(40.0, 95.0),
|
|
||||||
"angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)})
|
|
||||||
var mineral := _texture.resource_path.get_file().get_basename()
|
|
||||||
_dust_color = {"iron": Color("be9b75"), "titanium": Color("9ecbe9"), "uranium": Color("a9db71"), "aluminium": Color("d3dce0"), "sulfur": Color("e8cd65")}.get(mineral, Color("b4a99a"))
|
|
||||||
for index in 28:
|
for index in 28:
|
||||||
_dust.append({"direction": Vector2.RIGHT.rotated(_rng.randf_range(0.0, TAU)),
|
_dust.append({"velocity": Vector2.from_angle(randf() * TAU) * randf_range(45.0, 180.0), "size": randf_range(1.0, 3.0)})
|
||||||
"speed": _rng.randf_range(45.0, 210.0), "size": _rng.randf_range(1.0, 3.0)})
|
|
||||||
z_index = 35
|
|
||||||
light_mask = 4
|
light_mask = 4
|
||||||
|
|
||||||
func _fracture_edge(start: Vector2, end: Vector2, jagged: bool) -> PackedVector2Array:
|
func _fracture_edge(endpoint: Vector2, count: int) -> PackedVector2Array:
|
||||||
var edge := PackedVector2Array([start])
|
var edge := PackedVector2Array([Vector2.ZERO])
|
||||||
if jagged:
|
var normal := endpoint.orthogonal().normalized()
|
||||||
var normal := (end - start).orthogonal().normalized()
|
var roughness := endpoint.length() * minf(0.14, 0.65 / count)
|
||||||
var roughness := start.distance_to(end) * 0.13
|
var side := -1.0 if randf() < 0.5 else 1.0
|
||||||
var side := 1.0 if _rng.randf() > 0.5 else -1.0
|
for index in 5:
|
||||||
for index in 3:
|
var along := float(index + 1) / 6.0
|
||||||
var along := (index + 1.0) / 4.0 + _rng.randf_range(-0.05, 0.05)
|
var offset := roughness * sin(along * PI) * side * randf_range(0.6, 1.0)
|
||||||
edge.append(start.lerp(end, along) + normal * roughness * side * _rng.randf_range(0.5, 1.0))
|
edge.append(endpoint * along + normal * offset)
|
||||||
side *= -1.0
|
side *= -1.0
|
||||||
edge.append(end)
|
edge.append(endpoint)
|
||||||
return edge
|
return edge
|
||||||
|
|
||||||
func _edge_vertices(edge: PackedVector2Array, backwards: bool) -> PackedVector2Array:
|
func _edge_point(distance: float) -> Vector2:
|
||||||
var vertices := PackedVector2Array()
|
var t := fposmod(distance, 4.0)
|
||||||
# The next edge supplies the end vertex, avoiding duplicate polygon points.
|
if t < 1.0:
|
||||||
for index in edge.size() - 1:
|
return Vector2(-0.5 + t, -0.5)
|
||||||
vertices.append(edge[edge.size() - 1 - index] if backwards else edge[index])
|
if t < 2.0:
|
||||||
return vertices
|
return Vector2(0.5, -0.5 + t - 1.0)
|
||||||
|
if t < 3.0:
|
||||||
|
return Vector2(0.5 - (t - 2.0), 0.5)
|
||||||
|
return Vector2(-0.5, 0.5 - (t - 3.0))
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
_elapsed += delta
|
_elapsed += delta
|
||||||
for fragment in _fragments:
|
if _elapsed <= 1.0:
|
||||||
fragment.position += fragment.velocity * delta
|
queue_redraw()
|
||||||
fragment.velocity *= exp(-1.2 * delta)
|
elif not _dust.is_empty():
|
||||||
fragment.angle += fragment.spin * delta
|
|
||||||
fragment.spin *= exp(-0.7 * delta)
|
|
||||||
queue_redraw()
|
|
||||||
# Leave the settled debris visible without updating it every frame.
|
|
||||||
if _elapsed >= SETTLE_TIME:
|
|
||||||
_dust.clear()
|
_dust.clear()
|
||||||
set_process(false)
|
queue_redraw()
|
||||||
|
if _elapsed > 1.0 and get_child_count() == 0:
|
||||||
|
queue_free()
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
for fragment in _fragments:
|
var fade := maxf(0.0, 1.0 - _elapsed)
|
||||||
draw_set_transform(fragment.position, fragment.angle)
|
|
||||||
draw_polygon(fragment.points, PackedColorArray([Color.WHITE]), fragment.uv, _texture)
|
|
||||||
draw_set_transform(Vector2.ZERO)
|
|
||||||
var dust_fade := maxf(0.0, 1.0 - _elapsed / 0.8)
|
|
||||||
for mote in _dust:
|
for mote in _dust:
|
||||||
var travel: Vector2 = mote.direction * mote.speed * (1.0 - exp(-2.0 * _elapsed)) / 2.0
|
draw_circle(mote.velocity * (1.0 - exp(-2.0 * _elapsed)) * 0.5, mote.size, Color(0.7, 0.75, 0.8, fade * 0.65))
|
||||||
draw_circle(travel, mote.size * (1.0 + _elapsed), Color(_dust_color, dust_fade * 0.65))
|
|
||||||
if _elapsed < 0.18:
|
if _elapsed < 0.18:
|
||||||
draw_circle(Vector2.ZERO, 16.0 * (1.0 - _elapsed / 0.18), Color(0.85, 0.9, 1.0, (1.0 - _elapsed / 0.18) * 0.55))
|
draw_circle(Vector2.ZERO, 16.0 * (1.0 - _elapsed / 0.18), Color(0.85, 0.9, 1.0, fade * 0.5))
|
||||||
|
|||||||
116
scripts/game/combat_drone.gd
Normal file
116
scripts/game/combat_drone.gd
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn")
|
||||||
|
const FIRE_INTERVAL := 0.25
|
||||||
|
const HULL_TEXTURE := preload("res://assets/Ships/combat_drone.png")
|
||||||
|
var mother: CharacterBody2D
|
||||||
|
var formation_index := 0
|
||||||
|
var damage_per_shot := 1.0
|
||||||
|
var remaining := 10.0
|
||||||
|
var _fire_timer := 0.0
|
||||||
|
var _elapsed := 0.0
|
||||||
|
var _flash := 0.0
|
||||||
|
var velocity := Vector2.ZERO
|
||||||
|
var _turn_velocity := 0.0
|
||||||
|
const FORMATION := [Vector2(0, -110), Vector2(-35, -78), Vector2(35, -78)]
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("player_combat_drones")
|
||||||
|
z_index = 4
|
||||||
|
scale = Vector2.ONE * 0.5
|
||||||
|
rotation = mother.rotation
|
||||||
|
velocity = mother.velocity
|
||||||
|
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||||
|
_fire_timer = formation_index * FIRE_INTERVAL / 3.0
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
remaining -= delta
|
||||||
|
if remaining <= 0.0 or not is_instance_valid(mother) or bool(mother.get("_is_destroyed")) or bool(mother.get("is_docked")):
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
_elapsed += delta
|
||||||
|
_flash = maxf(0.0, _flash - delta)
|
||||||
|
_fire_timer = maxf(0.0, _fire_timer - delta)
|
||||||
|
var target := _find_target()
|
||||||
|
var destination := formation_position()
|
||||||
|
var escort_velocity := mother.velocity
|
||||||
|
if target != null:
|
||||||
|
var anchor := mother.global_position.lerp(target.global_position, 0.45)
|
||||||
|
destination = anchor + Vector2.from_angle(TAU * formation_index / 3.0 + _elapsed * 0.7) * 85.0
|
||||||
|
escort_velocity *= 0.55
|
||||||
|
else:
|
||||||
|
# Rotating formation slots have tangential velocity as the mother turns.
|
||||||
|
var offset := destination - mother.global_position
|
||||||
|
escort_velocity += Vector2(-offset.y, offset.x) * float(mother.get("angular_velocity"))
|
||||||
|
var error := destination - global_position
|
||||||
|
var correction := (error * 3.0).limit_length(260.0)
|
||||||
|
var desired_velocity := (escort_velocity + correction).limit_length(maxf(360.0, mother.velocity.length() + 200.0))
|
||||||
|
velocity = velocity.move_toward(desired_velocity, maxf(650.0, mother.velocity.length()) * delta)
|
||||||
|
global_position += velocity * delta
|
||||||
|
var desired_rotation := mother.rotation
|
||||||
|
if target != null:
|
||||||
|
var aim := target.global_position - global_position
|
||||||
|
desired_rotation = aim.angle() + PI * 0.5
|
||||||
|
if _fire_timer <= 0.0 and aim.length() <= 650.0 and _clear_shot(target):
|
||||||
|
_fire(target)
|
||||||
|
_fire_timer = FIRE_INTERVAL
|
||||||
|
elif error.length() > 55.0 and velocity.length() > 30.0:
|
||||||
|
desired_rotation = velocity.angle() + PI * 0.5
|
||||||
|
var angle_error := angle_difference(rotation, desired_rotation)
|
||||||
|
_turn_velocity = move_toward(_turn_velocity, clampf(angle_error * 6.0, -4.5, 4.5), 14.0 * delta)
|
||||||
|
var turn := _turn_velocity * delta
|
||||||
|
if absf(turn) >= absf(angle_error) and signf(turn) == signf(angle_error):
|
||||||
|
rotation = desired_rotation
|
||||||
|
_turn_velocity = 0.0
|
||||||
|
else:
|
||||||
|
rotation += turn
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func formation_position() -> Vector2:
|
||||||
|
return mother.global_position + FORMATION[clampi(formation_index, 0, 2)].rotated(mother.rotation)
|
||||||
|
|
||||||
|
func _find_target() -> Node2D:
|
||||||
|
var closest: Node2D
|
||||||
|
var distance := 800.0
|
||||||
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
|
||||||
|
continue
|
||||||
|
var candidate := global_position.distance_to(enemy.global_position)
|
||||||
|
if candidate < distance and mother.global_position.distance_to(enemy.global_position) < 1000.0:
|
||||||
|
distance = candidate
|
||||||
|
closest = enemy
|
||||||
|
return closest
|
||||||
|
|
||||||
|
func _clear_shot(target: Node2D) -> bool:
|
||||||
|
var query := PhysicsRayQueryParameters2D.create(global_position, target.global_position, 13)
|
||||||
|
var hit := get_world_2d().direct_space_state.intersect_ray(query)
|
||||||
|
return hit.is_empty() or hit.get("collider") == target
|
||||||
|
|
||||||
|
func _fire(target: Node2D) -> void:
|
||||||
|
var direction := (target.global_position - global_position).normalized()
|
||||||
|
var bullet := PROJECTILE.instantiate()
|
||||||
|
bullet.call("configure", global_position + direction * 9.0, direction, ItemDefinition.WeaponKind.MACHINE_GUN, 1100.0, damage_per_shot, 650.0)
|
||||||
|
get_parent().add_child(bullet)
|
||||||
|
_flash = 0.06
|
||||||
|
mother.get_node("ExtensionSystems").call("break_cloak_on_fire")
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
modulate.a = minf(clampf(_elapsed / 0.2, 0.0, 1.0), clampf(remaining / 0.6, 0.0, 1.0))
|
||||||
|
var flicker := sin(_elapsed * 33.0 + formation_index * 2.1)
|
||||||
|
for x in [-5.5, 5.5]:
|
||||||
|
var exhaust := Vector2(x, 19.5)
|
||||||
|
draw_circle(exhaust + Vector2(0, 5), 11.0 + flicker, Color(0.08, 0.65, 1.0, 0.08))
|
||||||
|
draw_circle(exhaust + Vector2(0, 3), 6.0, Color(0.12, 0.8, 1.0, 0.18))
|
||||||
|
draw_colored_polygon(PackedVector2Array([exhaust + Vector2(-3, 0), exhaust + Vector2(0, 15 + flicker * 2), exhaust + Vector2(3, 0)]), Color(0.15, 0.72, 1.0, 0.65))
|
||||||
|
draw_colored_polygon(PackedVector2Array([exhaust + Vector2(-1.4, 0), exhaust + Vector2(0, 8 + flicker), exhaust + Vector2(1.4, 0)]), Color("c8fbff"))
|
||||||
|
var hull_size := HULL_TEXTURE.get_size() * (60.0 / HULL_TEXTURE.get_height())
|
||||||
|
var hull_rect := Rect2(-hull_size * 0.5, hull_size)
|
||||||
|
draw_texture_rect(HULL_TEXTURE, Rect2(hull_rect.position + Vector2(3, 5), hull_size), false, Color(0, 0, 0, 0.4))
|
||||||
|
draw_texture_rect(HULL_TEXTURE, hull_rect, false)
|
||||||
|
if _flash > 0.0:
|
||||||
|
for x in [-5.5, 5.5]:
|
||||||
|
var muzzle := Vector2(x, -20)
|
||||||
|
draw_circle(muzzle, 8.0, Color(1.0, 0.6, 0.18, 0.15))
|
||||||
|
draw_colored_polygon(PackedVector2Array([muzzle + Vector2(-3, 0), muzzle + Vector2(-1.5, -4), muzzle + Vector2(0, -12), muzzle + Vector2(1.5, -4), muzzle + Vector2(3, 0)]), Color("ffbd68"))
|
||||||
|
draw_line(muzzle, muzzle + Vector2(0, -7), Color("fff2cb"), 2.0, true)
|
||||||
1
scripts/game/combat_drone.gd.uid
Normal file
1
scripts/game/combat_drone.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://f2rww506fhi7
|
||||||
@ -4,7 +4,8 @@ const VFX := preload("res://scripts/game/extension_vfx.gd")
|
|||||||
const MINE_TEXTURE := preload("res://assets/icons/extensions/mine_art.png")
|
const MINE_TEXTURE := preload("res://assets/icons/extensions/mine_art.png")
|
||||||
const LIFETIME := 60.0
|
const LIFETIME := 60.0
|
||||||
const ARM_TIME := 0.7
|
const ARM_TIME := 0.7
|
||||||
const TRIGGER_RADIUS := 34.0
|
const TRIGGER_RADIUS := 60.0
|
||||||
|
const BLAST_RADIUS := 140.0
|
||||||
var drift := Vector2.ZERO
|
var drift := Vector2.ZERO
|
||||||
var _age := 0.0
|
var _age := 0.0
|
||||||
var _triggered := false
|
var _triggered := false
|
||||||
@ -27,17 +28,30 @@ func _physics_process(delta: float) -> void:
|
|||||||
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
|
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
|
||||||
continue
|
continue
|
||||||
if global_position.distance_to(enemy.global_position) <= TRIGGER_RADIUS + float(enemy.call("get_visual_size")) * 0.35:
|
if global_position.distance_to(enemy.global_position) <= TRIGGER_RADIUS + float(enemy.call("get_visual_size")) * 0.35:
|
||||||
_triggered = true
|
_detonate()
|
||||||
enemy.call("apply_mine_hit")
|
|
||||||
var explosion := Node2D.new()
|
|
||||||
explosion.set_script(preload("res://scripts/game/deployment_flash.gd"))
|
|
||||||
explosion.set("color", Color("ffbc75"))
|
|
||||||
explosion.set("blast", true)
|
|
||||||
explosion.position = position
|
|
||||||
get_parent().add_child(explosion)
|
|
||||||
queue_free()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
func _detonate() -> void:
|
||||||
|
if _triggered:
|
||||||
|
return
|
||||||
|
_triggered = true
|
||||||
|
# Resolve the entire blast once; each nearby enemy receives one hit.
|
||||||
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0:
|
||||||
|
continue
|
||||||
|
var hull_radius := float(enemy.call("get_visual_size")) * 0.35
|
||||||
|
if global_position.distance_to(enemy.global_position) <= BLAST_RADIUS + hull_radius:
|
||||||
|
enemy.call("apply_mine_hit")
|
||||||
|
var explosion := Node2D.new()
|
||||||
|
explosion.set_script(preload("res://scripts/game/deployment_flash.gd"))
|
||||||
|
explosion.set("color", Color("ffbc75"))
|
||||||
|
explosion.set("blast", true)
|
||||||
|
# The shockwave reaches the same radius as the area damage.
|
||||||
|
explosion.scale = Vector2.ONE * (BLAST_RADIUS / 88.0)
|
||||||
|
explosion.position = position
|
||||||
|
get_parent().add_child(explosion)
|
||||||
|
queue_free()
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
var armed := _age >= ARM_TIME
|
var armed := _age >= ARM_TIME
|
||||||
var deployment := smoothstep(0.0, 0.35, _age)
|
var deployment := smoothstep(0.0, 0.35, _age)
|
||||||
|
|||||||
55
scripts/game/enemy_mine.gd
Normal file
55
scripts/game/enemy_mine.gd
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const ARM_TIME := 0.7
|
||||||
|
const LIFETIME := 60.0
|
||||||
|
const TRIGGER_RADIUS := 54.0
|
||||||
|
var player: CharacterBody2D
|
||||||
|
var drift := Vector2.ZERO
|
||||||
|
var _age := 0.0
|
||||||
|
var _triggered := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("enemy_mines")
|
||||||
|
z_index = 12
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
_age += delta
|
||||||
|
position += drift * delta
|
||||||
|
drift = drift.move_toward(Vector2.ZERO, 100.0 * delta)
|
||||||
|
queue_redraw()
|
||||||
|
if _age >= LIFETIME:
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
if _triggered or _age < ARM_TIME or not is_instance_valid(player) or bool(player.get("_is_destroyed")):
|
||||||
|
return
|
||||||
|
if global_position.distance_to(player.global_position) <= TRIGGER_RADIUS:
|
||||||
|
_triggered = true
|
||||||
|
player.call("take_damage", float(player.get("max_hull")) * 0.05, false, DamageTypes.Type.KINETIC)
|
||||||
|
var explosion := Node2D.new()
|
||||||
|
explosion.set_script(preload("res://scripts/game/deployment_flash.gd"))
|
||||||
|
explosion.set("color", Color("ff4265"))
|
||||||
|
explosion.set("blast", true)
|
||||||
|
explosion.position = position
|
||||||
|
get_parent().add_child(explosion)
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var armed := _age >= ARM_TIME
|
||||||
|
var pulse := 0.65 + 0.35 * sin(_age * 7.0)
|
||||||
|
var accent := Color("ff4265") if armed else Color("ffb45c")
|
||||||
|
var fade := clampf(LIFETIME - _age, 0.0, 1.0)
|
||||||
|
draw_circle(Vector2.ZERO, 30.0, Color(accent, 0.08 * pulse * fade))
|
||||||
|
var shell := PackedVector2Array()
|
||||||
|
for index in 16:
|
||||||
|
var radius := 24.0 if index % 2 == 0 else 13.0
|
||||||
|
shell.append(Vector2.from_angle(TAU * index / 16.0) * radius)
|
||||||
|
draw_colored_polygon(shell, Color("292236") * Color(1, 1, 1, fade))
|
||||||
|
var outline := shell.duplicate()
|
||||||
|
outline.append(shell[0])
|
||||||
|
draw_polyline(outline, Color(accent, 0.9 * fade), 1.5, true)
|
||||||
|
draw_circle(Vector2.ZERO, 10.0, Color("13101e") * Color(1, 1, 1, fade))
|
||||||
|
draw_arc(Vector2.ZERO, 11.0, 0.0, TAU, 32, Color(accent, fade), 2.0, true)
|
||||||
|
for index in 3:
|
||||||
|
var direction := Vector2.from_angle(-PI * 0.5 + TAU * index / 3.0)
|
||||||
|
draw_line(direction * 4.0, direction * 8.0, Color(accent, pulse * fade), 2.5, true)
|
||||||
|
draw_circle(Vector2.ZERO, 3.0, Color(accent, pulse * fade))
|
||||||
1
scripts/game/enemy_mine.gd.uid
Normal file
1
scripts/game/enemy_mine.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dgo884psqgnsd
|
||||||
@ -21,6 +21,10 @@ const LASER_MISS_CHANCE := 0.35
|
|||||||
@export var weapon_range := 680.0
|
@export var weapon_range := 680.0
|
||||||
@export var weapon_cooldown := 1.5
|
@export var weapon_cooldown := 1.5
|
||||||
@export var weapon_damage := 8.0
|
@export var weapon_damage := 8.0
|
||||||
|
enum Tactic { RAM, KEEP_DISTANCE }
|
||||||
|
@export_enum("Losowa", "Taran", "Dystans") var tactic_choice := 0
|
||||||
|
var tactic := Tactic.RAM
|
||||||
|
var _orbit_direction := 1.0
|
||||||
|
|
||||||
var max_shields := 20.0
|
var max_shields := 20.0
|
||||||
var shields := 20.0
|
var shields := 20.0
|
||||||
@ -31,12 +35,15 @@ var _shield_visual: Node2D
|
|||||||
var hull := 50.0
|
var hull := 50.0
|
||||||
var _player: CharacterBody2D
|
var _player: CharacterBody2D
|
||||||
var _activated := false
|
var _activated := false
|
||||||
|
var _retreat_mine_rolled := false
|
||||||
var _sprite: Sprite2D
|
var _sprite: Sprite2D
|
||||||
var _weapon_timer := 0.0
|
var _weapon_timer := 0.0
|
||||||
var _slow_remaining := 0.0
|
var _slow_remaining := 0.0
|
||||||
var _combat_target: Node2D
|
var _combat_target: Node2D
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
tactic = randi_range(0, 1) if tactic_choice == 0 else tactic_choice - 1
|
||||||
|
_orbit_direction = -1.0 if randf() < 0.5 else 1.0
|
||||||
hull = max_hull
|
hull = max_hull
|
||||||
_sprite = $Sprite2D
|
_sprite = $Sprite2D
|
||||||
_shield_visual = Node2D.new()
|
_shield_visual = Node2D.new()
|
||||||
@ -108,10 +115,16 @@ func _physics_process(delta: float) -> void:
|
|||||||
_activated = true
|
_activated = true
|
||||||
if _activated and to_player.length_squared() > 1.0:
|
if _activated and to_player.length_squared() > 1.0:
|
||||||
var direction := to_player.normalized()
|
var direction := to_player.normalized()
|
||||||
var desired_rotation := direction.angle() + PI * 0.5
|
var fleeing := is_retreating()
|
||||||
|
if fleeing and not _retreat_mine_rolled:
|
||||||
|
_retreat_mine_rolled = true
|
||||||
|
if randf() < 0.15:
|
||||||
|
_drop_retreat_mine(to_player.normalized())
|
||||||
|
var movement := combat_movement(to_player)
|
||||||
|
var desired_rotation := (movement if fleeing else direction).angle() + PI * 0.5
|
||||||
rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta)
|
rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta)
|
||||||
velocity = velocity.move_toward(direction * approach_speed * get_slow_multiplier(), approach_acceleration * get_slow_multiplier() * delta)
|
velocity = velocity.move_toward(movement * approach_speed * (0.5 if fleeing else 1.0) * get_slow_multiplier(), approach_acceleration * get_slow_multiplier() * delta)
|
||||||
if to_player.length() <= weapon_range and _weapon_timer <= 0.0:
|
if not fleeing and to_player.length() <= weapon_range and _weapon_timer <= 0.0:
|
||||||
_fire_plasma(direction)
|
_fire_plasma(direction)
|
||||||
_weapon_timer = get_fire_interval()
|
_weapon_timer = get_fire_interval()
|
||||||
else:
|
else:
|
||||||
@ -120,6 +133,37 @@ func _physics_process(delta: float) -> void:
|
|||||||
move_and_slide()
|
move_and_slide()
|
||||||
_check_player_collision()
|
_check_player_collision()
|
||||||
|
|
||||||
|
func is_retreating() -> bool:
|
||||||
|
return hull > 0.0 and hull < max_hull * 0.1
|
||||||
|
|
||||||
|
func _drop_retreat_mine(toward_target: Vector2) -> void:
|
||||||
|
var mine := Node2D.new()
|
||||||
|
mine.set_script(preload("res://scripts/game/enemy_mine.gd"))
|
||||||
|
mine.set("player", _player)
|
||||||
|
mine.set("drift", toward_target * 36.0)
|
||||||
|
mine.position = position + toward_target * (get_visual_size() * 0.5 + 16.0)
|
||||||
|
get_parent().add_child.call_deferred(mine)
|
||||||
|
|
||||||
|
func combat_movement(to_target: Vector2) -> Vector2:
|
||||||
|
var distance := to_target.length()
|
||||||
|
var direction := to_target.normalized()
|
||||||
|
if is_retreating():
|
||||||
|
var escape := -direction
|
||||||
|
var half := preload("res://scripts/levels/system_generator.gd").WORLD_SIZE * 0.5 - Vector2(120, 120)
|
||||||
|
if absf(global_position.x) > half.x:
|
||||||
|
escape.x = -signf(global_position.x)
|
||||||
|
if absf(global_position.y) > half.y:
|
||||||
|
escape.y = -signf(global_position.y)
|
||||||
|
return escape.normalized()
|
||||||
|
if tactic == Tactic.RAM:
|
||||||
|
return direction
|
||||||
|
var preferred := minf(weapon_range * 0.65, 420.0)
|
||||||
|
if distance < preferred * 0.8:
|
||||||
|
return -direction
|
||||||
|
if distance > preferred * 1.15:
|
||||||
|
return direction
|
||||||
|
return direction.orthogonal() * _orbit_direction * 0.55
|
||||||
|
|
||||||
func get_slow_multiplier() -> float:
|
func get_slow_multiplier() -> float:
|
||||||
return 0.5 if _slow_remaining > 0.0 else 1.0
|
return 0.5 if _slow_remaining > 0.0 else 1.0
|
||||||
|
|
||||||
@ -144,7 +188,7 @@ func apply_mine_hit() -> void:
|
|||||||
return
|
return
|
||||||
_slow_remaining = 5.0
|
_slow_remaining = 5.0
|
||||||
velocity = velocity.limit_length(approach_speed * 0.5)
|
velocity = velocity.limit_length(approach_speed * 0.5)
|
||||||
take_damage(max_hull * 0.05, false, DamageTypes.Type.KINETIC, true)
|
take_damage(hull * 0.5, false, DamageTypes.Type.KINETIC, true)
|
||||||
|
|
||||||
func _check_player_collision() -> void:
|
func _check_player_collision() -> void:
|
||||||
for collision_index in get_slide_collision_count():
|
for collision_index in get_slide_collision_count():
|
||||||
|
|||||||
@ -5,6 +5,8 @@ const PULSE := preload("res://scripts/game/extension_pulse.gd")
|
|||||||
var cooldowns: Dictionary = {}
|
var cooldowns: Dictionary = {}
|
||||||
var cloak_remaining := 0.0
|
var cloak_remaining := 0.0
|
||||||
var solar_remaining := 0.0
|
var solar_remaining := 0.0
|
||||||
|
var drones_remaining := 0.0
|
||||||
|
var _drones: Array[Node2D] = []
|
||||||
var _cloak_material: ShaderMaterial
|
var _cloak_material: ShaderMaterial
|
||||||
var _old_material: Material
|
var _old_material: Material
|
||||||
var _shadow_was_visible := false
|
var _shadow_was_visible := false
|
||||||
@ -15,11 +17,16 @@ var _has_destination := false
|
|||||||
|
|
||||||
func reset_combat_state() -> void:
|
func reset_combat_state() -> void:
|
||||||
cooldowns.clear()
|
cooldowns.clear()
|
||||||
|
_clear_drones()
|
||||||
solar_remaining = 0.0
|
solar_remaining = 0.0
|
||||||
if cloak_remaining > 0.0:
|
if cloak_remaining > 0.0:
|
||||||
_end_cloak()
|
_end_cloak()
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
|
if drones_remaining > 0.0:
|
||||||
|
drones_remaining = maxf(0.0, drones_remaining - delta)
|
||||||
|
if drones_remaining <= 0.0 or bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
|
||||||
|
_clear_drones()
|
||||||
for id in cooldowns.keys():
|
for id in cooldowns.keys():
|
||||||
cooldowns[id] = maxf(0.0, float(cooldowns[id]) - delta)
|
cooldowns[id] = maxf(0.0, float(cooldowns[id]) - delta)
|
||||||
if cloak_remaining > 0.0:
|
if cloak_remaining > 0.0:
|
||||||
@ -66,6 +73,25 @@ func has_teleport_destination() -> bool:
|
|||||||
|
|
||||||
func activate(kind: int) -> void:
|
func activate(kind: int) -> void:
|
||||||
match kind:
|
match kind:
|
||||||
|
5:
|
||||||
|
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||||
|
if inventory.ammunition < 50 or drones_remaining > 0.0:
|
||||||
|
return
|
||||||
|
inventory.ammunition -= 50
|
||||||
|
inventory.ammunition_changed.emit()
|
||||||
|
drones_remaining = 10.0
|
||||||
|
break_cloak_on_fire()
|
||||||
|
var shared_dps := inventory.get_total_dps() * 0.5
|
||||||
|
for index in 3:
|
||||||
|
var drone := Node2D.new()
|
||||||
|
drone.set_script(preload("res://scripts/game/combat_drone.gd"))
|
||||||
|
drone.set("mother", ship)
|
||||||
|
drone.set("formation_index", index)
|
||||||
|
drone.set("damage_per_shot", shared_dps / 3.0 * 0.25)
|
||||||
|
drone.position = ship.position + Vector2.from_angle(TAU * index / 3.0) * 48.0
|
||||||
|
ship.get_parent().add_child(drone)
|
||||||
|
_drones.append(drone)
|
||||||
|
_deployment_effect(ship.position, Vector2.DOWN.rotated(ship.rotation), Color("70f5df"))
|
||||||
0:
|
0:
|
||||||
if not has_teleport_destination():
|
if not has_teleport_destination():
|
||||||
return
|
return
|
||||||
@ -103,6 +129,16 @@ func activate(kind: int) -> void:
|
|||||||
solar_remaining = 5.0
|
solar_remaining = 5.0
|
||||||
_pulse(ship.global_position, Color("70dfff"), 50.0)
|
_pulse(ship.global_position, Color("70dfff"), 50.0)
|
||||||
|
|
||||||
|
func _clear_drones() -> void:
|
||||||
|
drones_remaining = 0.0
|
||||||
|
for drone in _drones:
|
||||||
|
if is_instance_valid(drone):
|
||||||
|
drone.queue_free()
|
||||||
|
_drones.clear()
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
_clear_drones()
|
||||||
|
|
||||||
func _teleport_effect(point: Vector2, arriving: bool) -> void:
|
func _teleport_effect(point: Vector2, arriving: bool) -> void:
|
||||||
var effect := Node2D.new()
|
var effect := Node2D.new()
|
||||||
effect.set_script(preload("res://scripts/game/teleport_vortex.gd"))
|
effect.set_script(preload("res://scripts/game/teleport_vortex.gd"))
|
||||||
@ -138,6 +174,10 @@ func _start_cloak() -> void:
|
|||||||
visual.modulate = Color(visual.modulate, 0.08)
|
visual.modulate = Color(visual.modulate, 0.08)
|
||||||
_pulse(ship.global_position, Color("81e4ef"), 40.0)
|
_pulse(ship.global_position, Color("81e4ef"), 40.0)
|
||||||
|
|
||||||
|
func break_cloak_on_fire() -> void:
|
||||||
|
if cloak_remaining > 0.0:
|
||||||
|
_end_cloak()
|
||||||
|
|
||||||
func _end_cloak() -> void:
|
func _end_cloak() -> void:
|
||||||
cloak_remaining = 0.0
|
cloak_remaining = 0.0
|
||||||
ship.get_node("Sprite2D").set("material", _old_material)
|
ship.get_node("Sprite2D").set("material", _old_material)
|
||||||
|
|||||||
@ -148,6 +148,11 @@ func _ready() -> void:
|
|||||||
_window_manager.call("register_window", _inventory_clip, inventory_panel, inventory_panel.get_node("Contents/Header"))
|
_window_manager.call("register_window", _inventory_clip, inventory_panel, inventory_panel.get_node("Contents/Header"))
|
||||||
$HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred())
|
$HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred())
|
||||||
_create_ability_bar()
|
_create_ability_bar()
|
||||||
|
var objectives := PanelContainer.new()
|
||||||
|
objectives.name = "MissionObjectives"
|
||||||
|
objectives.set_script(preload("res://scripts/ui/mission_objectives.gd"))
|
||||||
|
objectives.set("game", get_parent())
|
||||||
|
$HUDRoot.add_child(objectives)
|
||||||
debug_toggle.pressed.connect(_toggle_debug)
|
debug_toggle.pressed.connect(_toggle_debug)
|
||||||
inventory_button.pressed.connect(_toggle_inventory)
|
inventory_button.pressed.connect(_toggle_inventory)
|
||||||
_update_debug_toggle()
|
_update_debug_toggle()
|
||||||
@ -178,6 +183,14 @@ func _create_sidebar() -> void:
|
|||||||
preload("res://scripts/ui/hud_button_style.gd").apply(_debug_item_button, Color("a9ef83"))
|
preload("res://scripts/ui/hud_button_style.gd").apply(_debug_item_button, Color("a9ef83"))
|
||||||
_debug_item_button.pressed.connect(_give_debug_item)
|
_debug_item_button.pressed.connect(_give_debug_item)
|
||||||
_debug_actions.add_child(_debug_item_button)
|
_debug_actions.add_child(_debug_item_button)
|
||||||
|
var ammunition_button := Button.new()
|
||||||
|
ammunition_button.name = "AddAmmunition"
|
||||||
|
ammunition_button.text = "Dodaj amunicję"
|
||||||
|
ammunition_button.tooltip_text = "+10 zwykłej amunicji, +10 radiacyjnej amunicji i +10 rakiet"
|
||||||
|
ammunition_button.custom_minimum_size.y = 36
|
||||||
|
preload("res://scripts/ui/hud_button_style.gd").apply(ammunition_button, Color("efb568"))
|
||||||
|
ammunition_button.pressed.connect(_give_debug_ammunition)
|
||||||
|
_debug_actions.add_child(ammunition_button)
|
||||||
_debug_item_status = Label.new()
|
_debug_item_status = Label.new()
|
||||||
_debug_item_status.add_theme_font_size_override("font_size", 10)
|
_debug_item_status.add_theme_font_size_override("font_size", 10)
|
||||||
_debug_item_status.add_theme_color_override("font_color", Color("a9ef83"))
|
_debug_item_status.add_theme_color_override("font_color", Color("a9ef83"))
|
||||||
@ -348,7 +361,7 @@ func _update_energy_bank_button() -> void:
|
|||||||
var inventory := player.get_node("ShipInventory") as ShipInventory
|
var inventory := player.get_node("ShipInventory") as ShipInventory
|
||||||
var count := inventory.get_energy_bank_count()
|
var count := inventory.get_energy_bank_count()
|
||||||
(_energy_bank_button.get_node("Amount") as Label).text = str(count)
|
(_energy_bank_button.get_node("Amount") as Label).text = str(count)
|
||||||
_energy_bank_button.tooltip_text = "Bank energii: %d\nR • Przywraca 10%% maksymalnej energii" % count
|
_energy_bank_button.tooltip_text = "Bank energii: %d\nR • Przywraca %d%% maksymalnej energii" % [count, roundi(GameState.captain.get_energy_bank_fraction() * 100.0)]
|
||||||
_energy_bank_button.disabled = count == 0 or float(player.get("energy")) >= float(player.get("max_energy")) or bool(player.get("_is_destroyed")) or bool(player.get("is_docked"))
|
_energy_bank_button.disabled = count == 0 or float(player.get("energy")) >= float(player.get("max_energy")) or bool(player.get("_is_destroyed")) or bool(player.get("is_docked"))
|
||||||
_energy_bank_button.call("set_visual_state", 0.0, false, _energy_bank_button.disabled)
|
_energy_bank_button.call("set_visual_state", 0.0, false, _energy_bank_button.disabled)
|
||||||
|
|
||||||
@ -389,6 +402,14 @@ func _toggle_debug() -> void:
|
|||||||
_update_debug_toggle()
|
_update_debug_toggle()
|
||||||
_layout_debug()
|
_layout_debug()
|
||||||
|
|
||||||
|
func _give_debug_ammunition() -> void:
|
||||||
|
var inventory := player.get_node("ShipInventory") as ShipInventory
|
||||||
|
inventory.ammunition += 10
|
||||||
|
inventory.irradiated_ammunition += 10
|
||||||
|
inventory.missile_ammunition += 10
|
||||||
|
inventory.ammunition_changed.emit()
|
||||||
|
inventory.inventory_changed.emit()
|
||||||
|
|
||||||
func _give_debug_item() -> bool:
|
func _give_debug_item() -> bool:
|
||||||
var inventory := player.get_node("ShipInventory") as ShipInventory
|
var inventory := player.get_node("ShipInventory") as ShipInventory
|
||||||
_debug_item_status.show()
|
_debug_item_status.show()
|
||||||
|
|||||||
@ -400,7 +400,7 @@ func start_boost(speed_bonus: float, acceleration_time: float) -> void:
|
|||||||
|
|
||||||
func get_active_ability(slot_index: int) -> ActiveAbility:
|
func get_active_ability(slot_index: int) -> ActiveAbility:
|
||||||
var item: ItemInstance = $ShipInventory.get_item("extension", slot_index)
|
var item: ItemInstance = $ShipInventory.get_item("extension", slot_index)
|
||||||
return item.definition.active_ability if item != null and item.definition != null else null
|
return item.get_active_ability() if item != null and item.definition != null else null
|
||||||
|
|
||||||
func get_luck_chance() -> float:
|
func get_luck_chance() -> float:
|
||||||
return GameState.captain.get_luck_chance(luck_percent)
|
return GameState.captain.get_luck_chance(luck_percent)
|
||||||
@ -436,7 +436,7 @@ func activate_ability(slot_index: int) -> bool:
|
|||||||
func use_energy_bank() -> void:
|
func use_energy_bank() -> void:
|
||||||
if $ShipInventory.use_energy_bank():
|
if $ShipInventory.use_energy_bank():
|
||||||
var popup := DAMAGE_POPUP_SCENE.instantiate() as Label
|
var popup := DAMAGE_POPUP_SCENE.instantiate() as Label
|
||||||
popup.text = "+10% ENERGII"
|
popup.text = "+%d%% ENERGII" % roundi(GameState.captain.get_energy_bank_fraction() * 100.0)
|
||||||
popup.set("text_color", Color("70f5df"))
|
popup.set("text_color", Color("70f5df"))
|
||||||
popup.position = position + Vector2(-48, -60)
|
popup.position = position + Vector2(-48, -60)
|
||||||
get_parent().add_child(popup)
|
get_parent().add_child(popup)
|
||||||
|
|||||||
@ -135,6 +135,7 @@ func try_fire_missile(slot_index: int = 2) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
func _fire_projectile(item: ItemInstance) -> void:
|
func _fire_projectile(item: ItemInstance) -> void:
|
||||||
|
_break_cloak_on_fire()
|
||||||
var definition := item.definition
|
var definition := item.definition
|
||||||
var hit: Dictionary = ship.call("roll_weapon_hit", item)
|
var hit: Dictionary = ship.call("roll_weapon_hit", item)
|
||||||
var muzzle_position := _muzzle_position()
|
var muzzle_position := _muzzle_position()
|
||||||
@ -194,6 +195,7 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
|||||||
line.visible = false
|
line.visible = false
|
||||||
return
|
return
|
||||||
WEAPON_AUDIO_SCRIPT.play(ship.get_parent(), muzzle_position, ItemDefinition.WeaponKind.LASER)
|
WEAPON_AUDIO_SCRIPT.play(ship.get_parent(), muzzle_position, ItemDefinition.WeaponKind.LASER)
|
||||||
|
_break_cloak_on_fire()
|
||||||
if not result.is_empty():
|
if not result.is_empty():
|
||||||
var hit: Dictionary = ship.call("roll_weapon_hit", item)
|
var hit: Dictionary = ship.call("roll_weapon_hit", item)
|
||||||
_apply_damage(result.get("collider"), float(hit["damage"]), bool(hit["critical"]))
|
_apply_damage(result.get("collider"), float(hit["damage"]), bool(hit["critical"]))
|
||||||
@ -201,6 +203,11 @@ func _fire_laser(slot_index: int, item: ItemInstance) -> void:
|
|||||||
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
|
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
|
||||||
_shot_timers[slot_index] = definition.fire_interval
|
_shot_timers[slot_index] = definition.fire_interval
|
||||||
|
|
||||||
|
func _break_cloak_on_fire() -> void:
|
||||||
|
var systems := ship.get_node_or_null("ExtensionSystems")
|
||||||
|
if systems != null:
|
||||||
|
systems.call("break_cloak_on_fire")
|
||||||
|
|
||||||
func _aim_direction_from(origin: Vector2) -> Vector2:
|
func _aim_direction_from(origin: Vector2) -> Vector2:
|
||||||
var offset := ship.get_global_mouse_position() - origin
|
var offset := ship.get_global_mouse_position() - origin
|
||||||
return offset.normalized() if offset.length_squared() > 0.001 else Vector2.UP.rotated(ship.rotation)
|
return offset.normalized() if offset.length_squared() > 0.001 else Vector2.UP.rotated(ship.rotation)
|
||||||
|
|||||||
@ -1,33 +1,35 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
|
const VORTEX := preload("res://scripts/game/wormhole_vortex.gdshader")
|
||||||
var _time := 0.0
|
var _time := 0.0
|
||||||
|
var _vortex_material: ShaderMaterial
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
z_index = -2
|
z_index = -2
|
||||||
|
light_mask = 4
|
||||||
|
var vortex := ColorRect.new()
|
||||||
|
vortex.name = "Vortex"
|
||||||
|
vortex.position = Vector2(-144, -144)
|
||||||
|
vortex.size = Vector2(288, 288)
|
||||||
|
vortex.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_vortex_material = ShaderMaterial.new()
|
||||||
|
_vortex_material.shader = VORTEX
|
||||||
|
vortex.material = _vortex_material
|
||||||
|
add_child(vortex)
|
||||||
var label := Label.new()
|
var label := Label.new()
|
||||||
|
label.name = "EntryLabel"
|
||||||
label.text = "WORMHOLE\nWleć, aby przejść do następnego układu"
|
label.text = "WORMHOLE\nWleć, aby przejść do następnego układu"
|
||||||
label.position = Vector2(-180, 72)
|
label.position = Vector2(-200, 106)
|
||||||
label.size = Vector2(360, 44)
|
label.size = Vector2(400, 48)
|
||||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
label.add_theme_font_size_override("font_size", 13)
|
label.add_theme_font_size_override("font_size", 13)
|
||||||
label.add_theme_color_override("font_color", Color("89e8ff"))
|
label.add_theme_color_override("font_color", Color("a0e7ff"))
|
||||||
|
label.add_theme_color_override("font_outline_color", Color("030916"))
|
||||||
|
label.add_theme_constant_override("outline_size", 5)
|
||||||
add_child(label)
|
add_child(label)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
_time += delta
|
_time += delta
|
||||||
queue_redraw()
|
if _vortex_material != null:
|
||||||
|
_vortex_material.set_shader_parameter("elapsed", _time)
|
||||||
func _draw() -> void:
|
|
||||||
var pulse := 1.0 + sin(_time * 2.5) * 0.06
|
|
||||||
for ring in range(6, 0, -1):
|
|
||||||
draw_circle(Vector2.ZERO, (48.0 + ring * 4.0) * pulse, Color(0.28, 0.35, 0.95, 0.025 * (7 - ring)))
|
|
||||||
draw_circle(Vector2.ZERO, 43.0, Color("040917"))
|
|
||||||
for arm in 3:
|
|
||||||
var points := PackedVector2Array()
|
|
||||||
for segment in 65:
|
|
||||||
var progress := segment / 64.0
|
|
||||||
var angle := progress * TAU * 1.3 + _time * 1.6 + TAU * arm / 3.0
|
|
||||||
points.append(Vector2.RIGHT.rotated(angle) * (8.0 + progress * 44.0) * pulse)
|
|
||||||
draw_polyline(points, Color(0.35 + arm * 0.12, 0.55, 1.0, 0.8), 3.0, true)
|
|
||||||
draw_arc(Vector2.ZERO, 49.0 * pulse, 0, TAU, 96, Color("8beaff"), 2.0, true)
|
|
||||||
|
|||||||
62
scripts/game/wormhole_vortex.gdshader
Normal file
62
scripts/game/wormhole_vortex.gdshader
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
render_mode unshaded;
|
||||||
|
|
||||||
|
uniform float elapsed = 0.0;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
}
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x),
|
||||||
|
mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y);
|
||||||
|
}
|
||||||
|
float turbulence(vec2 p) {
|
||||||
|
return noise(p) * 0.55 + noise(p * 2.07) * 0.28 + noise(p * 4.13) * 0.17;
|
||||||
|
}
|
||||||
|
void fragment() {
|
||||||
|
vec2 p = (UV - 0.5) * 2.0;
|
||||||
|
float r = length(p);
|
||||||
|
float a = atan(p.y, p.x);
|
||||||
|
float t = elapsed;
|
||||||
|
float reveal = smoothstep(0.0, 1.2, t);
|
||||||
|
float pulse = 1.0 + 0.012 * sin(t * 2.1);
|
||||||
|
float radius = r / pulse;
|
||||||
|
// Use circular coordinates for seamless turbulent filaments.
|
||||||
|
float twist = a + 2.6 * log(max(radius, 0.035)) - t * 0.65;
|
||||||
|
vec2 flow = vec2(cos(twist), sin(twist));
|
||||||
|
float clouds = turbulence(flow * 5.0 + vec2(radius * 13.0, -t * 0.3));
|
||||||
|
float filaments = pow(max(0.0, sin(twist * 7.0 + clouds * 5.0)), 5.0);
|
||||||
|
float disc = exp(-pow((radius - 0.46) / 0.125, 2.0));
|
||||||
|
float rim_r = 0.432 + 0.008 * sin(a * 6.0 - t * 1.4) + (clouds - 0.5) * 0.017;
|
||||||
|
float rim = exp(-abs(radius - rim_r) * 170.0);
|
||||||
|
float bloom = exp(-abs(radius - rim_r) * 25.0);
|
||||||
|
float halo = exp(-pow(radius / 0.66, 4.0));
|
||||||
|
vec3 cyan = vec3(0.16, 0.73, 1.0);
|
||||||
|
vec3 violet = vec3(0.40, 0.15, 0.93);
|
||||||
|
vec3 tint = mix(violet, cyan, 0.5 + 0.5 * sin(twist * 2.0 + t * 0.3));
|
||||||
|
vec3 color = tint * disc * (0.18 + clouds * 0.55 + filaments * 0.85);
|
||||||
|
color += cyan * bloom * 0.3 + vec3(0.63, 0.92, 1.0) * rim * 1.5;
|
||||||
|
color += violet * halo * 0.055;
|
||||||
|
// A dark throat with contracting spiral bands suggests a deep tunnel.
|
||||||
|
float inside = 1.0 - smoothstep(0.37, 0.425, radius);
|
||||||
|
float depth = -log(max(radius / 0.425, 0.015));
|
||||||
|
float tunnel = pow(max(0.0, sin(depth * 16.0 - t * 3.3 + a * 2.0)), 12.0);
|
||||||
|
float throat_fade = smoothstep(0.035, 0.3, radius);
|
||||||
|
vec3 throat = vec3(0.003, 0.006, 0.026);
|
||||||
|
throat += mix(violet, cyan, radius / 0.425) * tunnel * throat_fade * 0.22;
|
||||||
|
throat += cyan * pow(max(0.0, sin(twist * 5.0 + clouds * 2.0)), 14.0) * throat_fade * 0.11;
|
||||||
|
color = mix(color, throat, inside);
|
||||||
|
// Orbiting pinpoints and their short luminous trails, outside the event horizon.
|
||||||
|
float orbit_angle = a - t * 0.24;
|
||||||
|
vec2 orbit = vec2(cos(orbit_angle), sin(orbit_angle)) * radius * 43.0;
|
||||||
|
float sparks = pow(turbulence(orbit), 18.0) * 32.0;
|
||||||
|
sparks *= smoothstep(0.45, 0.52, radius) * (1.0 - smoothstep(0.62, 0.86, radius));
|
||||||
|
color += vec3(0.56, 0.83, 1.0) * sparks;
|
||||||
|
float alpha = max(inside, max(disc * (0.5 + clouds * 0.5), bloom * 0.8));
|
||||||
|
alpha = max(alpha, halo * 0.1 + sparks);
|
||||||
|
alpha *= (1.0 - smoothstep(0.84, 0.98, radius)) * reveal;
|
||||||
|
COLOR = vec4(color, clamp(alpha, 0.0, 1.0));
|
||||||
|
}
|
||||||
1
scripts/game/wormhole_vortex.gdshader.uid
Normal file
1
scripts/game/wormhole_vortex.gdshader.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://cintxpcnpffcx
|
||||||
@ -4,12 +4,14 @@ const MINERALS := ["iron", "titanium", "uranium", "aluminium", "sulfur"]
|
|||||||
const NAMES := {"iron": "Żelazo", "titanium": "Tytan", "uranium": "Uran", "aluminium": "Aluminium", "sulfur": "Siarka"}
|
const NAMES := {"iron": "Żelazo", "titanium": "Tytan", "uranium": "Uran", "aluminium": "Aluminium", "sulfur": "Siarka"}
|
||||||
const COLORS := {"iron": Color("c5b5a0"), "titanium": Color("9ecbe9"), "uranium": Color("a9ef83"), "aluminium": Color("dce8ef"), "sulfur": Color("f4d466")}
|
const COLORS := {"iron": Color("c5b5a0"), "titanium": Color("9ecbe9"), "uranium": Color("a9ef83"), "aluminium": Color("dce8ef"), "sulfur": Color("f4d466")}
|
||||||
const IDS := ["parts", "ammunition", "missile", "energy_bank", "irradiated_ammunition"]
|
const IDS := ["parts", "ammunition", "missile", "energy_bank", "irradiated_ammunition"]
|
||||||
|
# Base asteroid yield increased from mean 2 (1-3) to mean 6 (5-7).
|
||||||
|
# Triple mineral costs to preserve base crafts per asteroid; miner remains a bonus.
|
||||||
const DATA := {
|
const DATA := {
|
||||||
"parts": {"name": "Części", "amount": 1, "cost": {"titanium": 1, "iron": 1, "aluminium": 1}},
|
"parts": {"name": "Części", "amount": 1, "cost": {"titanium": 3, "iron": 3, "aluminium": 3}},
|
||||||
"ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 1, "sulfur": 1}},
|
"ammunition": {"name": "Amunicja", "amount": 50, "cost": {"titanium": 3, "sulfur": 3}},
|
||||||
"missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 1, "sulfur": 1, "uranium": 1}},
|
"missile": {"name": "Rakieta", "amount": 1, "cost": {"iron": 3, "sulfur": 3, "uranium": 3}},
|
||||||
"energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 1, "aluminium": 1}},
|
"energy_bank": {"name": "Powerbank", "amount": 1, "cost": {"iron": 3, "aluminium": 3}},
|
||||||
"irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 1, "uranium": 1}},
|
"irradiated_ammunition": {"name": "Amunicja radiacyjna", "amount": 50, "cost": {"titanium": 3, "uranium": 3}},
|
||||||
}
|
}
|
||||||
|
|
||||||
static func icon(id: String) -> Texture2D:
|
static func icon(id: String) -> Texture2D:
|
||||||
|
|||||||
27
scripts/items/extension_cooldowns.gd
Normal file
27
scripts/items/extension_cooldowns.gd
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const MIN_SECONDS := 5
|
||||||
|
const BASE_SECONDS := 30
|
||||||
|
# Cumulative P(cooldown <= seconds); interpolate exponentially between anchors.
|
||||||
|
const SECONDS := [5, 10, 15, 20, 25, 30]
|
||||||
|
const CHANCES := [0.0002, 0.0015, 0.01, 0.05, 0.25, 1.0]
|
||||||
|
|
||||||
|
static func cumulative_chance(seconds: int) -> float:
|
||||||
|
if seconds < MIN_SECONDS:
|
||||||
|
return 0.0
|
||||||
|
if seconds >= BASE_SECONDS:
|
||||||
|
return 1.0
|
||||||
|
for index in SECONDS.size() - 1:
|
||||||
|
if seconds <= SECONDS[index + 1]:
|
||||||
|
var progress := float(seconds - SECONDS[index]) / float(SECONDS[index + 1] - SECONDS[index])
|
||||||
|
return exp(lerpf(log(CHANCES[index]), log(CHANCES[index + 1]), progress))
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
static func from_roll(roll: float) -> int:
|
||||||
|
for seconds in range(MIN_SECONDS, BASE_SECONDS):
|
||||||
|
if roll < cumulative_chance(seconds):
|
||||||
|
return seconds
|
||||||
|
return BASE_SECONDS
|
||||||
|
|
||||||
|
static func roll_seconds() -> int:
|
||||||
|
return from_roll(randf())
|
||||||
1
scripts/items/extension_cooldowns.gd.uid
Normal file
1
scripts/items/extension_cooldowns.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://4nswtu72lqfp
|
||||||
@ -1,6 +1,7 @@
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const DEFINITIONS := [
|
const DEFINITIONS := [
|
||||||
|
preload("res://resources/items/combat_drones.tres"),
|
||||||
preload("res://resources/items/teleport.tres"),
|
preload("res://resources/items/teleport.tres"),
|
||||||
preload("res://resources/items/cloak.tres"),
|
preload("res://resources/items/cloak.tres"),
|
||||||
preload("res://resources/items/mine_layer.tres"),
|
preload("res://resources/items/mine_layer.tres"),
|
||||||
@ -48,6 +49,7 @@ static func generate_for_definition(definition: ItemDefinition, level: int, enem
|
|||||||
item.primary_value_override = (item.damage_min + item.damage_max) * 0.5
|
item.primary_value_override = (item.damage_min + item.damage_max) * 0.5
|
||||||
item.energy_regeneration_override = item.definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2)
|
item.energy_regeneration_override = item.definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2)
|
||||||
if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION:
|
if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION:
|
||||||
|
item.extension_cooldown = ItemInstance.EXTENSION_COOLDOWNS.roll_seconds()
|
||||||
return item
|
return item
|
||||||
var roll := randf()
|
var roll := randf()
|
||||||
var count := 1 if roll < 0.5 else (2 if roll < 0.75 else (3 if roll < 0.89 else (4 if roll < 0.97 else 5)))
|
var count := 1 if roll < 0.5 else (2 if roll < 0.75 else (3 if roll < 0.89 else (4 if roll < 0.97 else 5)))
|
||||||
|
|||||||
@ -2,6 +2,30 @@ extends Resource
|
|||||||
class_name ItemInstance
|
class_name ItemInstance
|
||||||
|
|
||||||
const ENERGY_REGENERATION_BONUS_MULTIPLIER := 0.5
|
const ENERGY_REGENERATION_BONUS_MULTIPLIER := 0.5
|
||||||
|
const EXTENSION_COOLDOWNS := preload("res://scripts/items/extension_cooldowns.gd")
|
||||||
|
# Exported with the item; old items default to 30 seconds without rerolling.
|
||||||
|
@export_range(5, 30, 1) var extension_cooldown: int = 30
|
||||||
|
var _instance_ability: ActiveAbility
|
||||||
|
|
||||||
|
func get_active_ability() -> ActiveAbility:
|
||||||
|
if definition == null or definition.active_ability == null:
|
||||||
|
return null
|
||||||
|
if definition.category != ItemDefinition.Category.ACTIVE_EXTENSION:
|
||||||
|
return definition.active_ability
|
||||||
|
if _instance_ability != null and _instance_ability.cooldown == clampi(extension_cooldown, 5, 30):
|
||||||
|
return _instance_ability
|
||||||
|
if _instance_ability == null:
|
||||||
|
# Never mutate the shared ability resource in the item definition.
|
||||||
|
_instance_ability = definition.active_ability.duplicate() as ActiveAbility
|
||||||
|
_instance_ability.cooldown = clampi(extension_cooldown, 5, 30)
|
||||||
|
var description := definition.active_ability.description
|
||||||
|
var fixed_cooldown := RegEx.new()
|
||||||
|
fixed_cooldown.compile(" (Odnowienie:|Zrzut co) [0-9]+ s\\.")
|
||||||
|
description = fixed_cooldown.sub(description, "", true)
|
||||||
|
if definition.item_id == "mine_layer":
|
||||||
|
description = "Zostawia minę na 60 s. Uzbraja się po 0,7 s. Wykrywa bliski przelot wroga. Wybuch w promieniu 140 jednostek zadaje każdemu wrogowi 50% aktualnego kadłuba bez redukcji absorpcji (tarcze pochłaniają obrażenia) i spowalnia o 50% na 5 s."
|
||||||
|
_instance_ability.description = description + "\nOdnowienie: %d s." % _instance_ability.cooldown
|
||||||
|
return _instance_ability
|
||||||
|
|
||||||
@export var definition: ItemDefinition
|
@export var definition: ItemDefinition
|
||||||
@export_range(1, 1000, 1) var item_level: int = 1
|
@export_range(1, 1000, 1) var item_level: int = 1
|
||||||
@ -110,7 +134,7 @@ func get_tooltip_text() -> String:
|
|||||||
if definition.category == ItemDefinition.Category.WEAPON:
|
if definition.category == ItemDefinition.Category.WEAPON:
|
||||||
primary_text = "Obrażenia: %s" % get_damage_range_text()
|
primary_text = "Obrażenia: %s" % get_damage_range_text()
|
||||||
elif definition.active_ability != null:
|
elif definition.active_ability != null:
|
||||||
primary_text = definition.active_ability.description
|
primary_text = get_active_ability().description
|
||||||
var lines: Array[String] = [definition.display_name, "%s • Poziom %d" % [get_rarity_name(), item_level], "", definition.description, "", "CECHA GŁÓWNA", primary_text]
|
var lines: Array[String] = [definition.display_name, "%s • Poziom %d" % [get_rarity_name(), item_level], "", definition.description, "", "CECHA GŁÓWNA", primary_text]
|
||||||
if definition.energy_regeneration_bonus > 0.0:
|
if definition.energy_regeneration_bonus > 0.0:
|
||||||
lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))
|
lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))
|
||||||
|
|||||||
48
scripts/items/mineral_pickup.gd
Normal file
48
scripts/items/mineral_pickup.gd
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const PICKUP_RADIUS := 48.0
|
||||||
|
const ATTRACTION_RADIUS := 150.0
|
||||||
|
var mineral := "iron"
|
||||||
|
var texture: Texture2D
|
||||||
|
var points := PackedVector2Array()
|
||||||
|
var uv := PackedVector2Array()
|
||||||
|
var drift := Vector2.ZERO
|
||||||
|
var spin := 0.0
|
||||||
|
var _age := 0.0
|
||||||
|
var _collected := false
|
||||||
|
var _player: CharacterBody2D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("loot")
|
||||||
|
add_to_group("mineral_pickups")
|
||||||
|
_player = get_tree().current_scene.get_node_or_null("PlayerShip") as CharacterBody2D
|
||||||
|
z_index = 18
|
||||||
|
light_mask = 4
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
_age += delta
|
||||||
|
position += drift * delta
|
||||||
|
drift *= exp(-1.8 * delta)
|
||||||
|
rotation += spin * delta
|
||||||
|
spin *= exp(-0.7 * delta)
|
||||||
|
queue_redraw()
|
||||||
|
if _collected or _age < 0.8 or not is_instance_valid(_player) or bool(_player.get("_is_destroyed")):
|
||||||
|
return
|
||||||
|
var distance := global_position.distance_to(_player.global_position)
|
||||||
|
if distance <= PICKUP_RADIUS:
|
||||||
|
_collected = true
|
||||||
|
GameState.captain.minerals[mineral] = int(GameState.captain.minerals.get(mineral, 0)) + 1
|
||||||
|
GameState.captain.emit_changed()
|
||||||
|
var popup := preload("res://scenes/game/damage_popup.tscn").instantiate() as Label
|
||||||
|
popup.text = "+1 %s" % {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu", "sulfur": "siarki"}.get(mineral, mineral)
|
||||||
|
popup.set("text_color", Color("c9e8ff"))
|
||||||
|
popup.position = _player.position + Vector2(-40, -50)
|
||||||
|
get_tree().current_scene.add_child.call_deferred(popup)
|
||||||
|
queue_free()
|
||||||
|
elif distance <= ATTRACTION_RADIUS:
|
||||||
|
global_position = global_position.move_toward(_player.global_position, 180.0 * delta)
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if texture == null or points.size() < 3:
|
||||||
|
return
|
||||||
|
draw_polygon(points, PackedColorArray([Color.WHITE]), uv, texture)
|
||||||
1
scripts/items/mineral_pickup.gd.uid
Normal file
1
scripts/items/mineral_pickup.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://cssyy83yo8lss
|
||||||
@ -93,7 +93,7 @@ func use_energy_bank() -> bool:
|
|||||||
var current := float(ship.get("energy"))
|
var current := float(ship.get("energy"))
|
||||||
if current >= maximum:
|
if current >= maximum:
|
||||||
return false
|
return false
|
||||||
ship.set("energy", minf(maximum, current + maximum * 0.1))
|
ship.set("energy", minf(maximum, current + maximum * GameState.captain.get_energy_bank_fraction()))
|
||||||
energy_banks -= 1
|
energy_banks -= 1
|
||||||
inventory_changed.emit()
|
inventory_changed.emit()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -58,7 +58,7 @@ func _update_label() -> void:
|
|||||||
_label.text = display_name + "\nGwiazda centralna"
|
_label.text = display_name + "\nGwiazda centralna"
|
||||||
_label.add_theme_color_override("font_color", Color("ffdb98"))
|
_label.add_theme_color_override("font_color", Color("ffdb98"))
|
||||||
elif is_protected:
|
elif is_protected:
|
||||||
_label.text = display_name + " — zamieszkana\n" + ("USŁUGI DOSTĘPNE" if is_safe else "POKONAJ WSZYSTKICH WROGÓW")
|
_label.text = display_name + " — zamieszkana" + ("\nUSŁUGI DOSTĘPNE" if is_safe else "")
|
||||||
_label.add_theme_color_override("font_color", Color("77e8bc") if is_safe else Color("ffbd83"))
|
_label.add_theme_color_override("font_color", Color("77e8bc") if is_safe else Color("ffbd83"))
|
||||||
else:
|
else:
|
||||||
_label.text = display_name
|
_label.text = display_name
|
||||||
|
|||||||
@ -121,9 +121,15 @@ func claim() -> bool:
|
|||||||
var summary := ""
|
var summary := ""
|
||||||
var scale := reward_scale()
|
var scale := reward_scale()
|
||||||
if kind == "asteroid":
|
if kind == "asteroid":
|
||||||
var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale)) + roundi(GameState.captain.get_skill_bonus("miner"))
|
var amount := _rng.randi_range(5, 7) + maxi(0, roundi(GameState.captain.get_skill_bonus("miner")))
|
||||||
GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount
|
var debris := Node2D.new()
|
||||||
summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu", "sulfur": "siarki"}[_mineral]]
|
debris.set_script(ASTEROID_DESTRUCTION)
|
||||||
|
debris.name = "AsteroidDebris"
|
||||||
|
debris.call("configure", _asteroid_sprite, amount, _mineral)
|
||||||
|
get_parent().add_child.call_deferred(debris)
|
||||||
|
set_physics_process(false)
|
||||||
|
queue_free()
|
||||||
|
return true
|
||||||
elif kind == "ruin":
|
elif kind == "ruin":
|
||||||
var xp := roundi(_rng.randi_range(10, 20) * scale)
|
var xp := roundi(_rng.randi_range(10, 20) * scale)
|
||||||
GameState.captain.add_rewards(xp, 0)
|
GameState.captain.add_rewards(xp, 0)
|
||||||
@ -156,13 +162,6 @@ func claim() -> bool:
|
|||||||
popup.position = position + Vector2(-80, -65)
|
popup.position = position + Vector2(-80, -65)
|
||||||
get_parent().add_child.call_deferred(popup)
|
get_parent().add_child.call_deferred(popup)
|
||||||
set_physics_process(false)
|
set_physics_process(false)
|
||||||
if kind == "asteroid" and is_instance_valid(_asteroid_sprite):
|
|
||||||
var debris := Node2D.new()
|
|
||||||
debris.set_script(ASTEROID_DESTRUCTION)
|
|
||||||
debris.name = "AsteroidDebris"
|
|
||||||
debris.call("configure", _asteroid_sprite)
|
|
||||||
# The POI may be destroyed during a physics query; add debris outside it.
|
|
||||||
get_parent().add_child.call_deferred(debris)
|
|
||||||
queue_free()
|
queue_free()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,6 @@ var _panel: PanelContainer
|
|||||||
var _contents: VBoxContainer
|
var _contents: VBoxContainer
|
||||||
var _bar: HBoxContainer
|
var _bar: HBoxContainer
|
||||||
var _buttons: Dictionary = {}
|
var _buttons: Dictionary = {}
|
||||||
var _was_paused := false
|
|
||||||
var _owns_pause := false
|
|
||||||
var _refresh_pending := false
|
var _refresh_pending := false
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@ -78,19 +76,11 @@ func open_management() -> void:
|
|||||||
if bool(player.get("_is_destroyed")):
|
if bool(player.get("_is_destroyed")):
|
||||||
return
|
return
|
||||||
if not _screen.visible:
|
if not _screen.visible:
|
||||||
_was_paused = get_tree().paused
|
|
||||||
_owns_pause = true
|
|
||||||
get_tree().paused = true
|
|
||||||
_screen.show()
|
_screen.show()
|
||||||
_build_management()
|
_build_management()
|
||||||
|
|
||||||
func _close() -> void:
|
func _close() -> void:
|
||||||
if not _captain.skill_offers.is_empty():
|
|
||||||
return
|
|
||||||
_screen.hide()
|
_screen.hide()
|
||||||
if _owns_pause:
|
|
||||||
get_tree().paused = _was_paused
|
|
||||||
_owns_pause = false
|
|
||||||
|
|
||||||
func _build_management() -> void:
|
func _build_management() -> void:
|
||||||
for child in _contents.get_children():
|
for child in _contents.get_children():
|
||||||
@ -103,13 +93,12 @@ func _build_management() -> void:
|
|||||||
var title := _label("AWANS KAPITANA" if choosing else "UMIEJĘTNOŚCI KAPITANA", 25, Color("91dbea"))
|
var title := _label("AWANS KAPITANA" if choosing else "UMIEJĘTNOŚCI KAPITANA", 25, Color("91dbea"))
|
||||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
heading.add_child(title)
|
heading.add_child(title)
|
||||||
if not choosing:
|
var close := Button.new()
|
||||||
var close := Button.new()
|
close.name = "Close"
|
||||||
close.name = "Close"
|
close.text = "WRÓĆ DO LOTU"
|
||||||
close.text = "WRÓĆ DO LOTU"
|
close.pressed.connect(_close)
|
||||||
close.pressed.connect(_close)
|
heading.add_child(close)
|
||||||
heading.add_child(close)
|
_contents.add_child(_label("POZIOM %d" % _captain.level, 13, Color("91aabd")))
|
||||||
_contents.add_child(_label("POZIOM %d · GRA WSTRZYMANA" % _captain.level, 13, Color("91aabd")))
|
|
||||||
if choosing:
|
if choosing:
|
||||||
_contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16))
|
_contents.add_child(_label("Wybierz jedną umiejętność. Wybór obowiązuje przez całą rozgrywkę.", 16))
|
||||||
var choices := HBoxContainer.new()
|
var choices := HBoxContainer.new()
|
||||||
@ -327,6 +316,4 @@ func _layout() -> void:
|
|||||||
else:
|
else:
|
||||||
_bar.position = Vector2(16, viewport_size.y - 190)
|
_bar.position = Vector2(16, viewport_size.y - 190)
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
if _owns_pause and is_inside_tree():
|
|
||||||
get_tree().paused = _was_paused
|
|
||||||
|
|||||||
@ -24,4 +24,10 @@ func _draw() -> void:
|
|||||||
var warning_color := Color(1.0, 0.32, 0.26, pulse)
|
var warning_color := Color(1.0, 0.32, 0.26, pulse)
|
||||||
draw_rect(Rect2(Vector2(3, 3), size - Vector2(6, 6)), warning_color, false, 3)
|
draw_rect(Rect2(Vector2(3, 3), size - Vector2(6, 6)), warning_color, false, 3)
|
||||||
var warning := "NISKI KADŁUB — %d%%" % roundi(hull / max_hull * 100)
|
var warning := "NISKI KADŁUB — %d%%" % roundi(hull / max_hull * 100)
|
||||||
draw_string(FONT, Vector2(0, 34), warning, HORIZONTAL_ALIGNMENT_CENTER, size.x, 18, warning_color)
|
# Keep the warning below the navigation buttons, including after a resize.
|
||||||
|
var navigation := get_parent().get_node_or_null("TopNavigation") as Control
|
||||||
|
var warning_top := 104.0
|
||||||
|
if is_instance_valid(navigation):
|
||||||
|
warning_top = navigation.get_global_rect().end.y - global_position.y + 12.0
|
||||||
|
var baseline := warning_top + FONT.get_ascent(18)
|
||||||
|
draw_string(FONT, Vector2(0, baseline), warning, HORIZONTAL_ALIGNMENT_CENTER, size.x, 18, warning_color)
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func _draw() -> void:
|
|||||||
if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50:
|
if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50:
|
||||||
return
|
return
|
||||||
var outline := PackedVector2Array([Vector2(24, 0), Vector2(size.x - 24, 0), Vector2(size.x, 24), Vector2(size.x, size.y - 12), Vector2(size.x - 12, size.y), Vector2(12, size.y), Vector2(0, size.y - 12), Vector2(0, 24)])
|
var outline := PackedVector2Array([Vector2(24, 0), Vector2(size.x - 24, 0), Vector2(size.x, 24), Vector2(size.x, size.y - 12), Vector2(size.x - 12, size.y), Vector2(12, size.y), Vector2(0, size.y - 12), Vector2(0, 24)])
|
||||||
draw_colored_polygon(outline, Color(0.025, 0.041, 0.063, 1.0))
|
draw_colored_polygon(outline, Color(0.025, 0.041, 0.063, 0.78))
|
||||||
var closed := outline.duplicate()
|
var closed := outline.duplicate()
|
||||||
closed.append(outline[0])
|
closed.append(outline[0])
|
||||||
draw_polyline(closed, Color("376779"), 1.5, true)
|
draw_polyline(closed, Color("376779"), 1.5, true)
|
||||||
|
|||||||
50
scripts/ui/mission_objectives.gd
Normal file
50
scripts/ui/mission_objectives.gd
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
var game: Node2D
|
||||||
|
var _objective: Label
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.025, 0.041, 0.063, 0.72)
|
||||||
|
style.border_color = Color("376779")
|
||||||
|
style.set_border_width_all(1)
|
||||||
|
style.set_corner_radius_all(6)
|
||||||
|
style.set_content_margin_all(12)
|
||||||
|
add_theme_stylebox_override("panel", style)
|
||||||
|
var contents := VBoxContainer.new()
|
||||||
|
contents.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
contents.add_theme_constant_override("separation", 6)
|
||||||
|
add_child(contents)
|
||||||
|
var title := Label.new()
|
||||||
|
title.text = "CELE"
|
||||||
|
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
title.add_theme_font_size_override("font_size", 12)
|
||||||
|
title.add_theme_color_override("font_color", Color("91dbea"))
|
||||||
|
contents.add_child(title)
|
||||||
|
_objective = Label.new()
|
||||||
|
_objective.name = "CurrentObjective"
|
||||||
|
_objective.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_objective.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
_objective.add_theme_font_size_override("font_size", 11)
|
||||||
|
_objective.add_theme_color_override("font_color", Color("efb568"))
|
||||||
|
contents.add_child(_objective)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not is_instance_valid(game):
|
||||||
|
return
|
||||||
|
if bool(game.get("system_cleared")):
|
||||||
|
_objective.text = "Znajdź zamieszkaną planetę"
|
||||||
|
elif bool(game.get("_boss_spawned")):
|
||||||
|
_objective.text = "Pokonaj bossa"
|
||||||
|
else:
|
||||||
|
_objective.text = "Pokonaj wszystkich wrogów"
|
||||||
|
var cockpit := get_parent().get_node_or_null("Cockpit") as Control
|
||||||
|
var available_width := cockpit.position.x - 24.0 if is_instance_valid(cockpit) else 260.0
|
||||||
|
var panel_width := clampf(available_width, 180.0, 260.0)
|
||||||
|
size.x = panel_width
|
||||||
|
size.y = get_combined_minimum_size().y
|
||||||
|
var bottom: float = (get_parent() as Control).size.y - 12.0
|
||||||
|
if available_width < 180.0 and is_instance_valid(cockpit):
|
||||||
|
bottom = cockpit.position.y - 64.0
|
||||||
|
position = Vector2(12.0, bottom - size.y)
|
||||||
1
scripts/ui/mission_objectives.gd.uid
Normal file
1
scripts/ui/mission_objectives.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://bd8lju10fyxbr
|
||||||
111
tests/abilities/electrician_drones_tactics_test.gd
Normal file
111
tests/abilities/electrician_drones_tactics_test.gd
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const PLAYER := preload("res://scenes/game/player_ship.tscn")
|
||||||
|
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
|
||||||
|
const DRONES := preload("res://resources/items/combat_drones.tres")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
var ship := PLAYER.instantiate() as CharacterBody2D
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
var weapons := ship.get_node("WeaponController")
|
||||||
|
weapons.set_physics_process(false)
|
||||||
|
var systems := ship.get_node("ExtensionSystems")
|
||||||
|
systems.set_physics_process(false)
|
||||||
|
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||||
|
# Every rank replaces the baseline, respects the energy cap, and uses one bank.
|
||||||
|
for rank in range(4):
|
||||||
|
GameState.captain.skills["electrician"] = rank
|
||||||
|
inventory.energy_banks = 2
|
||||||
|
ship.set("energy", 0.0)
|
||||||
|
assert(inventory.use_energy_bank())
|
||||||
|
assert(is_equal_approx(float(ship.get("energy")), float(ship.get("max_energy")) * [0.10, 0.15, 0.20, 0.25][rank]))
|
||||||
|
ship.set("energy", float(ship.get("max_energy")) - 1.0)
|
||||||
|
assert(inventory.use_energy_bank())
|
||||||
|
assert(ship.get("energy") == ship.get("max_energy"))
|
||||||
|
assert(inventory.energy_banks == 0)
|
||||||
|
assert(preload("res://scripts/captain/captain_skills.gd").icon("electrician") != null)
|
||||||
|
assert(not GameState.captain.equip_skill(0, "electrician"))
|
||||||
|
# Tactics move toward, away from, or around their target, then flee below 50%.
|
||||||
|
var enemy := ENEMY.instantiate() as CharacterBody2D
|
||||||
|
enemy.position = Vector2(250, 0)
|
||||||
|
add_child(enemy)
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
enemy.set("_player", ship)
|
||||||
|
enemy.set("tactic", 0)
|
||||||
|
assert(enemy.call("combat_movement", Vector2(200, 0)).x > 0.0)
|
||||||
|
enemy.set("tactic", 1)
|
||||||
|
assert(enemy.call("combat_movement", Vector2(100, 0)).x < 0.0)
|
||||||
|
assert(enemy.call("combat_movement", Vector2(700, 0)).x > 0.0)
|
||||||
|
assert(absf(enemy.call("combat_movement", Vector2(420, 0)).y) > 0.0)
|
||||||
|
enemy.set("hull", float(enemy.get("max_hull")) * 0.5)
|
||||||
|
assert(not enemy.call("is_retreating"))
|
||||||
|
enemy.set("hull", float(enemy.get("max_hull")) * 0.49)
|
||||||
|
for tactic in [0, 1]:
|
||||||
|
enemy.set("tactic", tactic)
|
||||||
|
assert(enemy.call("is_retreating"))
|
||||||
|
assert(enemy.call("combat_movement", Vector2(500, 0)).x < 0.0)
|
||||||
|
enemy.set("hull", 10000.0)
|
||||||
|
# Module is lootable, charges exactly 50 ammo even with the ammunition passive.
|
||||||
|
assert(DRONES in preload("res://scripts/items/item_generator.gd").DEFINITIONS)
|
||||||
|
var module := ItemInstance.new()
|
||||||
|
module.definition = DRONES
|
||||||
|
inventory.extension_slots[0] = module
|
||||||
|
GameState.captain.skills["efficient_magazine"] = 3
|
||||||
|
inventory.ammunition = 49
|
||||||
|
assert(not ship.call("activate_ability", 0))
|
||||||
|
assert(inventory.ammunition == 49)
|
||||||
|
inventory.ammunition = 100
|
||||||
|
var dps := inventory.get_total_dps()
|
||||||
|
assert(dps > 0.0)
|
||||||
|
assert(ship.call("activate_ability", 0))
|
||||||
|
assert(inventory.ammunition == 50)
|
||||||
|
assert(not ship.call("activate_ability", 0))
|
||||||
|
var drones := get_tree().get_nodes_in_group("player_combat_drones")
|
||||||
|
assert(drones.size() == 3)
|
||||||
|
var total_dps := 0.0
|
||||||
|
for drone in drones:
|
||||||
|
drone.set_physics_process(false)
|
||||||
|
total_dps += float(drone.get("damage_per_shot")) / float(drone.FIRE_INTERVAL)
|
||||||
|
assert(is_equal_approx(total_dps, dps * 0.5))
|
||||||
|
assert(is_equal_approx(inventory.get_total_dps(), dps))
|
||||||
|
await get_tree().physics_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
var before := float(enemy.get("hull")) + float(enemy.get("shields"))
|
||||||
|
drones[0].position = Vector2(100, 0)
|
||||||
|
drones[0].call("_fire", enemy)
|
||||||
|
for bullet in get_children():
|
||||||
|
if bullet is Area2D and bullet.get_script() == preload("res://scripts/game/weapon_projectile.gd"):
|
||||||
|
bullet.call("_physics_process", 0.2)
|
||||||
|
assert(float(enemy.get("hull")) + float(enemy.get("shields")) < before, "Drone bullets must damage enemies")
|
||||||
|
assert(inventory.ammunition == 50, "No extra ammunition charged per shot")
|
||||||
|
systems.call("_physics_process", 10.0)
|
||||||
|
for drone in drones:
|
||||||
|
assert(drone.is_queued_for_deletion())
|
||||||
|
await get_tree().process_frame
|
||||||
|
assert(get_tree().get_nodes_in_group("player_combat_drones").is_empty())
|
||||||
|
systems.call("reset_combat_state")
|
||||||
|
assert(ship.call("activate_ability", 0))
|
||||||
|
var deployed := get_tree().get_nodes_in_group("player_combat_drones")
|
||||||
|
for drone in deployed:
|
||||||
|
drone.set_physics_process(false)
|
||||||
|
get_tree().paused = true
|
||||||
|
assert(not ship.call("activate_ability", 0))
|
||||||
|
await get_tree().create_timer(0.05, true).timeout
|
||||||
|
assert(float(systems.get("drones_remaining")) == 10.0)
|
||||||
|
get_tree().paused = false
|
||||||
|
ship.set("is_docked", true)
|
||||||
|
systems.call("_physics_process", 0.1)
|
||||||
|
await get_tree().process_frame
|
||||||
|
assert(get_tree().get_nodes_in_group("player_combat_drones").is_empty())
|
||||||
|
ship.set("is_docked", false)
|
||||||
|
systems.call("reset_combat_state")
|
||||||
|
inventory.ammunition = 50
|
||||||
|
assert(ship.call("activate_ability", 0))
|
||||||
|
systems.call("reset_combat_state")
|
||||||
|
await get_tree().process_frame
|
||||||
|
assert(get_tree().get_nodes_in_group("player_combat_drones").is_empty())
|
||||||
|
assert(float(systems.get("drones_remaining")) == 0.0)
|
||||||
|
print("ELECTRICIAN_DRONES_TACTICS_TEST: PASS (four bank strengths/caps, passive icon, tactics and retreat, loot module, exact ammo cost, 3 drones, shared 50% DPS, actual bullet damage, lifetime and reset)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/abilities/electrician_drones_tactics_test.gd.uid
Normal file
1
tests/abilities/electrician_drones_tactics_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://c1xp2w857yvwq
|
||||||
4
tests/abilities/electrician_drones_tactics_test.tscn
Normal file
4
tests/abilities/electrician_drones_tactics_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/abilities/electrician_drones_tactics_test.gd" id="1"]
|
||||||
|
[node name="ElectricianDronesTacticsTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
@ -80,6 +80,30 @@ func _ready() -> void:
|
|||||||
assert(enemy.call("select_combat_target") == ship)
|
assert(enemy.call("select_combat_target") == ship)
|
||||||
assert(not ship.call("activate_ability", 1))
|
assert(not ship.call("activate_ability", 1))
|
||||||
# Mine: 5% maximum hull damage despite armor; slow lasts five seconds; one hit only.
|
# Mine: 5% maximum hull damage despite armor; slow lasts five seconds; one hit only.
|
||||||
|
var weapons := ship.get_node("WeaponController")
|
||||||
|
for weapon_id in ["plasma_cannon", "machine_gun", "gamma_cannon", "missile_launcher", "beam_laser"]:
|
||||||
|
systems.get("cooldowns").clear()
|
||||||
|
assert(ship.call("activate_ability", 1))
|
||||||
|
var weapon := GENERATOR.generate_for_definition(load("res://resources/items/%s.tres" % weapon_id), 1)
|
||||||
|
if weapon_id == "beam_laser":
|
||||||
|
ship.set("energy", 0.0)
|
||||||
|
weapons.call("_fire_laser", 0, weapon)
|
||||||
|
assert(ship.call("is_cloaked"), "Failed shot must preserve cloak")
|
||||||
|
ship.set("energy", ship.get("max_energy"))
|
||||||
|
weapons.call("_fire_laser", 0, weapon)
|
||||||
|
elif weapon_id == "missile_launcher":
|
||||||
|
inventory.weapon_slots[2] = weapon
|
||||||
|
inventory.missile_ammunition = 0
|
||||||
|
assert(not weapons.call("try_fire_missile", 2))
|
||||||
|
assert(ship.call("is_cloaked"), "Empty launcher must preserve cloak")
|
||||||
|
inventory.missile_ammunition = 1
|
||||||
|
assert(weapons.call("try_fire_missile", 2))
|
||||||
|
else:
|
||||||
|
weapons.call("_fire_projectile", weapon)
|
||||||
|
assert(not ship.call("is_cloaked"), "Every fired weapon must break cloak")
|
||||||
|
assert(ship.get_node("Sprite2D").material == original_material)
|
||||||
|
assert(ship.call("get_ability_cooldown", 1) == 60.0)
|
||||||
|
assert(enemy.call("select_combat_target") == ship)
|
||||||
assert(ship.call("activate_ability", 2))
|
assert(ship.call("activate_ability", 2))
|
||||||
var mine: Node2D = get_tree().get_nodes_in_group("player_mines")[0]
|
var mine: Node2D = get_tree().get_nodes_in_group("player_mines")[0]
|
||||||
mine.set_physics_process(false)
|
mine.set_physics_process(false)
|
||||||
|
|||||||
@ -44,10 +44,10 @@ func _ready() -> void:
|
|||||||
captain.skill_durations.clear()
|
captain.skill_durations.clear()
|
||||||
var base_regeneration: float = ship.get_energy_regeneration()
|
var base_regeneration: float = ship.get_energy_regeneration()
|
||||||
assert(captain.activate_skill("reactor_overload", ship))
|
assert(captain.activate_skill("reactor_overload", ship))
|
||||||
assert(is_equal_approx(ship.get_energy_regeneration(), base_regeneration * [1.1, 1.2, 1.7][rank - 1]))
|
assert(is_equal_approx(ship.get_energy_regeneration(), base_regeneration * [1.2, 1.5, 2.0][rank - 1]))
|
||||||
ship.energy = 0.0
|
ship.energy = 0.0
|
||||||
ship._physics_process(0.1)
|
ship._physics_process(0.1)
|
||||||
assert(is_equal_approx(ship.energy, base_regeneration * [1.1, 1.2, 1.7][rank - 1] * 0.1))
|
assert(is_equal_approx(ship.energy, base_regeneration * [1.2, 1.5, 2.0][rank - 1] * 0.1))
|
||||||
assert(not captain.activate_skill("reactor_overload", ship))
|
assert(not captain.activate_skill("reactor_overload", ship))
|
||||||
assert(captain.activate_skill("tactical_maneuver", ship))
|
assert(captain.activate_skill("tactical_maneuver", ship))
|
||||||
assert(is_equal_approx(ship.get_evasion_chance(), [0.3, 0.6, 1.0][rank - 1]))
|
assert(is_equal_approx(ship.get_evasion_chance(), [0.3, 0.6, 1.0][rank - 1]))
|
||||||
@ -61,7 +61,7 @@ func _ready() -> void:
|
|||||||
ship.shields = ship.max_shields * 0.25
|
ship.shields = ship.max_shields * 0.25
|
||||||
assert(captain.activate_skill("power_redirect", ship))
|
assert(captain.activate_skill("power_redirect", ship))
|
||||||
assert(ship.shields == 0.0)
|
assert(ship.shields == 0.0)
|
||||||
assert(is_equal_approx(captain.energy_regeneration_multiplier(), 1.0 + [0.1, 0.2, 0.7][rank - 1] + [0.5, 0.9, 1.5][rank - 1]))
|
assert(is_equal_approx(captain.energy_regeneration_multiplier(), 1.0 + [0.2, 0.5, 1.0][rank - 1] + [0.5, 0.9, 1.5][rank - 1]))
|
||||||
ship.shield_on_damage_percent = 100.0
|
ship.shield_on_damage_percent = 100.0
|
||||||
ship.restore_shields_from_damage(100.0)
|
ship.restore_shields_from_damage(100.0)
|
||||||
assert(ship.shields == 0.0)
|
assert(ship.shields == 0.0)
|
||||||
|
|||||||
63
tests/ships/debug_ammo_drone_formation_test.gd
Normal file
63
tests/ships/debug_ammo_drone_formation_test.gd
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
var game := preload("res://scenes/game/game.tscn").instantiate()
|
||||||
|
add_child(game)
|
||||||
|
game.set_physics_process(false)
|
||||||
|
var ship := game.get_node("PlayerShip") as CharacterBody2D
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var systems := ship.get_node("ExtensionSystems")
|
||||||
|
systems.set_physics_process(false)
|
||||||
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
enemy.queue_free()
|
||||||
|
await get_tree().process_frame
|
||||||
|
var hud := game.get_node("HUD")
|
||||||
|
hud.call("_toggle_debug")
|
||||||
|
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||||
|
var before := Vector3i(inventory.ammunition, inventory.irradiated_ammunition, inventory.missile_ammunition)
|
||||||
|
var button := hud.get_node("HUDRoot/DebugActions/AddAmmunition") as Button
|
||||||
|
button.pressed.emit()
|
||||||
|
button.pressed.emit()
|
||||||
|
assert(Vector3i(inventory.ammunition, inventory.irradiated_ammunition, inventory.missile_ammunition) == before + Vector3i(20, 20, 20))
|
||||||
|
systems.call("activate", 5)
|
||||||
|
var drones := get_tree().get_nodes_in_group("player_combat_drones")
|
||||||
|
assert(drones.size() == 3)
|
||||||
|
for drone in drones:
|
||||||
|
drone.set_physics_process(false)
|
||||||
|
assert(drone.scale == Vector2(0.5, 0.5))
|
||||||
|
# Stationary formation converges to fixed positions in front, with no idle orbit.
|
||||||
|
_step(ship, drones, 120)
|
||||||
|
for drone in drones:
|
||||||
|
assert(drone.global_position.distance_to(drone.call("formation_position")) < 2.0)
|
||||||
|
assert((drone.global_position - ship.global_position).dot(Vector2.UP.rotated(ship.rotation)) > 60.0)
|
||||||
|
# Forward flight and a turn inherit mother velocity without position snapping.
|
||||||
|
ship.velocity = Vector2(0, -200)
|
||||||
|
var previous_velocity: Vector2 = drones[0].get("velocity")
|
||||||
|
_step(ship, drones, 1)
|
||||||
|
assert((drones[0].get("velocity") as Vector2).distance_to(previous_velocity) <= 650.0 / 60.0 + 0.01)
|
||||||
|
_step(ship, drones, 119)
|
||||||
|
for drone in drones:
|
||||||
|
assert(drone.global_position.distance_to(drone.call("formation_position")) < 12.0)
|
||||||
|
ship.set("angular_velocity", 0.6)
|
||||||
|
_step(ship, drones, 120, true)
|
||||||
|
for drone in drones:
|
||||||
|
assert(drone.global_position.distance_to(drone.call("formation_position")) < 15.0)
|
||||||
|
ship.set("angular_velocity", 0.0)
|
||||||
|
ship.velocity = Vector2.ZERO
|
||||||
|
_step(ship, drones, 120)
|
||||||
|
for drone in drones:
|
||||||
|
assert(drone.global_position.distance_to(drone.call("formation_position")) < 2.0)
|
||||||
|
assert((drone.get("velocity") as Vector2).length() < 3.0)
|
||||||
|
print("DEBUG_AMMO_DRONE_FORMATION_TEST: PASS (debug ammo x10 repeatable, half scale, fixed forward formation, acceleration bound, moving/turning mother tracking, smooth stop)")
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _step(ship: CharacterBody2D, drones: Array[Node], frames: int, turning := false) -> void:
|
||||||
|
for frame in frames:
|
||||||
|
if turning:
|
||||||
|
ship.rotation += 0.6 / 60.0
|
||||||
|
ship.velocity = Vector2.UP.rotated(ship.rotation) * 200.0
|
||||||
|
ship.position += ship.velocity / 60.0
|
||||||
|
for drone in drones:
|
||||||
|
drone.call("_physics_process", 1.0 / 60.0)
|
||||||
1
tests/ships/debug_ammo_drone_formation_test.gd.uid
Normal file
1
tests/ships/debug_ammo_drone_formation_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b0bk1yhyw8t0y
|
||||||
4
tests/ships/debug_ammo_drone_formation_test.tscn
Normal file
4
tests/ships/debug_ammo_drone_formation_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/ships/debug_ammo_drone_formation_test.gd" id="1"]
|
||||||
|
[node name="DebugAmmoDroneFormationTest" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
Loading…
x
Reference in New Issue
Block a user