diff --git a/assets/art_prompts.json b/assets/art_prompts.json new file mode 100644 index 0000000..f81ff9a --- /dev/null +++ b/assets/art_prompts.json @@ -0,0 +1,70 @@ +{ + "tool": "built-in imagegen", + "assets": [ + { + "id": "shield", + "path": "assets/icons/shield.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: compact shield generator with luminous cyan hexagonal force-field dome. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "armor", + "path": "assets/icons/armor.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: layered spaceship armor plating, heavy beveled gunmetal plates with orange rivets. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "computer", + "path": "assets/icons/computer.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: ship computer processor core, precision circuit board and cyan holographic microchip. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "engine", + "path": "assets/icons/engine.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: spaceship engine turbine nozzle with bright blue exhaust glow. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "reactor", + "path": "assets/icons/reactor.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: fusion reactor, circular magnetic containment chamber with amber energy core. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "laser", + "path": "assets/icons/laser.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: sleek spaceship laser cannon, single precision barrel emitting narrow red beam. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "machine_gun", + "path": "assets/icons/machine_gun.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: spaceship automatic gun, rotary multi-barrel cannon with brass ammunition belt. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "plasma", + "path": "assets/icons/plasma.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: heavy plasma cannon, short thick barrel and violet plasma chamber. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "homing_missile", + "path": "assets/icons/homing_missile.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: guided missile launcher pod, missile with fins and a small cyan targeting radar ring. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "unguided_missile", + "path": "assets/icons/unguided_missile.png", + "prompt": "Use case: stylized-concept. Asset type: square sci-fi equipment inventory icon. Subject: unguided rocket launcher pod, chunky rockets with orange exhaust, no targeting ring. One centered distinct item, occupying 80% of the square. Premium polished game item art, detailed gunmetal machinery, crisp silhouette legible at 64 pixels, dramatic soft studio lighting, restrained cyan and amber accents. Consistent three-quarter view. Transparent background. No text, frame, border, UI tile, background scenery, watermark or other items." + }, + { + "id": "asteroid_rock", + "path": "assets/asteroids/asteroid_rock.png", + "prompt": "Use case: stylized-concept. Asset type: isolated top-down asteroid sprite for a 2D space game. Subject: irregular rugged charcoal-gray asteroid with deep craters and sharp fractured ridges. Square composition, single object centered, occupying 82% of the canvas. Polished painted realistic game art, excellent silhouette at 70 pixels, neutral lighting from upper left, subtle three-dimensional volume. Transparent background. No text, border, stars, shadow outside object, UI, other objects, or glow." + }, + { + "id": "asteroid_metal", + "path": "assets/asteroids/asteroid_metal.png", + "prompt": "Use case: stylized-concept. Asset type: isolated top-down asteroid sprite for a 2D space game. Subject: angular dense iron asteroid with dark metallic striations and sparse copper mineral seams. Square composition, single object centered, occupying 82% of the canvas. Polished painted realistic game art, excellent silhouette at 70 pixels, neutral lighting from upper left, subtle three-dimensional volume. Transparent background. No text, border, stars, shadow outside object, UI, other objects, or glow." + }, + { + "id": "asteroid_ice", + "path": "assets/asteroids/asteroid_ice.png", + "prompt": "Use case: stylized-concept. Asset type: isolated top-down asteroid sprite for a 2D space game. Subject: rounded fractured gray asteroid with icy blue crystalline deposits and small craters. Square composition, single object centered, occupying 82% of the canvas. Polished painted realistic game art, excellent silhouette at 70 pixels, neutral lighting from upper left, subtle three-dimensional volume. Transparent background. No text, border, stars, shadow outside object, UI, other objects, or glow." + } + ] +} diff --git a/assets/asteroids/asteroid_ice.png b/assets/asteroids/asteroid_ice.png new file mode 100644 index 0000000..96c00e7 Binary files /dev/null and b/assets/asteroids/asteroid_ice.png differ diff --git a/assets/asteroids/asteroid_ice.png.import b/assets/asteroids/asteroid_ice.png.import new file mode 100644 index 0000000..afdc8c0 --- /dev/null +++ b/assets/asteroids/asteroid_ice.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bqsxivk08rrv1" +path="res://.godot/imported/asteroid_ice.png-0d420456aba47c55a85abbd8896759f2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/asteroids/asteroid_ice.png" +dest_files=["res://.godot/imported/asteroid_ice.png-0d420456aba47c55a85abbd8896759f2.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 diff --git a/assets/asteroids/asteroid_metal.png b/assets/asteroids/asteroid_metal.png new file mode 100644 index 0000000..af822ad Binary files /dev/null and b/assets/asteroids/asteroid_metal.png differ diff --git a/assets/asteroids/asteroid_metal.png.import b/assets/asteroids/asteroid_metal.png.import new file mode 100644 index 0000000..4d87d7b --- /dev/null +++ b/assets/asteroids/asteroid_metal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dknnnjlnfiokv" +path="res://.godot/imported/asteroid_metal.png-260765715b40227a400ab8450694da75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/asteroids/asteroid_metal.png" +dest_files=["res://.godot/imported/asteroid_metal.png-260765715b40227a400ab8450694da75.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 diff --git a/assets/asteroids/asteroid_rock.png b/assets/asteroids/asteroid_rock.png new file mode 100644 index 0000000..6c766f2 Binary files /dev/null and b/assets/asteroids/asteroid_rock.png differ diff --git a/assets/asteroids/asteroid_rock.png.import b/assets/asteroids/asteroid_rock.png.import new file mode 100644 index 0000000..4cec5b1 --- /dev/null +++ b/assets/asteroids/asteroid_rock.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x70qj5c7yurt" +path="res://.godot/imported/asteroid_rock.png-679dcdbe656749145612318b46f7c3d4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/asteroids/asteroid_rock.png" +dest_files=["res://.godot/imported/asteroid_rock.png-679dcdbe656749145612318b46f7c3d4.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 diff --git a/assets/avatars/captain.png b/assets/avatars/captain.png new file mode 100644 index 0000000..316e309 Binary files /dev/null and b/assets/avatars/captain.png differ diff --git a/assets/avatars/captain.png.import b/assets/avatars/captain.png.import new file mode 100644 index 0000000..a2657ef --- /dev/null +++ b/assets/avatars/captain.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dumlma4agiydf" +path="res://.godot/imported/captain.png-9a0afd3b17a97167234f3e3ea4d8c400.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/avatars/captain.png" +dest_files=["res://.godot/imported/captain.png-9a0afd3b17a97167234f3e3ea4d8c400.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 diff --git a/assets/avatars/captain.prompt.md b/assets/avatars/captain.prompt.md new file mode 100644 index 0000000..fae490a --- /dev/null +++ b/assets/avatars/captain.prompt.md @@ -0,0 +1,3 @@ +Generated with the built-in imagegen tool. + +Use case: stylized-concept. Asset type: square captain avatar portrait for a polished sci-fi spaceship game HUD, legible at 80 pixels. Subject: experienced adult male space captain, short dark hair with silver temples, subtle stubble, calm focused expression, navy flight suit with small amber and cyan accents, no logos. Composition: centered head and shoulders, face large, square crop. Style: polished hand-painted game concept art, realistic anatomy, crisp silhouette, restrained detail. Lighting: cyan rim light and warm amber key light. Background: dark navy cockpit blur. No text, no watermark, no decorative frame. diff --git a/assets/icons/armor.png b/assets/icons/armor.png new file mode 100644 index 0000000..8def135 Binary files /dev/null and b/assets/icons/armor.png differ diff --git a/assets/icons/armor.png.import b/assets/icons/armor.png.import new file mode 100644 index 0000000..b9bc670 --- /dev/null +++ b/assets/icons/armor.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://twjypsovswrj" +path="res://.godot/imported/armor.png-cd0de3e584bc414792dc7177f7f97118.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/armor.png" +dest_files=["res://.godot/imported/armor.png-cd0de3e584bc414792dc7177f7f97118.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 diff --git a/assets/icons/computer.png b/assets/icons/computer.png new file mode 100644 index 0000000..1a334a6 Binary files /dev/null and b/assets/icons/computer.png differ diff --git a/assets/icons/computer.png.import b/assets/icons/computer.png.import new file mode 100644 index 0000000..9a9bdbc --- /dev/null +++ b/assets/icons/computer.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dswokqx8fjbl7" +path="res://.godot/imported/computer.png-06f9e1e3ec9eb03dac33e53d3e226b18.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/computer.png" +dest_files=["res://.godot/imported/computer.png-06f9e1e3ec9eb03dac33e53d3e226b18.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 diff --git a/assets/icons/engine.png b/assets/icons/engine.png new file mode 100644 index 0000000..82dfb7b Binary files /dev/null and b/assets/icons/engine.png differ diff --git a/assets/icons/engine.png.import b/assets/icons/engine.png.import new file mode 100644 index 0000000..ccc44bb --- /dev/null +++ b/assets/icons/engine.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfsy4ts4kr5iu" +path="res://.godot/imported/engine.png-90c3579541e0f551c023fcf5f45c8938.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/engine.png" +dest_files=["res://.godot/imported/engine.png-90c3579541e0f551c023fcf5f45c8938.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 diff --git a/assets/icons/homing_missile.png b/assets/icons/homing_missile.png new file mode 100644 index 0000000..7559f9a Binary files /dev/null and b/assets/icons/homing_missile.png differ diff --git a/assets/icons/homing_missile.png.import b/assets/icons/homing_missile.png.import new file mode 100644 index 0000000..efcc424 --- /dev/null +++ b/assets/icons/homing_missile.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmlhgaubsmoso" +path="res://.godot/imported/homing_missile.png-0ff3770387c0b8d591a6d726f544d23d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/homing_missile.png" +dest_files=["res://.godot/imported/homing_missile.png-0ff3770387c0b8d591a6d726f544d23d.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 diff --git a/assets/icons/laser.png b/assets/icons/laser.png new file mode 100644 index 0000000..e61b5e2 Binary files /dev/null and b/assets/icons/laser.png differ diff --git a/assets/icons/laser.png.import b/assets/icons/laser.png.import new file mode 100644 index 0000000..395e5a7 --- /dev/null +++ b/assets/icons/laser.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cg31nor3aqfcd" +path="res://.godot/imported/laser.png-78f84fc3b728b76788f1d7e6bb3d09a0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/laser.png" +dest_files=["res://.godot/imported/laser.png-78f84fc3b728b76788f1d7e6bb3d09a0.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 diff --git a/assets/icons/machine_gun.png b/assets/icons/machine_gun.png new file mode 100644 index 0000000..dd64e0a Binary files /dev/null and b/assets/icons/machine_gun.png differ diff --git a/assets/icons/machine_gun.png.import b/assets/icons/machine_gun.png.import new file mode 100644 index 0000000..dfb956e --- /dev/null +++ b/assets/icons/machine_gun.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dglx1hqhd660y" +path="res://.godot/imported/machine_gun.png-82b5627260e455aa7509fcb24baf4ee1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/machine_gun.png" +dest_files=["res://.godot/imported/machine_gun.png-82b5627260e455aa7509fcb24baf4ee1.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 diff --git a/assets/icons/plasma.png b/assets/icons/plasma.png new file mode 100644 index 0000000..21d5e67 Binary files /dev/null and b/assets/icons/plasma.png differ diff --git a/assets/icons/plasma.png.import b/assets/icons/plasma.png.import new file mode 100644 index 0000000..011f37d --- /dev/null +++ b/assets/icons/plasma.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcysvo1y1k2n" +path="res://.godot/imported/plasma.png-010e12ed95db210261727cb258030992.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/plasma.png" +dest_files=["res://.godot/imported/plasma.png-010e12ed95db210261727cb258030992.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 diff --git a/assets/icons/reactor.png b/assets/icons/reactor.png new file mode 100644 index 0000000..98a13ba Binary files /dev/null and b/assets/icons/reactor.png differ diff --git a/assets/icons/reactor.png.import b/assets/icons/reactor.png.import new file mode 100644 index 0000000..4ea7a33 --- /dev/null +++ b/assets/icons/reactor.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://civi7r36r2ua4" +path="res://.godot/imported/reactor.png-55fcc7bf0233d2989c491f5381ead349.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/reactor.png" +dest_files=["res://.godot/imported/reactor.png-55fcc7bf0233d2989c491f5381ead349.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 diff --git a/assets/icons/shield.png b/assets/icons/shield.png new file mode 100644 index 0000000..c46e671 Binary files /dev/null and b/assets/icons/shield.png differ diff --git a/assets/icons/shield.png.import b/assets/icons/shield.png.import new file mode 100644 index 0000000..7f2fa75 --- /dev/null +++ b/assets/icons/shield.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmeso5ns28mil" +path="res://.godot/imported/shield.png-dc46181106b01d9c5b9cf3dd1ba81451.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/shield.png" +dest_files=["res://.godot/imported/shield.png-dc46181106b01d9c5b9cf3dd1ba81451.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 diff --git a/assets/icons/unguided_missile.png b/assets/icons/unguided_missile.png new file mode 100644 index 0000000..22863da Binary files /dev/null and b/assets/icons/unguided_missile.png differ diff --git a/assets/icons/unguided_missile.png.import b/assets/icons/unguided_missile.png.import new file mode 100644 index 0000000..3c19d32 --- /dev/null +++ b/assets/icons/unguided_missile.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8oeu45j01e17" +path="res://.godot/imported/unguided_missile.png-1dd7a9dcb8b847e813c29b7540ac1098.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/unguided_missile.png" +dest_files=["res://.godot/imported/unguided_missile.png-1dd7a9dcb8b847e813c29b7540ac1098.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 diff --git a/assets/other/asteroids.jpg b/assets/other/asteroids.jpg new file mode 100644 index 0000000..c43ad37 Binary files /dev/null and b/assets/other/asteroids.jpg differ diff --git a/assets/other/asteroids.jpg.import b/assets/other/asteroids.jpg.import new file mode 100644 index 0000000..997229b --- /dev/null +++ b/assets/other/asteroids.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnjux8sn6b5xl" +path="res://.godot/imported/asteroids.jpg-2fcfc4129a9d368d61d5aef72c84c9ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/other/asteroids.jpg" +dest_files=["res://.godot/imported/asteroids.jpg-2fcfc4129a9d368d61d5aef72c84c9ad.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 diff --git a/resources/asteroid_cutout.gdshader b/resources/asteroid_cutout.gdshader new file mode 100644 index 0000000..290db63 --- /dev/null +++ b/resources/asteroid_cutout.gdshader @@ -0,0 +1,8 @@ +shader_type canvas_item; + +// Remove the white backing from the original JPEG atlas at render time. +void fragment() { + vec4 texel = texture(TEXTURE, UV); + float background = smoothstep(0.86, 0.98, min(texel.r, min(texel.g, texel.b))); + COLOR.a *= 1.0 - background; +} diff --git a/resources/asteroid_cutout.gdshader.uid b/resources/asteroid_cutout.gdshader.uid new file mode 100644 index 0000000..04dd586 --- /dev/null +++ b/resources/asteroid_cutout.gdshader.uid @@ -0,0 +1 @@ +uid://c6nuwnxc0kygw diff --git a/resources/items/affixes/credits_percent.tres b/resources/items/affixes/credits_percent.tres index c8bbe18..66f54df 100644 --- a/resources/items/affixes/credits_percent.tres +++ b/resources/items/affixes/credits_percent.tres @@ -9,7 +9,7 @@ display_name = "Kredyty" stat_key = "credits_percent" unit = "%" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 2 maximum_value_level_1 = 5 minimum_value_level_1000 = 40 diff --git a/resources/items/affixes/critical_chance_percent.tres b/resources/items/affixes/critical_chance_percent.tres index 7935605..5fc050b 100644 --- a/resources/items/affixes/critical_chance_percent.tres +++ b/resources/items/affixes/critical_chance_percent.tres @@ -8,7 +8,7 @@ affix_id = "critical_chance_percent" display_name = "Szansa na trafienie krytyczne" stat_key = "critical_chance_percent" unit = "%" -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 1.0 maximum_value_level_1 = 2.0 minimum_value_level_1000 = 10.0 diff --git a/resources/items/affixes/critical_damage_percent.tres b/resources/items/affixes/critical_damage_percent.tres index 009086b..c94da61 100644 --- a/resources/items/affixes/critical_damage_percent.tres +++ b/resources/items/affixes/critical_damage_percent.tres @@ -8,7 +8,7 @@ affix_id = "critical_damage_percent" display_name = "Obrażenia krytyczne" stat_key = "critical_damage_percent" unit = "%" -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 5.0 maximum_value_level_1 = 10.0 minimum_value_level_1000 = 50.0 diff --git a/resources/items/affixes/energy_regeneration.tres b/resources/items/affixes/energy_regeneration.tres index 04341c6..33321d1 100644 --- a/resources/items/affixes/energy_regeneration.tres +++ b/resources/items/affixes/energy_regeneration.tres @@ -9,7 +9,7 @@ display_name = "Regeneracja energii" stat_key = "energy_regeneration" unit = "/s" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 0.2 maximum_value_level_1 = 0.5 minimum_value_level_1000 = 100 diff --git a/resources/items/affixes/experience_percent.tres b/resources/items/affixes/experience_percent.tres index 0b8ee4f..5dba5af 100644 --- a/resources/items/affixes/experience_percent.tres +++ b/resources/items/affixes/experience_percent.tres @@ -9,7 +9,7 @@ display_name = "Doświadczenie (EXP)" stat_key = "experience_percent" unit = "%" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 2 maximum_value_level_1 = 5 minimum_value_level_1000 = 40 diff --git a/resources/items/affixes/hull_percent.tres b/resources/items/affixes/hull_percent.tres index 46dd7d6..0d52156 100644 --- a/resources/items/affixes/hull_percent.tres +++ b/resources/items/affixes/hull_percent.tres @@ -9,7 +9,7 @@ display_name = "Kadłub" stat_key = "hull_percent" unit = "%" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 1 maximum_value_level_1 = 3 minimum_value_level_1000 = 25 diff --git a/resources/items/affixes/missile_cooldown.tres b/resources/items/affixes/missile_cooldown.tres index 118e494..fbef4ac 100644 --- a/resources/items/affixes/missile_cooldown.tres +++ b/resources/items/affixes/missile_cooldown.tres @@ -6,7 +6,7 @@ affix_id = "missile_cooldown" display_name = "Skrócenie cooldownu rakiet" stat_key = "missile_cooldown_reduction" unit = "s" -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 0.1 maximum_value_level_1 = 0.15 minimum_value_level_1000 = 1.0 diff --git a/resources/items/affixes/shield_on_damage.tres b/resources/items/affixes/shield_on_damage.tres index b461031..adce365 100644 --- a/resources/items/affixes/shield_on_damage.tres +++ b/resources/items/affixes/shield_on_damage.tres @@ -7,7 +7,7 @@ display_name = "Odzysk tarcz z obrażeń" description = "Globalnie odnawia tarcze o procent faktycznie zadanych obrażeń każdą bronią." stat_key = "shield_on_damage_percent" unit = "%" -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 1.0 maximum_value_level_1 = 1.0 minimum_value_level_1000 = 5.0 diff --git a/resources/items/affixes/shield_percent.tres b/resources/items/affixes/shield_percent.tres index c41995e..2040dc2 100644 --- a/resources/items/affixes/shield_percent.tres +++ b/resources/items/affixes/shield_percent.tres @@ -9,7 +9,7 @@ display_name = "Tarcze" stat_key = "shield_percent" unit = "%" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 1 maximum_value_level_1 = 3 minimum_value_level_1000 = 25 diff --git a/resources/items/affixes/shield_regeneration.tres b/resources/items/affixes/shield_regeneration.tres index d2dd53c..1c03fc1 100644 --- a/resources/items/affixes/shield_regeneration.tres +++ b/resources/items/affixes/shield_regeneration.tres @@ -9,7 +9,7 @@ display_name = "Regeneracja tarcz" stat_key = "shield_regeneration" unit = "/s" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 0.5 maximum_value_level_1 = 1 minimum_value_level_1000 = 200 diff --git a/resources/items/affixes/speed_percent.tres b/resources/items/affixes/speed_percent.tres index bde71fe..0d87bfe 100644 --- a/resources/items/affixes/speed_percent.tres +++ b/resources/items/affixes/speed_percent.tres @@ -9,7 +9,7 @@ display_name = "Prędkość statku" stat_key = "speed_percent" unit = "%" selection_weight = 1.0 -category_mask = 63 +category_mask = 95 minimum_value_level_1 = 1 maximum_value_level_1 = 2 minimum_value_level_1000 = 15 diff --git a/resources/items/afterburner.tres b/resources/items/afterburner.tres index 10af8e8..2bc9915 100644 --- a/resources/items/afterburner.tres +++ b/resources/items/afterburner.tres @@ -3,6 +3,8 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bpaiuqwsxswii" path="res://scripts/abilities/afterburner_ability.gd" id="2"] +[ext_resource type="Texture2D" path="res://assets/icons/engine.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") display_name = "Dopalacz" @@ -10,6 +12,7 @@ description = "Szybko rozpędza do 300% zwykłej prędkości (+200%), następnie [resource] script = ExtResource("1") +icon = ExtResource("icon") item_id = "afterburner" display_name = "Dopalacz" category = 5 diff --git a/resources/items/beam_laser.tres b/resources/items/beam_laser.tres index a2675e0..d30aefc 100644 --- a/resources/items/beam_laser.tres +++ b/resources/items/beam_laser.tres @@ -1,9 +1,12 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/laser.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "beam_laser" display_name = "Laser wiązkowy" category = 0 diff --git a/resources/items/compact_generator.tres b/resources/items/compact_generator.tres index 814b00b..b7c5786 100644 --- a/resources/items/compact_generator.tres +++ b/resources/items/compact_generator.tres @@ -1,11 +1,14 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/reactor.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "compact_generator" -display_name = "Kompaktowy generator" +display_name = "Reaktor" category = 3 description = "Zwiększa maksymalną pojemność energii." primary_stat = "max_energy" diff --git a/resources/items/engine_extension.tres b/resources/items/engine_extension.tres index c7735eb..66c7957 100644 --- a/resources/items/engine_extension.tres +++ b/resources/items/engine_extension.tres @@ -2,10 +2,13 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/engine.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "engine_extension" -display_name = "Ulepszony silnik" +display_name = "Silnik" description = "Pasywny moduł prędkości. Na poziomie 1 bonus wynosi 1–5 px/s i rośnie wraz z poziomem przedmiotu." primary_stat = "max_speed" primary_value = 3.0 diff --git a/resources/items/hull_plating.tres b/resources/items/hull_plating.tres index b13d6ff..3d58d78 100644 --- a/resources/items/hull_plating.tres +++ b/resources/items/hull_plating.tres @@ -1,11 +1,14 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/armor.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "hull_plating" -display_name = "Płyty kadłubowe" +display_name = "Pancerz" category = 2 description = "Zwiększa maksymalną wytrzymałość kadłuba." primary_stat = "max_hull" diff --git a/resources/items/machine_gun.tres b/resources/items/machine_gun.tres index 126fa4a..97dc267 100644 --- a/resources/items/machine_gun.tres +++ b/resources/items/machine_gun.tres @@ -1,9 +1,12 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/machine_gun.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "machine_gun" display_name = "Działko maszynowe" category = 0 diff --git a/resources/items/missile_launcher.tres b/resources/items/missile_launcher.tres index 64a6004..a116f28 100644 --- a/resources/items/missile_launcher.tres +++ b/resources/items/missile_launcher.tres @@ -1,9 +1,12 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/homing_missile.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "missile_launcher" display_name = "Wyrzutnia rakiet naprowadzanych" category = 0 diff --git a/resources/items/plasma_cannon.tres b/resources/items/plasma_cannon.tres index b1c8f90..9bd54d4 100644 --- a/resources/items/plasma_cannon.tres +++ b/resources/items/plasma_cannon.tres @@ -1,9 +1,12 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/plasma.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "plasma_cannon" display_name = "Działko plazmowe" category = 0 diff --git a/resources/items/reinforced_shield.tres b/resources/items/reinforced_shield.tres index 84ae7db..e05ad5c 100644 --- a/resources/items/reinforced_shield.tres +++ b/resources/items/reinforced_shield.tres @@ -1,11 +1,14 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/shield.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "reinforced_shield" -display_name = "Wzmocniony emiter" +display_name = "Tarcze" category = 1 description = "Zwiększa maksymalną pojemność tarcz." primary_stat = "max_shields" diff --git a/resources/items/repair_drones.tres b/resources/items/repair_drones.tres index f91b16a..8abca17 100644 --- a/resources/items/repair_drones.tres +++ b/resources/items/repair_drones.tres @@ -3,6 +3,8 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bgofku10gduus" path="res://scripts/abilities/repair_drones_ability.gd" id="2"] +[ext_resource type="Texture2D" path="res://assets/icons/armor.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") display_name = "Drony naprawcze" @@ -10,6 +12,7 @@ description = "Naprawiają tyle kadłuba, ile pozwalają części i brakujące p [resource] script = ExtResource("1") +icon = ExtResource("icon") item_id = "repair_drones" display_name = "Drony naprawcze" category = 5 diff --git a/resources/items/scanner.tres b/resources/items/scanner.tres deleted file mode 100644 index df01663..0000000 --- a/resources/items/scanner.tres +++ /dev/null @@ -1,14 +0,0 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=4 format=3] -[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] -[ext_resource type="Script" path="res://scripts/abilities/scanner_ability.gd" id="2"] -[sub_resource type="Resource" id="Ability"] -script = ExtResource("2") -display_name = "Skaner" -description = "Przez 8 sekund pokazuje kierunki do wrogów, asteroid, wraków i planet z antycznymi znaleziskami. Wymaga i zużywa 50% maksymalnej energii." -[resource] -script = ExtResource("1") -item_id = "scanner" -display_name = "Skaner" -category = 5 -description = "Aktywne rozszerzenie eksploracyjne. Zamontuj w slocie modułu i aktywuj przypisanym klawiszem 1–6." -active_ability = SubResource("Ability") diff --git a/resources/items/shield_charge.tres b/resources/items/shield_charge.tres index b053ed2..8defe39 100644 --- a/resources/items/shield_charge.tres +++ b/resources/items/shield_charge.tres @@ -3,6 +3,8 @@ [ext_resource type="Script" uid="uid://d2fdtd4wjk7d" path="res://scripts/items/item_definition.gd" id="1"] [ext_resource type="Script" uid="uid://bwka1dwxq64sl" path="res://scripts/abilities/shield_charge_ability.gd" id="2"] +[ext_resource type="Texture2D" path="res://assets/icons/shield.png" id="icon"] + [sub_resource type="Resource" id="Ability"] script = ExtResource("2") display_name = "Ładowanie tarcz" @@ -10,6 +12,7 @@ description = "Przywraca 100 punktów tarcz, do ich maksimum. Wymaga i zużywa 1 [resource] script = ExtResource("1") +icon = ExtResource("icon") item_id = "shield_charge" display_name = "Ładowanie tarcz" category = 5 diff --git a/resources/items/shield_recharger.tres b/resources/items/shield_recharger.tres index 360d643..3f03da8 100644 --- a/resources/items/shield_recharger.tres +++ b/resources/items/shield_recharger.tres @@ -1,13 +1,16 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/shield.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "shield_recharger" -display_name = "Generator tarcz" +display_name = "Tarcze" category = 1 -description = "Odnawia tarcze statku z szybkością 1 punktu na sekundę." -primary_stat = "shield_regeneration" -primary_value = 1.0 -stat_suffix = " tarczy/s" +description = "Zwiększa maksymalną pojemność tarcz statku." +primary_stat = "max_shields" +primary_value = 15.0 +stat_suffix = " tarczy" diff --git a/resources/items/ship_computer.tres b/resources/items/ship_computer.tres new file mode 100644 index 0000000..956041f --- /dev/null +++ b/resources/items/ship_computer.tres @@ -0,0 +1,14 @@ +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] +[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"] +[ext_resource type="Texture2D" path="res://assets/icons/computer.png" id="icon"] + +[resource] +script = ExtResource("1") +icon = ExtResource("icon") +item_id = "ship_computer" +display_name = "Komputer" +category = 6 +description = "Zwiększa zasięg stałego skanera wykrywającego wrogów, asteroidy, wraki i antyczne znaleziska." +primary_stat = "scanner_range" +primary_value = 300.0 +stat_suffix = " px" diff --git a/resources/items/unguided_missile.tres b/resources/items/unguided_missile.tres index fded306..1be9a01 100644 --- a/resources/items/unguided_missile.tres +++ b/resources/items/unguided_missile.tres @@ -1,7 +1,10 @@ -[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1_definition"] +[ext_resource type="Texture2D" path="res://assets/icons/unguided_missile.png" id="icon"] + [resource] script = ExtResource("1_definition") +icon = ExtResource("icon") item_id = "unguided_missile" display_name = "Wyrzutnia rakiet niekierowanych" category = 0 diff --git a/scenes/ui/captain_panel.tscn b/scenes/ui/captain_panel.tscn index 0f058bd..f49b1e0 100644 --- a/scenes/ui/captain_panel.tscn +++ b/scenes/ui/captain_panel.tscn @@ -74,8 +74,3 @@ theme_override_font_sizes/font_size = 11 [node name="Skills" type="VBoxContainer" parent="Contents"] layout_mode = 2 theme_override_constants/separation = 2 - -[node name="Critical" type="Label" parent="Contents"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.98, 0.68, 0.5, 1) -theme_override_font_sizes/font_size = 10 diff --git a/scenes/ui/inventory_slot_view.tscn b/scenes/ui/inventory_slot_view.tscn index 2c76fd5..946b063 100644 --- a/scenes/ui/inventory_slot_view.tscn +++ b/scenes/ui/inventory_slot_view.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://scripts/ui/equipment_slot_view.gd" id="1_slot"] [node name="InventorySlot" type="PanelContainer"] -custom_minimum_size = Vector2(72, 48) +custom_minimum_size = Vector2(64, 64) script = ExtResource("1_slot") [node name="SlotLabel" type="Label" parent="."] diff --git a/scenes/ui/ship_equipment_panel.tscn b/scenes/ui/ship_equipment_panel.tscn index d4d7874..ccd7920 100644 --- a/scenes/ui/ship_equipment_panel.tscn +++ b/scenes/ui/ship_equipment_panel.tscn @@ -39,11 +39,11 @@ theme_override_constants/separation = 6 layout_mode = 2 theme_override_colors/font_color = Color(0.8, 0.84, 0.88, 1) theme_override_font_sizes/font_size = 12 -text = "WYPOSAŻENIE STATKU" +text = "SYSTEMY POKŁADOWE" horizontal_alignment = 1 [node name="EquipmentLayout" type="Control" parent="Contents"] -custom_minimum_size = Vector2(306, 318) +custom_minimum_size = Vector2(284, 456) layout_mode = 2 mouse_filter = 2 @@ -59,92 +59,132 @@ script = ExtResource("1_schematic") [node name="Weapon1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 0.0 +offset_left = 14.0 offset_top = 22.0 -offset_right = 90.0 -offset_bottom = 66.0 +offset_right = 78.0 +offset_bottom = 86.0 slot_kind = "weapon" slot_index = 0 overlay_slot = true [node name="Weapon2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 216.0 +offset_left = 206.0 offset_top = 22.0 -offset_right = 306.0 -offset_bottom = 66.0 +offset_right = 270.0 +offset_bottom = 86.0 slot_kind = "weapon" slot_index = 1 overlay_slot = true -[node name="Missile" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +[node name="Weapon3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 108.0 +offset_left = 110.0 offset_top = 22.0 -offset_right = 198.0 -offset_bottom = 66.0 +offset_right = 174.0 +offset_bottom = 86.0 slot_kind = "weapon" slot_index = 2 overlay_slot = true [node name="Module1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 0.0 -offset_top = 100.0 -offset_right = 104.0 -offset_bottom = 144.0 +offset_left = 14.0 +offset_top = 108.0 +offset_right = 78.0 +offset_bottom = 172.0 slot_kind = "module" slot_index = 0 overlay_slot = true [node name="Module2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 202.0 -offset_top = 100.0 -offset_right = 306.0 -offset_bottom = 144.0 +offset_left = 14.0 +offset_top = 178.0 +offset_right = 78.0 +offset_bottom = 242.0 slot_kind = "module" slot_index = 1 overlay_slot = true [node name="Module3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 0.0 -offset_top = 180.0 -offset_right = 104.0 -offset_bottom = 224.0 +offset_left = 14.0 +offset_top = 248.0 +offset_right = 78.0 +offset_bottom = 312.0 slot_kind = "module" slot_index = 2 overlay_slot = true [node name="Module4" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 202.0 -offset_top = 180.0 -offset_right = 306.0 -offset_bottom = 224.0 +offset_left = 14.0 +offset_top = 318.0 +offset_right = 78.0 +offset_bottom = 382.0 slot_kind = "module" slot_index = 3 overlay_slot = true [node name="Module5" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 0.0 -offset_top = 260.0 -offset_right = 104.0 -offset_bottom = 304.0 +offset_left = 14.0 +offset_top = 388.0 +offset_right = 78.0 +offset_bottom = 452.0 slot_kind = "module" slot_index = 4 overlay_slot = true -[node name="Module6" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +[node name="Extension1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 202.0 -offset_top = 260.0 -offset_right = 306.0 -offset_bottom = 304.0 -slot_kind = "module" -slot_index = 5 +offset_left = 206.0 +offset_top = 108.0 +offset_right = 270.0 +offset_bottom = 172.0 +slot_kind = "extension" +slot_index = 0 +overlay_slot = true + +[node name="Extension2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +layout_mode = 0 +offset_left = 206.0 +offset_top = 178.0 +offset_right = 270.0 +offset_bottom = 242.0 +slot_kind = "extension" +slot_index = 1 +overlay_slot = true + +[node name="Extension3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +layout_mode = 0 +offset_left = 206.0 +offset_top = 248.0 +offset_right = 270.0 +offset_bottom = 312.0 +slot_kind = "extension" +slot_index = 2 +overlay_slot = true + +[node name="Extension4" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +layout_mode = 0 +offset_left = 206.0 +offset_top = 318.0 +offset_right = 270.0 +offset_bottom = 382.0 +slot_kind = "extension" +slot_index = 3 +overlay_slot = true + +[node name="Extension5" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] +layout_mode = 0 +offset_left = 206.0 +offset_top = 388.0 +offset_right = 270.0 +offset_bottom = 452.0 +slot_kind = "extension" +slot_index = 4 overlay_slot = true [node name="InventoryButton" type="Button" parent="Contents"] diff --git a/scenes/ui/ship_inventory_panel.tscn b/scenes/ui/ship_inventory_panel.tscn index 2a6917a..143e940 100644 --- a/scenes/ui/ship_inventory_panel.tscn +++ b/scenes/ui/ship_inventory_panel.tscn @@ -59,12 +59,6 @@ size_flags_horizontal = 3 theme_override_font_sizes/font_size = 11 text = "AMUNICJA: 50" -[node name="CraftButton" type="Button" parent="Contents/Ammunition"] -layout_mode = 2 -theme_override_font_sizes/font_size = 11 -text = "TWÓRZ +100 AMUNICJI (50 części)" -tooltip_text = "Zamień 50 części na 100 sztuk amunicji do działka maszynowego." - [node name="CargoGrid" type="GridContainer" parent="Contents"] layout_mode = 2 size_flags_horizontal = 4 diff --git a/scripts/abilities/scanner_ability.gd b/scripts/abilities/scanner_ability.gd deleted file mode 100644 index f68dcb0..0000000 --- a/scripts/abilities/scanner_ability.gd +++ /dev/null @@ -1,24 +0,0 @@ -extends ActiveAbility - -const SCAN_DURATION := 8.0 -const OVERLAY := preload("res://scripts/ui/scanner_overlay.gd") - -func unavailable_reason(ship: CharacterBody2D) -> String: - if ship.has_node("ScannerOverlay"): - return "Skaner jest aktywny" - return "Wymagane 50% energii" if float(ship.get("energy")) < float(ship.get("max_energy")) * 0.5 else "" - -func activate(ship: CharacterBody2D) -> void: - if not unavailable_reason(ship).is_empty(): - return - ship.set("energy", float(ship.get("energy")) - float(ship.get("max_energy")) * 0.5) - var layer := CanvasLayer.new() - layer.name = "ScannerOverlay" - layer.layer = 8 - ship.add_child(layer) - var overlay := Control.new() - overlay.set_script(OVERLAY) - overlay.set("player", ship) - overlay.set("remaining", SCAN_DURATION) - layer.add_child(overlay) - overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) diff --git a/scripts/abilities/scanner_ability.gd.uid b/scripts/abilities/scanner_ability.gd.uid deleted file mode 100644 index 7ec96ce..0000000 --- a/scripts/abilities/scanner_ability.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dhayld6xbljao diff --git a/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index 82567b5..f5043eb 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -14,11 +14,17 @@ var _ability_blocked_style: StyleBoxFlat var _inventory_clip: Control var _sidebar: PanelContainer var _sidebar_toggle: Button -var _sidebar_open := false +var _sidebar_open := true var _sidebar_tween: Tween -const SIDEBAR_WIDTH := 344.0 +const SIDEBAR_WIDTH := 306.0 +var _debug_scroll: ScrollContainer +var _captain_panel: PanelContainer +var _cockpit: Control func _ready() -> void: + _sidebar_open = bool(GameState.hud_panel_state.get("ship_open", true)) + _debug_expanded = bool(GameState.hud_panel_state.get("debug_expanded", true)) + debug_info.visible = _debug_expanded var energy_indicator := Node2D.new() energy_indicator.name = "EnergyIndicator" energy_indicator.set_script(preload("res://scripts/game/ship_energy_indicator.gd")) @@ -29,7 +35,9 @@ func _ready() -> void: feedback.set_script(preload("res://scripts/ui/combat_feedback.gd")) feedback.set("player", player) $HUDRoot.add_child(feedback) - debug_info.add_theme_font_size_override("font_size", 9) + debug_info.add_theme_font_size_override("font_size", 10) + # The display font has unusually tall vertical metrics; keep debug rows compact. + debug_info.add_theme_constant_override("line_spacing", mini(0, 13 - ceili(debug_info.get_theme_font("font").get_height(10)))) _create_sidebar() _inventory_clip = Control.new() _inventory_clip.name = "InventoryDrawer" @@ -39,21 +47,37 @@ func _ready() -> void: inventory_panel.reparent(_inventory_clip, false) inventory_panel.set("layout_mode", 0) inventory_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) - inventory_panel.size = Vector2(780, inventory_panel.get_combined_minimum_size().y) + inventory_panel.size = Vector2(560, inventory_panel.get_combined_minimum_size().y) $HUDRoot.resized.connect(func() -> void: _layout_sidebar.call_deferred()) _create_ability_bar() debug_toggle.pressed.connect(_toggle_debug) inventory_button.pressed.connect(_toggle_inventory) _update_debug_toggle() _layout_sidebar.call_deferred() + if _sidebar_open and bool(GameState.hud_panel_state.get("inventory_open", false)): + inventory_panel.call("_set_drawer_open", true) func _create_sidebar() -> void: + _captain_panel = $HUDRoot/CaptainPanel + _captain_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) + _captain_panel.position = Vector2(12, 12) + _captain_panel.size = Vector2(320, _captain_panel.get_combined_minimum_size().y) + _captain_panel.resized.connect(_layout_debug) + _debug_scroll = ScrollContainer.new() + _debug_scroll.name = "DebugScroll" + _debug_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + $HUDRoot.add_child(_debug_scroll) + debug_info.reparent(_debug_scroll, false) + debug_info.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) + debug_info.size_flags_horizontal = Control.SIZE_EXPAND_FILL + debug_info.mouse_filter = Control.MOUSE_FILTER_IGNORE _sidebar = PanelContainer.new() - _sidebar.name = "CaptainShipDrawer" + _sidebar.name = "ShipDrawer" var style := StyleBoxFlat.new() - style.bg_color = Color(0.018, 0.028, 0.043, 0.97) - style.border_color = Color("52677e") + style.bg_color = Color(0.025, 0.042, 0.064, 0.97) + style.border_color = Color("376779") style.set_border_width_all(1) + style.set_corner_radius_all(8) style.set_content_margin_all(8) _sidebar.add_theme_stylebox_override("panel", style) $HUDRoot.add_child(_sidebar) @@ -64,50 +88,70 @@ func _create_sidebar() -> void: var contents := VBoxContainer.new() contents.name = "Contents" contents.size_flags_horizontal = Control.SIZE_EXPAND_FILL - contents.add_theme_constant_override("separation", 8) scroll.add_child(contents) - var captain := $HUDRoot/CaptainPanel as PanelContainer var equipment := $HUDRoot/ShipEquipmentPanel as PanelContainer - for panel in [captain, equipment]: - panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) - panel.reparent(contents, false) - panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL - panel.add_theme_stylebox_override("panel", StyleBoxEmpty.new()) - if panel == captain: - contents.add_child(HSeparator.new()) + equipment.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) + equipment.reparent(contents, false) + equipment.size_flags_horizontal = Control.SIZE_EXPAND_FILL + equipment.add_theme_stylebox_override("panel", StyleBoxEmpty.new()) + var crafting := PanelContainer.new() + crafting.name = "CraftingPanel" + crafting.set_script(preload("res://scripts/ui/ship_crafting_panel.gd")) + equipment.get_node("Contents").add_child(crafting) + contents.minimum_size_changed.connect(func() -> void: _layout_sidebar.call_deferred()) _sidebar_toggle = Button.new() - _sidebar_toggle.name = "CaptainShipToggle" - _sidebar_toggle.text = "KAPITAN / STATEK ◀" + _sidebar_toggle.name = "ShipToggle" + _sidebar_toggle.text = "STATEK ▾" if _sidebar_open else "STATEK ◀" _sidebar_toggle.add_theme_font_size_override("font_size", 12) _sidebar_toggle.focus_mode = Control.FOCUS_NONE $HUDRoot.add_child(_sidebar_toggle) _sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT) - _sidebar_toggle.offset_left = -210 - _sidebar_toggle.offset_right = -8 - _sidebar_toggle.offset_top = 8 - _sidebar_toggle.offset_bottom = 38 + _sidebar_toggle.offset_left = -180 + _sidebar_toggle.offset_right = -12 + _sidebar_toggle.offset_top = 12 + _sidebar_toggle.offset_bottom = 40 _sidebar_toggle.pressed.connect(_toggle_sidebar) - _sidebar.hide() + _cockpit = Control.new() + _cockpit.name = "Cockpit" + _cockpit.set_script(preload("res://scripts/ui/flight_cockpit.gd")) + _cockpit.set("player", player) + _cockpit.set("game", get_parent()) + $HUDRoot.add_child(_cockpit) + _layout_debug() + +func _layout_debug() -> void: + if _debug_scroll == null: + return + var top := _captain_panel.position.y + _captain_panel.size.y + 6.0 + debug_toggle.position = Vector2(12, top) + _debug_scroll.position = Vector2(18, top + 30) + _debug_scroll.size = Vector2(310, maxf(40, $HUDRoot.size.y - top - 42)) + _debug_scroll.visible = _debug_expanded func _toggle_sidebar() -> void: _sidebar_open = not _sidebar_open + GameState.hud_panel_state["ship_open"] = _sidebar_open if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() - _sidebar_toggle.text = "ZAMKNIJ ▶" if _sidebar_open else "KAPITAN / STATEK ◀" + _sidebar_toggle.text = "STATEK ▾" if _sidebar_open else "STATEK ◀" if _sidebar_open: _sidebar.show() else: close_inventory() _sidebar_tween = create_tween() - _sidebar_tween.tween_property(_sidebar, "position:x", $HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 0.25).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT if _sidebar_open else Tween.EASE_IN) + _sidebar_tween.tween_property(_sidebar, "position:x", $HUDRoot.size.x - _sidebar.size.x - 8 if _sidebar_open else $HUDRoot.size.x, 0.25).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT if _sidebar_open else Tween.EASE_IN) if not _sidebar_open: _sidebar_tween.tween_callback(_sidebar.hide) func _layout_sidebar() -> void: if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() - _sidebar.size = Vector2(SIDEBAR_WIDTH, maxf(100.0, $HUDRoot.size.y - 52.0)) - _sidebar.position = Vector2($HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 44) + _sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, $HUDRoot.size.y - 64.0))) + _cockpit.scale = Vector2.ONE * 0.85 + _cockpit.size = Vector2(minf(640.0, ($HUDRoot.size.x - 24) / 0.85), 126) + _cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12) + _layout_debug() + _sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x - 8 if _sidebar_open else $HUDRoot.size.x, 44) _sidebar.visible = _sidebar_open _inventory_clip.size = inventory_panel.size _layout_inventory() @@ -116,7 +160,7 @@ func _layout_inventory() -> void: if _inventory_clip == null: return var equipment := _sidebar.get_node("Scroll/Contents/ShipEquipmentPanel") as Control - _inventory_clip.position = Vector2(_sidebar.position.x - _inventory_clip.size.x - 8, minf(equipment.global_position.y, maxf(8.0, $HUDRoot.size.y - inventory_panel.size.y - 8))) + _inventory_clip.position = Vector2(maxf(12.0, _sidebar.position.x - _inventory_clip.size.x - 8), minf(equipment.global_position.y, maxf(8.0, $HUDRoot.size.y - inventory_panel.size.y - 8))) func _process(_delta: float) -> void: _layout_inventory() @@ -132,7 +176,7 @@ AKTUALNA REGENERACJA Energia: %.1f pkt/s Tarcze: %.1f pkt/s -%s""" % [ +%s""".replace("\r", "") % [ GameState.system_level, int(game.call("get_remaining_enemies")), player.velocity.length(), @@ -143,7 +187,9 @@ Tarcze: %.1f pkt/s func _toggle_debug() -> void: _debug_expanded = not _debug_expanded + GameState.hud_panel_state["debug_expanded"] = _debug_expanded debug_info.visible = _debug_expanded + _debug_scroll.visible = _debug_expanded _update_debug_toggle() func _update_debug_toggle() -> void: @@ -178,7 +224,7 @@ Energia: %+.1f pkt Prędkość maks.: %+.1f%% Skrócenie cooldownu rakiet: %.2f s Doświadczenie: %+.1f%% -Kredyty: %+.1f%%""" % [ +Kredyty: %+.1f%%""".replace("\r", "") % [ (float(player.call("get_weapon_damage_multiplier")) - 1.0) * 100.0, float(player.call("get_critical_chance")) * 100.0, float(player.get("critical_damage_bonus")) * 100.0, @@ -202,18 +248,18 @@ func _create_ability_bar() -> void: var panel := PanelContainer.new() panel.name = "ActiveAbilities" $HUDRoot.add_child(panel) - panel.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT) - panel.offset_left = 12.0 - panel.offset_right = 12.0 - panel.offset_top = -54.0 - panel.offset_bottom = -12.0 + panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER_BOTTOM) + panel.offset_left = -300.0 + panel.offset_right = 300.0 + panel.offset_top = -188.0 + panel.offset_bottom = -146.0 _ability_bar = HBoxContainer.new() _ability_bar.add_theme_constant_override("separation", 4) panel.add_child(_ability_bar) - for index in 6: + for index in ShipInventory.EXTENSION_SLOT_COUNT: var button := Button.new() button.name = "Ability%d" % (index + 1) - button.custom_minimum_size = Vector2(120, 40) + button.custom_minimum_size = Vector2(110, 36) button.add_theme_font_size_override("font_size", 11) button.pressed.connect(func() -> void: player.call("activate_ability", index)) _ability_bar.add_child(button) diff --git a/scripts/game/game.gd b/scripts/game/game.gd index 8cd334b..b3aa220 100644 --- a/scripts/game/game.gd +++ b/scripts/game/game.gd @@ -10,6 +10,8 @@ const WORLD_SIZE := SYSTEM_GENERATOR.WORLD_SIZE const SERVICE_APPROACH_DISTANCE := 180.0 const WORMHOLE_PLANET_DISTANCE := 620.0 const WORMHOLE_ENTRY_RADIUS := 48.0 +const BOSS_SPAWN_DISTANCE := 900.0 +const BOSS_SPAWN_MARGIN := 160.0 const ENEMY_SCENE := preload("res://scenes/game/enemy_ship.tscn") const ENEMY_TEXTURES: Array[Texture2D] = preload("res://scripts/items/alien_ship_catalog.gd").TEXTURES @@ -21,6 +23,8 @@ var services_menu: Control var system_cleared := false var wormhole: Node2D var _transitioning := false +var _pending_boss: Dictionary = {} +var _boss_spawned := false func _ready() -> void: GameState.system_level = clampi(GameState.system_level, 1, LEVEL_BALANCE.MAX_LEVEL) @@ -85,12 +89,46 @@ func _spawn_celestial_bodies() -> void: func _spawn_enemies() -> void: for data in system_data["enemies"]: - var enemy := ENEMY_SCENE.instantiate() as CharacterBody2D - enemy.position = data["position"] - enemy.call("apply_level_settings", GameState.system_level, int(data["tier"])) - add_child(enemy) - var texture: Texture2D = ENEMY_TEXTURES[int(data["texture_index"])] - enemy.call("configure", player, texture) + if int(data["tier"]) == 2: + _pending_boss = data.duplicate() + else: + _spawn_enemy(data, data["position"]) + +func _spawn_enemy(data: Dictionary, spawn_position: Vector2) -> CharacterBody2D: + var enemy := ENEMY_SCENE.instantiate() as CharacterBody2D + enemy.position = spawn_position + enemy.call("apply_level_settings", GameState.system_level, int(data["tier"])) + add_child(enemy) + var texture: Texture2D = ENEMY_TEXTURES[int(data["texture_index"])] + enemy.call("configure", player, texture) + return enemy + +func _spawn_boss() -> void: + if _boss_spawned or _pending_boss.is_empty(): + return + _boss_spawned = true + _spawn_enemy(_pending_boss, _find_boss_spawn_position()) + _pending_boss.clear() + +func _find_boss_spawn_position() -> Vector2: + var rng := RandomNumberGenerator.new() + rng.seed = GameState.system_seed ^ 0x424f5353 + var angle := rng.randf_range(0.0, TAU) + for step in 64: + var candidate := player.global_position + Vector2.RIGHT.rotated(angle + TAU * step / 64.0) * BOSS_SPAWN_DISTANCE + if absf(candidate.x) > WORLD_SIZE.x * 0.5 - BOSS_SPAWN_MARGIN or absf(candidate.y) > WORLD_SIZE.y * 0.5 - BOSS_SPAWN_MARGIN: + continue + if candidate.length() < 300.0: + continue + var clear := true + for planet_data in system_data["planets"]: + if candidate.distance_to(planet_data["position"]) < float(planet_data["radius"]) + 130.0: + clear = false + break + if clear: + return candidate + # At the map edge, place the encounter toward the center. + return player.global_position + (-player.global_position).normalized() * BOSS_SPAWN_DISTANCE func get_remaining_enemies() -> int: var remaining := 0 @@ -104,8 +142,11 @@ func _physics_process(_delta: float) -> void: services_menu.call("hide_services") return if not system_cleared and get_remaining_enemies() == 0: - system_cleared = true - protected_planet.call("set_safe", true) + if not _boss_spawned and not _pending_boss.is_empty(): + _spawn_boss() + else: + system_cleared = true + protected_planet.call("set_safe", true) if system_cleared and is_near_protected_planet(): if not services_menu.visible: services_menu.call("show_services", GameState.system_level >= LEVEL_BALANCE.MAX_LEVEL, is_instance_valid(wormhole)) diff --git a/scripts/game/game_state.gd b/scripts/game/game_state.gd index 40298ac..b055bc2 100644 --- a/scripts/game/game_state.gd +++ b/scripts/game/game_state.gd @@ -8,6 +8,7 @@ var restart_inventory: Dictionary = {} var system_level: int = 1 var system_seed: int = 0 var explored_pois: Dictionary = {} +var hud_panel_state: Dictionary = {} func create_system_seed() -> int: var rng := RandomNumberGenerator.new() @@ -20,6 +21,7 @@ func start_new_run() -> void: system_seed = create_system_seed() restart_inventory.clear() explored_pois.clear() + hud_panel_state.clear() func _ready() -> void: var game_window := get_tree().root diff --git a/scripts/game/ship_controller.gd b/scripts/game/ship_controller.gd index 76d576a..b326f86 100644 --- a/scripts/game/ship_controller.gd +++ b/scripts/game/ship_controller.gd @@ -21,6 +21,8 @@ const MINI_BOOST_ENERGY_PER_SECOND := 20.0 @export var base_max_hull := 100.0 @export var base_max_shields := 100.0 +@export var base_scanner_range := 1000.0 +var scanner_range := 1000.0 @export var base_max_energy := 100.0 @export var base_max_speed := 90.0 @export var base_energy_regeneration := 5.0 @@ -69,6 +71,7 @@ func _enter_tree() -> void: func apply_equipment_bonuses(bonuses: Dictionary) -> void: max_hull = (base_max_hull + float(bonuses.get("max_hull", 0.0))) * (1.0 + float(bonuses.get("hull_percent", 0.0)) / 100.0) max_shields = (base_max_shields + float(bonuses.get("max_shields", 0.0))) * (1.0 + float(bonuses.get("shield_percent", 0.0)) / 100.0) + scanner_range = base_scanner_range + float(bonuses.get("scanner_range", 0.0)) max_energy = base_max_energy + float(bonuses.get("max_energy", 0.0)) max_speed = (base_max_speed + float(bonuses.get("max_speed", 0.0))) * (1.0 + float(bonuses.get("speed_percent", 0.0)) / 100.0) damage_multiplier = 1.0 + float(bonuses.get("damage_percent", 0.0)) / 100.0 @@ -87,7 +90,7 @@ func apply_equipment_bonuses(bonuses: Dictionary) -> void: _update_shield_visual() func _ready() -> void: - for index in 6: + for index in ShipInventory.EXTENSION_SLOT_COUNT: _ensure_input_action(StringName("ability_%d" % (index + 1)), (KEY_1 + index) as Key) _ensure_input_action(&"ship_thrust", KEY_W) _ensure_input_action(&"ship_mini_boost", KEY_TAB) @@ -97,6 +100,15 @@ func _ready() -> void: _ensure_input_action(&"ship_strafe_left", KEY_Q) _ensure_input_action(&"ship_strafe_right", KEY_E) + var scanner_layer := CanvasLayer.new() + scanner_layer.name = "ScannerOverlay" + scanner_layer.layer = 8 + add_child(scanner_layer) + var scanner := Control.new() + scanner.set_script(preload("res://scripts/ui/scanner_overlay.gd")) + scanner.set("player", self) + scanner_layer.add_child(scanner) + scanner.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _sprite = $Sprite2D var texture_path := SHIP_CATALOG.texture_path(SHIP_CATALOG.for_path(GameState.selected_ship_path)) if texture_path.is_empty() or not ResourceLoader.exists(texture_path): @@ -339,7 +351,7 @@ func start_boost(speed_bonus: float, acceleration_time: float) -> void: _boost_velocity_acceleration = (max_speed + _boost_target) / maxf(0.01, acceleration_time) func get_active_ability(slot_index: int) -> ActiveAbility: - var item: ItemInstance = $ShipInventory.get_item("module", 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 func ability_unavailable_reason(slot_index: int) -> String: @@ -357,7 +369,7 @@ func activate_ability(slot_index: int) -> bool: func _unhandled_key_input(event: InputEvent) -> void: if event is InputEventKey and event.echo: return - for index in 6: + for index in ShipInventory.EXTENSION_SLOT_COUNT: if event.is_action_pressed("ability_%d" % (index + 1)): activate_ability(index) get_viewport().set_input_as_handled() diff --git a/scripts/game/weapon_controller.gd b/scripts/game/weapon_controller.gd index c9f4633..87e8ecb 100644 --- a/scripts/game/weapon_controller.gd +++ b/scripts/game/weapon_controller.gd @@ -8,21 +8,20 @@ const RAY_COLLISION_MASK := 13 # World, enemies, and the weapon test target. @onready var ship: CharacterBody2D = get_parent() @onready var inventory: ShipInventory = ship.get_node("ShipInventory") -var _shot_timers := [0.0, 0.0] -var _laser_pulse_timers := [0.0, 0.0] +var _shot_timers := [0.0, 0.0, 0.0] +var _laser_pulse_timers := [0.0, 0.0, 0.0] var _laser_lines: Array[Line2D] = [] -var _fire_buttons_down := [false, false] -var _fire_just_pressed := [false, false] +var _fire_buttons_down := [false, false, false] +var _fire_just_pressed := [false, false, false] var _drag_fire_blocked := false var missile_cooldown_remaining := 0.0 var missile_cooldown_duration := 5.0 -var _missile_just_pressed := false func _ready() -> void: _ensure_input_action(&"ship_fire_primary", MOUSE_BUTTON_LEFT) _ensure_input_action(&"ship_fire_secondary", MOUSE_BUTTON_RIGHT) _ensure_input_action(&"ship_fire_missile", MOUSE_BUTTON_MIDDLE) - for slot_index in 2: + for slot_index in ShipInventory.WEAPON_SLOT_COUNT: var beam := Line2D.new() beam.width = 4.0 beam.default_color = Color(0.35, 0.9, 1.0, 0.92) @@ -46,8 +45,7 @@ func _unhandled_input(event: InputEvent) -> void: return var slot_index := -1 if event.button_index == MOUSE_BUTTON_MIDDLE: - _missile_just_pressed = event.pressed - return + slot_index = 2 if event.button_index == MOUSE_BUTTON_LEFT: slot_index = 0 elif event.button_index == MOUSE_BUTTON_RIGHT: @@ -63,30 +61,25 @@ func _physics_process(delta: float) -> void: if ship.get_viewport().gui_is_dragging(): _drag_fire_blocked = true if _drag_fire_blocked: - _missile_just_pressed = false - for index in 2: + for index in ShipInventory.WEAPON_SLOT_COUNT: _fire_buttons_down[index] = false _fire_just_pressed[index] = false _laser_pulse_timers[index] = 0.0 _laser_lines[index].visible = false _shot_timers[index] = maxf(0.0, _shot_timers[index] - delta) # Require release after dropping, so the drag gesture cannot become a shot. - if not ship.get_viewport().gui_is_dragging() and not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): + if not ship.get_viewport().gui_is_dragging() and not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) and not Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE): _drag_fire_blocked = false return if bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")): - _missile_just_pressed = false for line in _laser_lines: line.visible = false return var mouse_over_ui := ship.get_viewport().gui_get_hovered_control() != null - if not mouse_over_ui and (Input.is_action_just_pressed("ship_fire_missile") or _missile_just_pressed): - try_fire_missile() - _missile_just_pressed = false - for slot_index in 2: + for slot_index in ShipInventory.WEAPON_SLOT_COUNT: _shot_timers[slot_index] = maxf(0.0, _shot_timers[slot_index] - delta) _laser_pulse_timers[slot_index] = maxf(0.0, _laser_pulse_timers[slot_index] - delta) - var action_name := "ship_fire_primary" if slot_index == 0 else "ship_fire_secondary" + var action_name: String = ["ship_fire_primary", "ship_fire_secondary", "ship_fire_missile"][slot_index] var is_pressed: bool = not mouse_over_ui and ( Input.is_action_pressed(action_name) or _fire_buttons_down[slot_index] ) @@ -112,18 +105,19 @@ func _physics_process(delta: float) -> void: _laser_lines[slot_index].visible = _laser_pulse_timers[slot_index] > 0.0 _fire_laser(slot_index, item) ItemDefinition.WeaponKind.MISSILE: - pass # Rockets fire exclusively from slot 3. + if is_just_pressed: + try_fire_missile(slot_index) _: if _shot_timers[slot_index] <= 0.0 and _consume_shot_resource(item): _fire_projectile(item) _shot_timers[slot_index] = definition.fire_interval - for slot_index in 2: + for slot_index in ShipInventory.WEAPON_SLOT_COUNT: _fire_just_pressed[slot_index] = false -func try_fire_missile() -> bool: +func try_fire_missile(slot_index: int = 2) -> bool: if missile_cooldown_remaining > 0.0 or bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")): return false - var item := inventory.get_item("weapon", ShipInventory.MISSILE_SLOT) + var item := inventory.get_item("weapon", slot_index) if item == null or item.definition.weapon_kind != ItemDefinition.WeaponKind.MISSILE: return false if inventory.missile_ammunition <= 0: diff --git a/scripts/items/item_affix_definition.gd b/scripts/items/item_affix_definition.gd index 95531c4..5d4bdaa 100644 --- a/scripts/items/item_affix_definition.gd +++ b/scripts/items/item_affix_definition.gd @@ -10,7 +10,7 @@ class_name ItemAffixDefinition @export_range(0.0, 1000.0, 0.1) var selection_weight: float = 1.0 @export_group("Dozwolone przedmioty") -@export_flags("Weapon", "Shield", "Hull", "Generator", "Extension", "Active extension") var category_mask: int = 63 +@export_flags("Weapon", "Shield", "Hull", "Reactor", "Engine", "Active extension", "Computer") var category_mask: int = 95 # Empty means every item in the allowed categories. @export var allowed_item_ids: PackedStringArray = PackedStringArray() @export var excluded_item_ids: PackedStringArray = PackedStringArray() @@ -32,6 +32,7 @@ func can_roll(item: ItemDefinition, level: int) -> bool: return ( enabled and selection_weight > 0.0 and not affix_id.is_empty() and not stat_key.is_empty() and item != null + and item.category != ItemDefinition.Category.ACTIVE_EXTENSION and level >= minimum_level and level <= maximum_level and (category_mask & (1 << item.category)) != 0 and (allowed_item_ids.is_empty() or item.item_id in allowed_item_ids) diff --git a/scripts/items/item_definition.gd b/scripts/items/item_definition.gd index e20f1e6..63028d0 100644 --- a/scripts/items/item_definition.gd +++ b/scripts/items/item_definition.gd @@ -1,12 +1,13 @@ extends Resource class_name ItemDefinition -enum Category { WEAPON, SHIELD, HULL, GENERATOR, EXTENSION, ACTIVE_EXTENSION } +enum Category { WEAPON, SHIELD, ARMOR, REACTOR, ENGINE, ACTIVE_EXTENSION, COMPUTER } enum WeaponKind { PLASMA, MACHINE_GUN, LASER, MISSILE } @export var item_id: String = "" @export var display_name: String = "" -@export var category: Category = Category.EXTENSION +@export var icon: Texture2D +@export var category: Category = Category.ENGINE @export_multiline var description: String = "" @export var primary_stat: String = "" @export var primary_value: float = 0.0 diff --git a/scripts/items/item_generator.gd b/scripts/items/item_generator.gd index b985275..f31df05 100644 --- a/scripts/items/item_generator.gd +++ b/scripts/items/item_generator.gd @@ -14,7 +14,7 @@ const DEFINITIONS := [ preload("res://resources/items/afterburner.tres"), preload("res://resources/items/shield_charge.tres"), preload("res://resources/items/repair_drones.tres"), - preload("res://resources/items/scanner.tres"), + preload("res://resources/items/ship_computer.tres"), ] const AFFIX_DATABASE := preload("res://resources/items/affix_database.tres") @@ -36,6 +36,8 @@ static func generate_for_definition(definition: ItemDefinition, level: int, enem item.damage_max = maxf(item.damage_min + 1.0, roundf((lower + spread) * scale_factor)) item.primary_value_override = (item.damage_min + item.damage_max) * 0.5 item.energy_regeneration_override = item.definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2) + if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + return item var roll := randf() var count := 1 if roll < 0.5 else (2 if roll < 0.75 else (3 if roll < 0.89 else (4 if roll < 0.97 else 5))) if enemy_tier == 1: diff --git a/scripts/items/item_instance.gd b/scripts/items/item_instance.gd index 7e7353d..fbf7840 100644 --- a/scripts/items/item_instance.gd +++ b/scripts/items/item_instance.gd @@ -116,7 +116,7 @@ func get_tooltip_text() -> String: if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN: lines.append("Zasięg: %.0f px • Amunicja: 1 / strzał" % definition.weapon_range) elif definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE: - lines.append("ŚPM • Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii") + lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii") else: lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()]) lines.append("Odstęp strzałów: %.2f s" % maxf(definition.fire_interval, definition.weapon_cooldown)) @@ -129,4 +129,4 @@ func get_tooltip_text() -> String: return "\n".join(lines) static func stat_name(key: String) -> String: - return str({"shield_on_damage_percent": "Odzysk tarcz z obrażeń", "damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key)) + return str({"missile_cooldown_reduction": "Skrócenie cooldownu rakiet", "scanner_range": "Zasięg skanera", "shield_on_damage_percent": "Odzysk tarcz z obrażeń", "damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key)) diff --git a/scripts/items/ship_inventory.gd b/scripts/items/ship_inventory.gd index 490dc1d..0a788a2 100644 --- a/scripts/items/ship_inventory.gd +++ b/scripts/items/ship_inventory.gd @@ -6,12 +6,14 @@ signal ammunition_changed const CARGO_SIZE := 20 const WEAPON_SLOT_COUNT := 3 -const MISSILE_SLOT := 2 const STARTING_MISSILE_AMMUNITION := 2 const MISSILE_CRAFT_PARTS_COST := 50.0 const MISSILE_BUY_BASE_COST := 10 const MISSILE_DEFINITIONS := [preload("res://resources/items/missile_launcher.tres"), preload("res://resources/items/unguided_missile.tres")] -const MODULE_SLOT_COUNT := 6 +const MODULE_SLOT_COUNT := 5 +const EXTENSION_SLOT_COUNT := 5 +const MODULE_CATEGORIES := [ItemDefinition.Category.SHIELD, ItemDefinition.Category.ARMOR, ItemDefinition.Category.ENGINE, ItemDefinition.Category.REACTOR, ItemDefinition.Category.COMPUTER] +const MODULE_NAMES := ["Tarcze", "Pancerz", "Silnik", "Reaktor", "Komputer"] const STARTING_AMMUNITION := 50 const AMMUNITION_CRAFT_AMOUNT := 100 const AMMUNITION_CRAFT_PARTS_COST := 50.0 @@ -24,6 +26,7 @@ var missile_ammunition: int = STARTING_MISSILE_AMMUNITION var cargo: Array[ItemInstance] = [] var weapon_slots: Array[ItemInstance] = [] var module_slots: Array[ItemInstance] = [] +var extension_slots: Array[ItemInstance] = [] @onready var ship: CharacterBody2D = get_parent() @@ -32,16 +35,18 @@ func _ready() -> void: cargo.resize(CARGO_SIZE) weapon_slots.resize(WEAPON_SLOT_COUNT) module_slots.resize(MODULE_SLOT_COUNT) + extension_slots.resize(EXTENSION_SLOT_COUNT) if GameState.restart_inventory.is_empty(): _add_starter_items() else: cargo.assign(GameState.restart_inventory["cargo"]) weapon_slots.assign(GameState.restart_inventory["weapon"]) weapon_slots.resize(WEAPON_SLOT_COUNT) - module_slots.assign(GameState.restart_inventory["module"]) + _restore_modules(GameState.restart_inventory) ammunition = int(GameState.restart_inventory.get("ammunition", STARTING_AMMUNITION)) missile_ammunition = int(GameState.restart_inventory.get("missile_ammunition", STARTING_MISSILE_AMMUNITION)) GameState.restart_inventory.clear() + _remove_extension_addons() _recalculate_ship_stats() inventory_changed.emit() @@ -58,6 +63,7 @@ func preserve_for_restart() -> void: "cargo": cargo.duplicate(), "weapon": weapon_slots.duplicate(), "module": module_slots.duplicate(), + "extension": extension_slots.duplicate(), "ammunition": ammunition, "missile_ammunition": missile_ammunition, } @@ -105,6 +111,9 @@ func buy_ammunition() -> bool: func add_item(item: ItemInstance) -> bool: if item == null or item.definition == null: return false + if item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + item.additional_modifiers.clear() + item.rarity = 0 for index in cargo.size(): if cargo[index] == null: cargo[index] = item @@ -141,8 +150,12 @@ func can_move_item(from_kind: String, from_index: int, to_kind: String, to_index func _accepts_item(kind: String, index: int, item: ItemInstance) -> bool: if kind == "weapon": - return item.definition.category == ItemDefinition.Category.WEAPON and (item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE) == (index == MISSILE_SLOT) - return kind != "module" or item.definition.category != ItemDefinition.Category.WEAPON + return item.definition.category == ItemDefinition.Category.WEAPON + if kind == "module": + return item.definition.category == MODULE_CATEGORIES[index] + if kind == "extension": + return item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION and item.definition.active_ability != null + return kind == "cargo" func get_missile_count() -> int: return missile_ammunition @@ -196,6 +209,7 @@ func move_item(from_kind: String, from_index: int, to_kind: String, to_index: in var displaced := destination[to_index] destination[to_index] = source[from_index] source[from_index] = displaced + _remove_extension_addons() _recalculate_ship_stats() inventory_changed.emit() return true @@ -208,6 +222,8 @@ func _get_slots(slot_kind: String) -> Array[ItemInstance]: return weapon_slots "module": return module_slots + "extension": + return extension_slots return [] func _recalculate_ship_stats() -> void: @@ -215,7 +231,7 @@ func _recalculate_ship_stats() -> void: for item in weapon_slots + module_slots: if item == null or item.definition == null: continue - for stat_key in ["max_hull", "max_shields", "max_energy", "max_speed", "energy_regeneration", "shield_regeneration", "hull_percent", "shield_percent", "damage_percent", "speed_percent", "experience_percent", "credits_percent", "critical_chance_percent", "critical_damage_percent"]: + for stat_key in ["shield_on_damage_percent", "scanner_range", "max_hull", "max_shields", "max_energy", "max_speed", "energy_regeneration", "shield_regeneration", "hull_percent", "shield_percent", "damage_percent", "speed_percent", "experience_percent", "credits_percent", "critical_chance_percent", "critical_damage_percent"]: bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key) ship.call("apply_equipment_bonuses", bonuses) @@ -228,3 +244,80 @@ func _add_starter_items() -> void: item.definition = definition item.item_level = 1 weapon_slots[0] = item + +func _remove_extension_addons() -> void: + for item in cargo + extension_slots: + if item != null and item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + item.additional_modifiers.clear() + item.rarity = 0 + +func _restore_modules(saved: Dictionary) -> void: + if saved.has("extension"): + extension_slots.assign(saved["extension"]) + extension_slots.resize(EXTENSION_SLOT_COUNT) + for item in saved.get("module", []): + if item == null or item.definition == null: + continue + var index := MODULE_CATEGORIES.find(item.definition.category) + if index >= 0 and module_slots[index] == null: + module_slots[index] = item + elif item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION and extension_slots.has(null): + extension_slots[extension_slots.find(null)] = item + else: + add_item(item) + +func get_item_comparison(item: ItemInstance, equipped: ItemInstance) -> Array[Dictionary]: + var before := _comparison_stats(_comparison_bonuses()) + var after := _comparison_stats(_comparison_bonuses(item, equipped)) + var rows: Array[Dictionary] = [] + for key in before: + var difference: float = float(after[key]["value"]) - float(before[key]["value"]) + if is_zero_approx(difference): + continue + rows.append({"key": key, "name": before[key]["name"], "delta": difference, "unit": before[key]["unit"]}) + return rows + +func _comparison_bonuses(incoming: ItemInstance = null, outgoing: ItemInstance = null) -> Dictionary: + var bonuses := {} + for item in weapon_slots + module_slots: + if item == null or item == outgoing: + continue + _add_comparison_bonuses(bonuses, item) + if incoming != null: + _add_comparison_bonuses(bonuses, incoming) + return bonuses + +func _add_comparison_bonuses(bonuses: Dictionary, item: ItemInstance) -> void: + for key in ["max_hull", "max_shields", "max_energy", "max_speed", "scanner_range", "hull_percent", "shield_percent", "speed_percent", "damage_percent", "critical_damage_percent", "critical_chance_percent", "shield_on_damage_percent", "energy_regeneration", "shield_regeneration", "missile_cooldown_reduction", "experience_percent", "credits_percent"]: + bonuses[key] = float(bonuses.get(key, 0.0)) + item.get_stat_total(key) + +func _comparison_stats(bonuses: Dictionary) -> Dictionary: + var stats := {} + for data in [["max_hull", "base_max_hull", "hull_percent", "Pancerz", "pkt"], ["max_shields", "base_max_shields", "shield_percent", "Tarcze", "pkt"], ["max_speed", "base_max_speed", "speed_percent", "Prędkość", "px/s"]]: + var value := (float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0))) * (1.0 + float(bonuses.get(data[2], 0.0)) / 100.0) + stats[data[0]] = {"value": value, "name": data[3], "unit": data[4]} + for data in [["max_energy", "base_max_energy", "Energia", "pkt"], ["scanner_range", "base_scanner_range", "Zasięg skanera", "px"], ["energy_regeneration", "base_energy_regeneration", "Regeneracja energii", "pkt/s"], ["shield_regeneration", "base_shield_regeneration", "Regeneracja tarcz", "pkt/s"]]: + stats[data[0]] = {"value": float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0)), "name": data[2], "unit": data[3]} + var captain_damage := 1.0 + GameState.captain.get_skill_bonus("gunner") + var damage_factor := (1.0 + float(bonuses.get("damage_percent", 0.0)) / 100.0) * captain_damage + var critical_factor := preload("res://scripts/captain/captain_balance.gd").BASE_CRITICAL_MULTIPLIER * (1.0 + maxf(0.0, float(bonuses.get("critical_damage_percent", 0.0)) / 100.0)) + for index in weapon_slots.size(): + var weapon := weapon_slots[index] + if weapon == null: + continue + var damage := weapon.get_stat_total("damage") * damage_factor + var binding: String = ["LPM", "PPM", "ŚPM"][index] + stats["weapon_damage_%d" % index] = {"value": damage, "name": "Śr. obrażenia [%s]" % binding, "unit": "DMG"} + stats["weapon_critical_%d" % index] = {"value": damage * critical_factor, "name": "Śr. krytyk [%s]" % binding, "unit": "DMG"} + if weapon.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE: + stats["missile_cooldown_%d" % index] = {"value": -maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0))), "name": "Skrócenie cooldownu [%s]" % binding, "unit": "s"} + var chance := clampf(GameState.captain.get_skill_bonus("sharp_eye") + float(bonuses.get("critical_chance_percent", 0.0)) / 100.0, 0.0, 1.0) + stats["critical_chance"] = {"value": chance * 100.0, "name": "Krytyki na 100 strzałów", "unit": "trafień"} + stats["shield_recovery"] = {"value": maxf(0.0, float(bonuses.get("shield_on_damage_percent", 0.0))), "name": "Odzysk tarcz na 100 DMG", "unit": "pkt"} + var balance := preload("res://scripts/captain/captain_balance.gd") + var scale_factor := 1.0 + 0.05 * (clampi(GameState.system_level, 1, 1000) - 1) + for tier in 3: + var caption: String = ["wróg", "elita", "boss"][tier] + stats["xp_%d" % tier] = {"value": roundi(balance.KILL_XP[tier] * scale_factor * maxf(0.0, 1.0 + float(bonuses.get("experience_percent", 0.0)) / 100.0)), "name": "EXP / %s" % caption, "unit": "EXP"} + stats["credits_%d" % tier] = {"value": roundi(balance.KILL_CREDITS[tier] * scale_factor * maxf(0.0, 1.0 + float(bonuses.get("credits_percent", 0.0)) / 100.0)), "name": "Kredyty / %s" % caption, "unit": "kr"} + return stats diff --git a/scripts/levels/exploration_poi.gd b/scripts/levels/exploration_poi.gd index 18e3b35..5dbfb26 100644 --- a/scripts/levels/exploration_poi.gd +++ b/scripts/levels/exploration_poi.gd @@ -1,6 +1,7 @@ extends StaticBody2D const GENERATOR := preload("res://scripts/items/item_generator.gd") +const ASTEROID_TEXTURES := [preload("res://assets/asteroids/asteroid_rock.png"), preload("res://assets/asteroids/asteroid_metal.png"), preload("res://assets/asteroids/asteroid_ice.png")] const LOOT := preload("res://scripts/items/loot_pickup.gd") var kind := "wreck" var poi_id := "" @@ -14,6 +15,8 @@ var _label: Label var _rng := RandomNumberGenerator.new() var _mineral := "iron" var _points := PackedVector2Array() +var _asteroid_sprite: Sprite2D +var _asteroid_spin := 0.0 func _ready() -> void: add_to_group("exploration_pois") @@ -30,6 +33,14 @@ func _ready() -> void: add_child(shape) for index in 10: _points.append(Vector2.RIGHT.rotated(TAU * index / 10) * _rng.randf_range(23, 35)) + _asteroid_sprite = Sprite2D.new() + _asteroid_sprite.name = "AsteroidSprite" + _asteroid_sprite.texture = ASTEROID_TEXTURES[posmod(reward_seed, ASTEROID_TEXTURES.size())] + var texture_size := _asteroid_sprite.texture.get_size() + _asteroid_sprite.scale = Vector2.ONE * (78.0 / maxf(texture_size.x, texture_size.y)) + _asteroid_sprite.rotation = deg_to_rad(posmod(reward_seed, 360)) + _asteroid_spin = (0.025 + posmod(reward_seed, 12) * 0.005) * (1.0 if reward_seed % 2 == 0 else -1.0) + add_child(_asteroid_sprite) _label = Label.new() _label.light_mask = 4 _label.position = Vector2(-160, 40) @@ -48,17 +59,15 @@ func _ready() -> void: _update_label(false) queue_redraw() +func _process(delta: float) -> void: + if is_instance_valid(_asteroid_sprite) and not _claimed: + _asteroid_sprite.rotation += _asteroid_spin * delta + func reward_scale() -> float: return 1.0 + 0.05 * (clampi(GameState.system_level, 1, 1000) - 1) func _draw() -> void: - if kind == "asteroid": - draw_colored_polygon(_points, Color("655e56")) - var outline := _points.duplicate() - outline.append(_points[0]) - draw_polyline(outline, Color("a39a88"), 2, true) - draw_circle(Vector2(-8, -5), 7, Color("49443f")) - elif kind == "wreck": + if kind == "wreck": draw_colored_polygon(PackedVector2Array([Vector2(-38, 20), Vector2(-16, -30), Vector2(7, -12), Vector2(22, 27), Vector2(-5, 14)]), Color("626b78")) draw_line(Vector2(-12, -20), Vector2(4, 15), Color("df9c67"), 3, true) draw_colored_polygon(PackedVector2Array([Vector2(28, -16), Vector2(42, -5), Vector2(29, 8)]), Color("434e60")) diff --git a/scripts/ui/captain_panel.gd b/scripts/ui/captain_panel.gd index 79c9118..38589ae 100644 --- a/scripts/ui/captain_panel.gd +++ b/scripts/ui/captain_panel.gd @@ -8,14 +8,16 @@ const BALANCE := preload("res://scripts/captain/captain_balance.gd") @onready var xp_bar: ProgressBar = $Contents/XPBar @onready var credits_label: Label = $Contents/Wallet/Credits @onready var points_label: Label = $Contents/Wallet/Points -@onready var critical_label: Label = $Contents/Critical +var _collapsed := false +var _collapse_button: Button var _captain: CaptainProgress var _rank_labels: Dictionary = {} var _effect_labels: Dictionary = {} var _buttons: Dictionary = {} func _ready() -> void: + _build_portrait() _captain = GameState.captain _captain.normalize_skill_ranks() for skill in BALANCE.SKILL_NAMES: @@ -53,6 +55,8 @@ func _ready() -> void: _captain.changed.connect(_refresh) player.get_node("ShipInventory").connect("inventory_changed", _refresh) _refresh() + if bool(GameState.hud_panel_state.get("captain_collapsed", false)): + _toggle_collapsed() func _train(skill: String) -> void: _captain.train_skill(skill) @@ -73,4 +77,63 @@ func _refresh() -> void: var next_bonus := BALANCE.skill_bonus(skill, rank + 1) _buttons[skill].tooltip_text = BALANCE.SKILL_HELP[skill] + ("\nOsiągnięto limit 20 punktów." if rank >= BALANCE.MAX_SKILL_RANK else "\nNastępny punkt: %.1f%% → %.1f%%" % [bonus * 100.0, next_bonus * 100.0]) _buttons[skill].disabled = not _captain.can_train(skill) - critical_label.text = "Krytyk: %.1f%% · ×%.2f\nCzęści: %.1f" % [float(player.call("get_critical_chance")) * 100.0, float(player.call("get_critical_multiplier")), _captain.parts] + +func _build_portrait() -> void: + custom_minimum_size = Vector2(320, 0) + var style := StyleBoxFlat.new() + style.bg_color = Color(0.025, 0.042, 0.064, 0.96) + style.border_color = Color("376779") + style.set_border_width_all(1) + style.set_corner_radius_all(8) + style.set_content_margin_all(10) + add_theme_stylebox_override("panel", style) + _collapse_button = Button.new() + _collapse_button.name = "CollapseCaptain" + _collapse_button.text = "−" + _collapse_button.flat = true + _collapse_button.focus_mode = Control.FOCUS_NONE + _collapse_button.custom_minimum_size = Vector2(24, 24) + _collapse_button.pressed.connect(_toggle_collapsed) + $Contents/Header.add_child(_collapse_button) + var row := HBoxContainer.new() + row.name = "PortraitRow" + row.add_theme_constant_override("separation", 10) + $Contents.add_child(row) + $Contents.move_child(row, 1) + var frame := PanelContainer.new() + frame.name = "AvatarFrame" + var portrait_style := style.duplicate() as StyleBoxFlat + portrait_style.set_content_margin_all(2) + portrait_style.border_color = Color("daa85d") + frame.add_theme_stylebox_override("panel", portrait_style) + row.add_child(frame) + var portrait := TextureRect.new() + portrait.name = "CaptainAvatar" + portrait.custom_minimum_size = Vector2(76, 76) + portrait.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + portrait.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + portrait.mouse_filter = Control.MOUSE_FILTER_IGNORE + if ResourceLoader.exists("res://assets/avatars/captain.png"): + portrait.texture = load("res://assets/avatars/captain.png") + frame.add_child(portrait) + var info := VBoxContainer.new() + info.size_flags_horizontal = Control.SIZE_EXPAND_FILL + info.alignment = BoxContainer.ALIGNMENT_CENTER + row.add_child(info) + var designation := Label.new() + designation.text = "DOWÓDCA STATKU" + designation.add_theme_font_size_override("font_size", 11) + designation.add_theme_color_override("font_color", Color("91dbea")) + info.add_child(designation) + xp_label.reparent(info, false) + xp_bar.reparent(info, false) + xp_bar.custom_minimum_size.y = 5 + +func _toggle_collapsed() -> void: + _collapsed = not _collapsed + GameState.hud_panel_state["captain_collapsed"] = _collapsed + for child in $Contents.get_children(): + if child.name not in ["Header", "PortraitRow"]: + child.visible = not _collapsed + _collapse_button.text = "+" if _collapsed else "−" + size.y = get_combined_minimum_size().y diff --git a/scripts/ui/equipment_slot_view.gd b/scripts/ui/equipment_slot_view.gd index 6a8a52e..5a828a0 100644 --- a/scripts/ui/equipment_slot_view.gd +++ b/scripts/ui/equipment_slot_view.gd @@ -7,6 +7,7 @@ extends PanelContainer @onready var slot_label: Label = $SlotLabel var _inventory: ShipInventory +var _icon: TextureRect var _delete_button: Button func _ready() -> void: @@ -16,6 +17,7 @@ func _ready() -> void: add_child(overlay) _delete_button = Button.new() _delete_button.text = "x" + _delete_button.z_index = 5 _delete_button.tooltip_text = "Rozłóż przedmiot: +10 części" _delete_button.flat = true _delete_button.add_theme_font_size_override("font_size", 13) @@ -33,27 +35,19 @@ func _ready() -> void: if _inventory: _inventory.inventory_changed.connect(_refresh) _inventory.ammunition_changed.connect(_refresh) - match slot_kind: - "weapon": - custom_minimum_size = Vector2(90, 38) - "module": - custom_minimum_size = Vector2(104, 36) - _: - custom_minimum_size = Vector2(148, 56) - slot_label.add_theme_font_size_override("font_size", 10) - slot_label.custom_minimum_size.y = 36 - slot_label.size_flags_vertical = Control.SIZE_EXPAND_FILL - slot_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - slot_label.text_overrun_behavior = TextServer.OVERRUN_NO_TRIMMING + custom_minimum_size = Vector2(72, 72) if slot_kind == "cargo" else Vector2(64, 64) + slot_label.custom_minimum_size = Vector2.ZERO + slot_label.add_theme_font_size_override("font_size", 9) + _icon = TextureRect.new() + _icon.name = "ItemIcon" + _icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + _icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + _icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(_icon) if overlay_slot: - custom_minimum_size.y = 44 - slot_label.custom_minimum_size.y = 32 - slot_label.size_flags_vertical = Control.SIZE_EXPAND_FILL - slot_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - slot_label.text_overrun_behavior = TextServer.OVERRUN_NO_TRIMMING var binding := Label.new() binding.mouse_filter = Control.MOUSE_FILTER_IGNORE - binding.text = (["LPM", "PPM", "ŚPM"][slot_index]) if slot_kind == "weapon" else str(slot_index + 1) + binding.text = (["LPM", "PPM", "ŚPM"][slot_index]) if slot_kind == "weapon" else (str(slot_index + 1) if slot_kind == "extension" else ShipInventory.MODULE_NAMES[slot_index]) binding.position = Vector2(2, -14) binding.add_theme_font_size_override("font_size", 9) binding.add_theme_color_override("font_color", Color("a0b2c4")) @@ -65,11 +59,11 @@ func _ready() -> void: _refresh() func _process(_delta: float) -> void: - if slot_kind == "weapon" and slot_index == ShipInventory.MISSILE_SLOT: + if slot_kind == "weapon" and _has_missile(): queue_redraw() func _draw() -> void: - if slot_kind != "weapon" or slot_index != ShipInventory.MISSILE_SLOT or _inventory == null: + if slot_kind != "weapon" or not _has_missile() or _inventory == null: return var weapons := _inventory.ship.get_node("WeaponController") var remaining := float(weapons.get("missile_cooldown_remaining")) @@ -83,14 +77,17 @@ func _refresh() -> void: return var item: ItemInstance = _inventory.get_item(slot_kind, slot_index) _apply_slot_style(item) - tooltip_text = item.get_tooltip_text() if item != null else ("WYRZUTNIA [ŚPM] — przeciągnij wyrzutnię z magazynu." if slot_kind == "weapon" and slot_index == ShipInventory.MISSILE_SLOT else "") - if item != null and slot_kind == "weapon" and slot_index == ShipInventory.MISSILE_SLOT: + _icon.texture = item.definition.icon if item != null and item.definition != null else _empty_icon() + _icon.modulate = Color(1, 1, 1, 1.0 if item != null else 0.16) + slot_label.visible = _icon.texture == null + tooltip_text = item.get_tooltip_text() if item != null else _empty_slot_text() + if item != null and slot_kind == "weapon" and _has_missile(): tooltip_text += "\nCooldown po addonach: %.2f s\nZapas rakiet: %d" % [_inventory.get_missile_cooldown(item), _inventory.get_missile_count()] if _delete_button: _delete_button.visible = item != null if item == null or item.definition == null: slot_label.add_theme_color_override("font_color", Color("82909e")) - slot_label.text = "RAKIETY" if slot_kind == "weapon" and slot_index == ShipInventory.MISSILE_SLOT else "" + slot_label.text = "—" return var definition := item.definition slot_label.add_theme_color_override("font_color", Color("f1f4f8")) @@ -102,8 +99,9 @@ func _apply_slot_style(item: ItemInstance) -> void: style.border_color = Color("354455") style.set_border_width_all(1) style.set_content_margin_all(5) + style.set_corner_radius_all(4) if slot_kind == "cargo": - style.content_margin_right = 18 + style.content_margin_right = 5 if item != null and item.definition != null: style.border_color = item.get_type_color() style.bg_color = item.get_background_color() @@ -148,19 +146,61 @@ func _make_custom_tooltip(for_text: String) -> Object: label.add_text(("★ " if quality == 2 else "") + line + "\n") if quality > 0: label.pop() - panel.add_child(label) + var columns := HBoxContainer.new() + columns.add_theme_constant_override("separation", 16) + panel.add_child(columns) + columns.add_child(label) + if slot_kind == "cargo" and item != null: + _add_comparison_column(columns, item) return panel +func _add_comparison_column(columns: HBoxContainer, item: ItemInstance) -> void: + var module_index := ShipInventory.MODULE_CATEGORIES.find(item.definition.category) + if module_index < 0: + return + columns.add_child(VSeparator.new()) + var comparison := RichTextLabel.new() + comparison.name = "EquipmentComparison" + comparison.fit_content = true + comparison.scroll_active = false + comparison.mouse_filter = Control.MOUSE_FILTER_IGNORE + comparison.custom_minimum_size.x = 290 + comparison.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + comparison.add_theme_font_size_override("normal_font_size", 13) + comparison.add_theme_color_override("default_color", Color("f1f4f8")) + columns.add_child(comparison) + var equipped := _inventory.get_item("module", module_index) + comparison.add_text("ZMIANY PO ZAŁOŻENIU\n") + comparison.add_text("Slot: %s\n" % ShipInventory.MODULE_NAMES[module_index]) + comparison.add_text("Obecnie: %s\n\n" % ("%s • Poz. %d" % [equipped.definition.display_name, equipped.item_level] if equipped != null else "pusty")) + comparison.add_text("Zmiana statystyk statku:\n") + var rows := _inventory.get_item_comparison(item, equipped) + if rows.is_empty(): + comparison.add_text("Bez zmian w bonusach.\n") + for row in rows: + comparison.push_color(Color("79e6a0") if row["delta"] > 0.0 else Color("ff8989")) + var name: String = row["name"] + comparison.add_text("%s: %+.2f %s\n" % [name, row["delta"], row["unit"]]) + comparison.pop() + comparison.add_text("\nUwzględnia utratę bonusów zdejmowanego przedmiotu.") + func _get_drag_data(_at_position: Vector2) -> Variant: if not _inventory: return null var item: ItemInstance = _inventory.get_item(slot_kind, slot_index) if item == null: return null - var preview := Label.new() - preview.text = item.definition.display_name - preview.add_theme_color_override("font_color", Color(0.85, 0.94, 1.0)) - set_drag_preview(preview) + if item.definition.icon != null: + var preview := TextureRect.new() + preview.texture = item.definition.icon + preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + preview.custom_minimum_size = Vector2(64, 64) + set_drag_preview(preview) + else: + var preview := Label.new() + preview.text = item.definition.display_name + set_drag_preview(preview) return {"from_kind": slot_kind, "from_index": slot_index} func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: @@ -183,12 +223,19 @@ func _drop_data(_at_position: Vector2, data: Variant) -> void: slot_index ) +func _has_missile() -> bool: + if _inventory == null or slot_kind != "weapon": + return false + var item := _inventory.get_item(slot_kind, slot_index) + return item != null and item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE + func _empty_slot_text() -> String: if slot_kind == "weapon": - var weapon_binding := "LPM" if slot_index == 0 else "PPM" - return "BROŃ %d [%s]\nPUSTY" % [slot_index + 1, weapon_binding] + return "BROŃ [%s]" % ["LPM", "PPM", "ŚPM"][slot_index] if slot_kind == "module": - return "MODUŁ %d [%d]\nPUSTY" % [slot_index + 1, slot_index + 1] + return ShipInventory.MODULE_NAMES[slot_index].to_upper() + if slot_kind == "extension": + return "ROZSZ. [%d]" % (slot_index + 1) return "PUSTY" func _category_name(category: int) -> String: @@ -197,12 +244,14 @@ func _category_name(category: int) -> String: return "BROŃ" ItemDefinition.Category.SHIELD: return "TARCZA" - ItemDefinition.Category.HULL: - return "KADŁUB" - ItemDefinition.Category.GENERATOR: - return "GENERATOR" - ItemDefinition.Category.EXTENSION: - return "MODUŁ PASYWNY" + ItemDefinition.Category.ARMOR: + return "PANCERZ" + ItemDefinition.Category.REACTOR: + return "REAKTOR" + ItemDefinition.Category.ENGINE: + return "SILNIK" + ItemDefinition.Category.COMPUTER: + return "KOMPUTER" ItemDefinition.Category.ACTIVE_EXTENSION: return "ROZSZERZENIE" return "PRZEDMIOT" @@ -216,3 +265,9 @@ func _stat_text(item: ItemInstance) -> String: var total_value := item.get_stat_total(definition.primary_stat) var sign_text := "+" if total_value >= 0.0 else "" return "%s%.0f%s · Poz. %d" % [sign_text, total_value, definition.stat_suffix, item.item_level] + +func _empty_icon() -> Texture2D: + if slot_kind != "module": + return null + var names := ["shield", "armor", "engine", "reactor", "computer"] + return load("res://assets/icons/%s.png" % names[slot_index]) as Texture2D diff --git a/scripts/ui/flight_cockpit.gd b/scripts/ui/flight_cockpit.gd new file mode 100644 index 0000000..e021107 --- /dev/null +++ b/scripts/ui/flight_cockpit.gd @@ -0,0 +1,68 @@ +extends Control + +const FONT := preload("res://assets/fonts/font2.ttf") +const CYAN := Color("91dbea") +const WEAPON_DISPLAY_ORDER := [0, 2, 1] +const AMBER := Color("efb568") +var player: CharacterBody2D +var game: Node2D +var _inventory: ShipInventory + +func _ready() -> void: + mouse_filter = Control.MOUSE_FILTER_IGNORE + _inventory = player.get_node("ShipInventory") + +func _process(delta: float) -> void: + if is_instance_valid(player): + var screen := get_viewport().get_canvas_transform() * player.global_position + var local := get_global_transform().affine_inverse() * screen + var overlaps := Rect2(Vector2.ZERO, size).grow(36).has_point(local) + modulate.a = move_toward(modulate.a, 0.18 if overlaps else 1.0, delta * 4.0) + queue_redraw() + +func _text(value: String, point: Vector2, width: float, font_size: int, color: Color) -> void: + draw_string(FONT, point, value, HORIZONTAL_ALIGNMENT_CENTER, width, font_size, color) + +func _draw() -> void: + if _inventory == null or not is_instance_valid(player) or size.x < 50 or size.y < 50: + return + var outline := PackedVector2Array([Vector2(24, 0), Vector2(size.x - 24, 0), Vector2(size.x, 24), Vector2(size.x, size.y - 12), Vector2(size.x - 12, size.y), Vector2(12, size.y), Vector2(0, size.y - 12), Vector2(0, 24)]) + draw_colored_polygon(outline, Color(0.025, 0.041, 0.063, 0.96)) + var closed := outline.duplicate() + closed.append(outline[0]) + draw_polyline(closed, Color("376779"), 1.5, true) + draw_line(Vector2(28, 2), Vector2(size.x - 28, 2), CYAN, 2, true) + var cell := (size.x - 40) / 3.0 + var values := [_inventory.missile_ammunition, _inventory.ammunition, int(game.call("get_remaining_enemies"))] + var captions := ["RAKIETY", "AMUNICJA", "WROGOWIE"] + for index in 3: + var x := 20.0 + index * cell + var color := AMBER if index == 0 else (CYAN if index == 1 else Color("ff8989")) + _text(captions[index], Vector2(x, 21), cell, 9, Color("8aa9bb")) + _text(str(values[index]).pad_zeros(2), Vector2(x, 45), cell, 21, color) + if index < 2: + draw_line(Vector2(x + cell, 14), Vector2(x + cell, 45), Color("2b4354"), 1) + var card := Rect2(x + cell * 0.5 - 32, 52, 64, 64) + var slot_index: int = WEAPON_DISPLAY_ORDER[index] + var item := _inventory.weapon_slots[slot_index] + var border := item.get_type_color() if item != null else Color("344657") + draw_style_box(_card_style(border), card) + var binding: String = ["LPM", "PPM", "ŚPM"][slot_index] + _text(binding, card.position + Vector2(0, 14), card.size.x, 9, CYAN) + if item != null and item.definition.icon != null: + draw_texture_rect(item.definition.icon, Rect2(card.position + Vector2(11, 18), Vector2(42, 42)), false) + else: + _text("—", card.position + Vector2(0, 40), card.size.x, 14, Color("637d90")) + var ratio := float(player.get(["hull", "shields", "energy"][index])) / maxf(1, float(player.get(["max_hull", "max_shields", "max_energy"][index]))) + var gauge := Rect2(x + 5, 121, cell - 10, 3) + draw_rect(gauge, Color("172632")) + gauge.size.x *= clampf(ratio, 0, 1) + draw_rect(gauge, [Color("ea9277"), CYAN, AMBER][index]) + +func _card_style(color: Color) -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = Color("0b1520") + style.border_color = color + style.set_border_width_all(1) + style.set_corner_radius_all(4) + return style diff --git a/scripts/ui/flight_cockpit.gd.uid b/scripts/ui/flight_cockpit.gd.uid new file mode 100644 index 0000000..53c43ae --- /dev/null +++ b/scripts/ui/flight_cockpit.gd.uid @@ -0,0 +1 @@ +uid://srdh8udwbrvj diff --git a/scripts/ui/planet_services_menu.gd b/scripts/ui/planet_services_menu.gd index 7d13b47..5f63659 100644 --- a/scripts/ui/planet_services_menu.gd +++ b/scripts/ui/planet_services_menu.gd @@ -67,12 +67,12 @@ func _position_at_planet() -> void: var planet_radius := float(planet.get("radius")) * canvas.x.length() size = panel.get_combined_minimum_size() var viewport_size := get_viewport_rect().size - var sidebar := get_parent().get_node_or_null("CaptainShipDrawer") as Control + var sidebar := get_parent().get_node_or_null("ShipDrawer") as Control var screen_right := viewport_size.x - (sidebar.size.x + 8.0 if sidebar != null and sidebar.visible else 8.0) var target := planet_screen + Vector2(planet_radius + 24.0, -size.y * 0.5) if target.x + size.x > screen_right: target.x = planet_screen.x - planet_radius - 24.0 - size.x - position = Vector2(clampf(target.x, 8.0, maxf(8.0, screen_right - size.x)), clampf(target.y, 8.0, maxf(8.0, viewport_size.y - size.y - 80.0))) + position = Vector2(clampf(target.x, 8.0, maxf(8.0, screen_right - size.x)), clampf(target.y, 8.0, maxf(8.0, viewport_size.y - size.y - 150.0))) func _refresh_ammunition_service() -> void: _missile_button.disabled = _inventory == null or not _inventory.can_buy_missile() diff --git a/scripts/ui/scanner_overlay.gd b/scripts/ui/scanner_overlay.gd index 6eb6f3c..ef45940 100644 --- a/scripts/ui/scanner_overlay.gd +++ b/scripts/ui/scanner_overlay.gd @@ -2,27 +2,34 @@ extends Control const FONT := preload("res://assets/fonts/font2.ttf") var player: CharacterBody2D -var remaining := 8.0 var targets: Array[Node2D] = [] func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE - for group in ["enemies", "exploration_pois"]: - for target in get_tree().get_nodes_in_group(group): - if player.get_parent().is_ancestor_of(target): - targets.append(target) -func _process(delta: float) -> void: - remaining -= delta - if remaining <= 0 or not is_instance_valid(player) or bool(player.get("_is_destroyed")): - get_parent().queue_free() - return - queue_redraw() - -func _draw() -> void: +func _process(_delta: float) -> void: if not is_instance_valid(player): return - var bounds := Rect2(Vector2(45, 100), Vector2(maxf(100, size.x - 390), maxf(100, size.y - 210))) + targets.clear() + for group in ["enemies", "exploration_pois"]: + for target in get_tree().get_nodes_in_group(group): + if player.get_parent().is_ancestor_of(target) and is_target_in_range(target): + targets.append(target) + queue_redraw() + +func get_scan_range() -> float: + var transform := get_viewport().get_canvas_transform() + var half_screen := get_viewport_rect().size * 0.5 + var visible_radius := Vector2(half_screen.x / transform.x.length(), half_screen.y / transform.y.length()).length() + return maxf(float(player.get("base_scanner_range")), visible_radius + 150.0) + float(player.get("scanner_range")) - float(player.get("base_scanner_range")) + +func is_target_in_range(target: Node2D) -> bool: + return player.global_position.distance_to(target.global_position) <= get_scan_range() + +func _draw() -> void: + if not is_instance_valid(player) or bool(player.get("_is_destroyed")) or bool(player.get("is_docked")): + return + var bounds := Rect2(Vector2(344, 70), Vector2(maxf(100, size.x - 680), maxf(100, size.y - 230))) var origin := get_viewport().get_canvas_transform() * player.global_position var anchor := Vector2(clampf(origin.x, bounds.position.x, bounds.end.x), clampf(origin.y, bounds.position.y, bounds.end.y)) for target in targets: @@ -45,6 +52,8 @@ func _draw() -> void: color = Color("d2a4ff") caption = "?" var screen := get_viewport().get_canvas_transform() * target.global_position + if get_viewport_rect().has_point(screen): + continue var direction := (screen - anchor).normalized() var point := screen if not bounds.has_point(screen): @@ -55,4 +64,3 @@ func _draw() -> void: var side := Vector2(-direction.y, direction.x) draw_colored_polygon(PackedVector2Array([point + direction * 10, point - direction * 6 + side * 6, point - direction * 6 - side * 6]), color) draw_string(FONT, point + Vector2(-18, 25), caption, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, color) - draw_string(FONT, Vector2(32, 82), "SKANER %.1f s · czerwony: wrogowie · błękitny: asteroidy · złoty: wraki · fioletowy: antyczne planety" % maxf(0, remaining), HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color("c9e8ff")) diff --git a/scripts/ui/ship_crafting_panel.gd b/scripts/ui/ship_crafting_panel.gd new file mode 100644 index 0000000..8b19d32 --- /dev/null +++ b/scripts/ui/ship_crafting_panel.gd @@ -0,0 +1,73 @@ +extends PanelContainer + +var _inventory: ShipInventory +var _expanded := false +var _toggle: Button +var _details: VBoxContainer +var _parts: Label +var _stock: Label +var _ammo: Button +var _missile: Button + +func _ready() -> void: + add_theme_stylebox_override("panel", StyleBoxEmpty.new()) + _inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory + var contents := VBoxContainer.new() + contents.name = "Contents" + contents.add_theme_constant_override("separation", 6) + add_child(contents) + _toggle = Button.new() + _toggle.name = "CreateToggle" + _toggle.text = "TWÓRZ ▾" + _toggle.custom_minimum_size.y = 28 + _toggle.add_theme_font_size_override("font_size", 12) + _toggle.focus_mode = Control.FOCUS_NONE + _toggle.pressed.connect(_toggle_expanded) + contents.add_child(_toggle) + _details = VBoxContainer.new() + _details.name = "CraftingDetails" + _details.add_theme_constant_override("separation", 6) + contents.add_child(_details) + _parts = _label(Color("efb568")) + _stock = _label(Color("91dbea")) + _ammo = _button("CraftAmmunition", "+%d AMUNICJI · %.0f CZĘŚCI" % [ShipInventory.AMMUNITION_CRAFT_AMOUNT, ShipInventory.AMMUNITION_CRAFT_PARTS_COST]) + _ammo.tooltip_text = "Tworzy amunicję do działka maszynowego." + _ammo.pressed.connect(func() -> void: _inventory.craft_ammunition()) + _missile = _button("CraftMissile", "+1 RAKIETA · %.0f CZĘŚCI" % ShipInventory.MISSILE_CRAFT_PARTS_COST) + _missile.tooltip_text = "Tworzy rakietę do dowolnej wyrzutni." + _missile.pressed.connect(func() -> void: _inventory.craft_missile()) + _details.hide() + if bool(GameState.hud_panel_state.get("crafting_expanded", false)): + _toggle_expanded() + _inventory.ammunition_changed.connect(_refresh) + GameState.captain.changed.connect(_refresh) + _refresh() + +func _label(color: Color) -> Label: + var label := Label.new() + label.add_theme_font_size_override("font_size", 10) + label.add_theme_color_override("font_color", color) + _details.add_child(label) + return label + +func _button(node_name: String, caption: String) -> Button: + var button := Button.new() + button.name = node_name + button.text = caption + button.custom_minimum_size.y = 32 + button.add_theme_font_size_override("font_size", 10) + button.focus_mode = Control.FOCUS_NONE + _details.add_child(button) + return button + +func _toggle_expanded() -> void: + _expanded = not _expanded + GameState.hud_panel_state["crafting_expanded"] = _expanded + _details.visible = _expanded + _toggle.text = "TWÓRZ ▴" if _expanded else "TWÓRZ ▾" + +func _refresh() -> void: + _parts.text = "DOSTĘPNE CZĘŚCI: %.1f" % GameState.captain.parts + _stock.text = "AMUNICJA: %d / RAKIETY: %d" % [_inventory.ammunition, _inventory.missile_ammunition] + _ammo.disabled = not _inventory.can_craft_ammunition() + _missile.disabled = not _inventory.can_craft_missile() diff --git a/scripts/ui/ship_crafting_panel.gd.uid b/scripts/ui/ship_crafting_panel.gd.uid new file mode 100644 index 0000000..6e0cb3f --- /dev/null +++ b/scripts/ui/ship_crafting_panel.gd.uid @@ -0,0 +1 @@ +uid://njdq3ttvccwm diff --git a/scripts/ui/ship_inventory_panel.gd b/scripts/ui/ship_inventory_panel.gd index 2bc0587..7af0bab 100644 --- a/scripts/ui/ship_inventory_panel.gd +++ b/scripts/ui/ship_inventory_panel.gd @@ -7,31 +7,17 @@ const CARGO_SIZE := 20 @onready var close_button: Button = $Contents/Header/CloseButton @onready var title_label: Label = $Contents/Header/Title @onready var ammunition_label: Label = $Contents/Ammunition/Count -@onready var craft_button: Button = $Contents/Ammunition/CraftButton var _inventory: ShipInventory var _drawer_open := false var _slide_tween: Tween -var _missile_button: Button var _minerals_label: Label -var drawer_width := 780.0 +var drawer_width := 560.0 func _ready() -> void: close_button.pressed.connect(close_drawer) - craft_button.pressed.connect(_craft_ammunition) - craft_button.text = "TWÓRZ +%d AMUNICJI (%.0f części)" % [ShipInventory.AMMUNITION_CRAFT_AMOUNT, ShipInventory.AMMUNITION_CRAFT_PARTS_COST] - var missile_row := HBoxContainer.new() - $Contents.add_child(missile_row) - $Contents.move_child(missile_row, $Contents/CargoGrid.get_index()) - _missile_button = Button.new() - _missile_button.text = "TWÓRZ +1 RAKIETĘ (%.0f części)" % ShipInventory.MISSILE_CRAFT_PARTS_COST - _missile_button.tooltip_text = "Amunicja do obu typów wyrzutni rakiet. Wyrzutnie zdobywasz jako loot." - _missile_button.add_theme_font_size_override("font_size", 11) - _missile_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _missile_button.pressed.connect(func() -> void: _inventory.craft_missile()) - missile_row.add_child(_missile_button) _minerals_label = Label.new() - _minerals_label.add_theme_font_size_override("font_size", 12) + _minerals_label.add_theme_font_size_override("font_size", 10) $Contents.add_child(_minerals_label) $Contents.move_child(_minerals_label, $Contents/CargoGrid.get_index()) _inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory @@ -48,16 +34,10 @@ func _ready() -> void: func _refresh_title() -> void: _minerals_label.text = "SUROWCE · Tytan: %d · Żelazo: %d · Aluminium: %d" % [GameState.captain.minerals.get("titanium", 0), GameState.captain.minerals.get("iron", 0), GameState.captain.minerals.get("aluminium", 0)] - craft_button.disabled = _inventory == null or not _inventory.can_craft_ammunition() - _missile_button.disabled = _inventory == null or not _inventory.can_craft_missile() if _inventory: title_label.text = "MAGAZYN (%d/%d) · Części: %.1f" % [_inventory.get_cargo_used(), CARGO_SIZE, GameState.captain.parts] ammunition_label.text = "AMUNICJA: %d · RAKIETY: %d" % [_inventory.ammunition, _inventory.get_missile_count()] -func _craft_ammunition() -> void: - if _inventory: - _inventory.craft_ammunition() - func toggle_drawer() -> void: _set_drawer_open(not _drawer_open) @@ -66,6 +46,7 @@ func close_drawer(immediately: bool = false) -> void: if _slide_tween != null and _slide_tween.is_running(): _slide_tween.kill() _drawer_open = false + GameState.hud_panel_state["inventory_open"] = false position.x = drawer_width hide() return @@ -75,6 +56,7 @@ func _set_drawer_open(value: bool) -> void: if _slide_tween != null and _slide_tween.is_running(): _slide_tween.kill() _drawer_open = value + GameState.hud_panel_state["inventory_open"] = value if value and not visible: position = Vector2(drawer_width, 0) show() diff --git a/tests/abilities/active_abilities_test.gd b/tests/abilities/active_abilities_test.gd index 539c42b..d9adbc3 100644 --- a/tests/abilities/active_abilities_test.gd +++ b/tests/abilities/active_abilities_test.gd @@ -7,9 +7,11 @@ func equip(inventory: ShipInventory, id: String, slot: int) -> void: for index in inventory.cargo.size(): var item := inventory.cargo[index] if item != null and item.definition.item_id == id: - assert(inventory.move_item("cargo", index, "module", slot)) + assert(inventory.move_item("cargo", index, "extension", slot)) return - assert(false, "Starter extension missing: " + id) + var item := GENERATOR.generate_for_definition(load("res://resources/items/%s.tres" % id), 1) + assert(inventory.add_item(item)) + assert(inventory.move_item("cargo", inventory.cargo.find(item), "extension", slot)) func _ready() -> void: GameState.start_new_run() @@ -21,7 +23,7 @@ func _ready() -> void: var inventory: ShipInventory = ship.get_node("ShipInventory") equip(inventory, "afterburner", 0) equip(inventory, "shield_charge", 1) - equip(inventory, "repair_drones", 5) + equip(inventory, "repair_drones", 4) assert(ship.call("get_active_ability", 2) == null) assert(not ship.call("activate_ability", 2)) ship.set("energy", ship.get("max_energy") - 0.01) @@ -38,19 +40,22 @@ func _ready() -> void: assert(ship.get("shields") == minf(100.0, ship.get("max_shields"))) assert(not inventory.delete_cargo_item(-1)) assert(GameState.captain.parts == 0.0) + var salvage := ItemInstance.new() + salvage.definition = preload("res://resources/items/hull_plating.tres") + assert(inventory.add_item(salvage)) assert(inventory.delete_cargo_item(0)) assert(not inventory.delete_cargo_item(0)) assert(GameState.captain.parts == 10.0) ship.set("hull", ship.get("max_hull") - 4.25) ship.set("energy", 0.0) - assert(ship.call("activate_ability", 5)) + assert(ship.call("activate_ability", 4)) assert(ship.get("hull") == ship.get("max_hull")) assert(is_equal_approx(GameState.captain.parts, 5.75) and ship.get("energy") == 0.0) - assert(not ship.call("activate_ability", 5)) + assert(not ship.call("activate_ability", 4)) ship.set("hull", ship.get("max_hull") - 20.0) - assert(ship.call("activate_ability", 5)) + assert(ship.call("activate_ability", 4)) assert(GameState.captain.parts == 0.0) - assert(not ship.call("activate_ability", 5)) + assert(not ship.call("activate_ability", 4)) ship.set("energy", ship.get("max_energy")) ship.velocity = Vector2.ZERO var key := InputEventKey.new() @@ -74,15 +79,15 @@ func _ready() -> void: ship.set("energy", ship.get("max_energy")) assert(not ship.call("activate_ability", 0)) ship.set("is_docked", false) - var item := inventory.get_item("module", 0) - assert(inventory.move_item("module", 0, "cargo", 0)) + var item := inventory.get_item("extension", 0) + assert(inventory.move_item("extension", 0, "cargo", 0)) assert(ship.call("get_active_ability", 0) == null) - assert(inventory.move_item("cargo", 0, "module", 3)) + assert(inventory.move_item("cargo", 0, "extension", 3)) assert(ship.call("get_active_ability", 3) == item.definition.active_ability) for definition in GENERATOR.DEFINITIONS: if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: var loot := GENERATOR.generate_for_definition(definition, 100) - assert(loot.definition.active_ability != null and not loot.additional_modifiers.is_empty()) + assert(loot.definition.active_ability != null and loot.additional_modifiers.is_empty()) GameState.captain.parts = 17.5 inventory.preserve_for_restart() game.queue_free() @@ -91,6 +96,6 @@ func _ready() -> void: add_child(restarted) await get_tree().process_frame assert(GameState.captain.parts == 17.5) - assert(restarted.get_node("PlayerShip/ShipInventory").get_item("module", 3) == item) + assert(restarted.get_node("PlayerShip/ShipInventory").get_item("extension", 3) == item) assert(not restarted.get_node("PlayerShip").call("is_boosting")) print("ACTIVE_ABILITIES_TEST: PASS — costs, hotkeys, boost ramp/decay, salvage, repairs, equipment, loot, restart") diff --git a/tests/abilities/missile_system_test.gd b/tests/abilities/missile_system_test.gd index a2566f9..60ff70c 100644 --- a/tests/abilities/missile_system_test.gd +++ b/tests/abilities/missile_system_test.gd @@ -16,10 +16,10 @@ func _ready() -> void: var launcher := ItemInstance.new() launcher.definition = ShipInventory.MISSILE_DEFINITIONS[0] assert(inventory.add_item(launcher)) - assert(not inventory.move_item("cargo", 0, "weapon", 0)) - assert(not inventory.move_item("weapon", 0, "weapon", 2)) + assert(inventory.can_move_item("cargo", 0, "weapon", 0)) + assert(inventory.can_move_item("weapon", 0, "weapon", 2)) assert(inventory.move_item("cargo", 0, "weapon", 2)) - assert(not inventory.move_item("weapon", 2, "weapon", 0)) + assert(inventory.can_move_item("weapon", 2, "weapon", 0)) assert(not inventory.move_item("weapon", 2, "module", 0)) var energy_before := float(ship.get("energy")) var missile := inventory.weapon_slots[2] @@ -47,9 +47,9 @@ func _ready() -> void: weapons.set("missile_cooldown_remaining", 0.0) assert(not weapons.call("try_fire_missile")) assert(inventory.weapon_slots[2] == missile and ship.get("energy") == energy_before) - GameState.captain.parts = 19.0 + GameState.captain.parts = 49.0 assert(not inventory.craft_missile()) - GameState.captain.parts = 20.0 + GameState.captain.parts = 50.0 assert(inventory.craft_missile() and GameState.captain.parts == 0.0) var unguided := ItemInstance.new() unguided.definition = ShipInventory.MISSILE_DEFINITIONS[1] @@ -86,7 +86,7 @@ func _ready() -> void: if inventory.cargo[index] == null: inventory.cargo[index] = missile var ammo_before := inventory.missile_ammunition - assert(inventory.craft_missile() and GameState.captain.parts == 80.0) + assert(inventory.craft_missile() and GameState.captain.parts == 50.0) assert(inventory.buy_missile() and GameState.captain.credits == 70) assert(inventory.missile_ammunition == ammo_before + 2) addon.value = 100.0 diff --git a/tests/abilities/scanner_test.gd b/tests/abilities/scanner_test.gd index bd2881e..2dea1c2 100644 --- a/tests/abilities/scanner_test.gd +++ b/tests/abilities/scanner_test.gd @@ -3,44 +3,53 @@ const GENERATOR := preload("res://scripts/items/item_generator.gd") func _ready() -> void: GameState.start_new_run() var ship := preload("res://scenes/game/player_ship.tscn").instantiate() as CharacterBody2D - ship.name = "PlayerShip" add_child(ship) ship.set_physics_process(false) - ship.get_node("WeaponController").set_physics_process(false) + var weapons := ship.get_node("WeaponController") + weapons.set_physics_process(false) var inventory := ship.get_node("ShipInventory") as ShipInventory - var scanner := GENERATOR.generate_for_definition(preload("res://resources/items/scanner.tres"), 5) - assert(scanner.get_addon_count() > 0) - assert(inventory.add_item(scanner)) - assert(inventory.move_item("cargo", inventory.cargo.find(scanner), "module", 0)) - var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate() as CharacterBody2D - enemy.position = Vector2(900, 0) - add_child(enemy) - enemy.set_physics_process(false) - for kind in ["asteroid", "wreck", "ruin"]: - var poi := StaticBody2D.new() - poi.set_script(preload("res://scripts/levels/exploration_poi.gd")) - poi.set("kind", kind) - poi.set("player", ship) - poi.position = Vector2(1500, 100) - add_child(poi) - poi.set_physics_process(false) - var maximum := float(ship.get("max_energy")) - ship.set("energy", maximum * 0.49) - assert(not ship.activate_ability(0)) - ship.set("energy", maximum * 0.8) - assert(ship.activate_ability(0)) - assert(is_equal_approx(ship.get("energy"), maximum * 0.3)) - var layer := ship.get_node("ScannerOverlay") as CanvasLayer - var overlay := layer.get_child(0) as Control - assert(overlay.get("targets").size() == 4) - assert(overlay.mouse_filter == Control.MOUSE_FILTER_IGNORE) - assert(not ship.activate_ability(0)) - await get_tree().process_frame - overlay.call("_process", 8.1) - await get_tree().process_frame - assert(not ship.has_node("ScannerOverlay")) - ship.set("energy", maximum * 0.5) - assert(ship.activate_ability(0)) - assert(is_zero_approx(ship.get("energy"))) - print("SCANNER_TEST: PASS loot addons, module activation, 50% max energy cost, four target types, repeat prevention, 8s expiry, reactivation") + assert(inventory.module_slots.size() == 5 and inventory.extension_slots.size() == 5) + var overlay := ship.get_node("ScannerOverlay").get_child(0) + var base_range: float = overlay.call("get_scan_range") + var energy_before: float = ship.get("energy") + var target := Node2D.new() + target.position = Vector2(base_range + 100.0, 0) + add_child(target) + target.add_to_group("exploration_pois") + assert(not overlay.call("is_target_in_range", target)) + var computer := GENERATOR.generate_for_definition(preload("res://resources/items/ship_computer.tres"), 5) + assert(computer.get_addon_count() > 0) + assert(inventory.add_item(computer)) + for index in 4: + assert(not inventory.can_move_item("cargo", inventory.cargo.find(computer), "module", index)) + assert(not inventory.can_move_item("cargo", inventory.cargo.find(computer), "extension", 0)) + assert(inventory.move_item("cargo", inventory.cargo.find(computer), "module", 4)) + assert(overlay.call("is_target_in_range", target)) + overlay.call("_process", 20.0) + assert(overlay.get("targets").has(target) and ship.get("energy") == energy_before) + for definition in GENERATOR.DEFINITIONS: + if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + var extension := GENERATOR.generate_for_definition(definition, 1000, 2) + assert(extension.additional_modifiers.is_empty()) + assert(inventory.add_item(extension)) + assert(not inventory.can_move_item("cargo", inventory.cargo.find(extension), "module", 0)) + assert(inventory.move_item("cargo", inventory.cargo.find(extension), "extension", 0)) + var launcher := ItemInstance.new() + launcher.definition = ShipInventory.MISSILE_DEFINITIONS[0] + assert(inventory.add_item(launcher)) + for index in 3: + assert(inventory.move_item("cargo", inventory.cargo.find(launcher), "weapon", index)) + weapons.set("missile_cooldown_remaining", 0.0) + inventory.missile_ammunition = 1 + assert(weapons.call("try_fire_missile", index)) + assert(inventory.move_item("weapon", index, "cargo", inventory.cargo.find(null))) + assert(inventory.move_item("module", 4, "cargo", inventory.cargo.find(null))) + assert(is_equal_approx(overlay.call("get_scan_range"), base_range)) + assert(not overlay.call("is_target_in_range", target)) + inventory.preserve_for_restart() + var restored := preload("res://scenes/game/player_ship.tscn").instantiate() + add_child(restored) + restored.set_physics_process(false) + assert(restored.get_node("ShipInventory").extension_slots[0] == inventory.extension_slots[0]) + print("SCANNER_TEST: PASS categories, extensions without addons, passive range, computer removal, all weapon slots, restart") get_tree().quit() diff --git a/tests/captain/captain_system_test.gd b/tests/captain/captain_system_test.gd index 783608d..b7a828c 100644 --- a/tests/captain/captain_system_test.gd +++ b/tests/captain/captain_system_test.gd @@ -54,7 +54,7 @@ func _ready() -> void: assert(profile.credits == 0) await _collect_credits(game.get_node("PlayerShip") as CharacterBody2D) assert(profile.credits == 180) - var panel := game.get_node("HUD/HUDRoot/CaptainShipDrawer/Scroll/Contents/CaptainPanel") + var panel := game.get_node("HUD/HUDRoot/CaptainPanel") panel.get_node("Contents/Skills/gunner/Train_gunner").emit_signal("pressed") assert(profile.get_skill_rank("gunner") == 1 and profile.learning_points == 0) panel.get_node("Contents/Skills/piloting/Train_piloting").emit_signal("pressed") diff --git a/tests/items/equipment_comparison_test.gd b/tests/items/equipment_comparison_test.gd new file mode 100644 index 0000000..bc3d21d --- /dev/null +++ b/tests/items/equipment_comparison_test.gd @@ -0,0 +1,98 @@ +extends Node +const GAME := preload("res://scenes/game/game.tscn") + +func _modifier(key: String, value: float) -> ItemStatModifier: + var modifier := ItemStatModifier.new() + modifier.stat_key = key + modifier.value = value + return modifier + +func _item(value: float) -> ItemInstance: + var item := ItemInstance.new() + item.definition = preload("res://resources/items/reinforced_shield.tres") + item.primary_value_override = value + return item + +func _ready() -> void: + GameState.start_new_run() + var game := GAME.instantiate() + add_child(game) + game.set_physics_process(false) + var ship := game.get_node("PlayerShip") + ship.set_physics_process(false) + ship.get_node("WeaponController").set_physics_process(false) + for enemy in get_tree().get_nodes_in_group("enemies"): + enemy.set_physics_process(false) + var inventory: ShipInventory = ship.get_node("ShipInventory") + var old := _item(2.0) + old.additional_modifiers.append(_modifier("damage_percent", 10.0)) + old.additional_modifiers.append(_modifier("shield_regeneration", 1.0)) + var candidate := _item(3.0) + candidate.additional_modifiers.append(_modifier("damage_percent", 2.0)) + candidate.additional_modifiers.append(_modifier("damage_percent", 3.0)) + candidate.additional_modifiers.append(_modifier("energy_regeneration", 4.0)) + assert(inventory.add_item(old)) + assert(inventory.move_item("cargo", inventory.cargo.find(old), "module", 0)) + assert(inventory.add_item(candidate)) + var hull_before: float = ship.get("max_hull") + var shields_before: float = ship.get("max_shields") + var rows := inventory.get_item_comparison(candidate, old) + var deltas := {} + for row in rows: + deltas[row["key"]] = row["delta"] + assert(is_equal_approx(deltas["max_shields"], 1.0)) + var weapon: ItemInstance = inventory.weapon_slots[0] + assert(is_equal_approx(deltas["weapon_damage_0"], weapon.get_stat_total("damage") * -0.05)) + assert(is_equal_approx(deltas["shield_regeneration"], -1.0)) + assert(is_equal_approx(deltas["energy_regeneration"], 2.0)) + assert(inventory.get_item_comparison(old, old).is_empty()) + assert(inventory.get_item_comparison(candidate, null)[0]["delta"] == 3.0) + assert(ship.get("max_hull") == hull_before and ship.get("max_shields") == shields_before) + assert(inventory.module_slots[0] == old and inventory.cargo.has(candidate)) + # Percentage bonuses combine with flat stats and the rest of the equipment. + old.additional_modifiers.append(_modifier("shield_percent", 10.0)) + candidate.additional_modifiers.append(_modifier("shield_percent", 5.0)) + var armor := ItemInstance.new() + armor.definition = preload("res://resources/items/hull_plating.tres") + armor.additional_modifiers.append(_modifier("shield_percent", 20.0)) + assert(inventory.add_item(armor)) + assert(inventory.move_item("cargo", inventory.cargo.find(armor), "module", 1)) + var expected: float = (float(ship.get("base_max_shields")) + 3.0) * 1.25 - (float(ship.get("base_max_shields")) + 2.0) * 1.3 + var predicted := 0.0 + for row in inventory.get_item_comparison(candidate, old): + if row["key"] == "max_shields": + predicted = row["delta"] + assert(is_equal_approx(predicted, expected)) + var current_shields: float = ship.get("max_shields") + assert(inventory.move_item("cargo", inventory.cargo.find(candidate), "module", 0)) + assert(is_equal_approx(float(ship.get("max_shields")) - current_shields, predicted)) + assert(inventory.move_item("cargo", inventory.cargo.find(old), "module", 0)) + assert(inventory.move_item("module", 1, "cargo", inventory.cargo.find(null))) + old.additional_modifiers.pop_back() + candidate.additional_modifiers.pop_back() + inventory._recalculate_ship_stats() + var hud := game.get_node("HUD/HUDRoot") + var slot := preload("res://scenes/ui/inventory_slot_view.tscn").instantiate() + slot.slot_index = inventory.cargo.find(candidate) + hud.add_child(slot) + slot.hide() + var tooltip: PanelContainer = slot._make_custom_tooltip(candidate.get_tooltip_text()) + hud.add_child(tooltip) + tooltip.position = Vector2(240, 80) + var columns := tooltip.get_child(0) + assert(columns.get_child_count() == 3) + var comparison: RichTextLabel = columns.get_child(2) + assert(comparison.get_parsed_text().contains("Tarcze: +1.00 pkt")) + assert(comparison.get_parsed_text().contains("Śr. obrażenia [LPM]:")) + assert(not comparison.get_parsed_text().contains("→")) + assert(not comparison.get_parsed_text().contains("%")) + assert(not comparison.get_parsed_text().contains("p.p.")) + assert(comparison.get_parsed_text().contains("Regeneracja tarcz: -1.00 pkt/s")) + var debug: Label = hud.get_node("DebugScroll/DebugInfo") + assert(debug.get_theme_font("font").get_height(10) + debug.get_theme_constant("line_spacing") <= 13.0) + await get_tree().process_frame + await get_tree().process_frame + assert(tooltip.size.x < 1280 and tooltip.size.y < 620) + assert(debug.get_line_count() <= 26) + assert(debug.get_combined_minimum_size().y < 400) + print("EQUIPMENT_COMPARISON_TEST: PASS primary difference, addon loss, duplicate addon sum, regeneration scaling, empty slot, unchanged stats, side column, compact debug, actual percentage effects match equipping") diff --git a/tests/items/equipment_comparison_test.gd.uid b/tests/items/equipment_comparison_test.gd.uid new file mode 100644 index 0000000..7c4dab5 --- /dev/null +++ b/tests/items/equipment_comparison_test.gd.uid @@ -0,0 +1 @@ +uid://dwm652b3w5oli diff --git a/tests/items/equipment_comparison_test.tscn b/tests/items/equipment_comparison_test.tscn new file mode 100644 index 0000000..da41b14 --- /dev/null +++ b/tests/items/equipment_comparison_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/items/equipment_comparison_test.gd" id="1"] +[node name="EquipmentComparisonTest" type="Node"] +script = ExtResource("1") diff --git a/tests/items/item_icons_test.gd b/tests/items/item_icons_test.gd new file mode 100644 index 0000000..a7520d9 --- /dev/null +++ b/tests/items/item_icons_test.gd @@ -0,0 +1,39 @@ +extends Node +const GAME := preload("res://scenes/game/game.tscn") +const GENERATOR := preload("res://scripts/items/item_generator.gd") + +func _ready() -> void: + GameState.start_new_run() + var game := GAME.instantiate() + add_child(game) + game.set_physics_process(false) + var ship := game.get_node("PlayerShip") + ship.set_physics_process(false) + ship.get_node("WeaponController").set_physics_process(false) + for enemy in get_tree().get_nodes_in_group("enemies"): + enemy.set_physics_process(false) + var inventory: ShipInventory = ship.get_node("ShipInventory") + for definition in GENERATOR.DEFINITIONS: + assert(definition.icon != null) + assert(definition.icon.get_width() == definition.icon.get_height()) + if definition.item_id in ["reinforced_shield", "hull_plating", "engine_extension", "compact_generator", "ship_computer", "beam_laser", "machine_gun", "plasma_cannon", "missile_launcher", "unguided_missile"]: + assert(inventory.add_item(GENERATOR.generate_for_definition(definition, 8))) + var category_index := ShipInventory.MODULE_CATEGORIES.find(definition.category) + if category_index >= 0 and inventory.module_slots[category_index] == null: + var item := GENERATOR.generate_for_definition(definition, 8) + assert(inventory.add_item(item)) + assert(inventory.move_item("cargo", inventory.cargo.find(item), "module", category_index)) + var hud := game.get_node("HUD") + hud._toggle_inventory() + await get_tree().create_timer(0.3).timeout + var slots: Array = hud.inventory_panel.get_node("Contents/CargoGrid").get_children() + for slot in slots: + assert(is_equal_approx(slot.size.x, slot.size.y)) + var item := inventory.get_item("cargo", slot.slot_index) + if item != null: + assert(slot.get_node("ItemIcon").texture == item.definition.icon) + assert(not slot.get_node("SlotLabel").visible) + var style: StyleBoxFlat = slot.get_theme_stylebox("panel") + assert(style.bg_color == item.get_background_color()) + assert(style.border_color == item.get_type_color()) + print("ITEM_ICONS_TEST: PASS square textures and slots, icon assignment, rarity backgrounds and borders, inventory and equipment") diff --git a/tests/items/item_icons_test.gd.uid b/tests/items/item_icons_test.gd.uid new file mode 100644 index 0000000..bd02ceb --- /dev/null +++ b/tests/items/item_icons_test.gd.uid @@ -0,0 +1 @@ +uid://b1agw2jj0ncwg diff --git a/tests/items/item_icons_test.tscn b/tests/items/item_icons_test.tscn new file mode 100644 index 0000000..ec82ad1 --- /dev/null +++ b/tests/items/item_icons_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/items/item_icons_test.gd" id="1"] +[node name="ItemIconsTest" type="Node"] +script = ExtResource("1") diff --git a/tests/items/laser_energy_regeneration_test.gd b/tests/items/laser_energy_regeneration_test.gd index 400d636..4ac970b 100644 --- a/tests/items/laser_energy_regeneration_test.gd +++ b/tests/items/laser_energy_regeneration_test.gd @@ -22,10 +22,10 @@ func _ready() -> void: var laser := GENERATOR.generate_for_definition(preload("res://resources/items/beam_laser.tres"), 5) assert(inventory.add_item(laser)) assert(inventory.move_item("cargo", inventory.cargo.find(laser), "weapon", 0)) - # Five separate level-5 regeneration affixes, on five ordinary hull modules. + # Five separate level-5 regeneration affixes, on the five equipment categories. for index in 5: var module := ItemInstance.new() - module.definition = preload("res://resources/items/hull_plating.tres") + module.definition = [preload("res://resources/items/reinforced_shield.tres"), preload("res://resources/items/hull_plating.tres"), preload("res://resources/items/engine_extension.tres"), preload("res://resources/items/compact_generator.tres"), preload("res://resources/items/ship_computer.tres")][index] module.item_level = 5 var addon := ItemStatModifier.new() addon.affix = REGEN diff --git a/tests/items/machine_gun_shield_recovery_test.gd b/tests/items/machine_gun_shield_recovery_test.gd index 217bef5..d85d82c 100644 --- a/tests/items/machine_gun_shield_recovery_test.gd +++ b/tests/items/machine_gun_shield_recovery_test.gd @@ -36,7 +36,7 @@ func _ready() -> void: addon.value = addon.affix.roll_value(1) module.additional_modifiers.append(addon) assert(inventory.add_item(module)) - assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", 0)) + assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", 2)) assert(ship.get("shield_on_damage_percent") == 1.0) var indicator := Node2D.new() indicator.set_script(preload("res://scripts/game/ship_energy_indicator.gd")) diff --git a/tests/items/shield_on_damage_test.gd b/tests/items/shield_on_damage_test.gd index 8d1f307..5837b61 100644 --- a/tests/items/shield_on_damage_test.gd +++ b/tests/items/shield_on_damage_test.gd @@ -20,7 +20,7 @@ func _ready() -> void: module.definition = preload("res://resources/items/engine_extension.tres") module.additional_modifiers.append(modifier) assert(inventory.add_item(module)) - assert(inventory.move_item("cargo", 0, "module", 0)) + assert(inventory.move_item("cargo", 0, "module", 2)) assert(ship.get("shield_on_damage_percent") == 1.0) ship.set("shields", 0.0) var enemy := _enemy(ship, 100.0) @@ -53,7 +53,7 @@ func _ready() -> void: ship.set("shields", ship.get("max_shields") - 0.1) enemy.call("take_weapon_hit", 10.0, false) assert(ship.get("shields") == ship.get("max_shields")) - assert(inventory.move_item("module", 0, "cargo", 0)) + assert(inventory.move_item("module", 2, "cargo", 0)) inventory.weapon_slots[0].additional_modifiers.clear() inventory.call("_recalculate_ship_stats") ship.set("shields", 0.0) diff --git a/tests/levels/boss_spawn_test.gd b/tests/levels/boss_spawn_test.gd new file mode 100644 index 0000000..053a306 --- /dev/null +++ b/tests/levels/boss_spawn_test.gd @@ -0,0 +1,64 @@ +extends Node +const GAME := preload("res://scenes/game/game.tscn") + +func _ready() -> void: + GameState.start_new_run() + GameState.system_seed = 314159 + var game := GAME.instantiate() + add_child(game) + game.set_physics_process(false) + var player: CharacterBody2D = game.get_node("PlayerShip") + player.set_physics_process(false) + player.get_node("WeaponController").set_physics_process(false) + var enemies: Array[Node] = [] + for enemy in get_tree().get_nodes_in_group("enemies"): + if game.is_ancestor_of(enemy): + assert(enemy.loot_tier != 2) + enemy.set_physics_process(false) + enemies.append(enemy) + assert(enemies.size() == 4) + for index in enemies.size() - 1: + enemies[index].take_damage(100000.0) + game._physics_process(0.0) + assert(game.get_remaining_enemies() == 1 and not game._boss_spawned) + # Spawn positions remain clear and at the same distance near map edges. + for position in [Vector2.ZERO, Vector2(2500, 1700), Vector2(-2500, 1700), Vector2(2500, -1700), Vector2(-2500, -1700)]: + player.position = position + var candidate: Vector2 = game._find_boss_spawn_position() + assert(is_equal_approx(candidate.distance_to(position), game.BOSS_SPAWN_DISTANCE)) + assert(absf(candidate.x) <= game.WORLD_SIZE.x * 0.5 - game.BOSS_SPAWN_MARGIN) + assert(absf(candidate.y) <= game.WORLD_SIZE.y * 0.5 - game.BOSS_SPAWN_MARGIN) + assert(candidate.length() >= 300.0) + for planet in game.system_data["planets"]: + assert(candidate.distance_to(planet["position"]) >= planet["radius"] + 130.0) + player.position = Vector2(700, 500) + enemies.back().take_damage(100000.0) + player.set("_is_destroyed", true) + game._physics_process(0.0) + assert(not game._boss_spawned) + player.set("_is_destroyed", false) + game._physics_process(0.0) + assert(game._boss_spawned and game.get_remaining_enemies() == 1) + assert(not game.system_cleared and not game.services_menu.visible) + var boss: CharacterBody2D + for enemy in get_tree().get_nodes_in_group("enemies"): + if game.is_ancestor_of(enemy) and enemy.loot_tier == 2: + assert(boss == null) + boss = enemy + assert(boss != null) + boss.set_physics_process(false) + assert(is_equal_approx(boss.position.distance_to(player.position), game.BOSS_SPAWN_DISTANCE)) + game._physics_process(0.0) + assert(game.get_remaining_enemies() == 1) + assert(not game.open_wormhole()) + boss.take_damage(100000.0) + game._physics_process(0.0) + assert(game.system_cleared and game.get_remaining_enemies() == 0) + game._physics_process(0.0) + assert(game.get_remaining_enemies() == 0) + for definition in preload("res://scripts/items/item_generator.gd").DEFINITIONS: + var category_index := ShipInventory.MODULE_CATEGORIES.find(definition.category) + if category_index >= 0: + assert(definition.display_name == ShipInventory.MODULE_NAMES[category_index]) + print("BOSS_SPAWN_TEST: PASS delayed boss, elites, no early completion, distance, safe placement, single spawn, dead player, item names") + get_tree().quit() diff --git a/tests/levels/boss_spawn_test.gd.uid b/tests/levels/boss_spawn_test.gd.uid new file mode 100644 index 0000000..2ca87df --- /dev/null +++ b/tests/levels/boss_spawn_test.gd.uid @@ -0,0 +1 @@ +uid://ciu0fo2mdapum diff --git a/tests/levels/boss_spawn_test.tscn b/tests/levels/boss_spawn_test.tscn new file mode 100644 index 0000000..ba509a5 --- /dev/null +++ b/tests/levels/boss_spawn_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/levels/boss_spawn_test.gd" id="1"] +[node name="BossSpawnTest" type="Node"] +script = ExtResource("1") diff --git a/tests/levels/exploration_test.gd b/tests/levels/exploration_test.gd index 10bb548..31aa379 100644 --- a/tests/levels/exploration_test.gd +++ b/tests/levels/exploration_test.gd @@ -18,6 +18,14 @@ func _ready() -> void: poi.poi_id = kind + str(index) add_child(poi) if kind == "asteroid": + assert(poi.has_node("AsteroidSprite")) + assert(poi.get_node("AsteroidSprite").texture is Texture2D) + var asteroid = poi.get_node("AsteroidSprite") + var previous_rotation = asteroid.rotation + poi._process(1.0) + assert(not is_equal_approx(asteroid.rotation, previous_rotation)) + assert(absf(asteroid.rotation - previous_rotation) < 0.1) + assert(poi.rotation == 0.0) poi.take_weapon_hit(poi.hull - 1) assert(not poi._claimed and is_equal_approx(poi.hull, 1)) poi.take_weapon_hit(1) @@ -36,7 +44,10 @@ func _ready() -> void: for item in modules: if item.item_level == 2: ancient += 1 - assert(item.get_addon_count() >= 4) + if item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: + assert(item.get_addon_count() == 0) + else: + assert(item.get_addon_count() >= 4) for modifier in item.additional_modifiers: var bounds = modifier.affix.get_value_range(2) assert(modifier.value >= lerpf(bounds.x, bounds.y, 0.9) - 0.00001) diff --git a/tests/levels/system_lifecycle_test.gd b/tests/levels/system_lifecycle_test.gd index 077b63e..817aa65 100644 --- a/tests/levels/system_lifecycle_test.gd +++ b/tests/levels/system_lifecycle_test.gd @@ -20,7 +20,7 @@ func _ready() -> void: assert(star_light.shadow_filter == Light2D.SHADOW_FILTER_PCF5) assert(star_light.color == preload("res://scripts/levels/celestial_texture_catalog.gd").star_light_color(star.get("body_texture"))) assert(star.get_node("HullLight").get("range_item_cull_mask") == 2) - assert(GameState.system_level == 1 and int(game.call("get_remaining_enemies")) == 5) + assert(GameState.system_level == 1 and int(game.call("get_remaining_enemies")) == 4) assert(not game.has_node("WeaponTestTarget")) assert(game.get_node("CelestialBodies").get_child_count() >= 2) var player := game.get_node("PlayerShip") as CharacterBody2D @@ -34,8 +34,8 @@ func _ready() -> void: var module := ITEM_GENERATOR.generate_for_definition(preload("res://resources/items/hull_plating.tres"), 1) free_slot = inventory.cargo.find(null) assert(inventory.add_item(module)) - assert(inventory.move_item("cargo", free_slot, "module", 0)) - var equipped_module := inventory.get_item("module", 0) + assert(inventory.move_item("cargo", free_slot, "module", 1)) + var equipped_module := inventory.get_item("module", 1) var tooltip := weapon.get_tooltip_text() var initial_data: Dictionary = game.get("system_data") var initial_seed := GameState.system_seed @@ -58,8 +58,10 @@ func _ready() -> void: game.call("_physics_process", 0.0) assert(menu.visible) GameState.captain.credits = inventory.get_ammunition_buy_cost() + var ammunition_before := inventory.ammunition menu.get_node("Center/Panel/Contents/BuyAmmunitionButton").emit_signal("pressed") - assert(inventory.ammunition == 400 and GameState.captain.credits == 0) + assert(inventory.ammunition == ammunition_before + ShipInventory.AMMUNITION_BUY_AMOUNT and GameState.captain.credits == 0) + var learning_points_before := GameState.captain.learning_points assert(GameState.captain.train_skill("gunner")) GameState.captain.credits = GameState.captain.get_skill_reset_cost() GameState.captain.emit_changed() @@ -67,7 +69,7 @@ func _ready() -> void: assert(not reset_button.disabled) reset_button.emit_signal("pressed") assert(GameState.captain.get_skill_rank("gunner") == 0) - assert(GameState.captain.learning_points == 1 and GameState.captain.credits == 0) + assert(GameState.captain.learning_points == learning_points_before and GameState.captain.credits == 0) assert(reset_button.disabled) player.position = initial_data["player_start"] game.call("_physics_process", 0.0) @@ -85,9 +87,9 @@ func _ready() -> void: inventory = player.get_node("ShipInventory") as ShipInventory assert(GameState.system_level == 1 and GameState.system_seed == initial_seed) assert(game.get("system_data") == initial_data) - assert(int(game.call("get_remaining_enemies")) == 5) + assert(int(game.call("get_remaining_enemies")) == 4) assert(inventory.get_item("weapon", 0) == weapon) - assert(inventory.get_item("module", 0) == equipped_module) + assert(inventory.get_item("module", 1) == equipped_module) assert(weapon.get_tooltip_text() == tooltip) _assert_full_resources(player) _clear_enemies(game) @@ -122,7 +124,7 @@ func _ready() -> void: assert(GameState.system_level == 2 and GameState.system_seed != initial_seed) assert(game.get("wormhole") == null) assert(game.get("system_data") != initial_data) - assert(inventory.get_item("weapon", 0) == weapon and inventory.get_item("module", 0) == equipped_module) + assert(inventory.get_item("weapon", 0) == weapon and inventory.get_item("module", 1) == equipped_module) assert(weapon.get_tooltip_text() == tooltip) _assert_full_resources(player) var enemy := get_tree().get_nodes_in_group("enemies")[0] as CharacterBody2D @@ -145,7 +147,7 @@ func _ready() -> void: await get_tree().process_frame game = get_tree().current_scene as Node2D _freeze(game) - assert(int(game.call("get_remaining_enemies")) == 40) + assert(int(game.call("get_remaining_enemies")) == 39) _clear_enemies(game) player = game.get_node("PlayerShip") as CharacterBody2D planet = game.get("protected_planet") @@ -191,7 +193,14 @@ func _assert_boss_and_health(game: Node2D) -> void: boss = enemy elif enemy.loot_tier == 0: normal = enemy - assert(boss != null and normal != null) + assert(boss == null and normal != null) + # Compare boss stats separately: the actual encounter remains pending. + for data in game.system_data["enemies"]: + if data["tier"] == 2: + boss = game._spawn_enemy(data, Vector2(1500, 0)) + break + assert(boss != null) + boss.set_physics_process(false) for key in ["max_hull", "weapon_damage"]: assert(is_equal_approx(float(boss.get(key)), float(normal.get(key)) * 6.4)) for key in ["approach_speed", "approach_acceleration", "turn_speed", "activation_range", "weapon_range"]: @@ -207,11 +216,14 @@ func _assert_boss_and_health(game: Node2D) -> void: bar._process(0.0) assert(is_zero_approx(bar.global_rotation)) assert(bar.global_position == boss.global_position + Vector2(0, -68)) + boss.free() func _clear_enemies(game: Node2D) -> void: - for enemy in get_tree().get_nodes_in_group("enemies"): - if game.is_ancestor_of(enemy): - enemy.call("take_damage", 100000.0) + for phase in 2: + for enemy in get_tree().get_nodes_in_group("enemies"): + if game.is_ancestor_of(enemy): + enemy.call("take_damage", 100000.0) + game.call("_physics_process", 0.0) func _assert_full_resources(player: CharacterBody2D) -> void: for resource in ["hull", "shields", "energy"]: diff --git a/tests/ships/captain_ship_drawer_test.gd b/tests/ships/captain_ship_drawer_test.gd index c5d42a1..49ad151 100644 --- a/tests/ships/captain_ship_drawer_test.gd +++ b/tests/ships/captain_ship_drawer_test.gd @@ -18,11 +18,58 @@ func _ready() -> void: ship.get_node("WeaponController").set_physics_process(false) var hud := game.get_node("HUD") var root := hud.get_node("HUDRoot") as Control - var drawer := root.get_node("CaptainShipDrawer") as Control - var toggle := root.get_node("CaptainShipToggle") as Button - var captain := drawer.get_node("Scroll/Contents/CaptainPanel") + var drawer := root.get_node("ShipDrawer") as Control + var toggle := root.get_node("ShipToggle") as Button + var captain := root.get_node("CaptainPanel") var equipment := drawer.get_node("Scroll/Contents/ShipEquipmentPanel") - assert(captain.get_parent() == equipment.get_parent()) + assert(captain.get_parent() != equipment.get_parent()) + assert(captain.get_node("Contents/PortraitRow/AvatarFrame/CaptainAvatar").texture != null) + assert(root.has_node("Cockpit")) + var cockpit := root.get_node("Cockpit") as Control + assert(cockpit.scale.is_equal_approx(Vector2.ONE * 0.85)) + assert(cockpit.WEAPON_DISPLAY_ORDER == [0, 2, 1]) + var old_position := ship.global_position + ship.global_position = ship.get_viewport().get_canvas_transform().affine_inverse() * (cockpit.position + Vector2(60, 30)) + cockpit._process(1.0) + assert(is_equal_approx(cockpit.modulate.a, 0.18)) + ship.global_position = old_position + cockpit._process(1.0) + assert(is_equal_approx(cockpit.modulate.a, 1.0)) + var crafting := equipment.get_node("Contents/CraftingPanel") + assert(not crafting._details.visible) + var closed_height: float = drawer.size.y + crafting._toggle.pressed.emit() + await get_tree().process_frame + await get_tree().process_frame + assert(crafting._details.visible and drawer.size.y > closed_height) + GameState.captain.parts = ShipInventory.AMMUNITION_CRAFT_PARTS_COST + ShipInventory.MISSILE_CRAFT_PARTS_COST + GameState.captain.emit_changed() + var inv: ShipInventory = ship.get_node("ShipInventory") + var ammo_before := inv.ammunition + var missiles_before := inv.missile_ammunition + assert(not crafting._ammo.disabled and not crafting._missile.disabled) + crafting._ammo.pressed.emit() + assert(inv.ammunition == ammo_before + ShipInventory.AMMUNITION_CRAFT_AMOUNT) + crafting._missile.pressed.emit() + assert(inv.missile_ammunition == missiles_before + 1) + assert(GameState.captain.parts == 0 and crafting._ammo.disabled and crafting._missile.disabled) + crafting._toggle.pressed.emit() + await get_tree().process_frame + await get_tree().process_frame + assert(not crafting._details.visible and is_equal_approx(drawer.size.y, closed_height)) + var expanded_height: float = captain.size.y + captain.get_node("Contents/Header/CollapseCaptain").pressed.emit() + await get_tree().process_frame + await get_tree().process_frame + assert(captain.size.y < expanded_height) + assert(root.get_node("DebugToggle").position.y >= captain.position.y + captain.size.y) + captain.get_node("Contents/Header/CollapseCaptain").pressed.emit() + await get_tree().process_frame + await get_tree().process_frame + assert(captain.get_node("Contents/Skills").visible) + assert(drawer.visible) + toggle.pressed.emit() + await get_tree().create_timer(0.3).timeout assert(not drawer.visible) toggle.pressed.emit() await get_tree().create_timer(0.3).timeout @@ -39,8 +86,12 @@ func _ready() -> void: var item := ItemInstance.new() item.definition = preload("res://resources/items/engine_extension.tres") assert(inventory.add_item(item)) - assert(inventory.move_item("cargo", inventory.cargo.find(item), "module", 0)) + assert(inventory.move_item("cargo", inventory.cargo.find(item), "module", 2)) assert(ship.get("max_speed") > ship.get("base_max_speed")) + var equipment_slot := equipment.get_node("Contents/EquipmentLayout/Module3") + assert(is_equal_approx(equipment_slot.size.x, equipment_slot.size.y)) + assert(equipment_slot.get_node("ItemIcon").texture == item.definition.icon) + assert(item.definition.icon != null) toggle.pressed.emit() await get_tree().create_timer(0.3).timeout assert(not drawer.visible and not inventory_panel.visible) @@ -48,7 +99,7 @@ func _ready() -> void: await get_tree().create_timer(0.3).timeout assert(drawer.visible and not inventory_panel.visible) var original_size := root.size - root.size = Vector2(root.size.x, 480) + root.size = Vector2(root.size.x, 400) hud.call("_layout_sidebar") await get_tree().process_frame await get_tree().process_frame @@ -56,5 +107,25 @@ func _ready() -> void: assert(scroll.get_v_scroll_bar().visible) root.size = original_size hud.call("_layout_sidebar") - print("CAPTAIN_SHIP_DRAWER_TEST: PASS (merged panel, slide, close/reopen, skills, inventory, equipment stats)") + # The same autoload state is reused by death and level scene reloads. + captain._toggle_collapsed() + hud._toggle_debug() + crafting._toggle_expanded() + var saved_state := GameState.hud_panel_state.duplicate() + var restored := GAME.instantiate() as Node2D + add_child(restored) + await get_tree().process_frame + await get_tree().process_frame + var restored_hud := restored.get_node("HUD") + var restored_captain := restored.get_node("HUD/HUDRoot/CaptainPanel") + assert(restored_captain._collapsed == saved_state["captain_collapsed"]) + assert(restored_hud._debug_expanded == saved_state["debug_expanded"]) + assert(restored_hud._sidebar_open == saved_state["ship_open"]) + var restored_crafting := restored.get_node("HUD/HUDRoot/ShipDrawer/Scroll/Contents/ShipEquipmentPanel/Contents/CraftingPanel") + assert(restored_crafting._expanded == saved_state["crafting_expanded"]) + assert(not restored_captain.has_node("Contents/Critical")) + var layout := equipment.get_node("Contents/EquipmentLayout") + assert(layout.get_node("Weapon1").position.x < layout.get_node("Weapon3").position.x) + assert(layout.get_node("Weapon3").position.x < layout.get_node("Weapon2").position.x) + print("CAPTAIN_SHIP_DRAWER_TEST: PASS (separate captain, portrait, collapse, debug position, cockpit, ship slide, skills, inventory, equipment stats, small viewport)") get_tree().quit()